Skip to content

Commit d5d44d2

Browse files
core/vm: update 7702 EVM resolve code & gas estimation (#35)
content: - update core/vm - update EXT... gas estimation from: - ethereum/go-ethereum#30078 - ethereum/go-ethereum#31089 * core/vm: update 7702 gas calculation on impacted OpCode CALL, CALLCODE, STATICCALL, DELEGATECALL. EXTCODE... opcodes are aligned with the EIP update ethereum/EIPs#9248 * core/vm: EVM resolve code with delegations of 7702 * core/vm: add gas cost test * fixup! core/vm: add gas cost test * fixup! core/vm: add gas cost test
1 parent ec0c437 commit d5d44d2

File tree

10 files changed

+975
-13
lines changed

10 files changed

+975
-13
lines changed

core/blockchain_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,6 +2883,7 @@ func testDeleteRecreateSlots(t *testing.T, scheme string) {
28832883

28842884
chainConfig := *params.TestChainConfig
28852885
chainConfig.CancunBlock = nil
2886+
chainConfig.PragueBlock = nil
28862887
gspec := &Genesis{
28872888
Config: &chainConfig,
28882889
Alloc: GenesisAlloc{
@@ -2976,6 +2977,7 @@ func testDeleteRecreateAccount(t *testing.T, scheme string) {
29762977

29772978
chainConfig := *params.TestChainConfig
29782979
chainConfig.CancunBlock = nil
2980+
chainConfig.PragueBlock = nil
29792981
gspec := &Genesis{
29802982
Config: &chainConfig,
29812983
Alloc: GenesisAlloc{
@@ -3102,6 +3104,7 @@ func testDeleteRecreateSlotsAcrossManyBlocks(t *testing.T, scheme string) {
31023104
t.Logf("Destination address: %x\n", aa)
31033105
chainConfig := *params.TestChainConfig
31043106
chainConfig.CancunBlock = nil
3107+
chainConfig.PragueBlock = nil
31053108
gspec := &Genesis{
31063109
Config: &chainConfig,
31073110
Alloc: GenesisAlloc{
@@ -3439,7 +3442,7 @@ func testEIP2718Transition(t *testing.T, scheme string) {
34393442

34403443
// Expected gas is intrinsic + 2 * pc + hot load + cold load, since only one load is in the access list
34413444
expected := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas +
3442-
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
3445+
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
34433446
if block.GasUsed() != expected {
34443447
t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expected, block.GasUsed())
34453448

@@ -3542,7 +3545,7 @@ func testEIP1559Transition(t *testing.T, scheme string) {
35423545

35433546
// 1+2: Ensure EIP-1559 access lists are accounted for via gas usage.
35443547
expectedGas := params.TxGas + params.TxAccessListAddressGas + params.TxAccessListStorageKeyGas +
3545-
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
3548+
vm.GasQuickStep*2 + params.WarmStorageReadCostEIP2929 + params.ColdSloadCostEIP2929
35463549
if block.GasUsed() != expectedGas {
35473550
t.Fatalf("incorrect amount of gas spent: expected %d, got %d", expectedGas, block.GasUsed())
35483551
}

core/vm/eips.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var activators = map[int]func(*JumpTable){
3737
1884: enable1884,
3838
1344: enable1344,
3939
1153: enable1153,
40+
7702: enable7702,
4041
}
4142

4243
// EnableEIP enables the given EIP on the config.
@@ -319,3 +320,11 @@ func enable6780(jt *JumpTable) {
319320
halts: true,
320321
}
321322
}
323+
324+
// enable7702 the EIP-7702 changes to support delegation designators.
325+
func enable7702(jt *JumpTable) {
326+
jt[CALL].dynamicGas = gasCallEIP7702
327+
jt[CALLCODE].dynamicGas = gasCallCodeEIP7702
328+
jt[STATICCALL].dynamicGas = gasStaticCallEIP7702
329+
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP7702
330+
}

core/vm/evm.go

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -295,15 +295,15 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
295295
} else {
296296
// Initialise a new contract and set the code that is to be used by the EVM.
297297
// The contract is a scoped environment for this execution context only.
298-
code := evm.StateDB.GetCode(addr)
298+
code := evm.resolveCode(addr)
299299
if len(code) == 0 {
300300
ret, err = nil, nil // gas is unchanged
301301
} else {
302302
addrCopy := addr
303303
// If the account has no code, we can abort here
304304
// The depth-check is already done, and precompiles handled above
305305
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
306-
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
306+
contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), code)
307307
ret, err = evm.interpreter.Run(contract, input, false)
308308
gas = contract.Gas
309309
}
@@ -363,7 +363,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
363363
// Initialise a new contract and set the code that is to be used by the EVM.
364364
// The contract is a scoped environment for this execution context only.
365365
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
366-
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
366+
contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), evm.resolveCode(addrCopy))
367367
ret, err = evm.interpreter.Run(contract, input, false)
368368
gas = contract.Gas
369369
}
@@ -421,7 +421,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
421421
addrCopy := addr
422422
// Initialise a new contract and make initialise the delegate values
423423
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
424-
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
424+
contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), evm.resolveCode(addrCopy))
425425
ret, err = evm.interpreter.Run(contract, input, false)
426426
gas = contract.Gas
427427
}
@@ -477,7 +477,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
477477
// Initialise a new contract and set the code that is to be used by the EVM.
478478
// The contract is a scoped environment for this execution context only.
479479
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
480-
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
480+
contract.SetCallCode(&addrCopy, evm.resolveCodeHash(addrCopy), evm.resolveCode(addrCopy))
481481
// When an error was returned by the EVM or when setting the creation code
482482
// above we revert to the snapshot and consume any gas remaining. Additionally
483483
// when we're in Homestead this also counts for code storage gas errors.
@@ -666,17 +666,46 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
666666
return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2)
667667
}
668668

669+
// resolveCode returns the code associated with the provided account. After
670+
// Prague, it can also resolve code pointed to by a delegation designator.
671+
func (evm *EVM) resolveCode(addr common.Address) []byte {
672+
code := evm.StateDB.GetCode(addr)
673+
if !evm.chainRules.IsPrague {
674+
return code
675+
}
676+
if target, ok := types.ParseDelegation(code); ok {
677+
// Note we only follow one level of delegation.
678+
return evm.StateDB.GetCode(target)
679+
}
680+
return code
681+
}
682+
683+
// resolveCodeHash returns the code hash associated with the provided address.
684+
// After Prague, it can also resolve code hash of the account pointed to by a
685+
// delegation designator. Although this is not accessible in the EVM it is used
686+
// internally to associate jumpdest analysis to code.
687+
func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash {
688+
if evm.chainRules.IsPrague {
689+
code := evm.StateDB.GetCode(addr)
690+
if target, ok := types.ParseDelegation(code); ok {
691+
// Note we only follow one level of delegation.
692+
return evm.StateDB.GetCodeHash(target)
693+
}
694+
}
695+
return evm.StateDB.GetCodeHash(addr)
696+
}
697+
669698
// ChainConfig returns the environment's chain configuration
670699
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
671700

672701
// PublishEvent executes Publish function from OpEvent if OpCode is found in Context.PublishEvents
673702
func (evm *EVM) PublishEvent(
674-
opCode OpCode,
675-
counter uint64,
676-
from, to common.Address,
677-
value *big.Int,
678-
input, output []byte,
679-
err error,
703+
opCode OpCode,
704+
counter uint64,
705+
from, to common.Address,
706+
value *big.Int,
707+
input, output []byte,
708+
err error,
680709
) {
681710
context := evm.Context
682711
if context.CurrentTransaction == nil {

core/vm/interpreter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
7777
if cfg.JumpTable[STOP] == nil {
7878
var jt JumpTable
7979
switch {
80+
case evm.chainRules.IsPrague:
81+
jt = pragueInstructionSet
8082
case evm.chainRules.IsCancun:
8183
jt = cancunInstructionSet
8284
case evm.chainRules.IsShanghai:

core/vm/jump_table.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,18 @@ var (
6060
londonInstructionSet = newLondonInstructionSet()
6161
shanghaiInstructionSet = newShanghaiInstructionSet()
6262
cancunInstructionSet = newCancunInstructionSet()
63+
pragueInstructionSet = newPragueInstructionSet()
6364
)
6465

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

69+
func newPragueInstructionSet() JumpTable {
70+
instructionSet := newCancunInstructionSet()
71+
enable7702(&instructionSet) // EIP-7702 Setcode transaction type
72+
return instructionSet
73+
}
74+
6875
func newCancunInstructionSet() JumpTable {
6976
instructionSet := newShanghaiInstructionSet()
7077
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)

core/vm/operations_acl.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
"github.com/ethereum/go-ethereum/common"
2323
"github.com/ethereum/go-ethereum/common/math"
24+
"github.com/ethereum/go-ethereum/core/types"
2425
"github.com/ethereum/go-ethereum/params"
2526
)
2627

@@ -247,3 +248,70 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
247248
}
248249
return gasFunc
249250
}
251+
252+
var (
253+
gasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall)
254+
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall)
255+
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall)
256+
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode)
257+
)
258+
259+
func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc {
260+
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
261+
var (
262+
total uint64 // total dynamic gas used
263+
addr = common.Address(stack.Back(1).Bytes20())
264+
)
265+
266+
// Check slot presence in the access list
267+
if !evm.StateDB.AddressInAccessList(addr) {
268+
evm.StateDB.AddAddressToAccessList(addr)
269+
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so
270+
// the cost to charge for cold access, if any, is Cold - Warm
271+
coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
272+
// Charge the remaining difference here already, to correctly calculate available
273+
// gas for call
274+
if !contract.UseGas(coldCost) {
275+
return 0, ErrOutOfGas
276+
}
277+
total += coldCost
278+
}
279+
280+
// Check if code is a delegation and if so, charge for resolution.
281+
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
282+
var cost uint64
283+
if evm.StateDB.AddressInAccessList(target) {
284+
cost = params.WarmStorageReadCostEIP2929
285+
} else {
286+
evm.StateDB.AddAddressToAccessList(target)
287+
cost = params.ColdAccountAccessCostEIP2929
288+
}
289+
if !contract.UseGas(cost) {
290+
return 0, ErrOutOfGas
291+
}
292+
total += cost
293+
}
294+
295+
// Now call the old calculator, which takes into account
296+
// - create new account
297+
// - transfer value
298+
// - memory expansion
299+
// - 63/64ths rule
300+
old, err := oldCalculator(evm, contract, stack, mem, memorySize)
301+
if err != nil {
302+
return old, err
303+
}
304+
305+
// Temporarily add the gas charge back to the contract and return value. By
306+
// adding it to the return, it will be charged outside of this function, as
307+
// part of the dynamic gas. This will ensure it is correctly reported to
308+
// tracers.
309+
contract.Gas += total
310+
311+
var overflow bool
312+
if total, overflow = math.SafeAdd(old, total); overflow {
313+
return 0, ErrGasUintOverflow
314+
}
315+
return total, nil
316+
}
317+
}

0 commit comments

Comments
 (0)