Solidity

為什麼在使用組裝和還原時會用完所有的gasLimit?

  • March 6, 2019

嘗試transferFrom()使用程序集呼叫 erc20 令牌(以節省一些氣體),程式碼如下:

pragma solidity ^0.4.24;

contract TestAssemblyAndRevert {
   function test(address from, address to, uint256 value) public {
       // a standard erc20 token
       address token = 0xedc2d4aca4f9b6a23904fbb0e513ea0668737643;

       // call transferFrom() of token using assembly
       assembly { // LineA
           // keccak256('transferFrom(address,address,uint256)') & 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000
           mstore(0, 0x23b872dd00000000000000000000000000000000000000000000000000000000)

           // calldatacopy(t, f, s) copy s bytes from calldata at position f to mem at position t
           // copy from, to, value from calldata to memory
           calldatacopy(4, 4, 96)

           // call ERC20 Token contract transferFrom function
           let result := call(gas, token, 0, 0, 100, 0, 32)

           if eq(result, 1) {
               return(0, 0)
           }

           //revert(0, 0); // LineB
       }

       revert("TOKEN_TRANSFER_FROM_ERROR"); // LineC
   }
}

該代幣是一個標準的 ERC20 代幣,當一些消費者試圖在transferFrom()沒有足夠額度的情況下呼叫它時,它將恢復,在我們的例子中,這行:

let result := call(gas, token, 0, 0, 100, 0, 32)

result將為0。

令我驚訝的是,當這種情況發生時,交易將耗盡所有的 gasLimit。這是為什麼?

我嘗試了其他幾種情況,它們都不會耗盡氣體:

  1. 如果我註釋掉整個彙編程式碼塊,或者
  2. 如果我保留彙編程式碼塊,但註釋掉: revert("TOKEN_TRANSFER_FROM_ERROR"), LineC
  3. 如果我註釋掉 LineC,請保留組裝塊並取消註釋 LineB

原來是記憶體亂了造成的,如果我們使用空閒記憶體指針:let ptr := mload(0x40),burn-all-gas問題就會消失。

pragma solidity ^0.4.24;

contract TestAssemblyAndRevert {
   function test(address from, address to, uint256 value) public {
       // a standard erc20 token
       address token = 0xedc2d4aca4f9b6a23904fbb0e513ea0668737643;

       // call transferFrom() of token using assembly
       assembly {
           let ptr := mload(0x40)

           // keccak256('transferFrom(address,address,uint256)') & 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000
           mstore(ptr, 0x23b872dd00000000000000000000000000000000000000000000000000000000)

           // calldatacopy(t, f, s) copy s bytes from calldata at position f to mem at position t
           // copy from, to, value from calldata to memory
           calldatacopy(add(ptr, 4), 4, 96)

           // call ERC20 Token contract transferFrom function
           let result := call(gas, token, 0, ptr, 100, ptr, 32)

           if eq(result, 1) {
               return(0, 0)
           }
       }

       revert("TOKEN_TRANSFER_FROM_ERROR");
   }
}

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