código de operación de callvalue, ¿para qué?

¿Alguien sabe para qué sirve el código de operación CALLVALUE? Me gustaría entenderlo bien. ¿Tienes un ejemplo en el que pueda entenderlo fácilmente?

Respuestas (1)

Cuando ocurre una llamada de contrato, recibe algunos parámetros en la pila, entre estos parámetros, Valuecontiene la cantidad de ETH que recibirá este contrato para realizar algunas operaciones con él.

La emv.Callfunción se ve así:

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, gas, nil
    }

    // Fail if we're trying to execute above the call depth limit
    if evm.depth > int(params.CallCreateDepth) {
        return nil, gas, ErrDepth
    }
    // Fail if we're trying to transfer more than the available balance
    if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, gas, ErrInsufficientBalance
    }

    var (
        to       = AccountRef(addr)
        snapshot = evm.StateDB.Snapshot()
    )
    if !evm.StateDB.Exist(addr) {
        precompiles := PrecompiledContractsHomestead
        if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
            precompiles = PrecompiledContractsByzantium
        }
        if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 {
            // Calling a non existing account, don't do antything, but ping the tracer
            if evm.vmConfig.Debug && evm.depth == 0 {
                evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
                evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
            }
            return nil, gas, nil
        }
        evm.StateDB.CreateAccount(addr)
    }
    evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)

    // Initialise a new contract and set the code that is to be used by the EVM.
    // The contract is a scoped environment for this execution context only.
    contract := NewContract(caller, to, value, gas)
    contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

    start := time.Now()

    // Capture the tracer start/end events in debug mode
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)

        defer func() { // Lazy evaluation of the parameters
            evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
        }()
    }
    ret, err = run(evm, contract, input)

    // When an error was returned by the EVM or when setting the creation code
    // above we revert to the snapshot and consume any gas remaining. Additionally
    // when we're in homestead this also counts for code storage gas errors.
    if err != nil {
        evm.StateDB.RevertToSnapshot(snapshot)
        if err != errExecutionReverted {
            contract.UseGas(contract.Gas)
        }
    }
    return ret, contract.Gas, err
}

Si observa, obtiene los Valueparámetros de entrada y los almacena en la estructura del contrato:

// Contract represents an ethereum contract in the state database. It contains
// the the contract code, calling arguments. Contract implements ContractRef
type Contract struct {
    // CallerAddress is the result of the caller which initialised this
    // contract. However when the "call method" is delegated this value
    // needs to be initialised to that of the caller's caller.
    CallerAddress common.Address
    caller        ContractRef
    self          ContractRef

    jumpdests destinations // result of JUMPDEST analysis.

    Code     []byte
    CodeHash common.Hash
    CodeAddr *common.Address
    Input    []byte

    Gas   uint64
    value *big.Int

    Args []byte

    DelegateCall bool
}

Aquí es donde se Valuealmacena:

value *big.Int

Ahora en evm.Call()función ves esta línea:

contract := NewContract(caller, to, value, gas)

Esta función almacena el Valuepara su posterior ejecución:

// NewContract returns a new contract environment for the execution of EVM.
func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract {
    c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil}

    if parent, ok := caller.(*Contract); ok {
        // Reuse JUMPDEST analysis from parent context if available.
        c.jumpdests = parent.jumpdests
    } else {
        c.jumpdests = make(destinations)
    }

    // Gas should be a pointer so it can safely be reduced through the run
    // This pointer will be off the state transition
    c.Gas = gas
    // ensures a value is set
    c.value = value

    return c
}

Cuando el EVM encuentra un código de operación CALLVALUE, lo almacena Valuepreviamente en la Contractestructura:

func opCallValue(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
    stack.push(evm.interpreter.intPool.get().Set(contract.value))
    return nil, nil
}

Entonces, en resumen, CALLVALUEle da la cantidad de ETH transferida al contrato por contrato principal o transacción. Perdón por tan larga explicación.