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

imp(vm): define default JumpTable #3

Merged
merged 3 commits into from
Jan 10, 2023
Merged
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<!--
Guiding Principles:

Expand Down Expand Up @@ -40,4 +39,5 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Improvements

* [#3](https://github.com/evmos/go-ethereum/pull/3) Move the `JumpTable` defaults to a separate function.
* [#2](https://github.com/evmos/go-ethereum/pull/2) Define `Interpreter` interface for the EVM.
26 changes: 3 additions & 23 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,12 @@ type EVMInterpreter struct {
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// If jump table was not initialised we set the default one.
if cfg.JumpTable == nil {
switch {
case evm.chainRules.IsMerge:
cfg.JumpTable = &mergeInstructionSet
case evm.chainRules.IsLondon:
cfg.JumpTable = &londonInstructionSet
case evm.chainRules.IsBerlin:
cfg.JumpTable = &berlinInstructionSet
case evm.chainRules.IsIstanbul:
cfg.JumpTable = &istanbulInstructionSet
case evm.chainRules.IsConstantinople:
cfg.JumpTable = &constantinopleInstructionSet
case evm.chainRules.IsByzantium:
cfg.JumpTable = &byzantiumInstructionSet
case evm.chainRules.IsEIP158:
cfg.JumpTable = &spuriousDragonInstructionSet
case evm.chainRules.IsEIP150:
cfg.JumpTable = &tangerineWhistleInstructionSet
case evm.chainRules.IsHomestead:
cfg.JumpTable = &homesteadInstructionSet
default:
cfg.JumpTable = &frontierInstructionSet
}
cfg.JumpTable = DefaultJumpTable(evm.chainRules)

var extraEips []int
if len(cfg.ExtraEips) > 0 {
// Deep-copy jumptable to prevent modification of opcodes in other tables
cfg.JumpTable = copyJumpTable(cfg.JumpTable)
cfg.JumpTable = CopyJumpTable(cfg.JumpTable)
}
for _, eip := range cfg.ExtraEips {
if err := EnableEIP(eip, cfg.JumpTable); err != nil {
Expand Down
100 changes: 75 additions & 25 deletions core/vm/jump_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,37 +45,76 @@ type operation struct {
}

var (
frontierInstructionSet = newFrontierInstructionSet()
homesteadInstructionSet = newHomesteadInstructionSet()
tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet()
spuriousDragonInstructionSet = newSpuriousDragonInstructionSet()
byzantiumInstructionSet = newByzantiumInstructionSet()
constantinopleInstructionSet = newConstantinopleInstructionSet()
istanbulInstructionSet = newIstanbulInstructionSet()
berlinInstructionSet = newBerlinInstructionSet()
londonInstructionSet = newLondonInstructionSet()
mergeInstructionSet = newMergeInstructionSet()
FrontierInstructionSet = newFrontierInstructionSet()
HomesteadInstructionSet = newHomesteadInstructionSet()
TangerineWhistleInstructionSet = newTangerineWhistleInstructionSet()
SpuriousDragonInstructionSet = newSpuriousDragonInstructionSet()
ByzantiumInstructionSet = newByzantiumInstructionSet()
ConstantinopleInstructionSet = newConstantinopleInstructionSet()
IstanbulInstructionSet = newIstanbulInstructionSet()
BerlinInstructionSet = newBerlinInstructionSet()
LondonInstructionSet = newLondonInstructionSet()
MergeInstructionSet = newMergeInstructionSet()
)

// JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation

func validate(jt JumpTable) JumpTable {
// DefaultJumpTable defines the default jump table used by the EVM interpreter.
func DefaultJumpTable(rules params.Rules) (jumpTable *JumpTable) {
switch {
case rules.IsMerge:
jumpTable = &MergeInstructionSet
case rules.IsLondon:
jumpTable = &LondonInstructionSet
case rules.IsBerlin:
jumpTable = &BerlinInstructionSet
case rules.IsIstanbul:
jumpTable = &IstanbulInstructionSet
case rules.IsConstantinople:
jumpTable = &ConstantinopleInstructionSet
case rules.IsByzantium:
jumpTable = &ByzantiumInstructionSet
case rules.IsEIP158:
jumpTable = &SpuriousDragonInstructionSet
case rules.IsEIP150:
jumpTable = &TangerineWhistleInstructionSet
case rules.IsHomestead:
jumpTable = &HomesteadInstructionSet
default:
jumpTable = &FrontierInstructionSet
}

return jumpTable
}

// Validate checks if all the operations are set and if they are valid according to the
// interpreter assumptions.
func (jt JumpTable) Validate() error {
for i, op := range jt {
if op == nil {
panic(fmt.Sprintf("op %#x is not set", i))
return fmt.Errorf("op %#x is not set", i)
}

// 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 fmt.Errorf("op %v has dynamic memory but not dynamic gas", OpCode(i).String())
}
}
return jt

return nil
}

// MustValidate panics if the operations are not valid.
func (jt JumpTable) MustValidate() {
if err := jt.Validate(); err != nil {
panic(err)
}
}

func newMergeInstructionSet() JumpTable {
Expand All @@ -86,7 +125,8 @@ func newMergeInstructionSet() JumpTable {
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
return validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

// newLondonInstructionSet returns the frontier, homestead, byzantium,
Expand All @@ -95,15 +135,17 @@ 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 validate(instructionSet)
instructionSet.MustValidate()
return 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 validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

// newIstanbulInstructionSet returns the frontier, homestead, byzantium,
Expand All @@ -115,7 +157,8 @@ 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 validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

// newConstantinopleInstructionSet returns the frontier, homestead,
Expand Down Expand Up @@ -154,7 +197,8 @@ func newConstantinopleInstructionSet() JumpTable {
maxStack: maxStack(4, 1),
memorySize: memoryCreate2,
}
return validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

// newByzantiumInstructionSet returns the frontier, homestead and
Expand Down Expand Up @@ -190,14 +234,16 @@ func newByzantiumInstructionSet() JumpTable {
maxStack: maxStack(2, 0),
memorySize: memoryRevert,
}
return validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

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

// EIP 150 a.k.a Tangerine Whistle
Expand All @@ -210,7 +256,8 @@ func newTangerineWhistleInstructionSet() JumpTable {
instructionSet[CALL].constantGas = params.CallGasEIP150
instructionSet[CALLCODE].constantGas = params.CallGasEIP150
instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150
return validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

// newHomesteadInstructionSet returns the frontier and homestead
Expand All @@ -225,7 +272,8 @@ func newHomesteadInstructionSet() JumpTable {
maxStack: maxStack(6, 1),
memorySize: memoryDelegateCall,
}
return validate(instructionSet)
instructionSet.MustValidate()
return instructionSet
}

// newFrontierInstructionSet returns the frontier instructions
Expand Down Expand Up @@ -1041,10 +1089,12 @@ func newFrontierInstructionSet() JumpTable {
}
}

return validate(tbl)
tbl.MustValidate()
return tbl
}

func copyJumpTable(source *JumpTable) *JumpTable {
// CopyJumpTable creates copy of the operations from the provided source JumpTable.
func CopyJumpTable(source *JumpTable) *JumpTable {
dest := *source
for i, op := range source {
if op != nil {
Expand Down
2 changes: 1 addition & 1 deletion core/vm/jump_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestJumpTableCopy(t *testing.T) {
require.Equal(t, uint64(0), tbl[SLOAD].constantGas)

// a deep copy won't modify the shared jump table
deepCopy := copyJumpTable(&tbl)
deepCopy := CopyJumpTable(&tbl)
deepCopy[SLOAD].constantGas = 100
require.Equal(t, uint64(100), deepCopy[SLOAD].constantGas)
require.Equal(t, uint64(0), tbl[SLOAD].constantGas)
Expand Down