core/vm: added EOFCREATE opcode

pull/30511/head
Marius van der Wijden 5 months ago
parent a58ba40cf6
commit 9fa14a1af4
  1. 52
      core/vm/eof_instructions.go
  2. 2
      core/vm/errors.go
  3. 69
      core/vm/evm.go

@ -21,6 +21,8 @@ import (
"fmt"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params"
)
// opRjump implements the RJUMP opcode.
@ -120,7 +122,55 @@ func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
// opEOFCreate implements the EOFCREATE opcode
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
if interpreter.readOnly {
return nil, ErrWriteProtection
}
var (
code = scope.Contract.CodeAt(scope.CodeSection)
idx = code[*pc+1]
value = scope.Stack.pop()
salt = scope.Stack.pop()
offset, size = scope.Stack.pop(), scope.Stack.pop()
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
)
if int(idx) >= len(scope.Contract.Container.subContainerCodes) {
return nil, fmt.Errorf("invalid subcontainer")
}
// Deduct hashing charge
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
hashingCharge := (params.Keccak256WordGas) * ((uint64(len(scope.Contract.Container.subContainerCodes[idx])) + 31) / 32)
if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok {
return nil, ErrGasUintOverflow
}
if interpreter.evm.Config.Tracer != nil {
if interpreter.evm.Config.Tracer != nil {
interpreter.evm.Config.Tracer.OnOpcode(*pc, byte(EOFCREATE), 0, hashingCharge, scope, interpreter.returnData, interpreter.evm.depth, nil)
}
}
gas := scope.Contract.Gas
// Reuse last popped value from stack
stackvalue := size
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// Skip the immediate
*pc += 1
res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerCodes[idx], gas, &value, &salt)
if suberr != nil {
stackvalue.Clear()
} else {
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted {
interpreter.returnData = res // set REVERT data to return data buffer
return res, nil
}
interpreter.returnData = nil // clear dirty return data buffer
return nil, nil
}
// opReturnContract implements the RETURNCONTRACT opcode

@ -36,7 +36,9 @@ var (
ErrWriteProtection = errors.New("write protection")
ErrReturnDataOutOfBounds = errors.New("return data out of bounds")
ErrGasUintOverflow = errors.New("gas uint64 overflow")
ErrLegacyCode = errors.New("invalid code: EOF contract must not deploy legacy code")
ErrInvalidCode = errors.New("invalid code: must not begin with 0xef")
ErrInvalidEOF = errors.New("invalid eof")
ErrInvalidEOFInitcode = errors.New("invalid eof initcode")
ErrNonceUintOverflow = errors.New("nonce uint64 overflow")

@ -435,7 +435,7 @@ 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 *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *uint256.Int, address common.Address, typ OpCode, input []byte, allowEOF bool) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, typ, caller.Address(), address, codeAndHash.code, gas, value.ToBig())
defer func(startGas uint64) {
@ -450,6 +450,33 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
// 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. If
// the initcode is EOF, contract.Container will be set.
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)
contract.IsDeployment = true
// Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail.
isInitcodeEOF := hasEOFMagic(codeAndHash.code)
if isInitcodeEOF {
if allowEOF {
// If the initcode is EOF, verify it is well-formed.
var c Container
if err := c.UnmarshalBinary(codeAndHash.code, isInitcodeEOF); err != nil {
return nil, common.Address{}, gas, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, err)
}
if err := c.ValidateCode(evm.interpreter.tableEOF, isInitcodeEOF); err != nil {
return nil, common.Address{}, gas, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, err)
}
contract.Container = &c
} else {
// Don't allow EOF contract to execute legacy initcode.
return nil, common.Address{}, gas, ErrLegacyCode
}
}
// Check for nonce overflow and then update caller nonce by 1.
nonce := evm.StateDB.GetNonce(caller.Address())
if nonce+1 < nonce {
return nil, common.Address{}, gas, ErrNonceUintOverflow
@ -517,13 +544,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
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.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)
contract.IsDeployment = true
ret, err = evm.initNewContract(contract, address, value)
ret, err = evm.initNewContract(contract, address, value, isInitcodeEOF)
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
@ -535,7 +556,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// initNewContract runs a new contract's creation code, performs checks on the
// resulting code that is to be deployed, and consumes necessary gas.
func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int) ([]byte, error) {
func (evm *EVM) initNewContract(contract *Contract, address common.Address, value *uint256.Int, isInitcodeEOF bool) ([]byte, error) {
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
@ -543,7 +564,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu
}
}
ret, err := evm.interpreter.Run(contract, nil, false, true)
ret, err := evm.interpreter.Run(contract, nil, false, contract.IsDeployment)
if err != nil {
return ret, err
}
@ -553,9 +574,22 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu
return ret, ErrMaxCodeSizeExceeded
}
// Reject legacy contract deployment from EOF.
if isInitcodeEOF && !hasEOFMagic(ret) {
return ret, fmt.Errorf("%w: %v", ErrInvalidEOFInitcode, ErrLegacyCode)
}
// Reject EOF deployment from legacy.
if isInitcodeEOF && hasEOFMagic(ret) {
return ret, ErrLegacyCode
}
// Reject code starting with 0xEF if EIP-3541 is enabled.
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
return ret, ErrInvalidCode
if len(ret) >= 1 && HasEOFByte(ret) {
if evm.chainRules.IsPrague && isInitcodeEOF {
// Don't reject EOF contracts after Shanghai
} else if evm.chainRules.IsLondon {
err = ErrInvalidCode
}
}
if !evm.chainRules.IsEIP4762 {
@ -576,7 +610,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address, valu
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint256.Int, allowEOF bool) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE)
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr, CREATE, nil, allowEOF)
}
// Create2 creates a new contract using code as deployment code.
@ -586,7 +620,14 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *uint2
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
codeAndHash := &codeAndHash{code: code}
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, nil, false)
}
// EOFCreate creates a new eof contract.
func (evm *EVM) EOFCreate(caller ContractRef, input []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
codeAndHash := &codeAndHash{code: subcontainer}
contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, input, true)
}
// ChainConfig returns the environment's chain configuration

Loading…
Cancel
Save