Charge witness gas when calling/creating a contract (#60)
* Charge witness gas when calling/creating a contract Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com> * gofmt * replace checks with evm.Access!=nil with IsCancun * remove double-charging of witness access costs for contract creation initialization Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>
This commit is contained in:
@ -202,6 +202,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// proof of absence
|
||||
tryConsumeGas(&gas, evm.Accesses.TouchAndChargeProofOfAbsence(caller.Address().Bytes()))
|
||||
}
|
||||
// Calling a non existing account, don't do anything, but ping the tracer
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
@ -216,6 +220,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) && value.Sign() != 0 {
|
||||
callerBalanceBefore := evm.StateDB.GetBalanceLittleEndian(caller.Address())
|
||||
targetBalanceBefore := evm.StateDB.GetBalanceLittleEndian(addr)
|
||||
evm.Accesses.SetLeafValuesValueTransfer(caller.Address().Bytes()[:], addr[:], callerBalanceBefore, targetBalanceBefore)
|
||||
}
|
||||
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
|
||||
|
||||
// Capture the tracer start/end events in debug mode
|
||||
@ -240,21 +249,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||
// 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.
|
||||
code := evm.StateDB.GetCode(addr)
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(len(code))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr[:], codeSizeBytes[:])
|
||||
}
|
||||
|
||||
if len(code) == 0 {
|
||||
ret, err = nil, nil // gas is unchanged
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// Touch the account data
|
||||
var data [32]byte
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:])
|
||||
binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr))
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyNonce(addr[:]), data[:])
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes())
|
||||
binary.BigEndian.PutUint64(data[:], uint64(len(code)))
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:])
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())
|
||||
}
|
||||
|
||||
addrCopy := addr
|
||||
// If the account has no code, we can abort here
|
||||
// The depth-check is already done, and precompiles handled above
|
||||
@ -316,6 +320,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(evm.StateDB.GetCodeSize(addr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:])
|
||||
}
|
||||
addrCopy := addr
|
||||
// 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.
|
||||
@ -360,6 +370,12 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(evm.StateDB.GetCodeSize(addr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:])
|
||||
}
|
||||
addrCopy := addr
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
|
||||
@ -412,6 +428,12 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(p, input, gas)
|
||||
} else {
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
codeSize := uint64(evm.StateDB.GetCodeSize(addr))
|
||||
var codeSizeBytes [32]byte
|
||||
binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize)
|
||||
evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:])
|
||||
}
|
||||
// At this point, we use a copy of address. If we don't, the go compiler will
|
||||
// leak the 'contract' to the outer scope, and make allocation for 'contract'
|
||||
// even if the actual execution ends on RunPrecompiled above.
|
||||
@ -449,6 +471,13 @@ func (c *codeAndHash) Hash() common.Hash {
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) {
|
||||
var zeroVerkleLeaf [32]byte
|
||||
var balanceBefore []byte
|
||||
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
evm.Accesses.SetLeafValuesContractCreateInit(address.Bytes()[:], zeroVerkleLeaf[:])
|
||||
}
|
||||
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
@ -478,6 +507,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
if evm.chainRules.IsEIP158 {
|
||||
evm.StateDB.SetNonce(address, 1)
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// note: assumption is that the nonce, code size, code hash
|
||||
// will be 0x0000...00 at the target account before it is created
|
||||
// otherwise would imply contract creation collision which is
|
||||
// impossible if self-destruct is removed
|
||||
balanceBefore = evm.StateDB.GetBalanceLittleEndian(address)
|
||||
}
|
||||
|
||||
evm.Context.Transfer(evm.StateDB, caller.Address(), address, value)
|
||||
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
@ -535,6 +572,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:], value.Sign() != 0)) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
err = ErrOutOfGas
|
||||
} else {
|
||||
evm.Accesses.SetLeafValuesContractCreateCompleted(address.Bytes()[:], zeroVerkleLeaf[:], zeroVerkleLeaf[:])
|
||||
if value.Sign() != 0 {
|
||||
evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(address.Bytes()[:]), balanceBefore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if evm.Config.Debug {
|
||||
if evm.depth == 0 {
|
||||
evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
||||
)
|
||||
@ -408,13 +409,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||
transfersValue = !stack.Back(2).IsZero()
|
||||
address = common.Address(stack.Back(1).Bytes20())
|
||||
)
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// Charge witness costs
|
||||
for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ {
|
||||
index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i))
|
||||
gas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil)
|
||||
}
|
||||
}
|
||||
|
||||
if evm.chainRules.IsEIP158 {
|
||||
if transfersValue && evm.StateDB.Empty(address) {
|
||||
@ -442,6 +436,20 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:]))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
if transfersValue {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:]))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
@ -468,6 +476,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -484,6 +501,15 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -500,6 +526,15 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
|
||||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
if _, isPrecompile := evm.precompile(address); !isPrecompile {
|
||||
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
@ -520,6 +555,12 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
||||
}
|
||||
}
|
||||
|
||||
if evm.chainConfig.IsCancun(evm.Context.BlockNumber) {
|
||||
// TODO turn this into a panic (when we are sure this method
|
||||
// will never execute when verkle is enabled)
|
||||
log.Warn("verkle witness accumulation not supported for selfdestruct")
|
||||
}
|
||||
|
||||
if !evm.StateDB.HasSuicided(contract.Address()) {
|
||||
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package vm
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
@ -667,6 +668,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
||||
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
gas = scope.Contract.Gas
|
||||
)
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address()))
|
||||
statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:])
|
||||
if !tryConsumeGas(&gas, statelessGas) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
}
|
||||
if interpreter.evm.chainRules.IsEIP150 {
|
||||
gas -= gas / 64
|
||||
}
|
||||
@ -709,6 +717,14 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
||||
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
gas = scope.Contract.Gas
|
||||
)
|
||||
if interpreter.evm.chainConfig.IsCancun(interpreter.evm.Context.BlockNumber) {
|
||||
codeAndHash := &codeAndHash{code: input}
|
||||
contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
|
||||
statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:])
|
||||
if !tryConsumeGas(&gas, statelessGas) {
|
||||
return nil, ErrExecutionReverted
|
||||
}
|
||||
}
|
||||
|
||||
// Apply EIP150
|
||||
gas -= gas / 64
|
||||
|
@ -47,6 +47,9 @@ type StateDB interface {
|
||||
GetState(common.Address, common.Hash) common.Hash
|
||||
SetState(common.Address, common.Hash, common.Hash)
|
||||
|
||||
GetBalanceLittleEndian(address common.Address) []byte
|
||||
GetNonceLittleEndian(address common.Address) []byte
|
||||
|
||||
Suicide(common.Address) bool
|
||||
HasSuicided(common.Address) bool
|
||||
|
||||
|
Reference in New Issue
Block a user