Skip to content

Commit adb0ab7

Browse files
authored
vm.store: implement store cheatcode (#353)
Signed-off-by: Alexandru Gheorghe <[email protected]>
1 parent d31ea52 commit adb0ab7

File tree

6 files changed

+102
-6
lines changed

6 files changed

+102
-6
lines changed

crates/cheatcodes/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ macro_rules! ensure_not_precompile {
7474
}
7575

7676
#[cold]
77-
pub(crate) fn precompile_error(address: &Address) -> Error {
77+
pub fn precompile_error(address: &Address) -> Error {
7878
fmt_err!("cannot use precompile {address} as an argument")
7979
}
8080

crates/cheatcodes/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use spec::Status;
2222

2323
pub use Vm::ForgeContext;
2424
pub use config::CheatsConfig;
25-
pub use error::{Error, ErrorKind, Result};
25+
pub use error::{Error, ErrorKind, Result, precompile_error};
2626
pub use inspector::{
2727
BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor,
2828
};
@@ -181,7 +181,7 @@ impl std::ops::DerefMut for CheatsCtxt<'_, '_, '_, '_> {
181181

182182
impl CheatsCtxt<'_, '_, '_, '_> {
183183
#[inline]
184-
pub(crate) fn is_precompile(&self, address: &Address) -> bool {
184+
pub fn is_precompile(&self, address: &Address) -> bool {
185185
self.ecx.journaled_state.inner.precompiles.contains(address)
186186
}
187187
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use crate::{config::*, test_helpers::TEST_DATA_REVIVE};
2+
use foundry_test_utils::Filter;
3+
use revm::primitives::hardfork::SpecId;
4+
5+
#[tokio::test(flavor = "multi_thread")]
6+
async fn test_store_works() {
7+
let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive();
8+
let filter = Filter::new("testStoreWorks", "Store", ".*/revive/.*");
9+
10+
TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
11+
}
12+
13+
#[tokio::test(flavor = "multi_thread")]
14+
async fn test_store_fuzzed() {
15+
let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive();
16+
let filter = Filter::new("testStoreFuzzed", "Store", ".*/revive/.*");
17+
18+
TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
19+
}
20+
21+
#[tokio::test(flavor = "multi_thread")]
22+
async fn test_store_not_available_on_precompiles() {
23+
let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive();
24+
let filter = Filter::new("testStoreNotAvailableOnPrecompiles", "Store", ".*/revive/.*");
25+
26+
TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
27+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
//! Revive strategy tests
22
3+
pub mod cheat_store;
34
pub mod migration;

crates/revive-strategy/src/cheatcodes/mod.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ use alloy_sol_types::SolValue;
44
use foundry_cheatcodes::{
55
Broadcast, BroadcastableTransactions, CheatcodeInspectorStrategy,
66
CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyRunner, CheatsConfig, CheatsCtxt,
7-
CommonCreateInput, DealRecord, Ecx, EvmCheatcodeInspectorStrategyRunner, Result,
7+
CommonCreateInput, DealRecord, Ecx, Error, EvmCheatcodeInspectorStrategyRunner, Result,
88
Vm::{
99
dealCall, getNonce_0Call, loadCall, pvmCall, rollCall, setNonceCall, setNonceUnsafeCall,
10-
warpCall,
10+
storeCall, warpCall,
1111
},
12-
journaled_account,
12+
journaled_account, precompile_error,
1313
};
1414
use foundry_common::sh_err;
1515
use foundry_compilers::resolc::dual_compiled_contracts::DualCompiledContracts;
@@ -299,6 +299,26 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {
299299
.unwrap_or(B256::ZERO);
300300
Ok(result.abi_encode())
301301
}
302+
t if using_pvm && is::<storeCall>(t) => {
303+
tracing::info!(cheatcode = ?cheatcode.as_debug() , using_pvm = ?using_pvm);
304+
let &storeCall { target, slot, value } = cheatcode.as_any().downcast_ref().unwrap();
305+
if ccx.is_precompile(&target) {
306+
return Err(precompile_error(&target));
307+
}
308+
309+
let target_address_h160 = H160::from_slice(target.as_slice());
310+
let _ = execute_with_externalities(|externalities| {
311+
externalities.execute_with(|| {
312+
Pallet::<Runtime>::set_storage(
313+
target_address_h160,
314+
slot.into(),
315+
Some(value.to_vec()),
316+
)
317+
})
318+
})
319+
.map_err(|_| <&str as Into<Error>>::into("Could not set storage"))?;
320+
Ok(Default::default())
321+
}
302322
// Not custom, just invoke the default behavior
303323
_ => cheatcode.dyn_apply(ccx, executor),
304324
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity ^0.8.18;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
import "../../default/logs/console.sol";
7+
8+
contract Storage {
9+
uint256 public slot0 = 10;
10+
uint256 public slot1 = 20;
11+
}
12+
13+
contract StoreTest is DSTest {
14+
Vm constant vm = Vm(HEVM_ADDRESS);
15+
Storage store;
16+
17+
function setUp() public {
18+
vm.pvm(true);
19+
store = new Storage();
20+
}
21+
22+
function testStoreWorks() public {
23+
assertEq(store.slot0(), 10, "initial value for slot 0 is incorrect");
24+
assertEq(store.slot1(), 20, "initial value for slot 1 is incorrect");
25+
26+
vm.store(address(store), bytes32(0), bytes32(uint256(1)));
27+
assertEq(store.slot0(), 1, "store failed");
28+
assertEq(store.slot1(), 20, "store failed");
29+
}
30+
31+
function testStoreNotAvailableOnPrecompiles() public {
32+
assertEq(store.slot0(), 10, "initial value for slot 0 is incorrect");
33+
assertEq(store.slot1(), 20, "initial value for slot 1 is incorrect");
34+
35+
vm._expectCheatcodeRevert("cannot use precompile 0x0000000000000000000000000000000000000001 as an argument");
36+
vm.store(address(1), bytes32(0), bytes32(uint256(1)));
37+
}
38+
39+
function testStoreFuzzed(uint256 slot0, uint256 slot1) public {
40+
assertEq(store.slot0(), 10, "initial value for slot 0 is incorrect");
41+
assertEq(store.slot1(), 20, "initial value for slot 1 is incorrect");
42+
43+
vm.store(address(store), bytes32(0), bytes32(slot0));
44+
vm.store(address(store), bytes32(uint256(1)), bytes32(slot1));
45+
assertEq(store.slot0(), slot0, "store failed");
46+
assertEq(store.slot1(), slot1, "store failed");
47+
}
48+
}

0 commit comments

Comments
 (0)