Merge c015dc8b71
into 9aba6895b9
This commit is contained in:
commit
a0eb3dfd08
|
@ -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) {}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
112
core/vm/eof.go
112
core/vm/eof.go
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
115
core/vm/evm.go
115
core/vm/evm.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue