@@ -3,11 +3,42 @@ pragma solidity >=0.6.2 <0.9.0;
33
44pragma experimental ABIEncoderV2;
55
6- import "./Vm.sol " ;
6+ import {VmSafe} from "./Vm.sol " ;
77
8+ /**
9+ * StdChains provides information about EVM compatible chains that can be used in scripts/tests.
10+ * For each chain, the chain's name, chain ID, and a default RPC URL are provided. Chains are
11+ * identified by their alias, which is the same as the alias in the `[rpc_endpoints]` section of
12+ * the `foundry.toml` file. For best UX, ensure the alias in the `foundry.toml` file match the
13+ * alias used in this contract, which can be found as the first argument to the
14+ * `setChainWithDefaultRpcUrl` call in the `initialize` function.
15+ *
16+ * There are two main ways to use this contract:
17+ * 1. Set a chain with `setChain(string memory chainAlias, Chain memory chain)`
18+ * 2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
19+ *
20+ * The first time either of those are used, chains are initialized with the default set of RPC URLs.
21+ * This is done in `initialize`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in
22+ * `defaultRpcUrls`.
23+ *
24+ * The `setChain` function is straightforward, and it simply saves off the given chain data.
25+ *
26+ * The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
27+ * we want to retrieve `mainnet`'s RPC URL:
28+ * - If you haven't set any mainnet chain info with `setChain` and you haven't specified that
29+ * chain in `foundry.toml`, the default data and RPC URL will be returned.
30+ * - If you have set a mainnet RPC URL in `foundry.toml` it will return that, if valid (e.g. if
31+ * a URL is given or if an environment variable is given and that environment variable exists).
32+ * Otherwise, the default data is returned.
33+ * - If you specified data with `setChain` it will return that.
34+ *
35+ * Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> defaults.
36+ */
837abstract contract StdChains {
938 VmSafe private constant vm = VmSafe (address (uint160 (uint256 (keccak256 ("hevm cheat code " )))));
1039
40+ bool private initialized;
41+
1142 struct Chain {
1243 // The chain name.
1344 string name;
@@ -20,71 +51,139 @@ abstract contract StdChains {
2051 string rpcUrl;
2152 }
2253
23- bool private initialized;
24-
25- // Maps from a chain's key (matching the alias in the `foundry.toml` file) to chain data.
54+ // Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data.
2655 mapping (string => Chain) private chains;
27- // Maps from a chain ID to that chain's key name
28- mapping (uint256 => string ) private idToKey;
56+ // Maps from the chain's alias to it's default RPC URL.
57+ mapping (string => string ) private defaultRpcUrls;
58+ // Maps from a chain ID to it's alias.
59+ mapping (uint256 => string ) private idToAlias;
60+
61+ // The RPC URL will be fetched from config or defaultRpcUrls if possible.
62+ function getChain (string memory chainAlias ) internal virtual returns (Chain memory chain ) {
63+ require (bytes (chainAlias).length != 0 , "StdChains getChain(string): Chain alias cannot be the empty string. " );
2964
30- function getChain (string memory key ) internal virtual returns (Chain memory ) {
3165 initialize ();
32- return chains[key];
66+ chain = chains[chainAlias];
67+ require (
68+ chain.chainId != 0 ,
69+ string (abi.encodePacked ("StdChains getChain(string): Chain with alias \" " , chainAlias, "\" not found. " ))
70+ );
71+
72+ chain = getChainWithUpdatedRpcUrl (chainAlias, chain);
3373 }
3474
35- function getChain (uint256 chainId ) internal virtual returns (Chain memory ) {
75+ function getChain (uint256 chainId ) internal virtual returns (Chain memory chain ) {
76+ require (chainId != 0 , "StdChains getChain(uint256): Chain ID cannot be 0. " );
3677 initialize ();
37- return chains[idToKey[chainId]];
78+ string memory chainAlias = idToAlias[chainId];
79+
80+ chain = chains[chainAlias];
81+
82+ require (
83+ chain.chainId != 0 ,
84+ string (abi.encodePacked ("StdChains getChain(uint256): Chain with ID " , vm.toString (chainId), " not found. " ))
85+ );
86+
87+ chain = getChainWithUpdatedRpcUrl (chainAlias, chain);
3888 }
3989
40- function setChain (string memory key , string memory name , uint256 chainId , string memory rpcUrl ) internal virtual {
90+ // set chain info, with priority to argument's rpcUrl field.
91+ function setChain (string memory chainAlias , Chain memory chain ) internal virtual {
92+ require (
93+ bytes (chainAlias).length != 0 , "StdChains setChain(string,Chain): Chain alias cannot be the empty string. "
94+ );
95+
96+ require (chain.chainId != 0 , "StdChains setChain(string,Chain): Chain ID cannot be 0. " );
97+
98+ initialize ();
99+ string memory foundAlias = idToAlias[chain.chainId];
100+
41101 require (
42- keccak256 (bytes (idToKey[chainId])) == keccak256 (bytes ("" ))
43- || keccak256 (bytes (idToKey[chainId])) == keccak256 (bytes (key)),
102+ bytes (foundAlias).length == 0 || keccak256 (bytes (foundAlias)) == keccak256 (bytes (chainAlias)),
44103 string (
45104 abi.encodePacked (
46- "StdChains setChain(string,string,uint256,string ): Chain ID " ,
47- vm.toString (chainId),
105+ "StdChains setChain(string,Chain ): Chain ID " ,
106+ vm.toString (chain. chainId),
48107 " already used by \" " ,
49- idToKey[chainId] ,
108+ foundAlias ,
50109 "\" . "
51110 )
52111 )
53112 );
54113
55- uint256 oldChainId = chains[key].chainId;
56- delete idToKey[oldChainId];
114+ uint256 oldChainId = chains[chainAlias].chainId;
115+ delete idToAlias[oldChainId];
116+
117+ chains[chainAlias] = chain;
118+ idToAlias[chain.chainId] = chainAlias;
119+ }
57120
58- chains[key] = Chain (name, chainId, rpcUrl);
59- idToKey[chainId] = key;
121+ // lookup rpcUrl, in descending order of priority:
122+ // current -> config (foundry.toml) -> default
123+ function getChainWithUpdatedRpcUrl (string memory chainAlias , Chain memory chain )
124+ private
125+ view
126+ returns (Chain memory )
127+ {
128+ if (bytes (chain.rpcUrl).length == 0 ) {
129+ try vm.rpcUrl (chainAlias) returns (string memory configRpcUrl ) {
130+ chain.rpcUrl = configRpcUrl;
131+ } catch (bytes memory err ) {
132+ chain.rpcUrl = defaultRpcUrls[chainAlias];
133+ // distinguish 'not found' from 'cannot read'
134+ bytes memory notFoundError =
135+ abi.encodeWithSignature ("CheatCodeError " , string (abi.encodePacked ("invalid rpc url " , chainAlias)));
136+ if (keccak256 (notFoundError) != keccak256 (err) || bytes (chain.rpcUrl).length == 0 ) {
137+ /// @solidity memory-safe-assembly
138+ assembly {
139+ revert (add (32 , err), mload (err))
140+ }
141+ }
142+ }
143+ }
144+ return chain;
60145 }
61146
62147 function initialize () private {
63148 if (initialized) return ;
64149
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-
88150 initialized = true ;
151+
152+ // If adding an RPC here, make sure to test the default RPC URL in `testRpcs`
153+ setChainWithDefaultRpcUrl ("anvil " , Chain ("Anvil " , 31337 , "http://127.0.0.1:8545 " ));
154+ setChainWithDefaultRpcUrl (
155+ "mainnet " , Chain ("Mainnet " , 1 , "https://mainnet.infura.io/v3/6770454bc6ea42c58aac12978531b93f " )
156+ );
157+ setChainWithDefaultRpcUrl (
158+ "goerli " , Chain ("Goerli " , 5 , "https://goerli.infura.io/v3/6770454bc6ea42c58aac12978531b93f " )
159+ );
160+ setChainWithDefaultRpcUrl (
161+ "sepolia " , Chain ("Sepolia " , 11155111 , "https://sepolia.infura.io/v3/6770454bc6ea42c58aac12978531b93f " )
162+ );
163+ setChainWithDefaultRpcUrl ("optimism " , Chain ("Optimism " , 10 , "https://mainnet.optimism.io " ));
164+ setChainWithDefaultRpcUrl ("optimism_goerli " , Chain ("Optimism Goerli " , 420 , "https://goerli.optimism.io " ));
165+ setChainWithDefaultRpcUrl ("arbitrum_one " , Chain ("Arbitrum One " , 42161 , "https://arb1.arbitrum.io/rpc " ));
166+ setChainWithDefaultRpcUrl (
167+ "arbitrum_one_goerli " , Chain ("Arbitrum One Goerli " , 421613 , "https://goerli-rollup.arbitrum.io/rpc " )
168+ );
169+ setChainWithDefaultRpcUrl ("arbitrum_nova " , Chain ("Arbitrum Nova " , 42170 , "https://nova.arbitrum.io/rpc " ));
170+ setChainWithDefaultRpcUrl ("polygon " , Chain ("Polygon " , 137 , "https://polygon-rpc.com " ));
171+ setChainWithDefaultRpcUrl ("polygon_mumbai " , Chain ("Polygon Mumbai " , 80001 , "https://rpc-mumbai.maticvigil.com " ));
172+ setChainWithDefaultRpcUrl ("avalanche " , Chain ("Avalanche " , 43114 , "https://api.avax.network/ext/bc/C/rpc " ));
173+ setChainWithDefaultRpcUrl (
174+ "avalanche_fuji " , Chain ("Avalanche Fuji " , 43113 , "https://api.avax-test.network/ext/bc/C/rpc " )
175+ );
176+ setChainWithDefaultRpcUrl ("bnb_smart_chain " , Chain ("BNB Smart Chain " , 56 , "https://bsc-dataseed1.binance.org " ));
177+ setChainWithDefaultRpcUrl ("bnb_smart_chain_testnet " , Chain ("BNB Smart Chain Testnet " , 97 , "https://data-seed-prebsc-1-s1.binance.org:8545 " ));// forgefmt: disable-line
178+ setChainWithDefaultRpcUrl ("gnosis_chain " , Chain ("Gnosis Chain " , 100 , "https://rpc.gnosischain.com " ));
179+ }
180+
181+ // set chain info, with priority to chainAlias' rpc url in foundry.toml
182+ function setChainWithDefaultRpcUrl (string memory chainAlias , Chain memory chain ) private {
183+ string memory rpcUrl = chain.rpcUrl;
184+ defaultRpcUrls[chainAlias] = rpcUrl;
185+ chain.rpcUrl = "" ;
186+ setChain (chainAlias, chain);
187+ chain.rpcUrl = rpcUrl; // restore argument
89188 }
90189}
0 commit comments