Skip to content

Commit

Permalink
core/vm: Make PC relative to code section for EOF1 code
Browse files Browse the repository at this point in the history
  • Loading branch information
gumb0 committed Sep 29, 2022
1 parent 94b4522 commit 0932483
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 20 deletions.
36 changes: 33 additions & 3 deletions core/vm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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])
}

Expand Down Expand Up @@ -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
}
27 changes: 27 additions & 0 deletions core/vm/eof.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
12 changes: 12 additions & 0 deletions core/vm/eof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
51 changes: 43 additions & 8 deletions core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand All @@ -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 {
Expand Down
18 changes: 10 additions & 8 deletions core/vm/instructions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0932483

Please sign in to comment.