Skip to content

Commit 66fe662

Browse files
feat: add useSeed cheatcode to set RNG seed (#10698)
* feat: mock deterministic shuffle implementation * feat: implement useSeed() * fix(test): typo * fix(test): correct implementation of randomUint() chore: format * fix: refactor seed handling logic and remove redundant assertion message * chore: remove unused imports and redundant newline * fix(test): add seed validation and tests for shuffle consistency * chore: add Shuffle test * Adapt to updated proptest --------- Co-authored-by: grandizzy <[email protected]> Co-authored-by: grandizzy <[email protected]>
1 parent 893da4d commit 66fe662

File tree

7 files changed

+193
-0
lines changed

7 files changed

+193
-0
lines changed

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,6 +2879,10 @@ interface Vm {
28792879
#[cheatcode(group = Utilities)]
28802880
function shuffle(uint256[] calldata array) external returns (uint256[] memory);
28812881

2882+
/// Set RNG seed.
2883+
#[cheatcode(group = Utilities)]
2884+
function setSeed(uint256 seed) external;
2885+
28822886
/// Causes the next contract creation (via new) to fail and return its initcode in the returndata buffer.
28832887
/// This allows type-safe access to the initcode payload that would be used for contract creation.
28842888
/// Example usage:

crates/cheatcodes/src/inspector.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,13 @@ impl Cheatcodes {
12641264
})
12651265
}
12661266

1267+
pub fn set_seed(&mut self, seed: U256) {
1268+
self.test_runner = Some(TestRunner::new_with_rng(
1269+
proptest::test_runner::Config::default(),
1270+
TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1271+
));
1272+
}
1273+
12671274
/// Returns existing or set a default `ArbitraryStorage` option.
12681275
/// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage.
12691276
pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {

crates/cheatcodes/src/utils.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,14 @@ impl Cheatcode for shuffleCall {
273273
}
274274
}
275275

276+
impl Cheatcode for setSeedCall {
277+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
278+
let Self { seed } = self;
279+
ccx.state.set_seed(*seed);
280+
Ok(Default::default())
281+
}
282+
}
283+
276284
/// Helper to generate a random `uint` value (with given bits or bounded if specified)
277285
/// from type strategy.
278286
fn random_uint(state: &mut Cheatcodes, bits: Option<U256>, bounds: Option<(U256, U256)>) -> Result {

testdata/cheats/Vm.sol

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

testdata/default/cheats/Seed.t.sol

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.18;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
7+
contract SeedTest is DSTest {
8+
Vm constant vm = Vm(HEVM_ADDRESS);
9+
10+
function testSeedAffectsRandom() public {
11+
// Use a known seed
12+
uint256 seed = 123456789;
13+
vm.setSeed(seed);
14+
15+
// Call a foundry cheatcode to get a random value (this depends on the integration)
16+
uint256 rand1 = uint256(vm.randomUint());
17+
18+
// Reset the seed and verify the result is the same
19+
vm.setSeed(seed);
20+
uint256 rand2 = uint256(vm.randomUint());
21+
22+
uint256 rand3 = uint256(vm.randomUint());
23+
// If the seed is the same, the random value must be equal
24+
assertEq(rand1, rand2);
25+
assertTrue(rand1 != rand3);
26+
}
27+
28+
function testSeedChangesRandom() public {
29+
// Use one seed
30+
vm.setSeed(1);
31+
uint256 randA = uint256(vm.randomUint());
32+
33+
// Use a different seed
34+
vm.setSeed(2);
35+
uint256 randB = uint256(vm.randomUint());
36+
37+
// Values must be different
38+
assertTrue(randA != randB, "Random value must be different if seed is different");
39+
}
40+
41+
function testSeedAffectsShuffle() public {
42+
// Use a known seed
43+
uint256 seed = 123456789;
44+
vm.setSeed(seed);
45+
46+
// Create two identical arrays
47+
uint256[] memory array1 = new uint256[](5);
48+
uint256[] memory array2 = new uint256[](5);
49+
for (uint256 i = 0; i < 5; i++) {
50+
array1[i] = i;
51+
array2[i] = i;
52+
}
53+
54+
// Shuffle both arrays with the same seed
55+
array1 = vm.shuffle(array1);
56+
vm.setSeed(seed); // Reset the seed to get the same shuffle pattern
57+
array2 = vm.shuffle(array2);
58+
59+
// Compare elements - they should be identical after shuffle
60+
for (uint256 i = 0; i < array1.length; i++) {
61+
assertEq(array1[i], array2[i], "Arrays should be identical with same seed");
62+
}
63+
}
64+
65+
function testDifferentSeedsProduceDifferentShuffles() public {
66+
// Create the initial array
67+
uint256[] memory array1 = new uint256[](5);
68+
uint256[] memory array2 = new uint256[](5);
69+
for (uint256 i = 0; i < 5; i++) {
70+
array1[i] = i;
71+
array2[i] = i;
72+
}
73+
74+
// Use first seed
75+
vm.setSeed(1);
76+
array1 = vm.shuffle(array1);
77+
78+
// Use second seed
79+
vm.setSeed(2);
80+
array2 = vm.shuffle(array2);
81+
82+
// Arrays should be different (we'll check at least one difference exists)
83+
bool foundDifference = false;
84+
for (uint256 i = 0; i < array1.length; i++) {
85+
if (array1[i] != array2[i]) {
86+
foundDifference = true;
87+
break;
88+
}
89+
}
90+
assertTrue(foundDifference, "Arrays should be different with different seeds");
91+
}
92+
}

testdata/default/cheats/Shuffle.t.sol

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.18;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
7+
contract ShuffleTest is DSTest {
8+
Vm constant vm = Vm(HEVM_ADDRESS);
9+
10+
function testDeterministicShuffle() public {
11+
// Use a known seed
12+
uint256 seed = 123456789;
13+
vm.setSeed(seed);
14+
15+
// Create two identical arrays
16+
uint256[] memory array1 = new uint256[](5);
17+
uint256[] memory array2 = new uint256[](5);
18+
for (uint256 i = 0; i < 5; i++) {
19+
array1[i] = i;
20+
array2[i] = i;
21+
}
22+
23+
// Shuffle both arrays with the same seed
24+
array1 = vm.shuffle(array1);
25+
vm.setSeed(seed); // Reset the seed to get the same shuffle pattern
26+
array2 = vm.shuffle(array2);
27+
28+
// Compare elements - they should be identical after shuffle
29+
for (uint256 i = 0; i < array1.length; i++) {
30+
assertEq(array1[i], array2[i], "Arrays should be identical with same seed");
31+
}
32+
}
33+
34+
function testDifferentSeedsProduceDifferentShuffles() public {
35+
// Create the initial array
36+
uint256[] memory array1 = new uint256[](5);
37+
uint256[] memory array2 = new uint256[](5);
38+
for (uint256 i = 0; i < 5; i++) {
39+
array1[i] = i;
40+
array2[i] = i;
41+
}
42+
43+
// Use first seed
44+
vm.setSeed(1);
45+
array1 = vm.shuffle(array1);
46+
47+
// Use second seed
48+
vm.setSeed(2);
49+
array2 = vm.shuffle(array2);
50+
51+
// Arrays should be different (we'll check at least one difference exists)
52+
bool foundDifference = false;
53+
for (uint256 i = 0; i < array1.length; i++) {
54+
if (array1[i] != array2[i]) {
55+
foundDifference = true;
56+
break;
57+
}
58+
}
59+
assertTrue(foundDifference, "Arrays should be different with different seeds");
60+
}
61+
}

0 commit comments

Comments
 (0)