Solidity

在 Yul 中處理 uint32 記憶體數組時的奇怪行為

  • November 30, 2021

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]

請注意,thirdTestand之間的唯一區別是它們返回的順序, 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))

這樣,在您的彙編塊之外生成的程式碼將不會以任何方式覆蓋您的數據,因為空閒記憶體指針指向數組之後的記憶體區域。

引用自:https://ethereum.stackexchange.com/questions/114667