From 09324831c363a9c8f2cc480525654741e85f6609 Mon Sep 17 00:00:00 2001 From: Andrei Maiboroda Date: Thu, 29 Sep 2022 12:22:50 +0200 Subject: [PATCH] core/vm: Make PC relative to code section for EOF1 code --- core/vm/contract.go | 36 ++++++++++++++++++++++++++--- core/vm/eof.go | 27 ++++++++++++++++++++++ core/vm/eof_test.go | 12 ++++++++++ core/vm/evm.go | 51 ++++++++++++++++++++++++++++++++++------- core/vm/instructions.go | 18 ++++++++------- core/vm/interpreter.go | 2 +- 6 files changed, 126 insertions(+), 20 deletions(-) diff --git a/core/vm/contract.go b/core/vm/contract.go index bb0902969ec7..5fd9885db88f 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -50,6 +50,8 @@ type Contract struct { caller ContractRef self ContractRef + header EOF1Header + jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis. analysis bitvec // Locally cached result of JUMPDEST analysis @@ -143,7 +145,7 @@ func (c *Contract) AsDelegate() *Contract { // GetOp returns the n'th element in the contract's byte array func (c *Contract) GetOp(n uint64) OpCode { - if n < uint64(len(c.Code)) { + if n < c.CodeEndOffset() { return OpCode(c.Code[n]) } @@ -177,18 +179,46 @@ func (c *Contract) Value() *big.Int { return c.value } +// IsLegacy returns true if contract is not EOF +func (c *Contract) IsLegacy() bool { + // EOF1 doesn't allow contracts without code section + return c.header.codeSize == 0 +} + +// CodeBeginOffset returns starting offset of the code section +func (c *Contract) CodeBeginOffset() uint64 { + if c.IsLegacy() { + return 0 + } + return c.header.CodeBeginOffset() +} + +// CodeEndOffset returns offset of the code section end +func (c *Contract) CodeEndOffset() uint64 { + if c.IsLegacy() { + return uint64(len(c.Code)) + } + return c.header.CodeEndOffset() +} + // SetCallCode sets the code of the contract and address of the backing data // object -func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, header *EOF1Header) { c.Code = code c.CodeHash = hash c.CodeAddr = addr + + c.header.codeSize = header.codeSize + c.header.dataSize = header.dataSize } // SetCodeOptionalHash can be used to provide code, but it's optional to provide hash. // In case hash is not provided, the jumpdest analysis will not be saved to the parent context -func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) { +func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash, header *EOF1Header) { c.Code = codeAndHash.code c.CodeHash = codeAndHash.hash c.CodeAddr = addr + + c.header.codeSize = header.codeSize + c.header.dataSize = header.dataSize } diff --git a/core/vm/eof.go b/core/vm/eof.go index fa4d3a3edd35..d76188fc7f4a 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -122,3 +122,30 @@ func validateEOF(code []byte) bool { _, err := readEOF1Header(code) return err == nil } + +// readValidEOF1Header parses EOF1-formatted code header, assuming that it is already validated +func readValidEOF1Header(code []byte) EOF1Header { + var header EOF1Header + codeSizeOffset := 2 + eofMagicLen + header.codeSize = binary.BigEndian.Uint16(code[codeSizeOffset : codeSizeOffset+2]) + if code[codeSizeOffset+2] == 2 { + dataSizeOffset := codeSizeOffset + 3 + header.dataSize = binary.BigEndian.Uint16(code[dataSizeOffset : dataSizeOffset+2]) + } + return header +} + +// CodeBeginOffset returns starting offset of the code section +func (header *EOF1Header) CodeBeginOffset() uint64 { + if header.dataSize == 0 { + // len(magic) + version + code_section_id + code_section_size + terminator + return uint64(5 + eofMagicLen) + } + // len(magic) + version + code_section_id + code_section_size + data_section_id + data_section_size + terminator + return uint64(8 + eofMagicLen) +} + +// CodeEndOffset returns offset of the code section end +func (header *EOF1Header) CodeEndOffset() uint64 { + return header.CodeBeginOffset() + uint64(header.codeSize) +} diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 311fb2594296..c13970f6ac1a 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -142,3 +142,15 @@ func TestValidateEOF(t *testing.T) { } } } + +func TestReadValidEOF1Header(t *testing.T) { + for _, test := range eof1ValidTests { + header := readValidEOF1Header(common.Hex2Bytes(test.code)) + if header.codeSize != test.codeSize { + t.Errorf("code %v codeSize expected %v, got %v", test.code, test.codeSize, header.codeSize) + } + if header.dataSize != test.dataSize { + t.Errorf("code %v dataSize expected %v, got %v", test.code, test.dataSize, header.dataSize) + } + } +} diff --git a/core/vm/evm.go b/core/vm/evm.go index fc9caf7518ea..26e830ed4c8a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -221,10 +221,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ret, err = nil, nil // gas is unchanged } else { addrCopy := addr + + var header EOF1Header + if hasEIP3540(&evm.Config) && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above contract := NewContract(caller, AccountRef(addrCopy), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -278,10 +284,18 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr + + code := evm.StateDB.GetCode(addrCopy) + + var header EOF1Header + if hasEIP3540(&evm.Config) && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // 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(caller.Address()), value, gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -319,9 +333,17 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by ret, gas, err = RunPrecompiledContract(p, input, gas) } else { addrCopy := addr + + code := evm.StateDB.GetCode(addrCopy) + + var header EOF1Header + if hasEIP3540(&evm.Config) && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) ret, err = evm.interpreter.Run(contract, input, false) gas = contract.Gas } @@ -371,10 +393,18 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. addrCopy := addr + + code := evm.StateDB.GetCode(addrCopy) + + var header EOF1Header + if hasEIP3540(&evm.Config) && hasEOFMagic(code) { + header = readValidEOF1Header(code) + } + // 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(addrCopy), new(big.Int), gas) - contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code, &header) // 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. @@ -436,9 +466,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } - // Validate code if it claims to be EOF-formatted. - if hasEIP3540(&evm.Config) && hasEOFMagic(codeAndHash.code) && !validateEOF(codeAndHash.code) { - return nil, common.Address{}, gas, ErrInvalidCodeFormat + // Try to read code header if it claims to be EOF-formatted. + var header EOF1Header + if hasEIP3540(&evm.Config) && hasEOFMagic(codeAndHash.code) { + var err error + header, err = readEOF1Header(codeAndHash.code) + if err != nil { + return nil, common.Address{}, gas, ErrInvalidCodeFormat + } } // Create a new account on the state snapshot := evm.StateDB.Snapshot() @@ -451,7 +486,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // 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.SetCodeOptionalHash(&address, codeAndHash, &header) if evm.Config.Debug { if evm.depth == 0 { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 12490025f511..c5c23c84c42e 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -867,12 +867,13 @@ func makeLog(size int) executionFunc { // opPush1 is a specialized version of pushN func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( - codeLen = uint64(len(scope.Contract.Code)) + codeEnd = scope.Contract.CodeEndOffset() integer = new(uint256.Int) ) *pc += 1 - if *pc < codeLen { - scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) + dataPos := scope.Contract.CodeBeginOffset() + *pc + if dataPos < codeEnd { + scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[dataPos]))) } else { scope.Stack.push(integer.Clear()) } @@ -882,14 +883,15 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by // make push instruction function func makePush(size uint64, pushByteSize int) executionFunc { return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - codeLen := len(scope.Contract.Code) + codeEnd := int(scope.Contract.CodeEndOffset()) - startMin := codeLen - if int(*pc+1) < startMin { - startMin = int(*pc + 1) + startMin := codeEnd + pcAbsolute := scope.Contract.CodeBeginOffset() + *pc + if int(pcAbsolute+1) < startMin { + startMin = int(pcAbsolute + 1) } - endMin := codeLen + endMin := codeEnd if startMin+pushByteSize < endMin { endMin = startMin + pushByteSize } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 40fe23dc516c..951bc31eb12d 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -184,7 +184,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. - op = contract.GetOp(pc) + op = contract.GetOp(contract.CodeBeginOffset() + pc) operation := in.cfg.JumpTable[op] cost = operation.constantGas // For tracing // Validate stack