// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;
import {VmSafe} from "./Vm.sol";
/**
* StdChains provides information about EVM compatible chains that can be used in scripts/tests.
* For each chain, the chain's name, chain ID, and a default RPC URL are provided. Chains are
* identified by their alias, which is the same as the alias in the `[rpc_endpoints]` section of
* the `foundry.toml` file. For best UX, ensure the alias in the `foundry.toml` file match the
* alias used in this contract, which can be found as the first argument to the
* `setChainWithDefaultRpcUrl` call in the `initializeStdChains` function.
*
* There are two main ways to use this contract:
* 1. Set a chain with `setChain(string memory chainAlias, ChainData memory chain)` or
* `setChain(string memory chainAlias, Chain memory chain)`
* 2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
*
* The first time either of those are used, chains are initialized with the default set of RPC URLs.
* This is done in `initializeStdChains`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in
* `defaultRpcUrls`.
*
* The `setChain` function is straightforward, and it simply saves off the given chain data.
*
* The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
* we want to retrieve the RPC URL for `mainnet`:
* - If you have specified data with `setChain`, it will return that.
* - If you have configured a mainnet RPC URL in `foundry.toml`, it will return the URL, provided it
* is valid (e.g. a URL is specified, or an environment variable is given and exists).
* - If neither of the above conditions is met, the default data is returned.
*
* Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> environment variable -> defaults.
*/
abstract contract StdChains {
VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
bool private stdChainsInitialized;
struct ChainData {
string name;
uint256 chainId;
string rpcUrl;
}
struct Chain {
// The chain name.
string name;
// The chain's Chain ID.
uint256 chainId;
// The chain's alias. (i.e. what gets specified in `foundry.toml`).
string chainAlias;
// A default RPC endpoint for this chain.
// NOTE: This default RPC URL is included for convenience to facilitate quick tests and
// experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
// usage as you will be throttled and this is a disservice to others who need this endpoint.
string rpcUrl;
}
// Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data.
mapping(string => Chain) private chains;
// Maps from the chain's alias to it's default RPC URL.
mapping(string => string) private defaultRpcUrls;
// Maps from a chain ID to it's alias.
mapping(uint256 => string) private idToAlias;
bool private fallbackToDefaultRpcUrls = true;
// The RPC URL will be fetched from config or defaultRpcUrls if possible.
function getChain(string memory chainAlias) internal virtual returns (Chain memory chain) {
require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string.");
initializeStdChains();
chain = chains[chainAlias];
require(
chain.chainId != 0,
string(abi.encodePacked("StdChains getChain(string): Chain with alias \"", chainAlias, "\" not found."))
);
chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
}
function getChain(uint256 chainId) internal virtual returns (Chain memory chain) {
require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0.");
initializeStdChains();
string memory chainAlias = idToAlias[chainId];
chain = chains[chainAlias];
require(
chain.chainId != 0,
string(abi.encodePacked("StdChains getChain(uint256): Chain with ID ", vm.toString(chainId), " not found."))
);
chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
}
// set chain info, with priority to argument's rpcUrl field.
function setChain(string memory chainAlias, ChainData memory chain) internal virtual {
require(
bytes(chainAlias).length != 0,
"StdChains setChain(string,ChainData): Chain alias cannot be the empty string."
);
require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");
initializeStdChains();
string memory foundAlias = idToAlias[chain.chainId];
require(
bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),
string(
abi.encodePacked(
"StdChains setChain(string,ChainData): Chain ID ",
vm.toString(chain.chainId),
" already used by \"",
foundAlias,
"\"."
)
)
);
uint256 oldChainId = chains[chainAlias].chainId;
delete idToAlias[oldChainId];
chains[chainAlias] =
Chain({name: chain.name, chainId: chain.chainId, chainAlias: chainAlias, rpcUrl: chain.rpcUrl});
idToAlias[chain.chainId] = chainAlias;
}
// set chain info, with priority to argument's rpcUrl field.
function setChain(string memory chainAlias, Chain memory chain) internal virtual {
setChain(chainAlias, ChainData({name: chain.name, chainId: chain.chainId, rpcUrl: chain.rpcUrl}));
}
function _toUpper(string memory str) private pure returns (string memory) {
bytes memory strb = bytes(str);
bytes memory copy = new bytes(strb.length);
for (uint256 i = 0; i < strb.length; i++) {
bytes1 b = strb[i];
if (b >= 0x61 && b <= 0x7A) {
copy[i] = bytes1(uint8(b) - 32);
} else {
copy[i] = b;
}
}
return string(copy);
}
// lookup rpcUrl, in descending order of priority:
// current -> config (foundry.toml) -> environment variable -> default
function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain) private returns (Chain memory) {
if (bytes(chain.rpcUrl).length == 0) {
try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) {
chain.rpcUrl = configRpcUrl;
} catch (bytes memory err) {
string memory envName = string(abi.encodePacked(_toUpper(chainAlias), "_RPC_URL"));
if (fallbackToDefaultRpcUrls) {
chain.rpcUrl = vm.envOr(envName, defaultRpcUrls[chainAlias]);
} else {
chain.rpcUrl = vm.envString(envName);
}
// distinguish 'not found' from 'cannot read'
bytes memory notFoundError =
abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
if (keccak256(notFoundError) != keccak256(err) || bytes(chain.rpcUrl).length == 0) {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, err), mload(err))
}
}
}
}
return chain;
}
function setFallbackToDefaultRpcUrls(bool useDefault) internal {
fallbackToDefaultRpcUrls = useDefault;
}
function initializeStdChains() private {
if (stdChainsInitialized) return;
stdChainsInitialized = true;
// If adding an RPC here, make sure to test the default RPC URL in `testRpcs`
setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));
setChainWithDefaultRpcUrl(
"mainnet", ChainData("Mainnet", 1, "https://mainnet.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
);
setChainWithDefaultRpcUrl(
"goerli", ChainData("Goerli", 5, "https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
);
setChainWithDefaultRpcUrl(
"sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
);
setChainWithDefaultRpcUrl("optimism", ChainData("Optimism", 10, "https://mainnet.optimism.io"));
setChainWithDefaultRpcUrl("optimism_goerli", ChainData("Optimism Goerli", 420, "https://goerli.optimism.io"));
setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
setChainWithDefaultRpcUrl(
"arbitrum_one_goerli", ChainData("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc")
);
setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
setChainWithDefaultRpcUrl("polygon", ChainData("Polygon", 137, "https://polygon-rpc.com"));
setChainWithDefaultRpcUrl(
"polygon_mumbai", ChainData("Polygon Mumbai", 80001, "https://rpc-mumbai.maticvigil.com")
);
setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
setChainWithDefaultRpcUrl(
"avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
);
setChainWithDefaultRpcUrl(
"bnb_smart_chain", ChainData("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")
);
setChainWithDefaultRpcUrl(
"bnb_smart_chain_testnet",
ChainData("BNB Smart Chain Testnet", 97, "https://rpc.ankr.com/bsc_testnet_chapel")
);
setChainWithDefaultRpcUrl("gnosis_chain", ChainData("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
}
// set chain info, with priority to chainAlias' rpc url in foundry.toml
function setChainWithDefaultRpcUrl(string memory chainAlias, ChainData memory chain) private {
string memory rpcUrl = chain.rpcUrl;
defaultRpcUrls[chainAlias] = rpcUrl;
chain.rpcUrl = "";
setChain(chainAlias, chain);
chain.rpcUrl = rpcUrl; // restore argument
}
}