2

According to https://ethervm.io, when CALL instruction is executed, the stack layout like this:

[address, value, argOffset, argLen, retOffset, retLen]

So CALL would pop 6 words from stack bottom to retrieve necessary information.

But when I looked at the opcode of this Solidity code:

contract Test {
    function withdraw() public {
        uint x = 123;
        msg.sender.call.value(x)();
    }
}

I can see the related opcode is like below:

33  caller
90  swap1
82  dup3
90  swap1
60 00  push1    00
81  dup2
81  dup2
81  dup2
85  dup6
87  dup8
5a  gas
f1  call

We can see that before CALL, GAS push the remaining gas into stack bottom. But this changes the stack layout, and the bottom word is not caller address anymore.

Does this mean information from https://ethervm.io is incorrect, or do I miss something here?

Thanks!

1

Yes, I believe the https://ethervm.io/ is incorrect here.

From the yellowpaper:

Thus the operand order is: gas, to, value, in offset, in size, out offset, out size

There are seven parameters, with gas being the topmost on the stack.

JULIA also defines it with seven parameters:

call(g:u256, a:u256, v:u256, in:u256, insize:u256, out:u256, outsize:u256) -> r:u256

1

It does 7 pops but these pops are not visible as OPCODES during regular EVM traces , they are executed in Golang

See for yourself (core/vm/instructions.go)

func opCall(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
    // Pop gas. The actual gas in in evm.callGasTemp.
    evm.interpreter.intPool.put(stack.pop())
    gas := evm.callGasTemp
    // Pop other call parameters.
    addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop()
    toAddr := common.BigToAddress(addr)
    value = math.U256(value)
    // Get the arguments from the memory.
    args := memory.Get(inOffset.Int64(), inSize.Int64())

    if value.Sign() != 0 {
        gas += params.CallStipend
    }
    ret, returnGas, err := evm.Call(contract, toAddr, args, gas, value)
    if err != nil {
        stack.push(evm.interpreter.intPool.getZero())
    } else {
        stack.push(evm.interpreter.intPool.get().SetUint64(1))
    }
    if err == nil || err == errExecutionReverted {
        memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
    }
    contract.Gas += returnGas

    evm.interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize)
    return ret, nil
}

pop is defined as:

func (st *Stack) pop() (ret *big.Int) {
    ret = st.data[len(st.data)-1]
    st.data = st.data[:len(st.data)-1]
    return
}
  • 1
    This code seems to show seven pops (one for gas and then six for the other parameters), confirming that the info on ethervm.io is incorrect. – smarx Aug 19 '18 at 17:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.