@@ -2,6 +2,7 @@ package vm_test
2
2
3
3
import (
4
4
"fmt"
5
+ "math/big"
5
6
"testing"
6
7
7
8
"github.com/holiman/uint256"
@@ -86,18 +87,25 @@ func TestNewStatefulPrecompile(t *testing.T) {
86
87
const gasLimit = 1e6
87
88
gasCost := rng .Uint64n (gasLimit )
88
89
89
- makeOutput := func (caller , self common.Address , input []byte , stateVal common.Hash ) []byte {
90
+ makeOutput := func (caller , self common.Address , input []byte , stateVal common.Hash , readOnly bool ) []byte {
90
91
return []byte (fmt .Sprintf (
91
- "Caller: %v Precompile: %v State: %v Input: %#x" ,
92
- caller , self , stateVal , input ,
92
+ "Caller: %v Precompile: %v State: %v Read-only: %t, Input: %#x" ,
93
+ caller , self , stateVal , readOnly , input ,
93
94
))
94
95
}
96
+ run := func (env vm.PrecompileEnvironment , input []byte ) ([]byte , error ) {
97
+ if got , want := env .StateDB () != nil , ! env .ReadOnly (); got != want {
98
+ return nil , fmt .Errorf ("PrecompileEnvironment().StateDB() must be non-nil i.f.f. not read-only; got non-nil? %t; want %t" , got , want )
99
+ }
100
+
101
+ addrs := env .Addresses ()
102
+ val := env .ReadOnlyState ().GetState (precompile , slot )
103
+ return makeOutput (addrs .Caller , addrs .Self , input , val , env .ReadOnly ()), nil
104
+ }
95
105
hooks := & hookstest.Stub {
96
106
PrecompileOverrides : map [common.Address ]libevm.PrecompiledContract {
97
107
precompile : vm .NewStatefulPrecompile (
98
- func (state vm.StateDB , _ * params.Rules , caller , self common.Address , input []byte ) ([]byte , error ) {
99
- return makeOutput (caller , self , input , state .GetState (precompile , slot )), nil
100
- },
108
+ run ,
101
109
func (b []byte ) uint64 {
102
110
return gasCost
103
111
},
@@ -112,13 +120,175 @@ func TestNewStatefulPrecompile(t *testing.T) {
112
120
113
121
state , evm := ethtest .NewZeroEVM (t )
114
122
state .SetState (precompile , slot , value )
115
- wantReturnData := makeOutput (caller , precompile , input , value )
116
- wantGasLeft := gasLimit - gasCost
117
123
118
- gotReturnData , gotGasLeft , err := evm .Call (vm .AccountRef (caller ), precompile , input , gasLimit , uint256 .NewInt (0 ))
119
- require .NoError (t , err )
120
- assert .Equal (t , wantReturnData , gotReturnData )
121
- assert .Equal (t , wantGasLeft , gotGasLeft )
124
+ tests := []struct {
125
+ name string
126
+ call func () ([]byte , uint64 , error )
127
+ // Note that this only covers evm.readWrite being set to forceReadOnly,
128
+ // via StaticCall(). See TestInheritReadOnly for alternate case.
129
+ wantReadOnly bool
130
+ }{
131
+ {
132
+ name : "EVM.Call()" ,
133
+ call : func () ([]byte , uint64 , error ) {
134
+ return evm .Call (vm .AccountRef (caller ), precompile , input , gasLimit , uint256 .NewInt (0 ))
135
+ },
136
+ wantReadOnly : false ,
137
+ },
138
+ {
139
+ name : "EVM.CallCode()" ,
140
+ call : func () ([]byte , uint64 , error ) {
141
+ return evm .CallCode (vm .AccountRef (caller ), precompile , input , gasLimit , uint256 .NewInt (0 ))
142
+ },
143
+ wantReadOnly : false ,
144
+ },
145
+ {
146
+ name : "EVM.DelegateCall()" ,
147
+ call : func () ([]byte , uint64 , error ) {
148
+ return evm .DelegateCall (vm .AccountRef (caller ), precompile , input , gasLimit )
149
+ },
150
+ wantReadOnly : false ,
151
+ },
152
+ {
153
+ name : "EVM.StaticCall()" ,
154
+ call : func () ([]byte , uint64 , error ) {
155
+ return evm .StaticCall (vm .AccountRef (caller ), precompile , input , gasLimit )
156
+ },
157
+ wantReadOnly : true ,
158
+ },
159
+ }
160
+
161
+ for _ , tt := range tests {
162
+ t .Run (tt .name , func (t * testing.T ) {
163
+ wantReturnData := makeOutput (caller , precompile , input , value , tt .wantReadOnly )
164
+ wantGasLeft := gasLimit - gasCost
165
+
166
+ gotReturnData , gotGasLeft , err := tt .call ()
167
+ require .NoError (t , err )
168
+ assert .Equal (t , string (wantReturnData ), string (gotReturnData ))
169
+ assert .Equal (t , wantGasLeft , gotGasLeft )
170
+ })
171
+ }
172
+ }
173
+
174
+ func TestInheritReadOnly (t * testing.T ) {
175
+ // The regular test of stateful precompiles only checks the read-only state
176
+ // when called directly via vm.EVM.*Call*() methods. That approach will not
177
+ // result in a read-only state via inheritance, which occurs when already in
178
+ // a read-only environment there is a non-static call to a precompile.
179
+ //
180
+ // Test strategy:
181
+ //
182
+ // 1. Create a precompile that echoes its read-only status in the return
183
+ // data. We MUST NOT assert inside the precompile as we need proof that
184
+ // the precompile was actually called.
185
+ //
186
+ // 2. Create a bytecode contract that calls the precompile with CALL and
187
+ // propagates the return data. Using CALL (i.e. not STATICCALL) means
188
+ // that we know for certain that [forceReadOnly] isn't being used and,
189
+ // instead, the read-only state is being read from
190
+ // evm.interpreter.readOnly.
191
+ //
192
+ // 3. Assert that the returned input is as expected for the read-only state.
193
+
194
+ // (1)
195
+
196
+ var precompile common.Address
197
+ const precompileAddr = 255
198
+ precompile [common .AddressLength - 1 ] = precompileAddr
199
+
200
+ const (
201
+ ifReadOnly = iota + 1 // see contract bytecode for rationale
202
+ ifNotReadOnly
203
+ )
204
+ hooks := & hookstest.Stub {
205
+ PrecompileOverrides : map [common.Address ]libevm.PrecompiledContract {
206
+ precompile : vm .NewStatefulPrecompile (
207
+ func (env vm.PrecompileEnvironment , input []byte ) ([]byte , error ) {
208
+ if env .ReadOnly () {
209
+ return []byte {ifReadOnly }, nil
210
+ }
211
+ return []byte {ifNotReadOnly }, nil
212
+ },
213
+ func ([]byte ) uint64 { return 0 },
214
+ ),
215
+ },
216
+ }
217
+ hookstest .Register (t , params.Extras [* hookstest.Stub , * hookstest.Stub ]{
218
+ NewRules : func (_ * params.ChainConfig , r * params.Rules , _ * hookstest.Stub , blockNum * big.Int , isMerge bool , timestamp uint64 ) * hookstest.Stub {
219
+ r .IsCancun = true // enable PUSH0
220
+ return hooks
221
+ },
222
+ })
223
+
224
+ // (2)
225
+
226
+ // See CALL signature: https://www.evm.codes/#f1?fork=cancun
227
+ const p0 = vm .PUSH0
228
+ contract := []vm.OpCode {
229
+ vm .PUSH1 , 1 , // retSize (bytes)
230
+ p0 , // retOffset
231
+ p0 , // argSize
232
+ p0 , // argOffset
233
+ p0 , // value
234
+ vm .PUSH1 , precompileAddr ,
235
+ p0 , // gas
236
+ vm .CALL ,
237
+ // It's ok to ignore the return status. If the CALL failed then we'll
238
+ // return []byte{0} next, and both non-failure return buffers are
239
+ // non-zero because of the `iota + 1`.
240
+ vm .PUSH1 , 1 , // size (byte)
241
+ p0 ,
242
+ vm .RETURN ,
243
+ }
244
+
245
+ state , evm := ethtest .NewZeroEVM (t )
246
+ rng := ethtest .NewPseudoRand (42 )
247
+ contractAddr := rng .Address ()
248
+ state .CreateAccount (contractAddr )
249
+ state .SetCode (contractAddr , contractCode (contract ))
250
+
251
+ // (3)
252
+
253
+ caller := vm .AccountRef (rng .Address ())
254
+ tests := []struct {
255
+ name string
256
+ call func () ([]byte , uint64 , error )
257
+ want byte
258
+ }{
259
+ {
260
+ name : "EVM.Call()" ,
261
+ call : func () ([]byte , uint64 , error ) {
262
+ return evm .Call (caller , contractAddr , []byte {}, 1e6 , uint256 .NewInt (0 ))
263
+ },
264
+ want : ifNotReadOnly ,
265
+ },
266
+ {
267
+ name : "EVM.StaticCall()" ,
268
+ call : func () ([]byte , uint64 , error ) {
269
+ return evm .StaticCall (vm .AccountRef (rng .Address ()), contractAddr , []byte {}, 1e6 )
270
+ },
271
+ want : ifReadOnly ,
272
+ },
273
+ }
274
+
275
+ for _ , tt := range tests {
276
+ t .Run (tt .name , func (t * testing.T ) {
277
+ got , _ , err := tt .call ()
278
+ require .NoError (t , err )
279
+ require .Equalf (t , []byte {tt .want }, got , "want %d if read-only, otherwise %d" , ifReadOnly , ifNotReadOnly )
280
+ })
281
+ }
282
+ }
283
+
284
+ // contractCode converts a slice of op codes into a byte buffer for storage as
285
+ // contract code.
286
+ func contractCode (ops []vm.OpCode ) []byte {
287
+ ret := make ([]byte , len (ops ))
288
+ for i , o := range ops {
289
+ ret [i ] = byte (o )
290
+ }
291
+ return ret
122
292
}
123
293
124
294
func TestCanCreateContract (t * testing.T ) {
0 commit comments