Skip to content

Commit 479be70

Browse files
committed
core/vm: implement EIP-3860: Limit and meter initcode (ethereum#23847)
1 parent 33e2eec commit 479be70

File tree

13 files changed

+169
-18
lines changed

13 files changed

+169
-18
lines changed

core/bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
8585
return func(i int, gen *BlockGen) {
8686
toaddr := common.Address{}
8787
data := make([]byte, nbytes)
88-
gas, _ := IntrinsicGas(data, nil, false, false)
88+
gas, _ := IntrinsicGas(data, nil, false, false, false)
8989
tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(benchRootAddr), toaddr, big.NewInt(1), gas, nil, data), types.HomesteadSigner{}, benchRootKey)
9090
gen.AddTx(tx)
9191
}

core/error.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ var (
4545
// by a transaction is higher than what's left in the block.
4646
ErrGasLimitReached = errors.New("gas limit reached")
4747

48+
// ErrMaxInitCodeSizeExceeded is returned if creation transaction provides the init code bigger
49+
// than init code size limit.
50+
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
51+
4852
// ErrInsufficientFunds is returned if the total cost of executing a transaction
4953
// is higher than the balance of the user's account.
5054
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")

core/state_transition.go

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,17 @@ type Message interface {
9090
}
9191

9292
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
93-
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead bool) (uint64, error) {
93+
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead bool, isEIP3860 bool) (uint64, error) {
9494
// Set the starting gas for the raw transaction
9595
var gas uint64
9696
if isContractCreation && isHomestead {
9797
gas = params.TxGasContractCreation
9898
} else {
9999
gas = params.TxGas
100100
}
101+
dataLen := uint64(len(data))
101102
// Bump the required gas by the amount of transactional data
102-
if len(data) > 0 {
103+
if dataLen > 0 {
103104
// Zero and non-zero bytes are priced differently
104105
var nz uint64
105106
for _, byt := range data {
@@ -113,11 +114,19 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
113114
}
114115
gas += nz * params.TxDataNonZeroGas
115116

116-
z := uint64(len(data)) - nz
117+
z := dataLen - nz
117118
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
118119
return 0, ErrGasUintOverflow
119120
}
120121
gas += z * params.TxDataZeroGas
122+
123+
if isContractCreation && isEIP3860 {
124+
lenWords := toWordSize(dataLen)
125+
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
126+
return 0, ErrGasUintOverflow
127+
}
128+
gas += lenWords * params.InitCodeWordGas
129+
}
121130
}
122131
if accessList != nil {
123132
gas += uint64(len(accessList)) * params.TxAccessListAddressGas
@@ -126,6 +135,15 @@ func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation,
126135
return gas, nil
127136
}
128137

138+
// toWordSize returns the ceiled word size required for init code payment calculation.
139+
func toWordSize(size uint64) uint64 {
140+
if size > math.MaxUint64-31 {
141+
return math.MaxUint64/32 + 1
142+
}
143+
144+
return (size + 31) / 32
145+
}
146+
129147
// NewStateTransition initialises and returns a new state transition object.
130148
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
131149
return &StateTransition{
@@ -286,14 +304,18 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
286304
if err = st.preCheck(); err != nil {
287305
return nil, 0, false, err, nil
288306
}
289-
msg := st.msg
290-
sender := st.from() // err checked in preCheck
291-
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
292-
eip3529 := st.evm.ChainConfig().IsEIP1559(st.evm.Context.BlockNumber)
293-
contractCreation := msg.To() == nil
307+
308+
var (
309+
msg = st.msg
310+
sender = st.from() // err checked in preCheck
311+
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber)
312+
homestead = rules.IsHomestead
313+
eip3529 = rules.IsEIP1559
314+
contractCreation = msg.To() == nil
315+
)
294316

295317
// Check clauses 4-5, subtract intrinsic gas if everything is correct
296-
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead)
318+
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, homestead, rules.IsEIP1559)
297319
if err != nil {
298320
return nil, 0, false, err, nil
299321
}
@@ -302,7 +324,12 @@ func (st *StateTransition) TransitionDb(owner common.Address) (ret []byte, usedG
302324
}
303325
st.gas -= gas
304326

305-
if rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber); rules.IsEIP1559 {
327+
// Check whether the init code size has been exceeded.
328+
if rules.IsEIP1559 && contractCreation && len(st.data) > params.MaxInitCodeSize {
329+
return nil, 0, false, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize), nil
330+
}
331+
332+
if rules.IsEIP1559 {
306333
st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
307334
}
308335

core/txpool/txpool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
702702

703703
if tx.To() == nil || (tx.To() != nil && !tx.IsSpecialTransaction()) {
704704
// Ensure the transaction has more gas than the basic tx fee.
705-
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true)
705+
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.eip1559)
706706
if err != nil {
707707
return err
708708
}

core/vm/eips.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626

2727
var activators = map[int]func(*JumpTable){
2828
3855: enable3855,
29+
3860: enable3860,
2930
3529: enable3529,
3031
3198: enable3198,
3132
2929: enable2929,
@@ -179,3 +180,10 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext)
179180
callContext.Stack.push(new(uint256.Int))
180181
return nil, nil
181182
}
183+
184+
// ebnable3860 enables "EIP-3860: Limit and meter initcode"
185+
// https://eips.ethereum.org/EIPS/eip-3860
186+
func enable3860(jt *JumpTable) {
187+
jt[CREATE].dynamicGas = gasCreateEip3860
188+
jt[CREATE2].dynamicGas = gasCreate2Eip3860
189+
}

core/vm/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ var (
2929
ErrInsufficientBalance = errors.New("insufficient balance for transfer")
3030
ErrContractAddressCollision = errors.New("contract address collision")
3131
ErrExecutionReverted = errors.New("execution reverted")
32+
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
3233
ErrMaxCodeSizeExceeded = errors.New("max code size exceeded")
3334
ErrInvalidJump = errors.New("invalid jump destination")
3435
ErrWriteProtection = errors.New("write protection")

core/vm/gas_table.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,40 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
300300
return gas, nil
301301
}
302302

303+
func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
304+
gas, err := memoryGasCost(mem, memorySize)
305+
if err != nil {
306+
return 0, err
307+
}
308+
size, overflow := stack.Back(2).Uint64WithOverflow()
309+
if overflow || size > params.MaxInitCodeSize {
310+
return 0, ErrGasUintOverflow
311+
}
312+
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
313+
moreGas := params.InitCodeWordGas * ((size + 31) / 32)
314+
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
315+
return 0, ErrGasUintOverflow
316+
}
317+
return gas, nil
318+
}
319+
320+
func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
321+
gas, err := memoryGasCost(mem, memorySize)
322+
if err != nil {
323+
return 0, err
324+
}
325+
size, overflow := stack.Back(2).Uint64WithOverflow()
326+
if overflow || size > params.MaxInitCodeSize {
327+
return 0, ErrGasUintOverflow
328+
}
329+
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
330+
moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32)
331+
if gas, overflow = math.SafeAdd(gas, moreGas); overflow {
332+
return 0, ErrGasUintOverflow
333+
}
334+
return gas, nil
335+
}
336+
303337
func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
304338
expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8)
305339

core/vm/gas_table_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package vm
1818

1919
import (
20+
"bytes"
2021
"math"
2122
"math/big"
23+
"sort"
2224
"testing"
2325

2426
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
@@ -106,3 +108,72 @@ func TestEIP2200(t *testing.T) {
106108
}
107109
}
108110
}
111+
112+
var createGasTests = []struct {
113+
code string
114+
eip3860 bool
115+
gasUsed uint64
116+
minimumGas uint64
117+
}{
118+
// legacy create(0, 0, 0xc000) without 3860 used
119+
{"0x61C00060006000f0" + "600052" + "60206000F3", false, 41237, 41237},
120+
// legacy create(0, 0, 0xc000) _with_ 3860
121+
{"0x61C00060006000f0" + "600052" + "60206000F3", true, 44309, 44309},
122+
// create2(0, 0, 0xc001, 0) without 3860
123+
{"0x600061C00160006000f5" + "600052" + "60206000F3", false, 50471, 100_000},
124+
// create2(0, 0, 0xc001, 0) (too large), with 3860
125+
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32012, 100_000},
126+
// create2(0, 0, 0xc000, 0)
127+
// This case is trying to deploy code at (within) the limit
128+
{"0x600061C00060006000f5" + "600052" + "60206000F3", true, 53528, 100_000},
129+
// create2(0, 0, 0xc001, 0)
130+
// This case is trying to deploy code exceeding the limit
131+
{"0x600061C00160006000f5" + "600052" + "60206000F3", true, 32024, 100_000}}
132+
133+
func TestCreateGas(t *testing.T) {
134+
for i, tt := range createGasTests {
135+
var gasUsed = uint64(0)
136+
doCheck := func(testGas int) bool {
137+
address := common.BytesToAddress([]byte("contract"))
138+
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
139+
statedb.CreateAccount(address)
140+
statedb.SetCode(address, hexutil.MustDecode(tt.code))
141+
statedb.Finalise(true)
142+
vmctx := BlockContext{
143+
CanTransfer: func(StateDB, common.Address, *big.Int) bool { return true },
144+
Transfer: func(StateDB, common.Address, common.Address, *big.Int) {},
145+
BlockNumber: big.NewInt(0),
146+
}
147+
config := Config{}
148+
if tt.eip3860 {
149+
config.ExtraEips = []int{3860}
150+
}
151+
152+
vmenv := NewEVM(vmctx, TxContext{}, statedb, nil, params.AllEthashProtocolChanges, config)
153+
var startGas = uint64(testGas)
154+
ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int))
155+
if err != nil {
156+
return false
157+
}
158+
gasUsed = startGas - gas
159+
if len(ret) != 32 {
160+
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
161+
}
162+
if bytes.Equal(ret, make([]byte, 32)) {
163+
// Failure
164+
return false
165+
}
166+
return true
167+
}
168+
minGas := sort.Search(100_000, doCheck)
169+
if uint64(minGas) != tt.minimumGas {
170+
t.Fatalf("test %d: min gas error, want %d, have %d", i, tt.minimumGas, minGas)
171+
}
172+
// If the deployment succeeded, we also check the gas used
173+
if minGas < 100_000 {
174+
if gasUsed != tt.gasUsed {
175+
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, gasUsed, tt.gasUsed)
176+
}
177+
}
178+
}
179+
}

core/vm/instructions.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
646646
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
647647
gas = scope.Contract.Gas
648648
)
649-
650649
// Apply EIP150
651650
gas -= gas / 64
652651
scope.Contract.UseGas(gas)

core/vm/jump_table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func newEip1559InstructionSet() JumpTable {
6464
instructionSet := newShanghaiInstructionSet()
6565
enable2929(&instructionSet) // Gas cost increases for state access opcodes https://eips.ethereum.org/EIPS/eip-2929
6666
enable3529(&instructionSet) // EIP-3529: Reduction in refunds https://eips.ethereum.org/EIPS/eip-3529
67+
enable3860(&instructionSet) // Limit and meter initcode
6768
return instructionSet
6869
}
6970

0 commit comments

Comments
 (0)