From 9fa14a1af4d029de723bfe7573db379f10085f3d Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 26 Sep 2024 14:06:25 +0200 Subject: [PATCH] core/vm: added EOFCREATE opcode --- core/vm/eof_instructions.go | 52 +++++++++++++++++++++++++++- core/vm/errors.go | 2 ++ core/vm/evm.go | 69 +++++++++++++++++++++++++++++-------- 3 files changed, 108 insertions(+), 15 deletions(-) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index d187342551..1b91819f87 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.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 diff --git a/core/vm/errors.go b/core/vm/errors.go index 0cfdcd7d74..ef3d285a21 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -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") diff --git a/core/vm/evm.go b/core/vm/evm.go index 7c979d96d3..73a3a78b63 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -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