contracts/lib/forge-std/src/StdChains.sol 13.9 K raw
1
// SPDX-License-Identifier: MIT
2
pragma solidity >=0.6.2 <0.9.0;
3
4
import {VmSafe} from "./Vm.sol";
5
6
/**
7
 * StdChains provides information about EVM compatible chains that can be used in scripts/tests.
8
 * For each chain, the chain's name, chain ID, and a default RPC URL are provided. Chains are
9
 * identified by their alias, which is the same as the alias in the `[rpc_endpoints]` section of
10
 * the `foundry.toml` file. For best UX, ensure the alias in the `foundry.toml` file match the
11
 * alias used in this contract, which can be found as the first argument to the
12
 * `setChainWithDefaultRpcUrl` call in the `initializeStdChains` function.
13
 *
14
 * There are two main ways to use this contract:
15
 *   1. Set a chain with `setChain(string memory chainAlias, ChainData memory chain)` or
16
 *      `setChain(string memory chainAlias, Chain memory chain)`
17
 *   2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
18
 *
19
 * The first time either of those are used, chains are initialized with the default set of RPC URLs.
20
 * This is done in `initializeStdChains`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in
21
 * `defaultRpcUrls`.
22
 *
23
 * The `setChain` function is straightforward, and it simply saves off the given chain data.
24
 *
25
 * The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
26
 * we want to retrieve the RPC URL for `mainnet`:
27
 *   - If you have specified data with `setChain`, it will return that.
28
 *   - If you have configured a mainnet RPC URL in `foundry.toml`, it will return the URL, provided it
29
 *     is valid (e.g. a URL is specified, or an environment variable is given and exists).
30
 *   - If neither of the above conditions is met, the default data is returned.
31
 *
32
 * Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> environment variable -> defaults.
33
 */
34
abstract contract StdChains {
35
    VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
36
37
    bool private stdChainsInitialized;
38
39
    struct ChainData {
40
        string name;
41
        uint256 chainId;
42
        string rpcUrl;
43
    }
44
45
    struct Chain {
46
        // The chain name.
47
        string name;
48
        // The chain's Chain ID.
49
        uint256 chainId;
50
        // The chain's alias. (i.e. what gets specified in `foundry.toml`).
51
        string chainAlias;
52
        // A default RPC endpoint for this chain.
53
        // NOTE: This default RPC URL is included for convenience to facilitate quick tests and
54
        // experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
55
        // usage as you will be throttled and this is a disservice to others who need this endpoint.
56
        string rpcUrl;
57
    }
58
59
    // Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data.
60
    mapping(string => Chain) private chains;
61
    // Maps from the chain's alias to it's default RPC URL.
62
    mapping(string => string) private defaultRpcUrls;
63
    // Maps from a chain ID to it's alias.
64
    mapping(uint256 => string) private idToAlias;
65
66
    bool private fallbackToDefaultRpcUrls = true;
67
68
    // The RPC URL will be fetched from config or defaultRpcUrls if possible.
69
    function getChain(string memory chainAlias) internal virtual returns (Chain memory chain) {
70
        require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string.");
71
72
        initializeStdChains();
73
        chain = chains[chainAlias];
74
        require(
75
            chain.chainId != 0,
76
            string(abi.encodePacked("StdChains getChain(string): Chain with alias \"", chainAlias, "\" not found."))
77
        );
78
79
        chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
80
    }
81
82
    function getChain(uint256 chainId) internal virtual returns (Chain memory chain) {
83
        require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0.");
84
        initializeStdChains();
85
        string memory chainAlias = idToAlias[chainId];
86
87
        chain = chains[chainAlias];
88
89
        require(
90
            chain.chainId != 0,
91
            string(abi.encodePacked("StdChains getChain(uint256): Chain with ID ", vm.toString(chainId), " not found."))
92
        );
93
94
        chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
95
    }
96
97
    // set chain info, with priority to argument's rpcUrl field.
98
    function setChain(string memory chainAlias, ChainData memory chain) internal virtual {
99
        require(
100
            bytes(chainAlias).length != 0,
101
            "StdChains setChain(string,ChainData): Chain alias cannot be the empty string."
102
        );
103
104
        require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");
105
106
        initializeStdChains();
107
        string memory foundAlias = idToAlias[chain.chainId];
108
109
        require(
110
            bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),
111
            string(
112
                abi.encodePacked(
113
                    "StdChains setChain(string,ChainData): Chain ID ",
114
                    vm.toString(chain.chainId),
115
                    " already used by \"",
116
                    foundAlias,
117
                    "\"."
118
                )
119
            )
120
        );
121
122
        uint256 oldChainId = chains[chainAlias].chainId;
123
        delete idToAlias[oldChainId];
124
125
        chains[chainAlias] =
126
            Chain({name: chain.name, chainId: chain.chainId, chainAlias: chainAlias, rpcUrl: chain.rpcUrl});
127
        idToAlias[chain.chainId] = chainAlias;
128
    }
129
130
    // set chain info, with priority to argument's rpcUrl field.
131
    function setChain(string memory chainAlias, Chain memory chain) internal virtual {
132
        setChain(chainAlias, ChainData({name: chain.name, chainId: chain.chainId, rpcUrl: chain.rpcUrl}));
133
    }
134
135
    function _toUpper(string memory str) private pure returns (string memory) {
136
        bytes memory strb = bytes(str);
137
        bytes memory copy = new bytes(strb.length);
138
        for (uint256 i = 0; i < strb.length; i++) {
139
            bytes1 b = strb[i];
140
            if (b >= 0x61 && b <= 0x7A) {
141
                copy[i] = bytes1(uint8(b) - 32);
142
            } else {
143
                copy[i] = b;
144
            }
145
        }
146
        return string(copy);
147
    }
148
149
    // lookup rpcUrl, in descending order of priority:
150
    // current -> config (foundry.toml) -> environment variable -> default
151
    function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain)
152
        private
153
        view
154
        returns (Chain memory)
155
    {
156
        if (bytes(chain.rpcUrl).length == 0) {
157
            try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) {
158
                chain.rpcUrl = configRpcUrl;
159
            } catch (bytes memory err) {
160
                string memory envName = string(abi.encodePacked(_toUpper(chainAlias), "_RPC_URL"));
161
                if (fallbackToDefaultRpcUrls) {
162
                    chain.rpcUrl = vm.envOr(envName, defaultRpcUrls[chainAlias]);
163
                } else {
164
                    chain.rpcUrl = vm.envString(envName);
165
                }
166
                // Distinguish 'not found' from 'cannot read'
167
                // The upstream error thrown by forge for failing cheats changed so we check both the old and new versions
168
                bytes memory oldNotFoundError =
169
                    abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
170
                bytes memory newNotFoundError = abi.encodeWithSignature(
171
                    "CheatcodeError(string)", string(abi.encodePacked("invalid rpc url: ", chainAlias))
172
                );
173
                bytes32 errHash = keccak256(err);
174
                if (
175
                    (errHash != keccak256(oldNotFoundError) && errHash != keccak256(newNotFoundError))
176
                        || bytes(chain.rpcUrl).length == 0
177
                ) {
178
                    /// @solidity memory-safe-assembly
179
                    assembly {
180
                        revert(add(32, err), mload(err))
181
                    }
182
                }
183
            }
184
        }
185
        return chain;
186
    }
187
188
    function setFallbackToDefaultRpcUrls(bool useDefault) internal {
189
        fallbackToDefaultRpcUrls = useDefault;
190
    }
191
192
    function initializeStdChains() private {
193
        if (stdChainsInitialized) return;
194
195
        stdChainsInitialized = true;
196
197
        // If adding an RPC here, make sure to test the default RPC URL in `test_Rpcs` in `StdChains.t.sol`
198
        setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));
199
        setChainWithDefaultRpcUrl("mainnet", ChainData("Mainnet", 1, "https://eth.llamarpc.com"));
200
        setChainWithDefaultRpcUrl(
201
            "sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
202
        );
203
        setChainWithDefaultRpcUrl("holesky", ChainData("Holesky", 17000, "https://rpc.holesky.ethpandaops.io"));
204
        setChainWithDefaultRpcUrl("hoodi", ChainData("Hoodi", 560048, "https://rpc.hoodi.ethpandaops.io"));
205
        setChainWithDefaultRpcUrl("optimism", ChainData("Optimism", 10, "https://mainnet.optimism.io"));
206
        setChainWithDefaultRpcUrl(
207
            "optimism_sepolia", ChainData("Optimism Sepolia", 11155420, "https://sepolia.optimism.io")
208
        );
209
        setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
210
        setChainWithDefaultRpcUrl(
211
            "arbitrum_one_sepolia", ChainData("Arbitrum One Sepolia", 421614, "https://sepolia-rollup.arbitrum.io/rpc")
212
        );
213
        setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
214
        setChainWithDefaultRpcUrl("polygon", ChainData("Polygon", 137, "https://polygon-rpc.com"));
215
        setChainWithDefaultRpcUrl(
216
            "polygon_amoy", ChainData("Polygon Amoy", 80002, "https://rpc-amoy.polygon.technology")
217
        );
218
        setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
219
        setChainWithDefaultRpcUrl(
220
            "avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
221
        );
222
        setChainWithDefaultRpcUrl(
223
            "bnb_smart_chain", ChainData("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")
224
        );
225
        setChainWithDefaultRpcUrl(
226
            "bnb_smart_chain_testnet",
227
            ChainData("BNB Smart Chain Testnet", 97, "https://rpc.ankr.com/bsc_testnet_chapel")
228
        );
229
        setChainWithDefaultRpcUrl("gnosis_chain", ChainData("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
230
        setChainWithDefaultRpcUrl("moonbeam", ChainData("Moonbeam", 1284, "https://rpc.api.moonbeam.network"));
231
        setChainWithDefaultRpcUrl(
232
            "moonriver", ChainData("Moonriver", 1285, "https://rpc.api.moonriver.moonbeam.network")
233
        );
234
        setChainWithDefaultRpcUrl("moonbase", ChainData("Moonbase", 1287, "https://rpc.testnet.moonbeam.network"));
235
        setChainWithDefaultRpcUrl("base_sepolia", ChainData("Base Sepolia", 84532, "https://sepolia.base.org"));
236
        setChainWithDefaultRpcUrl("base", ChainData("Base", 8453, "https://mainnet.base.org"));
237
        setChainWithDefaultRpcUrl("blast_sepolia", ChainData("Blast Sepolia", 168587773, "https://sepolia.blast.io"));
238
        setChainWithDefaultRpcUrl("blast", ChainData("Blast", 81457, "https://rpc.blast.io"));
239
        setChainWithDefaultRpcUrl("fantom_opera", ChainData("Fantom Opera", 250, "https://rpc.ankr.com/fantom/"));
240
        setChainWithDefaultRpcUrl(
241
            "fantom_opera_testnet", ChainData("Fantom Opera Testnet", 4002, "https://rpc.ankr.com/fantom_testnet/")
242
        );
243
        setChainWithDefaultRpcUrl("fraxtal", ChainData("Fraxtal", 252, "https://rpc.frax.com"));
244
        setChainWithDefaultRpcUrl("fraxtal_testnet", ChainData("Fraxtal Testnet", 2522, "https://rpc.testnet.frax.com"));
245
        setChainWithDefaultRpcUrl(
246
            "berachain_bartio_testnet", ChainData("Berachain bArtio Testnet", 80084, "https://bartio.rpc.berachain.com")
247
        );
248
        setChainWithDefaultRpcUrl("flare", ChainData("Flare", 14, "https://flare-api.flare.network/ext/C/rpc"));
249
        setChainWithDefaultRpcUrl(
250
            "flare_coston2", ChainData("Flare Coston2", 114, "https://coston2-api.flare.network/ext/C/rpc")
251
        );
252
253
        setChainWithDefaultRpcUrl("mode", ChainData("Mode", 34443, "https://mode.drpc.org"));
254
        setChainWithDefaultRpcUrl("mode_sepolia", ChainData("Mode Sepolia", 919, "https://sepolia.mode.network"));
255
256
        setChainWithDefaultRpcUrl("zora", ChainData("Zora", 7777777, "https://zora.drpc.org"));
257
        setChainWithDefaultRpcUrl(
258
            "zora_sepolia", ChainData("Zora Sepolia", 999999999, "https://sepolia.rpc.zora.energy")
259
        );
260
261
        setChainWithDefaultRpcUrl("race", ChainData("Race", 6805, "https://racemainnet.io"));
262
        setChainWithDefaultRpcUrl("race_sepolia", ChainData("Race Sepolia", 6806, "https://racemainnet.io"));
263
264
        setChainWithDefaultRpcUrl("metal", ChainData("Metal", 1750, "https://metall2.drpc.org"));
265
        setChainWithDefaultRpcUrl("metal_sepolia", ChainData("Metal Sepolia", 1740, "https://testnet.rpc.metall2.com"));
266
267
        setChainWithDefaultRpcUrl("binary", ChainData("Binary", 624, "https://rpc.zero.thebinaryholdings.com"));
268
        setChainWithDefaultRpcUrl(
269
            "binary_sepolia", ChainData("Binary Sepolia", 625, "https://rpc.zero.thebinaryholdings.com")
270
        );
271
272
        setChainWithDefaultRpcUrl("orderly", ChainData("Orderly", 291, "https://rpc.orderly.network"));
273
        setChainWithDefaultRpcUrl(
274
            "orderly_sepolia", ChainData("Orderly Sepolia", 4460, "https://testnet-rpc.orderly.org")
275
        );
276
    }
277
278
    // set chain info, with priority to chainAlias' rpc url in foundry.toml
279
    function setChainWithDefaultRpcUrl(string memory chainAlias, ChainData memory chain) private {
280
        string memory rpcUrl = chain.rpcUrl;
281
        defaultRpcUrls[chainAlias] = rpcUrl;
282
        chain.rpcUrl = "";
283
        setChain(chainAlias, chain);
284
        chain.rpcUrl = rpcUrl; // restore argument
285
    }
286
}