This commit is contained in:
Marius van der Wijden 2025-03-07 13:47:50 +01:00 committed by GitHub
commit a0eb3dfd08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1022 additions and 229 deletions

View File

@ -150,6 +150,9 @@ func Transition(ctx *cli.Context) error {
if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
return err
}
if err := applyEOFChecks(&prestate, chainConfig); err != nil {
return err
}
// Configure tracer
if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
@ -265,6 +268,30 @@ func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error {
return nil
}
// applyEOFChecks does a sanity check of the prestate EOF code validity, to avoid panic during state transition.
func applyEOFChecks(prestate *Prestate, chainConfig *params.ChainConfig) error {
if !chainConfig.IsOsaka(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) {
return nil
}
for addr, acc := range prestate.Pre {
if vm.HasEOFMagic(acc.Code) {
var (
c vm.Container
err error
)
err = c.UnmarshalBinary(acc.Code, false)
if err == nil {
jt := vm.NewEOFInstructionSetForTesting()
err = c.ValidateCode(&jt, false)
}
if err != nil {
return NewError(ErrorConfig, fmt.Errorf("code at %s considered invalid: %v", addr, err))
}
}
}
return nil
}
type Alloc map[common.Address]types.Account
func (g Alloc) OnRoot(common.Hash) {}

View File

@ -18,6 +18,7 @@ package core
import (
"bytes"
"errors"
"fmt"
"math"
"math/big"
@ -483,7 +484,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
)
if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value, rules.IsOsaka)
// Special case for EOF, if the initcode or deployed code is
// invalid, the tx is considered valid (so update nonce), but
// gas for initcode execution is not consumed.
// Only intrinsic creation transaction costs are charged.
if errors.Is(vmerr, vm.ErrInvalidEOFInitcode) {
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeInvalidEOF)
}
} else {
// Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)

View File

@ -43,6 +43,8 @@ type OpContext interface {
CallValue() *uint256.Int
CallInput() []byte
ContractCode() []byte
CurrentCodeSection() uint64
ReturnStackDepth() uint64
}
// StateDB gives tracers access to the whole state.
@ -371,4 +373,8 @@ const (
// NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure.
// It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal).
NonceChangeRevert NonceChangeReason = 6
// NonceChangeInvalidEOF is emitted when the nonce is changed when a new contract is created,
// but the creation fails because of an EOF error.
NonceChangeInvalidEOF NonceChangeReason = 6
)

View File

@ -0,0 +1,76 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"testing"
"github.com/ethereum/go-ethereum/common"
)
func TestEOFAnalysis(t *testing.T) {
tests := []struct {
code []byte
exp byte
which int
}{
{[]byte{byte(RJUMP), 0x01, 0x01, 0x01}, 0b0000_0110, 0},
{[]byte{byte(RJUMPI), byte(RJUMP), byte(RJUMP), byte(RJUMPI)}, 0b0011_0110, 0},
{[]byte{byte(RJUMPV), 0x02, byte(RJUMP), 0x00, byte(RJUMPI), 0x00}, 0b0011_1110, 0},
}
for i, test := range tests {
ret := eofCodeBitmap(test.code)
if ret[test.which] != test.exp {
t.Fatalf("test %d: expected %x, got %02x", i, test.exp, ret[test.which])
}
}
}
func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
var op OpCode
bencher := func(b *testing.B) {
code := make([]byte, analysisCodeSize)
b.SetBytes(analysisCodeSize)
for i := range code {
code[i] = byte(op)
}
bits := make(bitvec, len(code)/8+1+4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
clear(bits)
eofCodeBitmapInternal(code, bits)
}
}
for op = PUSH1; op <= PUSH32; op++ {
bench.Run(op.String(), bencher)
}
op = JUMPDEST
bench.Run(op.String(), bencher)
op = STOP
bench.Run(op.String(), bencher)
op = RJUMPV
bench.Run(op.String(), bencher)
op = EOFCREATE
bench.Run(op.String(), bencher)
}
func FuzzCodeAnalysis(f *testing.F) {
f.Add(common.FromHex("5e30303030"))
f.Fuzz(func(t *testing.T, data []byte) {
eofCodeBitmap(data)
})
}

View File

@ -105,31 +105,3 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
op = STOP
bench.Run(op.String(), bencher)
}
func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
var op OpCode
bencher := func(b *testing.B) {
code := make([]byte, analysisCodeSize)
b.SetBytes(analysisCodeSize)
for i := range code {
code[i] = byte(op)
}
bits := make(bitvec, len(code)/8+1+4)
b.ResetTimer()
for i := 0; i < b.N; i++ {
clear(bits)
eofCodeBitmapInternal(code, bits)
}
}
for op = PUSH1; op <= PUSH32; op++ {
bench.Run(op.String(), bencher)
}
op = JUMPDEST
bench.Run(op.String(), bencher)
op = STOP
bench.Run(op.String(), bencher)
op = RJUMPV
bench.Run(op.String(), bencher)
op = EOFCREATE
bench.Run(op.String(), bencher)
}

View File

@ -34,9 +34,10 @@ type Contract struct {
jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
analysis bitvec // Locally cached result of JUMPDEST analysis
Code []byte
CodeHash common.Hash
Input []byte
Code []byte
Container *Container
CodeHash common.Hash
Input []byte
// is the execution frame represented by this object a contract deployment
IsDeployment bool
@ -113,7 +114,6 @@ func (c *Contract) GetOp(n uint64) OpCode {
if n < uint64(len(c.Code)) {
return OpCode(c.Code[n])
}
return STOP
}
@ -158,7 +158,13 @@ func (c *Contract) Value() *uint256.Int {
return c.value
}
// SetCallCode sets the code of the contract,
// IsEOF returns whether the contract is EOF.
func (c *Contract) IsEOF() bool {
return c.Container != nil
}
// SetCallCode sets the code of the contract and address of the backing data
// object
func (c *Contract) SetCallCode(hash common.Hash, code []byte) {
c.Code = code
c.CodeHash = hash

View File

@ -48,13 +48,13 @@ const (
var eofMagic = []byte{0xef, 0x00}
// HasEOFByte returns true if code starts with 0xEF byte
func HasEOFByte(code []byte) bool {
// hasEOFByte returns true if code starts with 0xEF byte
func hasEOFByte(code []byte) bool {
return len(code) != 0 && code[0] == eofFormatByte
}
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
func hasEOFMagic(code []byte) bool {
// HasEOFMagic returns true if code starts with magic defined by EIP-3540
func HasEOFMagic(code []byte) bool {
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
}
@ -66,12 +66,12 @@ func isEOFVersion1(code []byte) bool {
// Container is an EOF container object.
type Container struct {
types []*functionMetadata
codeSections [][]byte
subContainers []*Container
subContainerCodes [][]byte
data []byte
dataSize int // might be more than len(data)
types []*functionMetadata
codeSectionOffsets []int // contains all the offsets of the codeSections. The last item marks the end
subContainers []*Container
subContainerOffsets []int // contains all the offsets of the subContainers. The last item marks the end
dataSize int // might be more than len(data)
rawContainer []byte
}
// functionMetadata is an EOF function signature.
@ -105,8 +105,44 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error {
return nil
}
// codeSectionAt returns the code section at index.
// returns an empty slice if the index is out of bounds.
func (c *Container) codeSectionAt(index int) []byte {
if index >= len(c.codeSectionOffsets)-1 || index < 0 {
return c.rawContainer[0:0]
}
return c.rawContainer[c.codeSectionOffsets[index]:c.codeSectionOffsets[index+1]]
}
// subContainerAt returns the sub container at index.
// returns an empty slice if the index is out of bounds.
func (c *Container) subContainerAt(index int) []byte {
if index >= len(c.subContainerOffsets)-1 || index < 0 {
return c.rawContainer[0:0]
}
return c.rawContainer[c.subContainerOffsets[index]:c.subContainerOffsets[index+1]]
}
func (c *Container) dataOffset() int {
if len(c.subContainerOffsets) > 0 {
return c.subContainerOffsets[len(c.subContainerOffsets)-1]
}
return c.codeSectionOffsets[len(c.codeSectionOffsets)-1]
}
func (c *Container) dataLen() int {
return len(c.rawContainer) - c.dataOffset()
}
func (c *Container) getDataAt(offset, length uint64) []byte {
return getData(c.rawContainer, uint64(c.dataOffset())+offset, length)
}
// MarshalBinary encodes an EOF container into binary format.
func (c *Container) MarshalBinary() []byte {
// Drop the end markers
codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1]
// Build EOF prefix.
b := make([]byte, 2)
copy(b, eofMagic)
@ -116,9 +152,9 @@ func (c *Container) MarshalBinary() []byte {
b = append(b, kindTypes)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
b = append(b, kindCode)
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
for _, codeSection := range c.codeSections {
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSectionOffsets)))
for s := range codeSectionOffsets {
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionAt(s))))
}
var encodedContainer [][]byte
if len(c.subContainers) != 0 {
@ -138,13 +174,13 @@ func (c *Container) MarshalBinary() []byte {
for _, ty := range c.types {
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
}
for _, code := range c.codeSections {
b = append(b, code...)
for s := range codeSectionOffsets {
b = append(b, c.codeSectionAt(s)...)
}
for _, section := range encodedContainer {
b = append(b, section...)
}
b = append(b, c.data...)
b = append(b, c.rawContainer[c.dataOffset():]...)
return b
}
@ -160,7 +196,7 @@ func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
}
func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
if !hasEOFMagic(b) {
if !HasEOFMagic(b) {
return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
}
if len(b) < 14 {
@ -282,21 +318,22 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
// Parse code sections.
idx += typesSize
codeSections := make([][]byte, len(codeSizes))
codeSectionOffsets := make([]int, len(codeSizes))
for i, size := range codeSizes {
if size == 0 {
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
}
codeSections[i] = b[idx : idx+size]
codeSectionOffsets[i] = idx
idx += size
}
c.codeSections = codeSections
// add the end marker to the codeSection offsets
c.codeSectionOffsets = append(codeSectionOffsets, idx)
// Parse the optional container sizes.
if len(containerSizes) != 0 {
if len(containerSizes) > maxContainerSections {
return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
}
subContainerCodes := make([][]byte, 0, len(containerSizes))
subContainerOffsets := make([]int, 0, len(containerSizes))
subContainers := make([]*Container, 0, len(containerSizes))
for i, size := range containerSizes {
if size == 0 || idx+size > len(b) {
@ -311,23 +348,21 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err
}
subContainers = append(subContainers, subC)
subContainerCodes = append(subContainerCodes, b[idx:end])
subContainerOffsets = append(subContainerOffsets, idx)
idx += size
}
c.subContainers = subContainers
c.subContainerCodes = subContainerCodes
// add the end marker to the subContainer offsets
c.subContainerOffsets = append(subContainerOffsets, idx)
}
//Parse data section.
end := len(b)
if !isInitcode {
end = min(idx+dataSize, len(b))
}
if topLevel && len(b) != idx+dataSize {
return errTruncatedTopLevelContainer
}
c.data = b[idx:end]
c.rawContainer = b
return nil
}
@ -355,7 +390,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
// should not mean 2 and 3 should be visited twice
var (
index = toVisit[0]
code = c.codeSections[index]
code = c.codeSectionAt(index)
)
if _, ok := visited[index]; !ok {
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
@ -387,7 +422,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
toVisit = toVisit[1:]
}
// Make sure every code section is visited at least once.
if len(visited) != len(c.codeSections) {
if len(visited) != len(c.codeSectionOffsets)-1 {
return errUnreachableCode
}
for idx, container := range c.subContainers {
@ -464,6 +499,8 @@ func sum(list []int) (s int) {
}
func (c *Container) String() string {
// Drop the end markers
codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1]
var output = []string{
"Header",
fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
@ -471,14 +508,13 @@ func (c *Container) String() string {
fmt.Sprintf(" - KindType: %02x", kindTypes),
fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
fmt.Sprintf(" - KindCode: %02x", kindCode),
fmt.Sprintf(" - Number of code sections: %d", len(codeSectionOffsets)),
fmt.Sprintf(" - KindData: %02x", kindData),
fmt.Sprintf(" - DataSize: %04x", len(c.data)),
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
fmt.Sprintf(" - DataSize: %04x", c.dataSize),
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
for i := range codeSectionOffsets {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(c.codeSectionAt(i))))
}
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
if len(c.subContainers) > 0 {
for i, section := range c.subContainers {
@ -490,12 +526,12 @@ func (c *Container) String() string {
output = append(output, fmt.Sprintf(" - Type %v: %x", i,
[]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
for i := range codeSectionOffsets {
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionAt(i)))
}
for i, section := range c.subContainers {
output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
}
output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffset():]))
return strings.Join(output, "\n")
}

View File

@ -16,97 +16,443 @@
package vm
import (
"encoding/binary"
"errors"
"fmt"
"math"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
// opRjump implements the RJUMP opcode.
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
offset = parseInt16(scope.Contract.Code[*pc+1:])
)
// move pc past op and operand (+3), add relative offset, subtract 1 to
// account for interpreter loop.
*pc = uint64(int64(*pc+3) + int64(offset) - 1)
return nil, nil
}
// opRjumpi implements the RJUMPI opcode
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
condition := scope.Stack.pop()
if condition.BitLen() == 0 {
// Not branching, just skip over immediate argument.
*pc += 2
return nil, nil
}
return opRjump(pc, interpreter, scope)
}
// opRjumpv implements the RJUMPV opcode
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
maxIndex = uint64(scope.Contract.Code[*pc+1]) + 1
idx = scope.Stack.pop()
)
if idx, overflow := idx.Uint64WithOverflow(); overflow || idx >= maxIndex {
// Index out-of-bounds, don't branch, just skip over immediate
// argument.
*pc += 1 + maxIndex*2
return nil, nil
}
offset := parseInt16(scope.Contract.Code[*pc+2+2*idx.Uint64():])
// move pc past op and count byte (2), move past count number of 16bit offsets (count*2), add relative offset, subtract 1 to
// account for interpreter loop.
*pc = uint64(int64(*pc+2+maxIndex*2) + int64(offset) - 1)
return nil, nil
}
// opCallf implements the CALLF opcode
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
idx = binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])
typ = scope.Contract.Container.types[idx]
)
if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 {
return nil, fmt.Errorf("stack overflow")
}
if scope.ReturnStack.Len() > 1024 {
return nil, fmt.Errorf("return stack overflow")
}
retCtx := &ReturnContext{
Section: scope.CodeSection,
Pc: *pc + 3,
StackHeight: scope.Stack.len() - int(typ.inputs),
}
scope.ReturnStack = append(scope.ReturnStack, retCtx)
scope.CodeSection = uint64(idx)
*pc = uint64(scope.Contract.Container.codeSectionOffsets[idx]) - 1
return nil, nil
}
// opRetf implements the RETF opcode
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
retCtx := scope.ReturnStack.Pop()
scope.CodeSection = retCtx.Section
*pc = retCtx.Pc - 1
// If returning from top frame, exit cleanly.
if scope.ReturnStack.Len() == 0 {
return nil, errStopToken
}
return nil, nil
}
// opJumpf implements the JUMPF opcode
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
idx = binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])
typ = scope.Contract.Container.types[idx]
)
if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 {
return nil, fmt.Errorf("stack overflow")
}
scope.CodeSection = uint64(idx)
*pc = uint64(scope.Contract.Container.codeSectionOffsets[idx]) - 1
return nil, nil
}
// 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 (
idx = scope.Contract.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.subContainerOffsets)-1 {
return nil, fmt.Errorf("invalid subcontainer")
}
subContainer := scope.Contract.Container.subContainerAt(int(idx))
// Deduct hashing charge
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
hashingCharge := (params.Keccak256WordGas) * ((uint64(len(subContainer)) + 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.Address(), input, subContainer, 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
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
if !scope.InitCodeMode {
return nil, errors.New("returncontract in non-initcode mode")
}
var (
idx = scope.Contract.Code[*pc+1]
offset = scope.Stack.pop()
size = scope.Stack.pop()
)
if int(idx) >= len(scope.Contract.Container.subContainerOffsets)-1 {
return nil, fmt.Errorf("invalid subcontainer")
}
ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
containerCode := scope.Contract.Container.subContainerAt(int(idx))
if len(containerCode) == 0 {
return nil, errors.New("nonexistant subcontainer")
}
// Validate the subcontainer
var c Container
if err := c.UnmarshalSubContainer(containerCode, false); err != nil {
return nil, err
}
if size.Uint64() > 0 {
// copy the container, so we don't rug parents referencing this data
oldContainer := c.rawContainer
c.rawContainer = make([]byte, 0, len(oldContainer)+len(ret))
c.rawContainer = append(c.rawContainer, oldContainer...)
c.rawContainer = append(c.rawContainer, ret...)
}
newDataSize := c.dataLen()
if newDataSize < c.dataSize {
return nil, errors.New("incomplete aux data")
}
c.dataSize = newDataSize
// probably unneeded as subcontainers are deeply validated
if err := c.ValidateCode(interpreter.tableEOF, false); err != nil {
return nil, err
}
// Restore context
retCtx := scope.ReturnStack.Pop()
scope.CodeSection = retCtx.Section
*pc = retCtx.Pc - 1 // account for interpreter loop
return c.MarshalBinary(), errStopToken
}
// opDataLoad implements the DATALOAD opcode
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
stackItem = scope.Stack.pop()
offset, overflow = stackItem.Uint64WithOverflow()
)
if overflow || offset > uint64(scope.Contract.Container.dataSize) {
stackItem.Clear()
scope.Stack.push(&stackItem)
} else {
data := scope.Contract.Container.getDataAt(offset, 32)
scope.Stack.push(stackItem.SetBytes(data))
}
return nil, nil
}
// opDataLoadN implements the DATALOADN opcode
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:]))
)
data := scope.Contract.Container.getDataAt(offset, 32)
scope.Stack.push(new(uint256.Int).SetBytes(data))
*pc += 2 // move past 2 byte immediate
return nil, nil
}
// opDataSize implements the DATASIZE opcode
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
item := uint256.NewInt(uint64(scope.Contract.Container.dataLen()))
scope.Stack.push(item)
return nil, nil
}
// opDataCopy implements the DATACOPY opcode
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
memOffset = scope.Stack.pop()
dataOffset = scope.Stack.pop()
size = scope.Stack.pop()
)
dataOffset64, overflow := dataOffset.Uint64WithOverflow()
if overflow || dataOffset64 > uint64(scope.Contract.Container.dataSize) {
// Setting to dataSize makes this a zero byte read
dataOffset64 = uint64(scope.Contract.Container.dataSize)
}
// memOffset and size are checked for overflow during memory expansion calculation.
data := scope.Contract.Container.getDataAt(dataOffset64, size.Uint64())
scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data)
return nil, nil
}
// opDupN implements the DUPN opcode
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
index = int(scope.Contract.Code[*pc+1]) + 1
)
scope.Stack.dup(index)
*pc += 1 // move past immediate
return nil, nil
}
// opSwapN implements the SWAPN opcode
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
index = int(scope.Contract.Code[*pc+1]) + 1
)
scope.Stack.swap(index + 1)
*pc += 1 // move past immediate
return nil, nil
}
// opExchange implements the EXCHANGE opcode
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
index = int(scope.Contract.Code[*pc+1])
n = (index >> 4) + 1
m = (index & 0x0F) + 1
)
scope.Stack.swapN(n+1, n+m+1)
*pc += 1 // move past immediate
return nil, nil
}
// opReturnDataLoad implements the RETURNDATALOAD opcode
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
var (
offset = scope.Stack.pop()
)
offset64, overflow := offset.Uint64WithOverflow()
if overflow {
offset64 = math.MaxUint64
}
scope.Stack.push(offset.SetBytes(getData(interpreter.returnData, offset64, 32)))
return nil, nil
}
// opExtCall implements the EOFCREATE opcode
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
stack := scope.Stack
// Use all available gas
gas := interpreter.evm.callGasTemp
// Pop other call parameters.
addr, inOffset, inSize, value := stack.pop(), stack.pop(), stack.pop(), stack.pop()
toAddr := common.Address(addr.Bytes20())
if addr.ByteLen() > 20 {
return nil, errors.New("address space extension")
}
// safe a memory alloc
temp := addr
// Get the arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
if interpreter.readOnly && !value.IsZero() {
return nil, ErrWriteProtection
}
var (
ret []byte
returnGas uint64
err error
)
if interpreter.evm.callGasTemp == 0 {
// zero temp call gas indicates a min retained gas error
ret, returnGas, err = nil, 0, ErrExecutionReverted
} else {
ret, returnGas, err = interpreter.evm.Call(scope.Contract.Address(), toAddr, args, gas, &value)
}
if errors.Is(err, ErrExecutionReverted) || errors.Is(err, ErrInsufficientBalance) || errors.Is(err, ErrDepth) {
temp.SetOne()
} else if err != nil {
temp.SetUint64(2)
} else {
temp.Clear()
}
stack.push(&temp)
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret
return ret, nil
}
// opExtDelegateCall implements the EXTDELEGATECALL opcode
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
stack := scope.Stack
// Use all available gas
gas := interpreter.evm.callGasTemp
// Pop other call parameters.
addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop()
toAddr := common.Address(addr.Bytes20())
if addr.ByteLen() > 20 {
return nil, errors.New("address space extension")
}
// safe a memory alloc
temp := addr
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
// Check that we're only calling non-legacy contracts
var (
err error
ret []byte
returnGas uint64
)
code := interpreter.evm.StateDB.GetCode(toAddr)
if !HasEOFMagic(code) {
// Delegate-calling a non-eof contract should return 1
err = ErrExecutionReverted
ret = nil
returnGas = gas
} else if interpreter.evm.callGasTemp == 0 {
// zero temp call gas indicates a min retained gas error
ret, returnGas, err = nil, 0, ErrExecutionReverted
} else {
ret, returnGas, err = interpreter.evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value, true)
}
if err == ErrExecutionReverted || err == ErrDepth {
temp.SetOne()
} else if err != nil {
temp.SetUint64(2)
} else {
temp.Clear()
}
stack.push(&temp)
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret
return ret, nil
}
// opExtStaticCall implements the EXTSTATICCALL opcode
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
panic("not implemented")
stack := scope.Stack
// Use all available gas
gas := interpreter.evm.callGasTemp
// Pop other call parameters.
addr, inOffset, inSize := stack.pop(), stack.pop(), stack.pop()
toAddr := common.Address(addr.Bytes20())
if addr.ByteLen() > 20 {
return nil, errors.New("address space extension")
}
// safe a memory alloc
temp := addr
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
var (
ret []byte
returnGas uint64
err error
)
if interpreter.evm.callGasTemp == 0 {
// zero temp call gas indicates a min retained gas error
ret, returnGas, err = nil, 0, ErrExecutionReverted
} else {
ret, returnGas, err = interpreter.evm.StaticCall(scope.Contract.Address(), toAddr, args, gas)
}
if err == ErrExecutionReverted || err == ErrDepth {
temp.SetOne()
} else if err != nil {
temp.SetUint64(2)
} else {
temp.Clear()
}
stack.push(&temp)
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
interpreter.returnData = ret
return ret, nil
}

View File

@ -24,6 +24,48 @@ import (
"github.com/ethereum/go-ethereum/common"
)
func MakeTestContainer(
types []*functionMetadata,
codeSections [][]byte,
subContainers []*Container,
data []byte,
dataSize int) Container {
testBytes := make([]byte, 0, 16*1024)
codeSectionOffsets := make([]int, 0, len(codeSections))
idx := 0
for _, code := range codeSections {
codeSectionOffsets = append(codeSectionOffsets, idx)
idx += len(code)
testBytes = append(testBytes, code...)
}
codeSectionOffsets = append(codeSectionOffsets, idx)
var subContainerOffsets []int
if len(subContainers) > 0 {
subContainerOffsets = make([]int, len(subContainers))
for _, subContainer := range subContainers {
containerBytes := subContainer.MarshalBinary()
subContainerOffsets = append(subContainerOffsets, idx)
idx += len(containerBytes)
testBytes = append(testBytes, containerBytes...)
}
// set the subContainer end marker
subContainerOffsets = append(subContainerOffsets, idx)
}
testBytes = append(testBytes, data...)
return Container{
types: types,
codeSectionOffsets: codeSectionOffsets,
subContainers: subContainers,
subContainerOffsets: subContainerOffsets,
dataSize: dataSize,
rawContainer: testBytes,
}
}
func TestEOFMarshaling(t *testing.T) {
for i, test := range []struct {
want Container
@ -31,18 +73,10 @@ func TestEOFMarshaling(t *testing.T) {
}{
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
},
},
{
want: Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSectionOffsets: []int{19, 22}, // 604200, endMarker
dataSize: 3,
rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"),
},
},
{
@ -52,12 +86,8 @@ func TestEOFMarshaling(t *testing.T) {
{inputs: 2, outputs: 3, maxStackHeight: 4},
{inputs: 1, outputs: 1, maxStackHeight: 1},
},
codeSections: [][]byte{
common.Hex2Bytes("604200"),
common.Hex2Bytes("6042604200"),
common.Hex2Bytes("00"),
},
data: []byte{},
codeSectionOffsets: []int{31, 34, 39, 40}, // 604200, 6042604200, 00, endMarker
rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"),
},
},
} {
@ -80,13 +110,13 @@ func TestEOFSubcontainer(t *testing.T) {
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
t.Fatal(err)
}
container := Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSections: [][]byte{common.Hex2Bytes("604200")},
subContainers: []*Container{subcontainer},
data: []byte{0x01, 0x02, 0x03},
dataSize: 3,
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{common.Hex2Bytes("604200")},
[]*Container{subcontainer},
[]byte{0x01, 0x02, 0x03},
3,
)
var (
b = container.MarshalBinary()
got Container

View File

@ -148,8 +148,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
case DATALOADN:
arg, _ := parseUint16(code[i+1:])
// TODO why are we checking this? We should just pad
if arg+32 > len(container.data) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
if arg+32 > container.dataLen() {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, container.dataLen(), i)
}
case RETURNCONTRACT:
if !isInitCode {
@ -176,8 +176,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
if arg >= len(container.subContainers) {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
}
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
if ct := container.subContainers[arg]; ct.dataLen() != ct.dataSize {
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, ct.dataLen(), ct.dataSize, i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)

View File

@ -246,12 +246,14 @@ func TestValidateCode(t *testing.T) {
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
},
} {
container := &Container{
types: test.metadata,
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
_, err := validateCode(test.code, test.section, container, &eofInstructionSet, false)
container := MakeTestContainer(
test.metadata,
[][]byte{test.code},
[]*Container{},
[]byte{},
0,
)
_, err := validateCode(test.code, test.section, &container, &eofInstructionSet, false)
if !errors.Is(err, test.err) {
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
}
@ -270,14 +272,16 @@ func BenchmarkRJUMPI(b *testing.B) {
code = append(code, snippet...)
}
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{code},
[]*Container{},
[]byte{},
0,
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
_, err := validateCode(code, 0, &container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}
@ -302,14 +306,16 @@ func BenchmarkRJUMPV(b *testing.B) {
}
code = append(code, byte(PUSH0))
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{code},
[]*Container{},
[]byte{},
0,
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
_, err := validateCode(code, 0, &container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}
@ -322,30 +328,32 @@ func BenchmarkRJUMPV(b *testing.B) {
// - or code to again call into 1024 code sections.
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation(b *testing.B) {
var container Container
var code []byte
maxSections := 1024
types := make([]*functionMetadata, maxSections)
codeSections := make([][]byte, maxSections)
var code []byte
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
// First container
container.codeSections = append(container.codeSections, append(code, byte(STOP)))
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
codeSections = append(codeSections, append(code, byte(STOP)))
types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(RETF),
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
codeSections = append(codeSections, inner)
types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
for i := 0; i < 12; i++ {
container.codeSections[i+1] = append(code, byte(RETF))
codeSections[i+1] = append(code, byte(RETF))
}
container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0)
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
@ -357,7 +365,7 @@ func BenchmarkEOFValidation(b *testing.B) {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
if err := container2.ValidateCode(&eofInstructionSet, false); err != nil {
b.Fatal(err)
}
}
@ -368,17 +376,18 @@ func BenchmarkEOFValidation(b *testing.B) {
// - contain calls to some other code sections.
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation2(b *testing.B) {
var container Container
var code []byte
maxSections := 1024
types := make([]*functionMetadata, maxSections)
codeSections := make([][]byte, maxSections)
var code []byte
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
code = append(code, byte(STOP))
// First container
container.codeSections = append(container.codeSections, code)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
codeSections = append(codeSections, code)
types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(CALLF), 0x03, 0xE8,
@ -397,10 +406,11 @@ func BenchmarkEOFValidation2(b *testing.B) {
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
codeSections = append(codeSections, inner)
types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0)
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
@ -412,7 +422,7 @@ func BenchmarkEOFValidation2(b *testing.B) {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
if err := container2.ValidateCode(&eofInstructionSet, false); err != nil {
b.Fatal(err)
}
}
@ -424,7 +434,9 @@ func BenchmarkEOFValidation2(b *testing.B) {
// - contain calls to other code sections
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
func BenchmarkEOFValidation3(b *testing.B) {
var container Container
maxSections := 1024
types := make([]*functionMetadata, maxSections)
codeSections := make([][]byte, maxSections)
var code []byte
snippet := []byte{
byte(PUSH0),
@ -437,25 +449,25 @@ func BenchmarkEOFValidation3(b *testing.B) {
}
code = append(code, snippet...)
// First container, calls into all other containers
maxSections := 1024
for i := 0; i < maxSections; i++ {
code = append(code, byte(CALLF))
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
}
code = append(code, byte(STOP))
container.codeSections = append(container.codeSections, code)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
codeSections = append(codeSections, code)
types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
// Other containers
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
codeSections = append(codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
// Other containers
for i := 0; i < 68; i++ {
container.codeSections[i+1] = append(snippet, byte(RETF))
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
codeSections[i+1] = append(snippet, byte(RETF))
types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
}
container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0)
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
@ -468,7 +480,7 @@ func BenchmarkEOFValidation3(b *testing.B) {
if err := container2.UnmarshalBinary(bin, true); err != nil {
b.Fatal(err)
}
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
if err := container2.ValidateCode(&eofInstructionSet, false); err != nil {
b.Fatal(err)
}
}
@ -487,14 +499,16 @@ func BenchmarkRJUMPI_2(b *testing.B) {
code = binary.BigEndian.AppendUint16(code, uint16(x))
}
code = append(code, byte(STOP))
container := &Container{
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
data: make([]byte, 0),
subContainers: make([]*Container, 0),
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{code},
[]*Container{},
[]byte{},
0,
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
_, err := validateCode(code, 0, &container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}
@ -512,6 +526,6 @@ func FuzzValidate(f *testing.F) {
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
var container Container
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
validateCode(code, 0, &container, &pragueInstructionSet, true)
validateCode(code, 0, &container, &eofInstructionSet, true)
})
}

View File

@ -36,7 +36,10 @@ 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")
// errStopToken is an internal token indicating interpreter loop termination,

View File

@ -18,7 +18,9 @@ package vm
import (
"errors"
"fmt"
"math/big"
"strings"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
@ -235,7 +237,8 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
contract := NewContract(caller, addr, value, gas, evm.jumpDests)
contract.IsSystemCall = isSystemCall(caller)
contract.SetCallCode(evm.resolveCodeHash(addr), code)
ret, err = evm.interpreter.Run(contract, input, false)
contract.Container = evm.parseContainer(code)
ret, err = evm.interpreter.Run(contract, input, false, false)
gas = contract.Gas
}
}
@ -293,8 +296,10 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
// 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, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.interpreter.Run(contract, input, false)
code := evm.StateDB.GetCode(addr)
contract.SetCallCode(evm.resolveCodeHash(addr), code)
contract.Container = evm.parseContainer(code)
ret, err = evm.interpreter.Run(contract, input, false, false)
gas = contract.Gas
}
if err != nil {
@ -314,7 +319,7 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
//
// DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas uint64, value *uint256.Int, fromEOF bool) (ret []byte, leftOverGas uint64, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil {
// DELEGATECALL inherits value from parent call
@ -333,12 +338,15 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas, evm.Config.Tracer)
} else {
code := evm.StateDB.GetCode(addr)
if fromEOF && !HasEOFMagic(code) {
return nil, gas, errors.New("extDelegateCall to non-eof contract")
}
// Initialise a new contract and make initialise the delegate values
//
// Note: The value refers to the original value from the parent call.
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.interpreter.Run(contract, input, false)
contract.Container = evm.parseContainer(code)
contract.SetCallCode(evm.resolveCodeHash(addr), code)
ret, err = evm.interpreter.Run(contract, input, false, false)
gas = contract.Gas
}
if err != nil {
@ -388,12 +396,13 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
// 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, addr, new(uint256.Int), gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
code := evm.StateDB.GetCode(addr)
contract.SetCallCode(evm.resolveCodeHash(addr), code)
contract.Container = evm.parseContainer(code)
// 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.
ret, err = evm.interpreter.Run(contract, input, true)
ret, err = evm.interpreter.Run(contract, input, true, false)
gas = contract.Gas
}
if err != nil {
@ -410,7 +419,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
}
// create creates a new contract using code as deployment code.
func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) {
func (evm *EVM) create(caller common.Address, code []byte, 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, code, gas, value.ToBig())
defer func(startGas uint64) {
@ -425,6 +434,32 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
if !evm.Context.CanTransfer(evm.StateDB, caller, 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, address, value, gas, evm.jumpDests)
contract.IsDeployment = true
// Validate initcode per EOF rules. If caller is EOF and initcode is legacy, fail.
isInitcodeEOF := HasEOFMagic(code)
if isInitcodeEOF {
if allowEOF {
// If the initcode is EOF, verify it is well-formed.
var c Container
if err := c.UnmarshalBinary(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)
if nonce+1 < nonce {
return nil, common.Address{}, gas, ErrNonceUintOverflow
@ -491,17 +526,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
gas = gas - statelessGas
}
evm.Context.Transfer(evm.StateDB, caller, 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, address, value, gas, evm.jumpDests)
// Explicitly set the code to a null hash to prevent caching of jump analysis
// for the initialization code.
contract.SetCallCode(common.Hash{}, code)
contract.IsDeployment = true
contract.Gas = gas
ret, err = evm.initNewContract(contract, address)
ret, err = evm.initNewContract(contract, input, address, isInitcodeEOF)
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
@ -513,8 +544,8 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui
// 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) ([]byte, error) {
ret, err := evm.interpreter.Run(contract, nil, false)
func (evm *EVM) initNewContract(contract *Contract, input []byte, address common.Address, isInitcodeEOF bool) ([]byte, error) {
ret, err := evm.interpreter.Run(contract, input, false, contract.IsDeployment)
if err != nil {
return ret, err
}
@ -524,9 +555,22 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
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.IsOsaka && isInitcodeEOF {
// Don't reject EOF contracts after Osaka
} else if evm.chainRules.IsLondon {
return ret, ErrInvalidCode
}
}
if !evm.chainRules.IsEIP4762 {
@ -545,9 +589,9 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
}
// Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *uint256.Int, allowEOF bool) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
return evm.create(caller, code, gas, value, contractAddr, CREATE)
return evm.create(caller, code, gas, value, contractAddr, CREATE, nil, allowEOF)
}
// Create2 creates a new contract using code as deployment code.
@ -556,7 +600,13 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas uint64, value *ui
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller common.Address, code []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(code))
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2, nil, false)
}
// EOFCreate creates a new eof contract.
func (evm *EVM) EOFCreate(caller common.Address, input []byte, subcontainer []byte, gas uint64, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), crypto.Keccak256(subcontainer))
return evm.create(caller, subcontainer, gas, endowment, contractAddr, EOFCREATE, input, true)
}
// resolveCode returns the code associated with the provided account. After
@ -630,3 +680,18 @@ func (evm *EVM) GetVMContext() *tracing.VMContext {
StateDB: evm.StateDB,
}
}
// parseContainer tries to parse an EOF container if the Shanghai fork is active. It expects the code to already be validated.
func (evm *EVM) parseContainer(b []byte) *Container {
if evm.chainRules.IsOsaka {
var c Container
if err := c.UnmarshalBinary(b, false); err != nil && strings.HasPrefix(err.Error(), "invalid magic") {
return nil
} else if err != nil {
// Code was already validated, so no other errors should be possible.
panic(fmt.Sprintf("unexpected error: %v\ncode: %s\n", err, common.Bytes2Hex(b)))
}
return &c
}
return nil
}

View File

@ -17,6 +17,7 @@
package vm
import (
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@ -52,3 +53,32 @@ func callGas(isEip150 bool, availableGas, base uint64, callCost *uint256.Int) (u
return callCost.Uint64(), nil
}
// extCallGas returns the actual gas cost for ext*call operations.
//
// EOF v1 includes EIP-150 rules (all but 1/64) with a floor of MIN_RETAINED_GAS (5000)
// and a minimum returned value of MIN_CALLE_GASS (2300).
// There is also no call gas, so all available gas is used.
//
// If the minimum retained gas constraint is violated, zero gas and no error is returned
func extCallGas(availableGas, base uint64) (uint64, error) {
if availableGas < base {
return 0, ErrOutOfGas
}
availableGas = availableGas - base
if availableGas < params.ExtCallMinRetainedGas {
return 0, nil
}
retainedGas := availableGas / 64
if retainedGas < params.ExtCallMinRetainedGas {
retainedGas = params.ExtCallMinRetainedGas
}
gas := availableGas - retainedGas
if gas < params.ExtCallMinCalleeGas {
return 0, nil
} else {
return gas, nil
}
}

View File

@ -504,18 +504,84 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
}
func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
var (
gas uint64
transfersValue = !stack.Back(3).IsZero()
address = common.Address(stack.Back(0).Bytes20())
overflow bool
)
if transfersValue {
if evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
}
if evm.chainRules.IsEIP4762 {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
} else {
gas += params.CallValueTransferGas
}
}
memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow
}
evm.callGasTemp, err = extCallGas(contract.Gas, gas)
if err != nil {
return 0, err
}
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
evm.callGasTemp, err = extCallGas(contract.Gas, gas)
if err != nil {
return 0, err
}
var overflow bool
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
evm.callGasTemp, err = extCallGas(contract.Gas, gas)
if err != nil {
return 0, err
}
var overflow bool
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
// gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be
// deducted in the opcode itself, since it depends on the immediate
func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
panic("not implemented")
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
}
// hashing charge needs to be deducted in the opcode itself, since it depends on the immediate
return gas, nil
}

View File

@ -323,6 +323,18 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
length = scope.Stack.pop()
)
if scope.Contract.IsEOF() {
dataOffset64, overflow := dataOffset.Uint64WithOverflow()
if overflow {
dataOffset64 = math.MaxUint64
}
// These values are checked for overflow during gas cost calculation
memOffset64 := memOffset.Uint64()
length64 := length.Uint64()
scope.Memory.Set(memOffset64, length64, getData(interpreter.returnData, dataOffset64, length64))
return nil, nil
}
offset64, overflow := dataOffset.Uint64WithOverflow()
if overflow {
return nil, ErrReturnDataOutOfBounds
@ -340,7 +352,13 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek()
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
// TODO this should not need to pull up the whole code
code := interpreter.evm.StateDB.GetCode(slot.Bytes20())
if isEOFVersion1(code) {
slot.SetUint64(2)
} else {
slot.SetUint64(uint64(len(code)))
}
return nil, nil
}
@ -372,6 +390,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
memOffset = stack.pop()
codeOffset = stack.pop()
length = stack.pop()
lengthU64 = length.Uint64()
codeCopy []byte
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
@ -379,9 +399,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
}
addr := common.Address(a.Bytes20())
code := interpreter.evm.StateDB.GetCode(addr)
codeCopy := getData(code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
if isEOFVersion1(code) {
codeCopy = getData(eofMagic, uint64CodeOffset, lengthU64)
} else {
codeCopy = getData(code, uint64CodeOffset, lengthU64)
}
scope.Memory.Set(memOffset.Uint64(), lengthU64, codeCopy)
return nil, nil
}
@ -417,7 +440,13 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if interpreter.evm.StateDB.Empty(address) {
slot.Clear()
} else {
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
// TODO this should not need to pull up the whole code
code := interpreter.evm.StateDB.GetCode(address)
if HasEOFMagic(code) {
slot.SetFromHex("0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5")
} else {
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
}
}
return nil, nil
}
@ -677,7 +706,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation)
res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract.Address(), input, gas, &value)
res, addr, returnGas, suberr := interpreter.evm.Create(scope.Contract.Address(), input, gas, &value, false)
// Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must
@ -818,7 +847,7 @@ func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
// Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value)
ret, returnGas, err := interpreter.evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, gas, scope.Contract.value, false)
if err != nil {
temp.Clear()
} else {

View File

@ -107,7 +107,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu
expected := new(uint256.Int).SetBytes(common.Hex2Bytes(test.Expected))
stack.push(x)
stack.push(y)
opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil})
opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", name, len(stack.data))
}
@ -221,7 +221,7 @@ func TestAddMod(t *testing.T) {
stack.push(z)
stack.push(y)
stack.push(x)
opAddmod(&pc, evm.interpreter, &ScopeContext{nil, stack, nil})
opAddmod(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
actual := stack.pop()
if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %x, got %x", i, expected, actual)
@ -247,7 +247,7 @@ func TestWriteExpectedValues(t *testing.T) {
y := new(uint256.Int).SetBytes(common.Hex2Bytes(param.y))
stack.push(x)
stack.push(y)
opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil})
opFn(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
actual := stack.pop()
result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)}
}
@ -283,7 +283,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var (
evm = NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
stack = newstack()
scope = &ScopeContext{nil, stack, nil}
scope = &ScopeContext{nil, stack, nil, 0, nil, false}
)
// convert args
intArgs := make([]*uint256.Int, len(args))
@ -528,13 +528,13 @@ func TestOpMstore(t *testing.T) {
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.push(new(uint256.Int).SetBytes(common.Hex2Bytes(v)))
stack.push(new(uint256.Int))
opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil})
opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v {
t.Fatalf("Mstore fail, got %v, expected %v", got, v)
}
stack.push(new(uint256.Int).SetUint64(0x1))
stack.push(new(uint256.Int))
opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil})
opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value")
}
@ -555,7 +555,7 @@ func BenchmarkOpMstore(bench *testing.B) {
for i := 0; i < bench.N; i++ {
stack.push(value)
stack.push(memStart)
opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil})
opMstore(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
}
}
@ -568,7 +568,7 @@ func TestOpTstore(t *testing.T) {
caller = common.Address{}
to = common.Address{1}
contract = NewContract(caller, to, new(uint256.Int), 0, nil)
scopeContext = ScopeContext{mem, stack, contract}
scopeContext = ScopeContext{mem, stack, contract, 0, nil, false}
value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700")
)
@ -613,7 +613,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
for i := 0; i < bench.N; i++ {
stack.push(uint256.NewInt(32))
stack.push(start)
opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil})
opKeccak256(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
}
}
@ -680,7 +680,7 @@ func TestCreate2Addresses(t *testing.T) {
stack.push(big.NewInt(int64(len(code)))) //size
stack.push(big.NewInt(0)) // memstart
stack.push(big.NewInt(0)) // value
gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0)
gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0, nil, false, 0)
fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String())
*/
expected := common.BytesToAddress(common.FromHex(tt.expected))
@ -707,7 +707,7 @@ func TestRandom(t *testing.T) {
stack = newstack()
pc = uint64(0)
)
opRandom(&pc, evm.interpreter, &ScopeContext{nil, stack, nil})
opRandom(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
@ -749,7 +749,7 @@ func TestBlobHash(t *testing.T) {
)
evm.SetTxContext(TxContext{BlobHashes: tt.hashes})
stack.push(uint256.NewInt(tt.idx))
opBlobHash(&pc, evm.interpreter, &ScopeContext{nil, stack, nil})
opBlobHash(&pc, evm.interpreter, &ScopeContext{nil, stack, nil, 0, nil, false})
if len(stack.data) != 1 {
t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data))
}
@ -889,7 +889,7 @@ func TestOpMCopy(t *testing.T) {
mem.Resize(memorySize)
}
// Do the copy
opMcopy(&pc, evm.interpreter, &ScopeContext{mem, stack, nil})
opMcopy(&pc, evm.interpreter, &ScopeContext{mem, stack, nil, 0, nil, false})
want := common.FromHex(strings.ReplaceAll(tc.want, " ", ""))
if have := mem.store; !bytes.Equal(want, have) {
t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have)

View File

@ -43,6 +43,10 @@ type ScopeContext struct {
Memory *Memory
Stack *Stack
Contract *Contract
CodeSection uint64
ReturnStack ReturnStack
InitCodeMode bool
}
// MemoryData returns the underlying memory slice. Callers must not modify the contents
@ -89,10 +93,47 @@ func (ctx *ScopeContext) ContractCode() []byte {
return ctx.Contract.Code
}
// CodeSection returns the current code section of the contract being executed.
func (ctx *ScopeContext) CurrentCodeSection() uint64 {
return ctx.CodeSection
}
// ReturnStackDepth returns the depth of the return stack.
func (ctx *ScopeContext) ReturnStackDepth() uint64 {
if ctx.Contract.IsEOF() {
return uint64(ctx.ReturnStack.Len() + 1)
} else {
return 0
}
}
type ReturnStack []*ReturnContext
// Pop removes an element from the return stack
// Panics if the return stack is empty, which should
// never happen, since EOF code is verified for that.
func (ctx *ReturnStack) Pop() *ReturnContext {
item := (*ctx)[ctx.Len()-1]
*ctx = (*ctx)[:ctx.Len()-1]
return item
}
// Len returns the length of the return stack
func (ctx *ReturnStack) Len() int {
return len(*ctx)
}
type ReturnContext struct {
Section uint64
Pc uint64
StackHeight int
}
// EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct {
evm *EVM
table *JumpTable
evm *EVM
table *JumpTable
tableEOF *JumpTable
hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes
@ -150,7 +191,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
}
}
evm.Config.ExtraEips = extraEips
return &EVMInterpreter{evm: evm, table: table}
return &EVMInterpreter{evm: evm, table: table, tableEOF: &eofInstructionSet}
}
// Run loops and evaluates the contract's code with the given input data and returns
@ -159,7 +200,7 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, isInitCode bool) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()
@ -181,13 +222,17 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
var (
jt *JumpTable // current jump table
op OpCode // current opcode
mem = NewMemory() // bound memory
stack = newstack() // local stack
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
Contract: contract,
Memory: mem,
Stack: stack,
Contract: contract,
CodeSection: 0,
ReturnStack: []*ReturnContext{{Section: 0, Pc: 0, StackHeight: 0}},
InitCodeMode: isInitCode,
}
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
@ -201,6 +246,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
res []byte // result of the opcode execution function
debug = in.evm.Config.Tracer != nil
)
// Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
@ -210,6 +256,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}()
contract.Input = input
if contract.IsEOF() {
jt = in.tableEOF
// Set EOF entrypoint
pc = uint64(contract.Container.codeSectionOffsets[0])
} else {
jt = in.table
}
if debug {
defer func() { // this deferred method handles exit-with-error
if err == nil {
@ -243,7 +297,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)
operation := in.table[op]
operation := jt[op]
cost = operation.constantGas // For tracing
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {

View File

@ -181,6 +181,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
input,
cfg.GasLimit,
uint256.MustFromBig(cfg.Value),
rules.IsOsaka,
)
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas}, err)

View File

@ -113,6 +113,14 @@ func (st *Stack) swap16() {
st.data[st.len()-17], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-17]
}
func (st *Stack) swap(n int) {
st.swapN(n, 1)
}
func (st *Stack) swapN(n, m int) {
st.data[st.len()-n], st.data[st.len()-m] = st.data[st.len()-m], st.data[st.len()-n]
}
func (st *Stack) dup(n int) {
st.push(&st.data[st.len()-n])
}

View File

@ -65,7 +65,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
tracer.OnTxStart(evm.GetVMContext(), types.NewTx(&types.LegacyTx{Gas: gasLimit, GasPrice: vmctx.txCtx.GasPrice}), contract.Caller())
tracer.OnEnter(0, byte(vm.CALL), contract.Caller(), contract.Address(), []byte{}, startGas, value.ToBig())
ret, err := evm.Interpreter().Run(contract, []byte{}, false)
ret, err := evm.Interpreter().Run(contract, []byte{}, false, false)
tracer.OnExit(0, ret, startGas-contract.Gas, err, true)
// Rest gas assumes no refund
tracer.OnTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)

View File

@ -18,6 +18,7 @@ var _ = (*structLogMarshaling)(nil)
func (s StructLog) MarshalJSON() ([]byte, error) {
type StructLog struct {
Pc uint64 `json:"pc"`
Section uint64 `json:"section,omitempty"`
Op vm.OpCode `json:"op"`
Gas math.HexOrDecimal64 `json:"gas"`
GasCost math.HexOrDecimal64 `json:"gasCost"`
@ -27,6 +28,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
ReturnData hexutil.Bytes `json:"returnData,omitempty"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
FunctionDepth uint64 `json:"functionDepth,omitempty"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
OpName string `json:"opName"`
@ -34,6 +36,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
}
var enc StructLog
enc.Pc = s.Pc
enc.Section = s.Section
enc.Op = s.Op
enc.Gas = math.HexOrDecimal64(s.Gas)
enc.GasCost = math.HexOrDecimal64(s.GasCost)
@ -48,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
enc.ReturnData = s.ReturnData
enc.Storage = s.Storage
enc.Depth = s.Depth
enc.FunctionDepth = s.FunctionDepth
enc.RefundCounter = s.RefundCounter
enc.Err = s.Err
enc.OpName = s.OpName()
@ -59,6 +63,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
func (s *StructLog) UnmarshalJSON(input []byte) error {
type StructLog struct {
Pc *uint64 `json:"pc"`
Section *uint64 `json:"section,omitempty"`
Op *vm.OpCode `json:"op"`
Gas *math.HexOrDecimal64 `json:"gas"`
GasCost *math.HexOrDecimal64 `json:"gasCost"`
@ -68,6 +73,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
ReturnData *hexutil.Bytes `json:"returnData,omitempty"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"`
FunctionDepth *uint64 `json:"functionDepth,omitempty"`
RefundCounter *uint64 `json:"refund"`
Err error `json:"-"`
}
@ -78,6 +84,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
if dec.Pc != nil {
s.Pc = *dec.Pc
}
if dec.Section != nil {
s.Section = *dec.Section
}
if dec.Op != nil {
s.Op = *dec.Op
}
@ -108,6 +117,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
if dec.Depth != nil {
s.Depth = *dec.Depth
}
if dec.FunctionDepth != nil {
s.FunctionDepth = *dec.FunctionDepth
}
if dec.RefundCounter != nil {
s.RefundCounter = *dec.RefundCounter
}

View File

@ -57,6 +57,7 @@ type Config struct {
// current internal state prior to the execution of the statement.
type StructLog struct {
Pc uint64 `json:"pc"`
Section uint64 `json:"section,omitempty"`
Op vm.OpCode `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
@ -66,6 +67,7 @@ type StructLog struct {
ReturnData []byte `json:"returnData,omitempty"`
Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"`
FunctionDepth uint64 `json:"functionDepth,omitempty"`
RefundCounter uint64 `json:"refund"`
Err error `json:"-"`
}
@ -274,7 +276,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
stack = scope.StackData()
stackLen = len(stack)
)
log := StructLog{pc, op, gas, cost, nil, len(memory), nil, nil, nil, depth, l.env.StateDB.GetRefund(), err}
log := StructLog{pc, scope.CurrentCodeSection(), op, gas, cost, nil, len(memory), nil, nil, nil, depth, scope.ReturnStackDepth(), l.env.StateDB.GetRefund(), err}
if l.cfg.EnableMemory {
log.Memory = memory
}

View File

@ -48,7 +48,7 @@ func TestStoreCapture(t *testing.T) {
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash
logger.OnTxStart(evm.GetVMContext(), nil, common.Address{})
_, err := evm.Interpreter().Run(contract, []byte{}, false)
_, err := evm.Interpreter().Run(contract, []byte{}, false, false)
if err != nil {
t.Fatal(err)
}

View File

@ -89,6 +89,8 @@ const (
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
ExtCallMinRetainedGas uint64 = 5000 // For EXT*CALL this is the minimum gas that the EIP158 1/64th rule must retain
ExtCallMinCalleeGas uint64 = 2300 // For EXT*CALL this is the minimum gas that must be passed to the callee, ignoring 63/64
TxDataNonZeroGasFrontier uint64 = 68 // Per byte of data attached to a transaction that is not equal to zero. NOTE: Not payable on data of calls between transactions.
TxDataNonZeroGasEIP2028 uint64 = 16 // Per byte of non zero data attached to a transaction after EIP 2028 (part in Istanbul)