Solidity
在 Yul 中處理 uint32 記憶體數組時的奇怪行為
Solidity 處理動態大小的記憶體數組的方式有些我無法理解。例如,讓我們考慮以下契約:
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); } }
我希望這兩個函式是等價的:聲明一個大小為 10 的動態大小的記憶體數組,然後檢索它的長度。但是,使用 Ganache 使用 web3py 呼叫它們,這是我得到的結果:
>>> 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]]
似乎在第二種情況下,數組被奇怪地初始化了,即使它是從空閒記憶體指針分配的,而沒有任何東西事先接觸過所述指針。
進一步調查,我嘗試執行以下功能:
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); }
這產生了以下結果:
>>> 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]
請注意,
thirdTest
and之間的唯一區別是它們返回的順序, andfourthTest
之間的唯一區別是前者執行before 計算。secondTest``thirdTest``mstore(res, 10)``len
總而言之,唯一有效的實現是第一個(第三個是空數組,或者看起來如此)。這是為什麼?其他的有什麼問題,這些行為是從哪裡來的?
您沒有更新可用記憶體指針,下面的程式碼應該以清晰的方式向您展示這些步驟。
function secondTest() public pure returns (uint len, uint32[] memory) { uint32[] memory res; assembly { res := mload(0x40) // Update the free memory pointer // Add 0x20 (one word) to account for the length of the array // Add one word per element as each of them requires 32bytes (1 word) mstore(0x40, add(add(res, 0x20), mul(10, 0x20))) // Store the length mstore(res, 10) len := mload(res) } return (len, res); }
問題是空閒記憶體指針的地址,當你創建一個塊並分配/釋放記憶體
0x40
時,你要對它負責。assembly
因此,
res
在空閒記憶體指針處聲明:res := mload(0x40)
但第一個字是為數組 (10) 的長度保留的,因此您必須增加記憶體指針以解決此問題:將0x20
(32) 添加到空閒記憶體指針。然後你知道你有 10 個元素,每個元素佔用 32 個字節或 1 個字,所以你添加
10 * 0x20
到記憶體指針。這兩個步驟組合在:
mstore(0x40, add(add(res, 0x20), mul(10, 0x20)))
假設您不處理可變大小,則此操作是恆定的,並且可以(應該)替換為:
mstore(0x40, add(res, 0x160))
這樣,在您的彙編塊之外生成的程式碼將不會以任何方式覆蓋您的數據,因為空閒記憶體指針指向數組之後的記憶體區域。