poseidon-solidity-example/lib/forge-std/src/StdChains.sol
lib/forge-std/src/StdChains.solRaw
1// SPDX-License-Identifier: MIT
2pragma solidity >=0.6.2 <0.9.0;
3
4pragma experimental ABIEncoderV2;
5
6import {VmSafe} from "./Vm.sol";
7
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, ChainData memory chain)` or
18 * `setChain(string memory chainAlias, Chain memory chain)`
19 * 2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
20 *
21 * The first time either of those are used, chains are initialized with the default set of RPC URLs.
22 * This is done in `initialize`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in
23 * `defaultRpcUrls`.
24 *
25 * The `setChain` function is straightforward, and it simply saves off the given chain data.
26 *
27 * The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
28 * we want to retrieve `mainnet`'s RPC URL:
29 * - If you haven't set any mainnet chain info with `setChain`, you haven't specified that
30 * chain in `foundry.toml` and no env var is set, the default data and RPC URL will be returned.
31 * - If you have set a mainnet RPC URL in `foundry.toml` it will return that, if valid (e.g. if
32 * a URL is given or if an environment variable is given and that environment variable exists).
33 * Otherwise, the default data is returned.
34 * - If you specified data with `setChain` it will return that.
35 *
36 * Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> environment variable -> defaults.
37 */
38abstract contract StdChains {
39 VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
40
41 bool private initialized;
42
43 struct ChainData {
44 string name;
45 uint256 chainId;
46 string rpcUrl;
47 }
48
49 struct Chain {
50 // The chain name.
51 string name;
52 // The chain's Chain ID.
53 uint256 chainId;
54 // The chain's alias. (i.e. what gets specified in `foundry.toml`).
55 string chainAlias;
56 // A default RPC endpoint for this chain.
57 // NOTE: This default RPC URL is included for convenience to facilitate quick tests and
58 // experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
59 // usage as you will be throttled and this is a disservice to others who need this endpoint.
60 string rpcUrl;
61 }
62
63 // Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data.
64 mapping(string => Chain) private chains;
65 // Maps from the chain's alias to it's default RPC URL.
66 mapping(string => string) private defaultRpcUrls;
67 // Maps from a chain ID to it's alias.
68 mapping(uint256 => string) private idToAlias;
69
70 // The RPC URL will be fetched from config or defaultRpcUrls if possible.
71 function getChain(string memory chainAlias) internal virtual returns (Chain memory chain) {
72 require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string.");
73
74 initialize();
75 chain = chains[chainAlias];
76 require(
77 chain.chainId != 0,
78 string(abi.encodePacked("StdChains getChain(string): Chain with alias \"", chainAlias, "\" not found."))
79 );
80
81 chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
82 }
83
84 function getChain(uint256 chainId) internal virtual returns (Chain memory chain) {
85 require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0.");
86 initialize();
87 string memory chainAlias = idToAlias[chainId];
88
89 chain = chains[chainAlias];
90
91 require(
92 chain.chainId != 0,
93 string(abi.encodePacked("StdChains getChain(uint256): Chain with ID ", vm.toString(chainId), " not found."))
94 );
95
96 chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
97 }
98
99 // set chain info, with priority to argument's rpcUrl field.
100 function setChain(string memory chainAlias, ChainData memory chain) internal virtual {
101 require(
102 bytes(chainAlias).length != 0,
103 "StdChains setChain(string,ChainData): Chain alias cannot be the empty string."
104 );
105
106 require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");
107
108 initialize();
109 string memory foundAlias = idToAlias[chain.chainId];
110
111 require(
112 bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),
113 string(
114 abi.encodePacked(
115 "StdChains setChain(string,ChainData): Chain ID ",
116 vm.toString(chain.chainId),
117 " already used by \"",
118 foundAlias,
119 "\"."
120 )
121 )
122 );
123
124 uint256 oldChainId = chains[chainAlias].chainId;
125 delete idToAlias[oldChainId];
126
127 chains[chainAlias] =
128 Chain({name: chain.name, chainId: chain.chainId, chainAlias: chainAlias, rpcUrl: chain.rpcUrl});
129 idToAlias[chain.chainId] = chainAlias;
130 }
131
132 // set chain info, with priority to argument's rpcUrl field.
133 function setChain(string memory chainAlias, Chain memory chain) internal virtual {
134 setChain(chainAlias, ChainData({name: chain.name, chainId: chain.chainId, rpcUrl: chain.rpcUrl}));
135 }
136
137 function _toUpper(string memory str) private pure returns (string memory) {
138 bytes memory strb = bytes(str);
139 bytes memory copy = new bytes(strb.length);
140 for (uint256 i = 0; i < strb.length; i++) {
141 bytes1 b = strb[i];
142 if (b >= 0x61 && b <= 0x7A) {
143 copy[i] = bytes1(uint8(b) - 32);
144 } else {
145 copy[i] = b;
146 }
147 }
148 return string(copy);
149 }
150
151 // lookup rpcUrl, in descending order of priority:
152 // current -> config (foundry.toml) -> environment variable -> default
153 function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain) private returns (Chain memory) {
154 if (bytes(chain.rpcUrl).length == 0) {
155 try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) {
156 chain.rpcUrl = configRpcUrl;
157 } catch (bytes memory err) {
158 chain.rpcUrl =
159 vm.envOr(string(abi.encodePacked(_toUpper(chainAlias), "_RPC_URL")), defaultRpcUrls[chainAlias]);
160 // distinguish 'not found' from 'cannot read'
161 bytes memory notFoundError =
162 abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
163 if (keccak256(notFoundError) != keccak256(err) || bytes(chain.rpcUrl).length == 0) {
164 /// @solidity memory-safe-assembly
165 assembly {
166 revert(add(32, err), mload(err))
167 }
168 }
169 }
170 }
171 return chain;
172 }
173
174 function initialize() private {
175 if (initialized) return;
176
177 initialized = true;
178
179 // If adding an RPC here, make sure to test the default RPC URL in `testRpcs`
180 setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));
181 setChainWithDefaultRpcUrl(
182 "mainnet", ChainData("Mainnet", 1, "https://mainnet.infura.io/v3/6770454bc6ea42c58aac12978531b93f")
183 );
184 setChainWithDefaultRpcUrl(
185 "goerli", ChainData("Goerli", 5, "https://goerli.infura.io/v3/6770454bc6ea42c58aac12978531b93f")
186 );
187 setChainWithDefaultRpcUrl(
188 "sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/6770454bc6ea42c58aac12978531b93f")
189 );
190 setChainWithDefaultRpcUrl("optimism", ChainData("Optimism", 10, "https://mainnet.optimism.io"));
191 setChainWithDefaultRpcUrl("optimism_goerli", ChainData("Optimism Goerli", 420, "https://goerli.optimism.io"));
192 setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
193 setChainWithDefaultRpcUrl(
194 "arbitrum_one_goerli", ChainData("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc")
195 );
196 setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
197 setChainWithDefaultRpcUrl("polygon", ChainData("Polygon", 137, "https://polygon-rpc.com"));
198 setChainWithDefaultRpcUrl(
199 "polygon_mumbai", ChainData("Polygon Mumbai", 80001, "https://rpc-mumbai.maticvigil.com")
200 );
201 setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
202 setChainWithDefaultRpcUrl(
203 "avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
204 );
205 setChainWithDefaultRpcUrl(
206 "bnb_smart_chain", ChainData("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")
207 );
208 setChainWithDefaultRpcUrl(
209 "bnb_smart_chain_testnet",
210 ChainData("BNB Smart Chain Testnet", 97, "https://rpc.ankr.com/bsc_testnet_chapel")
211 );
212 setChainWithDefaultRpcUrl("gnosis_chain", ChainData("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
213 }
214
215 // set chain info, with priority to chainAlias' rpc url in foundry.toml
216 function setChainWithDefaultRpcUrl(string memory chainAlias, ChainData memory chain) private {
217 string memory rpcUrl = chain.rpcUrl;
218 defaultRpcUrls[chainAlias] = rpcUrl;
219 chain.rpcUrl = "";
220 setChain(chainAlias, chain);
221 chain.rpcUrl = rpcUrl; // restore argument
222 }
223}