264 lines
12 KiB
Solidity
264 lines
12 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity >=0.6.2 <0.9.0;
|
|
|
|
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
|
|
view
|
|
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'
|
|
// The upstream error thrown by forge for failing cheats changed so we check both the old and new versions
|
|
bytes memory oldNotFoundError =
|
|
abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
|
|
bytes memory newNotFoundError = abi.encodeWithSignature(
|
|
"CheatcodeError(string)", string(abi.encodePacked("invalid rpc url: ", chainAlias))
|
|
);
|
|
bytes32 errHash = keccak256(err);
|
|
if (
|
|
(errHash != keccak256(oldNotFoundError) && errHash != keccak256(newNotFoundError))
|
|
|| 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 `test_Rpcs` in `StdChains.t.sol`
|
|
setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));
|
|
setChainWithDefaultRpcUrl(
|
|
"mainnet", ChainData("Mainnet", 1, "https://eth-mainnet.alchemyapi.io/v2/pwc5rmJhrdoaSEfimoKEmsvOjKSmPDrP")
|
|
);
|
|
setChainWithDefaultRpcUrl(
|
|
"sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
|
|
);
|
|
setChainWithDefaultRpcUrl("holesky", ChainData("Holesky", 17000, "https://rpc.holesky.ethpandaops.io"));
|
|
setChainWithDefaultRpcUrl("optimism", ChainData("Optimism", 10, "https://mainnet.optimism.io"));
|
|
setChainWithDefaultRpcUrl(
|
|
"optimism_sepolia", ChainData("Optimism Sepolia", 11155420, "https://sepolia.optimism.io")
|
|
);
|
|
setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
|
|
setChainWithDefaultRpcUrl(
|
|
"arbitrum_one_sepolia", ChainData("Arbitrum One Sepolia", 421614, "https://sepolia-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_amoy", ChainData("Polygon Amoy", 80002, "https://rpc-amoy.polygon.technology")
|
|
);
|
|
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"));
|
|
setChainWithDefaultRpcUrl("moonbeam", ChainData("Moonbeam", 1284, "https://rpc.api.moonbeam.network"));
|
|
setChainWithDefaultRpcUrl(
|
|
"moonriver", ChainData("Moonriver", 1285, "https://rpc.api.moonriver.moonbeam.network")
|
|
);
|
|
setChainWithDefaultRpcUrl("moonbase", ChainData("Moonbase", 1287, "https://rpc.testnet.moonbeam.network"));
|
|
setChainWithDefaultRpcUrl("base_sepolia", ChainData("Base Sepolia", 84532, "https://sepolia.base.org"));
|
|
setChainWithDefaultRpcUrl("base", ChainData("Base", 8453, "https://mainnet.base.org"));
|
|
setChainWithDefaultRpcUrl("blast_sepolia", ChainData("Blast Sepolia", 168587773, "https://sepolia.blast.io"));
|
|
setChainWithDefaultRpcUrl("blast", ChainData("Blast", 81457, "https://rpc.blast.io"));
|
|
setChainWithDefaultRpcUrl("fantom_opera", ChainData("Fantom Opera", 250, "https://rpc.ankr.com/fantom/"));
|
|
setChainWithDefaultRpcUrl(
|
|
"fantom_opera_testnet", ChainData("Fantom Opera Testnet", 4002, "https://rpc.ankr.com/fantom_testnet/")
|
|
);
|
|
setChainWithDefaultRpcUrl("fraxtal", ChainData("Fraxtal", 252, "https://rpc.frax.com"));
|
|
setChainWithDefaultRpcUrl("fraxtal_testnet", ChainData("Fraxtal Testnet", 2522, "https://rpc.testnet.frax.com"));
|
|
setChainWithDefaultRpcUrl(
|
|
"berachain_bartio_testnet", ChainData("Berachain bArtio Testnet", 80084, "https://bartio.rpc.berachain.com")
|
|
);
|
|
setChainWithDefaultRpcUrl("flare", ChainData("Flare", 14, "https://flare-api.flare.network/ext/C/rpc"));
|
|
setChainWithDefaultRpcUrl(
|
|
"flare_coston2", ChainData("Flare Coston2", 114, "https://coston2-api.flare.network/ext/C/rpc")
|
|
);
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|