Skip to content

Commit 9788f8b

Browse files
committed
test(vm): preprocessing gas charges
1 parent 53f6df3 commit 9788f8b

File tree

4 files changed

+247
-24
lines changed

4 files changed

+247
-24
lines changed

core/vm/hooks.libevm.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,19 @@ func (evm *EVM) overrideEVMResetArgs(txCtx TxContext, statedb StateDB) (TxContex
8989
args := libevmHooks.Get().OverrideEVMResetArgs(evm.chainRules, &EVMResetArgs{txCtx, statedb})
9090
return args.TxContext, args.StateDB
9191
}
92+
93+
// NOOPHooks implements [Hooks] such that every method is a noop.
94+
type NOOPHooks struct{}
95+
96+
var _ Hooks = NOOPHooks{}
97+
98+
// OverrideNewEVMArgs returns the args unchanged.
99+
func (NOOPHooks) OverrideNewEVMArgs(a *NewEVMArgs) *NewEVMArgs { return a }
100+
101+
// OverrideEVMResetArgs returns the args unchanged.
102+
func (NOOPHooks) OverrideEVMResetArgs(_ params.Rules, a *EVMResetArgs) *EVMResetArgs {
103+
return a
104+
}
105+
106+
// PreprocessingGasCharge returns (0, nil).
107+
func (NOOPHooks) PreprocessingGasCharge(common.Hash) (uint64, error) { return 0, nil }

core/vm/preprocess.libevm_test.go

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package vm_test
18+
19+
import (
20+
"fmt"
21+
"math"
22+
"math/big"
23+
"testing"
24+
25+
"github.com/ava-labs/libevm/common"
26+
"github.com/ava-labs/libevm/core"
27+
"github.com/ava-labs/libevm/core/rawdb"
28+
"github.com/ava-labs/libevm/core/state"
29+
"github.com/ava-labs/libevm/core/types"
30+
"github.com/ava-labs/libevm/core/vm"
31+
"github.com/ava-labs/libevm/crypto"
32+
"github.com/ava-labs/libevm/libevm/ethtest"
33+
"github.com/ava-labs/libevm/params"
34+
"github.com/holiman/uint256"
35+
"github.com/stretchr/testify/assert"
36+
"github.com/stretchr/testify/require"
37+
)
38+
39+
type preprocessingCharger struct {
40+
vm.NOOPHooks
41+
charge map[common.Hash]uint64
42+
}
43+
44+
func (p preprocessingCharger) PreprocessingGasCharge(tx common.Hash) (uint64, error) {
45+
c, ok := p.charge[tx]
46+
if !ok {
47+
return 0, fmt.Errorf("unknown tx %v", tx)
48+
}
49+
return c, nil
50+
}
51+
52+
func TestChargePreprocessingGas(t *testing.T) {
53+
tests := []struct {
54+
name string
55+
to *common.Address
56+
charge uint64
57+
txGas uint64
58+
wantVMErr error
59+
wantGasUsed uint64
60+
}{
61+
{
62+
name: "standard create",
63+
to: nil,
64+
txGas: params.TxGas + params.CreateGas,
65+
wantGasUsed: params.TxGas + params.CreateGas,
66+
},
67+
{
68+
name: "create with extra charge",
69+
to: nil,
70+
charge: 1234,
71+
txGas: params.TxGas + params.CreateGas + 2000,
72+
wantGasUsed: params.TxGas + params.CreateGas + 1234,
73+
},
74+
{
75+
name: "standard call",
76+
to: &common.Address{},
77+
txGas: params.TxGas,
78+
wantGasUsed: params.TxGas,
79+
},
80+
{
81+
name: "out of gas",
82+
to: &common.Address{},
83+
charge: 1000,
84+
txGas: params.TxGas + 999,
85+
wantGasUsed: params.TxGas + 999,
86+
wantVMErr: vm.ErrOutOfGas,
87+
},
88+
{
89+
name: "call with extra charge",
90+
to: &common.Address{},
91+
charge: 13579,
92+
txGas: params.TxGas + 20000,
93+
wantGasUsed: params.TxGas + 13579,
94+
},
95+
}
96+
97+
config := params.AllDevChainProtocolChanges
98+
key, err := crypto.GenerateKey()
99+
require.NoError(t, err, "crypto.GenerateKey()")
100+
eoa := crypto.PubkeyToAddress(key.PublicKey)
101+
102+
header := &types.Header{
103+
Number: big.NewInt(0),
104+
Difficulty: big.NewInt(0),
105+
BaseFee: big.NewInt(0),
106+
}
107+
signer := types.MakeSigner(config, header.Number, header.Time)
108+
109+
var txs types.Transactions
110+
charge := make(map[common.Hash]uint64)
111+
for _, tt := range tests {
112+
tx := types.MustSignNewTx(key, signer, &types.LegacyTx{
113+
To: tt.to,
114+
GasPrice: big.NewInt(1),
115+
Gas: tt.txGas,
116+
})
117+
txs = append(txs, tx)
118+
charge[tx.Hash()] = tt.charge
119+
}
120+
121+
vm.RegisterHooks(&preprocessingCharger{
122+
charge: charge,
123+
})
124+
t.Cleanup(vm.TestOnlyClearRegisteredHooks)
125+
126+
for i, tt := range tests {
127+
tx := txs[i]
128+
129+
t.Run(tt.name, func(t *testing.T) {
130+
t.Logf("Extra gas charge: %d", tt.charge)
131+
132+
t.Run("ApplyTransaction", func(t *testing.T) {
133+
sdb, err := state.New(
134+
types.EmptyRootHash,
135+
state.NewDatabase(rawdb.NewMemoryDatabase()),
136+
nil,
137+
)
138+
require.NoError(t, err, "state.New(types.EmptyRootHash, [memory db], nil)")
139+
sdb.SetTxContext(tx.Hash(), i)
140+
sdb.SetBalance(eoa, new(uint256.Int).SetAllOne())
141+
142+
var gotGasUsed uint64
143+
gp := core.GasPool(math.MaxUint64)
144+
145+
receipt, err := core.ApplyTransaction(
146+
config, ethtest.DummyChainContext(), &common.Address{},
147+
&gp, sdb, header, tx, &gotGasUsed, vm.Config{},
148+
)
149+
require.NoError(t, err, "core.ApplyTransaction(...)")
150+
151+
wantStatus := types.ReceiptStatusSuccessful
152+
if tt.wantVMErr != nil {
153+
wantStatus = types.ReceiptStatusFailed
154+
}
155+
assert.Equalf(t, wantStatus, receipt.Status, "%T.Status", receipt)
156+
157+
if got, want := gotGasUsed, tt.wantGasUsed; got != want {
158+
t.Errorf("core.ApplyTransaction(..., &gotGasUsed, ...) got %d; want %d", got, want)
159+
}
160+
if got, want := receipt.GasUsed, tt.wantGasUsed; got != want {
161+
t.Errorf("core.ApplyTransaction(...) -> %T.GasUsed = %d; want %d", receipt, got, want)
162+
}
163+
})
164+
165+
t.Run("VM_error", func(t *testing.T) {
166+
sdb, evm := ethtest.NewZeroEVM(t, ethtest.WithChainConfig(config))
167+
sdb.SetBalance(eoa, new(uint256.Int).SetAllOne())
168+
sdb.SetTxContext(tx.Hash(), i)
169+
170+
msg, err := core.TransactionToMessage(tx, signer, header.BaseFee)
171+
require.NoError(t, err, "core.TransactionToMessage(...)")
172+
173+
gp := core.GasPool(math.MaxUint64)
174+
got, err := core.ApplyMessage(evm, msg, &gp)
175+
require.NoError(t, err, "core.ApplyMessage(...)")
176+
require.ErrorIsf(t, got.Err, tt.wantVMErr, "%T.Err", got)
177+
})
178+
})
179+
}
180+
}

libevm/ethtest/dummy.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package ethtest
18+
19+
import (
20+
"github.com/ava-labs/libevm/common"
21+
"github.com/ava-labs/libevm/consensus"
22+
"github.com/ava-labs/libevm/core"
23+
"github.com/ava-labs/libevm/core/types"
24+
)
25+
26+
// DummyChainContext returns a dummy that returns [DummyEngine] when its
27+
// Engine() method is called, and panics when its GetHeader() method is called.
28+
func DummyChainContext() core.ChainContext {
29+
return chainContext{}
30+
}
31+
32+
// DummyEngine returns a dummy that panics when its Author() method is called.
33+
func DummyEngine() consensus.Engine {
34+
return engine{}
35+
}
36+
37+
type (
38+
chainContext struct{}
39+
engine struct{ consensus.Engine }
40+
)
41+
42+
func (chainContext) Engine() consensus.Engine { return engine{} }
43+
func (chainContext) GetHeader(common.Hash, uint64) *types.Header { panic("unimplemented") }
44+
func (engine) Author(h *types.Header) (common.Address, error) { panic("unimplemented") }

libevm/precompiles/parallel/parallel_test.go

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
"go.uber.org/goleak"
3333

3434
"github.com/ava-labs/libevm/common"
35-
"github.com/ava-labs/libevm/consensus"
3635
"github.com/ava-labs/libevm/core"
3736
"github.com/ava-labs/libevm/core/types"
3837
"github.com/ava-labs/libevm/core/vm"
@@ -190,19 +189,13 @@ func TestProcessor(t *testing.T) {
190189
}
191190
}
192191

193-
type noopHooks struct{}
194-
195-
func (noopHooks) OverrideNewEVMArgs(a *vm.NewEVMArgs) *vm.NewEVMArgs {
196-
return a
197-
}
198-
199-
func (noopHooks) OverrideEVMResetArgs(_ params.Rules, a *vm.EVMResetArgs) *vm.EVMResetArgs {
200-
return a
201-
}
202-
203192
type vmHooks struct {
204193
vm.Preprocessor // the [Processor]
205-
noopHooks
194+
vm.NOOPHooks
195+
}
196+
197+
func (h *vmHooks) PreprocessingGasCharge(tx common.Hash) (uint64, error) {
198+
return h.Preprocessor.PreprocessingGasCharge(tx)
206199
}
207200

208201
func TestIntegration(t *testing.T) {
@@ -214,7 +207,7 @@ func TestIntegration(t *testing.T) {
214207
sut := New(handler, 8)
215208
t.Cleanup(sut.Close)
216209

217-
vm.RegisterHooks(vmHooks{Preprocessor: sut})
210+
vm.RegisterHooks(&vmHooks{Preprocessor: sut})
218211
t.Cleanup(vm.TestOnlyClearRegisteredHooks)
219212

220213
stub := &hookstest.Stub{
@@ -313,7 +306,7 @@ func TestIntegration(t *testing.T) {
313306
var usedGas uint64
314307
receipt, err := core.ApplyTransaction(
315308
evm.ChainConfig(),
316-
chainContext{},
309+
ethtest.DummyChainContext(),
317310
&block.Header().Coinbase,
318311
&pool,
319312
state,
@@ -330,13 +323,3 @@ func TestIntegration(t *testing.T) {
330323
t.Errorf("%T diff (-want +got):\n%s", got, diff)
331324
}
332325
}
333-
334-
// Dummy implementations of interfaces required by [core.ApplyTransaction].
335-
type (
336-
chainContext struct{}
337-
engine struct{ consensus.Engine }
338-
)
339-
340-
func (chainContext) Engine() consensus.Engine { return engine{} }
341-
func (chainContext) GetHeader(common.Hash, uint64) *types.Header { panic("unimplemented") }
342-
func (engine) Author(h *types.Header) (common.Address, error) { return common.Address{}, nil }

0 commit comments

Comments
 (0)