There's something I can't understand in the way Solidity handles dynamic-size memory arrays. For instance, let us consider the following contract:
pragma solidity ^0.8.0;
contract Test {
function test() public pure returns (uint len, uint32[] memory) {
uint32[] memory res = new uint32[](10);
assembly {
len := mload(res)
}
return (len, res);
}
function secondTest() public pure returns (uint len, uint32[] memory) {
uint32[] memory res;
assembly {
res := mload(0x40)
mstore(res, 10)
len := mload(res)
}
return (len, res);
}
}
I would expect these two functions to be equivalent: declaring a dynamic-size memory array of size 10 and then retrieving its length. However, calling them with web3py using Ganache, here's the results I got:
>>> contract.functions.test().call()
[10, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
>>> contract.functions.secondTest().call()
[10, [64, 10, 64, 10, 64, 10, 64, 10, 64, 10]]
It seems that the array is weirdly initialized in the second case, even though it was allocated from the free-memory pointer without anything having touched said pointer beforehand.
Investigating further, I tried to run the following functions:
function thirdTest() public pure returns (uint len, uint32[] memory) {
uint32[] memory res;
assembly {
res := mload(0x40)
len := mload(res)
}
return (len, res);
}
function fourthTest() public pure returns (uint32[] memory, uint32 len) {
uint32[] memory res;
assembly {
res := mload(0x40)
len := mload(res)
}
return (res, len);
}
This yielded the following results:
>>> network.contract.functions.thirdTest().call()
[0, []]
>>> network.contract.functions.fourthTest().call()
[[0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64, 0, 64], 0]
Note that the only difference between thirdTest
and fourthTest
is the order in which they return, and that the only difference between secondTest
and thirdTest
is that the former performs an mstore(res, 10)
before computing len
.
All in all, the only valid implementation is the first one (and the third one for an empty array, or so it seems). Why is that? What is wrong with the other ones, and where do these behaviors come from?