hyperledger/solang

Incorrect encoding of integer arrays

TorstenStueber opened this issue · 2 comments

Describe the bug
Integer arrays returned by a Solidity smart contract are incorrectly Scale encoded when the contract is compiled for the Substrate compilation target.

To Reproduce
Compile the following contract via Solang using solang compile --target substrate ...

//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;

contract Test {
    function test(
        
    )
        external view
        returns (uint256[] memory _amounts)
    {
        _amounts = new uint256[](2);
        _amounts[0] = 0x201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201;
        _amounts[1] = 0x201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a090807060504030201;
    }
}

Then instantiate the wasm smart contract on a Substrate chain and send the message test. This message will return the following binary Scale encoded value consisting of 66 bytes:

0x080102030405060708090a0b0c0d0e0f...1415161718191a1b1c1d1e1f2000

However, the correct encoding of the vector should be the 65 bytes (1 byte for the initial 0x08 being the compact encoding of the number 2 for the length of the vector and then 32 bytes for each entry):

0x080102030405060708090a0b0c0d0e0f...1415161718191a1b1c1d1e1f20

i.e., there is an incorrect additional trailing 00-byte.

When sending the message test via the npm module @polkadot/api, then it will show the following error message:

createType(Vec<u256>):: Vec<u256>:: Decoded input doesn't match input, received 0x080102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f…02030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2000 (66 bytes), created 0x080102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f…0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 (65 bytes)

Similar errors
The contract will generate similar errors when using other uint data types whose number of bytes is not a power of two. For example, consider the following smart contract:

//SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;

contract Test {
    function test(
        
    )
        external view
        returns (uint24[] memory _amounts)
    {
        _amounts = new uint24[](2);
        _amounts[0] = 0x030201;
        _amounts[1] = 0x030201;
    }
}

The ABI of the compiled smart contract specifies the return type of test as Vec<u32>. However, it returns 0x08010203000102 instead of the correct 0x080102030001020300. The library @polkadot/api will show the following error message:

createType(Vec<u32>):: Vec<u32>:: Decoded input doesn't match input, received 0x08010203000102 (7 bytes), created 0x080102030001020000 (9 bytes)

Potential further problem
I didn't test but find it plausible that also the decoding of message arguments that are integer arrays is not working properly.

Solang version
solang version v0.3.1

@TorstenStueber thanks for reporting, good finds.

#1503 will fix the issue with the dynamic uint256 array.

Regarding the second problem. It seems that the encoder (and decoder) does not round up the integer size to the next power of two, I'm gonna look at this too.

Until we have #1344, in the metadata we have to round up integer size not the power of 2 to the next size that is a power of two.

Thanks @xermicus. Great. Fortunately, #1503 would be sufficient for our use case right now. I just found the other case (with not power of two uint types) by exploring the bug.