@@ -63,6 +63,10 @@ pub(super) fn call_subcall<B: EVMBackendExt>(
6363 // Cap maximum amount of gas that can be used.
6464 let max_gas = handle. remaining_gas ( ) ;
6565
66+ // Ensure that the subcall is read-only and cannot modify state when
67+ // the precompile is called using a static call.
68+ let read_only = handle. is_static ( ) ;
69+
6670 let result = backend
6771 . subcall (
6872 subcall:: SubcallInfo {
@@ -71,6 +75,7 @@ pub(super) fn call_subcall<B: EVMBackendExt>(
7175 body,
7276 max_depth : 8 ,
7377 max_gas,
78+ read_only,
7479 } ,
7580 ForbidReentrancy ,
7681 )
@@ -150,7 +155,7 @@ mod test {
150155 let dispatch_result = signer. call_evm (
151156 & ctx,
152157 contract_address. into ( ) ,
153- solabi:: selector!( "test (bytes,bytes)" ) ,
158+ solabi:: selector!( "test_call (bytes,bytes)" ) ,
154159 & (
155160 solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
156161 solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
@@ -182,7 +187,7 @@ mod test {
182187 let dispatch_result = signer. call_evm_opts (
183188 & ctx,
184189 contract_address. into ( ) ,
185- solabi:: selector!( "test (bytes,bytes)" ) ,
190+ solabi:: selector!( "test_call (bytes,bytes)" ) ,
186191 & (
187192 solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
188193 solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
@@ -233,7 +238,80 @@ mod test {
233238
234239 let events: Vec < GasUsedEvent > = cbor:: from_slice ( & tags[ 1 ] . value ) . unwrap ( ) ;
235240 assert_eq ! ( events. len( ) , 1 ) ; // Just one gas used event.
236- assert_eq ! ( events[ 0 ] . amount, 25742 ) ;
241+ assert_eq ! ( events[ 0 ] . amount, 25738 ) ;
242+ }
243+
244+ #[ test]
245+ fn test_read_only ( ) {
246+ let mut mock = Mock :: default ( ) ;
247+ let ctx = mock. create_ctx_for_runtime :: < TestRuntime > ( false ) ;
248+ let mut signer = EvmSigner :: new ( 0 , keys:: dave:: sigspec ( ) ) ;
249+
250+ // Create contract.
251+ let contract_address = init_and_deploy_contract ( & ctx, & mut signer, TEST_CONTRACT_CODE_HEX ) ;
252+
253+ // Transfer some tokens to the contract.
254+ let dispatch_result = signer. call (
255+ & ctx,
256+ "accounts.Transfer" ,
257+ accounts:: types:: Transfer {
258+ to : TestConfig :: map_address ( contract_address) ,
259+ amount : BaseUnits :: native ( 2_000 ) ,
260+ } ,
261+ ) ;
262+ assert ! (
263+ dispatch_result. result. is_success( ) ,
264+ "transfer should succeed"
265+ ) ;
266+
267+ // Call into the test contract.
268+ let dispatch_result = signer. call_evm_opts (
269+ & ctx,
270+ contract_address. into ( ) ,
271+ solabi:: selector!( "test_staticcall(bytes,bytes)" ) ,
272+ & (
273+ solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
274+ solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
275+ to : keys:: alice:: address ( ) ,
276+ amount : BaseUnits :: native ( 1_000 ) ,
277+ } ) ) ,
278+ ) ,
279+ CallOptions {
280+ fee : Fee {
281+ amount : BaseUnits :: native ( 100 ) ,
282+ gas : 1_000_000 ,
283+ ..Default :: default ( )
284+ } ,
285+ ..Default :: default ( )
286+ } ,
287+ ) ;
288+ assert ! (
289+ !dispatch_result. result. is_success( ) ,
290+ "call should fail as it is not read-only"
291+ ) ;
292+
293+ // Call into test contract again with a normal call.
294+ let dispatch_result = signer. call_evm_opts (
295+ & ctx,
296+ contract_address. into ( ) ,
297+ solabi:: selector!( "test_call(bytes,bytes)" ) ,
298+ & (
299+ solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
300+ solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
301+ to : keys:: alice:: address ( ) ,
302+ amount : BaseUnits :: native ( 1_000 ) ,
303+ } ) ) ,
304+ ) ,
305+ CallOptions {
306+ fee : Fee {
307+ amount : BaseUnits :: native ( 100 ) ,
308+ gas : 1_000_000 ,
309+ ..Default :: default ( )
310+ } ,
311+ ..Default :: default ( )
312+ } ,
313+ ) ;
314+ assert ! ( dispatch_result. result. is_success( ) , "call should succeed" ) ;
237315 }
238316
239317 #[ test]
@@ -285,14 +363,14 @@ mod test {
285363 let dispatch_result = signer. call_evm (
286364 & ctx,
287365 contract_address. into ( ) ,
288- solabi:: selector!( "test (bytes,bytes)" ) ,
366+ solabi:: selector!( "test_call (bytes,bytes)" ) ,
289367 & (
290368 solabi:: Bytes ( "evm.Call" . as_bytes ( ) ) ,
291369 solabi:: Bytes ( cbor:: to_vec ( evm:: types:: Call {
292370 address : contract_address. into ( ) ,
293371 value : 0 . into ( ) ,
294372 data : solabi:: encode_with_selector (
295- solabi:: selector!( "test (bytes,bytes)" ) ,
373+ solabi:: selector!( "test_call (bytes,bytes)" ) ,
296374 & (
297375 solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
298376 solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
@@ -339,7 +417,7 @@ mod test {
339417 let dispatch_result = signer. call_evm_opts (
340418 & ctx,
341419 contract_address. into ( ) ,
342- solabi:: selector!( "test (bytes,bytes)" ) ,
420+ solabi:: selector!( "test_call (bytes,bytes)" ) ,
343421 & (
344422 solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
345423 solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
@@ -365,7 +443,7 @@ mod test {
365443 let dispatch_result = signer. call_evm_opts (
366444 & ctx,
367445 contract_address. into ( ) ,
368- solabi:: selector!( "test (bytes,bytes)" ) ,
446+ solabi:: selector!( "test_call (bytes,bytes)" ) ,
369447 & (
370448 solabi:: Bytes ( "accounts.Transfer" . as_bytes ( ) ) ,
371449 solabi:: Bytes ( cbor:: to_vec ( accounts:: types:: Transfer {
0 commit comments