Skip to content

Commit c7d3030

Browse files
authored
feat: reimplement StdChains (#235)
* feat: reimplement `StdChains` - Fix long `via-ir` compile time * fix: use `ABIEncoderV2` * feat: add `getChain` by id * fix(vm): `Rpc.name` -> `Rpc.key` * fix: disallow duplicate chain ids * fix: add `.`
1 parent 08d0f48 commit c7d3030

File tree

8 files changed

+165
-81
lines changed

8 files changed

+165
-81
lines changed

src/Components.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity >=0.6.2 <0.9.0;
44
import "./console.sol";
55
import "./console2.sol";
66
import "./StdAssertions.sol";
7+
import "./StdChains.sol";
78
import "./StdCheats.sol";
89
import "./StdError.sol";
910
import "./StdJson.sol";

src/Script.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ pragma solidity >=0.6.2 <0.9.0;
33

44
import {CommonBase} from "./Common.sol";
55
// forgefmt: disable-next-line
6-
import {console, console2, StdCheatsSafe, stdJson, stdMath, StdStorage, stdStorageSafe, StdUtils, VmSafe} from "./Components.sol";
6+
import {console, console2, StdChains, StdCheatsSafe, stdJson, stdMath, StdStorage, stdStorageSafe, StdUtils, VmSafe} from "./Components.sol";
77

88
abstract contract ScriptBase is CommonBase {
99
VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS);
1010
}
1111

12-
abstract contract Script is StdCheatsSafe, StdUtils, ScriptBase {
12+
abstract contract Script is StdChains, StdCheatsSafe, StdUtils, ScriptBase {
1313
bool public IS_SCRIPT = true;
1414
}

src/StdChains.sol

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.6.2 <0.9.0;
3+
4+
pragma experimental ABIEncoderV2;
5+
6+
import "./Vm.sol";
7+
8+
abstract contract StdChains {
9+
VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
10+
11+
struct Chain {
12+
// The chain name.
13+
string name;
14+
// The chain's Chain ID.
15+
uint256 chainId;
16+
// A default RPC endpoint for this chain.
17+
// NOTE: This default RPC URL is included for convenience to facilitate quick tests and
18+
// experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
19+
// usage as you will be throttled and this is a disservice to others who need this endpoint.
20+
string rpcUrl;
21+
}
22+
23+
bool private initialized;
24+
25+
// Maps from a chain's key (matching the alias in the `foundry.toml` file) to chain data.
26+
mapping(string => Chain) private chains;
27+
// Maps from a chain ID to that chain's key name
28+
mapping(uint256 => string) private idToKey;
29+
30+
function getChain(string memory key) internal virtual returns (Chain memory) {
31+
initialize();
32+
return chains[key];
33+
}
34+
35+
function getChain(uint256 chainId) internal virtual returns (Chain memory) {
36+
initialize();
37+
return chains[idToKey[chainId]];
38+
}
39+
40+
function setChain(string memory key, string memory name, uint256 chainId, string memory rpcUrl) internal virtual {
41+
require(
42+
keccak256(bytes(idToKey[chainId])) == keccak256(bytes(""))
43+
|| keccak256(bytes(idToKey[chainId])) == keccak256(bytes(key)),
44+
string(
45+
abi.encodePacked(
46+
"StdChains setChain(string,string,uint256,string): Chain ID ",
47+
vm.toString(chainId),
48+
" already used by \"",
49+
idToKey[chainId],
50+
"\"."
51+
)
52+
)
53+
);
54+
55+
uint256 oldChainId = chains[key].chainId;
56+
delete idToKey[oldChainId];
57+
58+
chains[key] = Chain(name, chainId, rpcUrl);
59+
idToKey[chainId] = key;
60+
}
61+
62+
function initialize() private {
63+
if (initialized) return;
64+
65+
setChain("anvil", "Anvil", 31337, "http://127.0.0.1:8545");
66+
setChain("mainnet", "Mainnet", 1, "https://mainnet.infura.io/v3/6770454bc6ea42c58aac12978531b93f");
67+
setChain("goerli", "Goerli", 5, "https://goerli.infura.io/v3/6770454bc6ea42c58aac12978531b93f");
68+
setChain("sepolia", "Sepolia", 11155111, "https://rpc.sepolia.dev");
69+
setChain("optimism", "Optimism", 10, "https://mainnet.optimism.io");
70+
setChain("optimism_goerli", "Optimism Goerli", 420, "https://goerli.optimism.io");
71+
setChain("arbitrum_one", "Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc");
72+
setChain("arbitrum_one_goerli", "Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc");
73+
setChain("arbitrum_nova", "Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc");
74+
setChain("polygon", "Polygon", 137, "https://polygon-rpc.com");
75+
setChain("polygon_mumbai", "Polygon Mumbai", 80001, "https://rpc-mumbai.matic.today");
76+
setChain("avalanche", "Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc");
77+
setChain("avalanche_fuji", "Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc");
78+
setChain("bnb_smart_chain", "BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org");
79+
setChain("bnb_smart_chain_testnet", "BNB Smart Chain Testnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545");// forgefmt: disable-line
80+
setChain("gnosis_chain", "Gnosis Chain", 100, "https://rpc.gnosischain.com");
81+
82+
// Loop over RPC URLs in the config file to replace the default RPC URLs
83+
Vm.Rpc[] memory rpcs = vm.rpcUrlStructs();
84+
for (uint256 i = 0; i < rpcs.length; i++) {
85+
chains[rpcs[i].key].rpcUrl = rpcs[i].url;
86+
}
87+
88+
initialized = true;
89+
}
90+
}

src/StdCheats.sol

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,13 @@ pragma solidity >=0.6.2 <0.9.0;
33

44
pragma experimental ABIEncoderV2;
55

6+
import "./StdChains.sol";
67
import "./StdStorage.sol";
78
import "./Vm.sol";
89

9-
abstract contract StdCheatsSafe {
10+
abstract contract StdCheatsSafe is StdChains {
1011
VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
1112

12-
/// @dev To hide constructor warnings across solc versions due to different constructor visibility requirements and
13-
/// syntaxes, we put the constructor in a private method and assign an unused return value to a variable. This
14-
/// forces the method to run during construction, but without declaring an explicit constructor.
15-
uint256 private CONSTRUCTOR = _constructor();
16-
17-
struct Chain {
18-
// The chain name, using underscores as the separator to match `foundry.toml` conventions.
19-
string name;
20-
// The chain's Chain ID.
21-
uint256 chainId;
22-
// A default RPC endpoint for this chain.
23-
// NOTE: This default RPC URL is included for convenience to facilitate quick tests and
24-
// experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
25-
// usage as you will be throttled and this is a disservice to others who need this endpoint.
26-
string rpcUrl;
27-
}
28-
29-
// Maps from a chain's name (matching what's in the `foundry.toml` file) to chain data.
30-
mapping(string => Chain) internal stdChains;
31-
3213
// Data structures to parse Transaction objects from the broadcast artifact
3314
// that conform to EIP1559. The Raw structs is what is parsed from the JSON
3415
// and then converted to the one that is used by the user for better UX.
@@ -206,35 +187,7 @@ abstract contract StdCheatsSafe {
206187
string value;
207188
}
208189

209-
function _constructor() private returns (uint256) {
210-
// Initialize `stdChains` with the defaults.
211-
stdChains["anvil"] = Chain("Anvil", 31337, "http://127.0.0.1:8545");
212-
stdChains["hardhat"] = Chain("Hardhat", 31337, "http://127.0.0.1:8545");
213-
stdChains["mainnet"] = Chain("Mainnet", 1, "https://mainnet.infura.io/v3/6770454bc6ea42c58aac12978531b93f");
214-
stdChains["goerli"] = Chain("Goerli", 5, "https://goerli.infura.io/v3/6770454bc6ea42c58aac12978531b93f");
215-
stdChains["sepolia"] = Chain("Sepolia", 11155111, "https://rpc.sepolia.dev");
216-
stdChains["optimism"] = Chain("Optimism", 10, "https://mainnet.optimism.io");
217-
stdChains["optimism_goerli"] = Chain("Optimism Goerli", 420, "https://goerli.optimism.io");
218-
stdChains["arbitrum_one"] = Chain("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc");
219-
stdChains["arbitrum_one_goerli"] = Chain("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc");
220-
stdChains["arbitrum_nova"] = Chain("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc");
221-
stdChains["polygon"] = Chain("Polygon", 137, "https://polygon-rpc.com");
222-
stdChains["polygon_mumbai"] = Chain("Polygon Mumbai", 80001, "https://rpc-mumbai.matic.today");
223-
stdChains["avalanche"] = Chain("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc");
224-
stdChains["avalanche_fuji"] = Chain("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc");
225-
stdChains["bnb_smart_chain"] = Chain("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org");
226-
stdChains["bnb_smart_chain_testnet"] = Chain("BNB Smart Chain Testnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545");// forgefmt: disable-line
227-
stdChains["gnosis_chain"] = Chain("Gnosis Chain", 100, "https://rpc.gnosischain.com");
228-
229-
// Loop over RPC URLs in the config file to replace the default RPC URLs
230-
Vm.Rpc[] memory rpcs = vm.rpcUrlStructs();
231-
for (uint256 i = 0; i < rpcs.length; i++) {
232-
stdChains[rpcs[i].name].rpcUrl = rpcs[i].url;
233-
}
234-
return 0;
235-
}
236-
237-
function assumeNoPrecompiles(address addr) internal view virtual {
190+
function assumeNoPrecompiles(address addr) internal virtual {
238191
// Assembly required since `block.chainid` was introduced in 0.8.0.
239192
uint256 chainId;
240193
assembly {
@@ -243,21 +196,21 @@ abstract contract StdCheatsSafe {
243196
assumeNoPrecompiles(addr, chainId);
244197
}
245198

246-
function assumeNoPrecompiles(address addr, uint256 chainId) internal view virtual {
199+
function assumeNoPrecompiles(address addr, uint256 chainId) internal virtual {
247200
// Note: For some chains like Optimism these are technically predeploys (i.e. bytecode placed at a specific
248201
// address), but the same rationale for excluding them applies so we include those too.
249202

250203
// These should be present on all EVM-compatible chains.
251204
vm.assume(addr < address(0x1) || addr > address(0x9));
252205

253206
// forgefmt: disable-start
254-
if (chainId == stdChains["optimism"].chainId || chainId == stdChains["optimism_goerli"].chainId) {
207+
if (chainId == getChain("optimism").chainId || chainId == getChain("optimism_goerli").chainId) {
255208
// https://github.com/ethereum-optimism/optimism/blob/eaa371a0184b56b7ca6d9eb9cb0a2b78b2ccd864/op-bindings/predeploys/addresses.go#L6-L21
256209
vm.assume(addr < address(0x4200000000000000000000000000000000000000) || addr > address(0x4200000000000000000000000000000000000800));
257-
} else if (chainId == stdChains["arbitrum_one"].chainId || chainId == stdChains["arbitrum_one_goerli"].chainId) {
210+
} else if (chainId == getChain("arbitrum_one").chainId || chainId == getChain("arbitrum_one_goerli").chainId) {
258211
// https://developer.arbitrum.io/useful-addresses#arbitrum-precompiles-l2-same-on-all-arb-chains
259212
vm.assume(addr < address(0x0000000000000000000000000000000000000064) || addr > address(0x0000000000000000000000000000000000000068));
260-
} else if (chainId == stdChains["avalanche"].chainId || chainId == stdChains["avalanche_fuji"].chainId) {
213+
} else if (chainId == getChain("avalanche").chainId || chainId == getChain("avalanche_fuji").chainId) {
261214
// https://github.com/ava-labs/subnet-evm/blob/47c03fd007ecaa6de2c52ea081596e0a88401f58/precompile/params.go#L18-L59
262215
vm.assume(addr < address(0x0100000000000000000000000000000000000000) || addr > address(0x01000000000000000000000000000000000000ff));
263216
vm.assume(addr < address(0x0200000000000000000000000000000000000000) || addr > address(0x02000000000000000000000000000000000000FF));

src/Test.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ pragma solidity >=0.6.2 <0.9.0;
44
import {CommonBase} from "./Common.sol";
55
import "ds-test/test.sol";
66
// forgefmt: disable-next-line
7-
import {console, console2, StdAssertions, StdCheats, stdError, stdJson, stdMath, StdStorage, stdStorage, StdUtils, Vm} from "./Components.sol";
7+
import {console, console2, StdAssertions, StdChains, StdCheats, stdError, stdJson, stdMath, StdStorage, stdStorage, StdUtils, Vm} from "./Components.sol";
88

99
abstract contract TestBase is CommonBase {}
1010

11-
abstract contract Test is DSTest, StdAssertions, StdCheats, StdUtils, TestBase {}
11+
abstract contract Test is DSTest, StdAssertions, StdChains, StdCheats, StdUtils, TestBase {}

src/Vm.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface VmSafe {
1717
}
1818

1919
struct Rpc {
20-
string name;
20+
string key;
2121
string url;
2222
}
2323

test/StdChains.t.sol

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity >=0.7.0 <0.9.0;
3+
4+
import "../src/Test.sol";
5+
6+
contract StdChainsTest is Test {
7+
function testChainRpcInitialization() public {
8+
// RPCs specified in `foundry.toml` should be updated.
9+
assertEq(getChain(1).rpcUrl, "https://mainnet.infura.io/v3/7a8769b798b642f6933f2ed52042bd70");
10+
assertEq(getChain("optimism_goerli").rpcUrl, "https://goerli.optimism.io/");
11+
assertEq(getChain("arbitrum_one_goerli").rpcUrl, "https://goerli-rollup.arbitrum.io/rpc/");
12+
13+
// Other RPCs should remain unchanged.
14+
assertEq(getChain(31337).rpcUrl, "http://127.0.0.1:8545");
15+
assertEq(getChain("sepolia").rpcUrl, "https://rpc.sepolia.dev");
16+
}
17+
18+
// Ensure we can connect to the default RPC URL for each chain.
19+
function testRpcs() public {
20+
(string[2][] memory rpcs) = vm.rpcUrls();
21+
for (uint256 i = 0; i < rpcs.length; i++) {
22+
( /* string memory name */ , string memory rpcUrl) = (rpcs[i][0], rpcs[i][1]);
23+
vm.createSelectFork(rpcUrl);
24+
}
25+
}
26+
27+
function testCannotSetChain_ChainIdExists() public {
28+
setChain({key: "custom_chain", name: "Custom Chain", chainId: 123456789, rpcUrl: "https://custom.chain/"});
29+
30+
vm.expectRevert(
31+
'StdChains setChain(string,string,uint256,string): Chain ID 123456789 already used by "custom_chain".'
32+
);
33+
34+
setChain({key: "another_custom_chain", name: "", chainId: 123456789, rpcUrl: ""});
35+
}
36+
37+
function testSetChain() public {
38+
setChain({key: "custom_chain", name: "Custom Chain", chainId: 123456789, rpcUrl: "https://custom.chain/"});
39+
Chain memory customChain = getChain("custom_chain");
40+
assertEq(customChain.name, "Custom Chain");
41+
assertEq(customChain.chainId, 123456789);
42+
assertEq(customChain.rpcUrl, "https://custom.chain/");
43+
Chain memory chainById = getChain(123456789);
44+
assertEq(chainById.name, customChain.name);
45+
assertEq(chainById.chainId, customChain.chainId);
46+
assertEq(chainById.rpcUrl, customChain.rpcUrl);
47+
}
48+
49+
function testSetChain_ExistingOne() public {
50+
setChain({key: "custom_chain", name: "Custom Chain", chainId: 123456789, rpcUrl: "https://custom.chain/"});
51+
assertEq(getChain(123456789).chainId, 123456789);
52+
53+
setChain({key: "custom_chain", name: "Modified Chain", chainId: 999999999, rpcUrl: "https://modified.chain/"});
54+
assertEq(getChain(123456789).chainId, 0);
55+
56+
Chain memory modifiedChain = getChain(999999999);
57+
assertEq(modifiedChain.name, "Modified Chain");
58+
assertEq(modifiedChain.chainId, 999999999);
59+
assertEq(modifiedChain.rpcUrl, "https://modified.chain/");
60+
}
61+
}

test/StdCheats.t.sol

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -227,29 +227,8 @@ contract StdCheatsTest is Test {
227227
return number;
228228
}
229229

230-
function testChainRpcInitialization() public {
231-
// RPCs specified in `foundry.toml` should be updated.
232-
assertEq(stdChains["mainnet"].rpcUrl, "https://mainnet.infura.io/v3/7a8769b798b642f6933f2ed52042bd70");
233-
assertEq(stdChains["optimism_goerli"].rpcUrl, "https://goerli.optimism.io/");
234-
assertEq(stdChains["arbitrum_one_goerli"].rpcUrl, "https://goerli-rollup.arbitrum.io/rpc/");
235-
236-
// Other RPCs should remain unchanged.
237-
assertEq(stdChains["anvil"].rpcUrl, "http://127.0.0.1:8545");
238-
assertEq(stdChains["hardhat"].rpcUrl, "http://127.0.0.1:8545");
239-
assertEq(stdChains["sepolia"].rpcUrl, "https://rpc.sepolia.dev");
240-
}
241-
242-
// Ensure we can connect to the default RPC URL for each chain.
243-
function testRpcs() public {
244-
(string[2][] memory rpcs) = vm.rpcUrls();
245-
for (uint256 i = 0; i < rpcs.length; i++) {
246-
( /* string memory name */ , string memory rpcUrl) = (rpcs[i][0], rpcs[i][1]);
247-
vm.createSelectFork(rpcUrl);
248-
}
249-
}
250-
251230
function testAssumeNoPrecompiles(address addr) external {
252-
assumeNoPrecompiles(addr, stdChains["optimism_goerli"].chainId);
231+
assumeNoPrecompiles(addr, getChain("optimism_goerli").chainId);
253232
assertTrue(
254233
addr < address(1) || (addr > address(9) && addr < address(0x4200000000000000000000000000000000000000))
255234
|| addr > address(0x4200000000000000000000000000000000000800)

0 commit comments

Comments
 (0)