3

I have an array of addresses (array[]) and I need to encode it into bytes and vice-versa. The reason for that is that I need to pass such an array to a function which only accepts bytes.

I tried using abi.encode() and abi.decode() but I get a "This type cannot be encoded" compiler error:

address[] memory addresses;
addresses[0]= 0x...;
addresses[1] = 0x...;
addresses[2] = 0x...;
            
(bytes memory data) = abi.encode((address[]), addresses);

How should I really encode this? I'm planning to do the same encoding in web3.js too, so I would prefer an idea which works here too.

1 Answer 1

3

Finally I was able to solve my problem with some research:

The idea is simple, since addresses has a fixed length (20 bytes), so we can simple pack them next to each other and unpack it unambiguously.

Example packing:

address1:
0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
address2:
0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

packed:
0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

This tight packing can be achieved in Solidity by using abi.encodePacked(). You can find it here in the docs: https://docs.soliditylang.org/en/v0.5.3/abi-spec.html#non-standard-packed-mode

To pack any number of addresses I simple used it in a loop in the following way (may not be the most gas effective method):

function encodeAddressArray(address[] calldata addresses) external pure returns(bytes memory data){
    for(uint i=0; i<addresses.length; i++){
        data = abi.encodePacked(data, addresses[i]);
    }
}

To unpack simply take the first 20 bytes, which gives the first address and the second 20 bytes gives the second address and so on. For that I used the array slice. (Keep in mind, that slicing only works on calldata and x[start:end] includes x[start] but excludes x[end].)

function decodeAddressArray(bytes calldata data)external pure returns(address[] memory addresses){
    uint n = data.length/20;
    addresses = new address[](n);
        
    for(uint i=0; i<n; i++){
        addresses[i] = bytesToAddress(data[i*20:(i+1)*20]);
    }
}

where bytesToAddress() converts one address from bytes. This can be done by loading the corresponding bytes into the address variable using mload() in assembly:

function bytesToAddress(bytes calldata data) private pure returns (address addr) {
    bytes memory b = data;
    assembly {
        addr := mload(add(b, 20))
    } 
}

Finally the whole process is summarized in the following tiny library:


library AddressCoder{
    function bytesToAddress(bytes calldata data) private pure returns (address addr) {
        bytes memory b = data;
        assembly {
          addr := mload(add(b, 20))
        } 
    }

    
    function decodeAddressArray(bytes calldata data)external pure returns(address[] memory addresses){
        uint n = data.length/20;
        addresses = new address[](n);
        
        for(uint i=0; i<n; i++){
            addresses[i] = bytesToAddress(data[i*20:(i+1)*20]);
        }
    }
    
    
    function encodeAddressArray(address[] calldata addresses) external pure returns(bytes memory data){
        for(uint i=0; i<addresses.length; i++){
            data = abi.encodePacked(data, addresses[i]);
        }
    }
}

This encoding is also pretty simple in web3.js:

const encodeAddressArray = addresses => {
    let hex = "0x";
    hex += addresses.map(address => address.substr(2, 40)).join("");

    return web3.utils.hexToBytes(hex);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.