Solidity
為什麼在使用組裝和還原時會用完所有的gasLimit?
嘗試
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。這是為什麼?
我嘗試了其他幾種情況,它們都不會耗盡氣體:
- 如果我註釋掉整個彙編程式碼塊,或者
- 如果我保留彙編程式碼塊,但註釋掉:
revert("TOKEN_TRANSFER_FROM_ERROR")
, LineC- 如果我註釋掉 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"); } }