Warum das ganze gasLimit aufbrauchen, wenn man Montage und Umkehrung zusammen verwendet?

Versuchen Sie, erc20-Token transferFrom()mit Assembly aufzurufen (um etwas Gas zu sparen), Code wie folgt:

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
    }
}

Das Token ist ein Standard-ERC20-Token, das, wenn ein Spender versucht, ohne genügend Erlaubnis anzurufen transferFrom(), in unserem Fall diese Zeile zurücksetzt:

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

resultwird 0 sein.

Was mich überrascht, ist, dass die Transaktion in diesem Fall das gesamte gasLimit aufbraucht. Warum das?

Ich habe mehrere andere Fälle ausprobiert, keiner von ihnen wird das Gas verbrauchen:

  1. wenn ich den gesamten Assembly-Code-Block auskommentiere, oder
  2. wenn ich den Assemblercodeblock behalte, aber auskommentiere: revert("TOKEN_TRANSFER_FROM_ERROR"), LineC
  3. Wenn ich LineC auskommentiere, behalte den Assembly-Block bei und entkommentiere LineB

Antworten (1)

Es stellte sich heraus, dass es durch ein Durcheinander des Speichers verursacht wurde, wenn wir den freien Speicherzeiger verwenden: let ptr := mload(0x40), das Burn-All-Gas-Problem wird verschwinden.

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");
    }
}