@@ -17,15 +17,21 @@ package core_test
17
17
18
18
import (
19
19
"fmt"
20
+ "math/big"
20
21
"testing"
21
22
23
+ "github.com/holiman/uint256"
22
24
"github.com/stretchr/testify/require"
23
25
24
26
"github.com/ava-labs/libevm/common"
25
27
"github.com/ava-labs/libevm/core"
28
+ "github.com/ava-labs/libevm/core/types"
29
+ "github.com/ava-labs/libevm/core/vm"
30
+ "github.com/ava-labs/libevm/crypto"
26
31
"github.com/ava-labs/libevm/libevm"
27
32
"github.com/ava-labs/libevm/libevm/ethtest"
28
33
"github.com/ava-labs/libevm/libevm/hookstest"
34
+ "github.com/ava-labs/libevm/params"
29
35
)
30
36
31
37
func TestCanExecuteTransaction (t * testing.T ) {
@@ -54,3 +60,143 @@ func TestCanExecuteTransaction(t *testing.T) {
54
60
_ , err := core .ApplyMessage (evm , msg , new (core.GasPool ).AddGas (30e6 ))
55
61
require .EqualError (t , err , makeErr (msg .From , msg .To , value ).Error ())
56
62
}
63
+
64
+ func TestMinimumGasConsumption (t * testing.T ) {
65
+ // All transactions will be basic transfers so consume [params.TxGas] by
66
+ // default.
67
+ tests := []struct {
68
+ name string
69
+ gasLimit uint64
70
+ refund uint64
71
+ minConsumption uint64
72
+ wantUsed uint64
73
+ }{
74
+ {
75
+ name : "consume_extra" ,
76
+ gasLimit : 1e6 ,
77
+ minConsumption : 5e5 ,
78
+ wantUsed : 5e5 ,
79
+ },
80
+ {
81
+ name : "consume_extra" ,
82
+ gasLimit : 1e6 ,
83
+ minConsumption : 4e5 ,
84
+ wantUsed : 4e5 ,
85
+ },
86
+ {
87
+ name : "no_extra_consumption" ,
88
+ gasLimit : 50_000 ,
89
+ minConsumption : params .TxGas - 1 ,
90
+ wantUsed : params .TxGas ,
91
+ },
92
+ {
93
+ name : "zero_min" ,
94
+ gasLimit : 50_000 ,
95
+ minConsumption : 0 ,
96
+ wantUsed : params .TxGas ,
97
+ },
98
+ {
99
+ name : "consume_extra_by_one" ,
100
+ gasLimit : 1e6 ,
101
+ minConsumption : params .TxGas + 1 ,
102
+ wantUsed : params .TxGas + 1 ,
103
+ },
104
+ {
105
+ name : "min_capped_at_limit" ,
106
+ gasLimit : 1e6 ,
107
+ minConsumption : 2e6 ,
108
+ wantUsed : 1e6 ,
109
+ },
110
+ {
111
+ // Although this doesn't test minimum consumption, it demonstrates
112
+ // the expected outcome for comparison with the next test.
113
+ name : "refund_without_min_consumption" ,
114
+ gasLimit : 1e6 ,
115
+ refund : 1 ,
116
+ wantUsed : params .TxGas - 1 ,
117
+ },
118
+ {
119
+ name : "refund_with_min_consumption" ,
120
+ gasLimit : 1e6 ,
121
+ refund : 1 ,
122
+ minConsumption : params .TxGas ,
123
+ wantUsed : params .TxGas ,
124
+ },
125
+ }
126
+
127
+ // Very low gas price so we can calculate the expected balance in a uint64,
128
+ // but not 1 otherwise tests would pass without multiplying extra
129
+ // consumption by the price.
130
+ const gasPrice = 3
131
+
132
+ for _ , tt := range tests {
133
+ t .Run (tt .name , func (t * testing.T ) {
134
+ hooks := & hookstest.Stub {
135
+ MinimumGasConsumptionFn : func (limit uint64 ) uint64 {
136
+ require .Equal (t , tt .gasLimit , limit )
137
+ return tt .minConsumption
138
+ },
139
+ }
140
+ hooks .Register (t )
141
+
142
+ key , err := crypto .GenerateKey ()
143
+ require .NoError (t , err , "libevm/crypto.GenerateKey()" )
144
+
145
+ stateDB , evm := ethtest .NewZeroEVM (t )
146
+ signer := types .LatestSigner (evm .ChainConfig ())
147
+ tx := types .MustSignNewTx (
148
+ key , signer ,
149
+ & types.LegacyTx {
150
+ GasPrice : big .NewInt (gasPrice ),
151
+ Gas : tt .gasLimit ,
152
+ To : & common.Address {},
153
+ Value : big .NewInt (0 ),
154
+ },
155
+ )
156
+
157
+ const startingBalance = 10 * params .Ether
158
+ from := crypto .PubkeyToAddress (key .PublicKey )
159
+ stateDB .SetNonce (from , 0 )
160
+ stateDB .SetBalance (from , uint256 .NewInt (startingBalance ))
161
+ stateDB .AddRefund (tt .refund )
162
+
163
+ var (
164
+ // Both variables are passed as pointers to
165
+ // [core.ApplyTransaction], which will modify them.
166
+ gotUsed uint64
167
+ gotPool = core .GasPool (1e9 )
168
+ )
169
+ wantPool := gotPool - core .GasPool (tt .wantUsed )
170
+
171
+ receipt , err := core .ApplyTransaction (
172
+ evm .ChainConfig (), nil , & common.Address {}, & gotPool , stateDB ,
173
+ & types.Header {
174
+ BaseFee : big .NewInt (gasPrice ),
175
+ // Required but irrelevant fields
176
+ Number : big .NewInt (0 ),
177
+ Difficulty : big .NewInt (0 ),
178
+ },
179
+ tx , & gotUsed , vm.Config {},
180
+ )
181
+ require .NoError (t , err , "core.ApplyTransaction(...)" )
182
+
183
+ for desc , got := range map [string ]uint64 {
184
+ "receipt.GasUsed" : receipt .GasUsed ,
185
+ "receipt.CumulativeGasUsed" : receipt .CumulativeGasUsed ,
186
+ "core.ApplyTransaction(..., usedGas *uint64, ...)" : gotUsed ,
187
+ } {
188
+ if got != tt .wantUsed {
189
+ t .Errorf ("%s got %d; want %d" , desc , got , tt .wantUsed )
190
+ }
191
+ }
192
+ if gotPool != wantPool {
193
+ t .Errorf ("After core.ApplyMessage(..., *%T); got %[1]T = %[1]d; want %d" , gotPool , wantPool )
194
+ }
195
+
196
+ wantBalance := uint256 .NewInt (startingBalance - tt .wantUsed * gasPrice )
197
+ if got := stateDB .GetBalance (from ); ! got .Eq (wantBalance ) {
198
+ t .Errorf ("got remaining balance %d; want %d" , got , wantBalance )
199
+ }
200
+ })
201
+ }
202
+ }
0 commit comments