arnaucube
arnaucube/poseidon-solidity-examplePublic Self-hosted repository
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}