Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/vm: avoid memory expansion check for trivial ops (#24048) #652

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 25 additions & 29 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}

// 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)
Expand All @@ -212,6 +211,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
// increase context's counter
in.evm.Context.Counter++
cost = operation.constantGas // For tracing
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
Expand All @@ -230,47 +230,43 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}
}
// Static portion of gas
cost = operation.constantGas // For tracing
if !contract.UseGas(operation.constantGas) {
if !contract.UseGas(cost) {
return nil, ErrOutOfGas
}

var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Dynamic portion of gas
// consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
if operation.dynamicGas != nil {
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost // total cost, for debug tracing
cost += dynamicCost // for tracing
if err != nil || !contract.UseGas(dynamicCost) {
return nil, ErrOutOfGas
}
if memorySize > 0 {
mem.Resize(memorySize)
}
}
if memorySize > 0 {
mem.Resize(memorySize)
}

if debug {
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true
}

// execute the operation
res, err = operation.execute(&pc, in, callContext)
// if the operation clears the return data (e.g. it has returning data)
Expand Down
43 changes: 32 additions & 11 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package vm

import (
"fmt"

"github.com/ethereum/go-ethereum/params"
)

Expand Down Expand Up @@ -65,21 +67,39 @@ var (
// JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation

func validate(jt JumpTable) JumpTable {
for i, op := range jt {
if op == nil {
continue
}
// The interpreter has an assumption that if the memorySize function is
// set, then the dynamicGas function is also set. This is a somewhat
// arbitrary assumption, and can be removed if we need to -- but it
// allows us to avoid a condition check. As long as we have that assumption
// in there, this little sanity check prevents us from merging in a
// change which violates it.
if op.memorySize != nil && op.dynamicGas == nil {
panic(fmt.Sprintf("op %v has dynamic memory but not dynamic gas", OpCode(i).String()))
}
}
return jt
}

func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode)
enable1153(&instructionSet) // EIP-1153 "Transient Storage"
enable5656(&instructionSet) // EIP-5656 (MCOPY opcode)
enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction
return instructionSet
return validate(instructionSet)
}

func newShanghaiInstructionSet() JumpTable {
instructionSet := newLondonInstructionSet()
enable3855(&instructionSet) // PUSH0 instruction
enable3860(&instructionSet) // Limit and meter initcode
return instructionSet
return validate(instructionSet)
}

// newLondonInstructionSet returns the frontier, homestead, byzantium,
Expand All @@ -88,15 +108,15 @@ func newLondonInstructionSet() JumpTable {
instructionSet := newBerlinInstructionSet()
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198
return instructionSet
return validate(instructionSet)
}

// newBerlinInstructionSet returns the frontier, homestead, byzantium,
// contantinople, istanbul, petersburg and berlin instructions.
func newBerlinInstructionSet() JumpTable {
instructionSet := newIstanbulInstructionSet()
enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
return instructionSet
return validate(instructionSet)
}

// newIstanbulInstructionSet returns the frontier, homestead, byzantium,
Expand All @@ -108,7 +128,7 @@ func newIstanbulInstructionSet() JumpTable {
enable1884(&instructionSet) // Reprice reader opcodes - https://eips.ethereum.org/EIPS/eip-1884
enable2200(&instructionSet) // Net metered SSTORE - https://eips.ethereum.org/EIPS/eip-2200

return instructionSet
return validate(instructionSet)
}

// newConstantinopleInstructionSet returns the frontier, homestead,
Expand Down Expand Up @@ -149,7 +169,7 @@ func newConstantinopleInstructionSet() JumpTable {
writes: true,
returns: true,
}
return instructionSet
return validate(instructionSet)
}

// newByzantiumInstructionSet returns the frontier, homestead and
Expand Down Expand Up @@ -188,14 +208,14 @@ func newByzantiumInstructionSet() JumpTable {
reverts: true,
returns: true,
}
return instructionSet
return validate(instructionSet)
}

// EIP 158 a.k.a Spurious Dragon
func newSpuriousDragonInstructionSet() JumpTable {
instructionSet := newTangerineWhistleInstructionSet()
instructionSet[EXP].dynamicGas = gasExpEIP158
return instructionSet
return validate(instructionSet)

}

Expand All @@ -209,7 +229,7 @@ func newTangerineWhistleInstructionSet() JumpTable {
instructionSet[CALL].constantGas = params.CallGasEIP150
instructionSet[CALLCODE].constantGas = params.CallGasEIP150
instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150
return instructionSet
return validate(instructionSet)
}

// newHomesteadInstructionSet returns the frontier and homestead
Expand All @@ -225,13 +245,13 @@ func newHomesteadInstructionSet() JumpTable {
memorySize: memoryDelegateCall,
returns: true,
}
return instructionSet
return validate(instructionSet)
}

// newFrontierInstructionSet returns the frontier instructions
// that can be executed during the frontier phase.
func newFrontierInstructionSet() JumpTable {
return JumpTable{
tlb := JumpTable{
STOP: {
execute: opStop,
constantGas: 0,
Expand Down Expand Up @@ -1049,6 +1069,7 @@ func newFrontierInstructionSet() JumpTable {
writes: true,
},
}
return validate(tlb)
}

func copyJumpTable(source *JumpTable) *JumpTable {
Expand Down
Loading