Skip to content

Commit aea0b26

Browse files
Usability Updates for StdChains (#268)
* place chainAlias inside the Chain struct * allow environment variable overrides to chain rpc urls * Update src/StdChains.sol Co-authored-by: Chris Smith <[email protected]> * Update src/StdChains.sol Co-authored-by: Chris Smith <[email protected]> * Update src/StdChains.sol Co-authored-by: Chris Smith <[email protected]> * Update src/StdChains.sol Co-authored-by: Chris Smith <[email protected]> * Update src/StdChains.sol Co-authored-by: Chris Smith <[email protected]> * format fixes * reverse the RPC_URL and ALIAS ordering * more detailed alias comment * split Chain and ChainData * forge fmt * remove the forge fmt disable * update comments in setChain and at top; add overload to setChain for the Chain struct * forge fmt Co-authored-by: Chris Smith <[email protected]>
1 parent 1e1097d commit aea0b26

File tree

2 files changed

+104
-46
lines changed

2 files changed

+104
-46
lines changed

src/StdChains.sol

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {VmSafe} from "./Vm.sol";
1414
* `setChainWithDefaultRpcUrl` call in the `initialize` function.
1515
*
1616
* There are two main ways to use this contract:
17-
* 1. Set a chain with `setChain(string memory chainAlias, Chain memory chain)`
17+
* 1. Set a chain with `setChain(string memory chainAlias, ChainData memory chain)` or
18+
* `setChain(string memory chainAlias, Chain memory chain)`
1819
* 2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
1920
*
2021
* The first time either of those are used, chains are initialized with the default set of RPC URLs.
@@ -25,25 +26,33 @@ import {VmSafe} from "./Vm.sol";
2526
*
2627
* The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
2728
* we want to retrieve `mainnet`'s RPC URL:
28-
* - If you haven't set any mainnet chain info with `setChain` and you haven't specified that
29-
* chain in `foundry.toml`, the default data and RPC URL will be returned.
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.
3031
* - If you have set a mainnet RPC URL in `foundry.toml` it will return that, if valid (e.g. if
3132
* a URL is given or if an environment variable is given and that environment variable exists).
3233
* Otherwise, the default data is returned.
3334
* - If you specified data with `setChain` it will return that.
3435
*
35-
* Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> defaults.
36+
* Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> environment variable -> defaults.
3637
*/
3738
abstract contract StdChains {
3839
VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
3940

4041
bool private initialized;
4142

43+
struct ChainData {
44+
string name;
45+
uint256 chainId;
46+
string rpcUrl;
47+
}
48+
4249
struct Chain {
4350
// The chain name.
4451
string name;
4552
// The chain's Chain ID.
4653
uint256 chainId;
54+
// The chain's alias. (i.e. what gets specified in `foundry.toml`).
55+
string chainAlias;
4756
// A default RPC endpoint for this chain.
4857
// NOTE: This default RPC URL is included for convenience to facilitate quick tests and
4958
// experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
@@ -88,12 +97,13 @@ abstract contract StdChains {
8897
}
8998

9099
// set chain info, with priority to argument's rpcUrl field.
91-
function setChain(string memory chainAlias, Chain memory chain) internal virtual {
100+
function setChain(string memory chainAlias, ChainData memory chain) internal virtual {
92101
require(
93-
bytes(chainAlias).length != 0, "StdChains setChain(string,Chain): Chain alias cannot be the empty string."
102+
bytes(chainAlias).length != 0,
103+
"StdChains setChain(string,ChainData): Chain alias cannot be the empty string."
94104
);
95105

96-
require(chain.chainId != 0, "StdChains setChain(string,Chain): Chain ID cannot be 0.");
106+
require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");
97107

98108
initialize();
99109
string memory foundAlias = idToAlias[chain.chainId];
@@ -102,7 +112,7 @@ abstract contract StdChains {
102112
bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),
103113
string(
104114
abi.encodePacked(
105-
"StdChains setChain(string,Chain): Chain ID ",
115+
"StdChains setChain(string,ChainData): Chain ID ",
106116
vm.toString(chain.chainId),
107117
" already used by \"",
108118
foundAlias,
@@ -114,22 +124,39 @@ abstract contract StdChains {
114124
uint256 oldChainId = chains[chainAlias].chainId;
115125
delete idToAlias[oldChainId];
116126

117-
chains[chainAlias] = chain;
127+
chains[chainAlias] =
128+
Chain({name: chain.name, chainId: chain.chainId, chainAlias: chainAlias, rpcUrl: chain.rpcUrl});
118129
idToAlias[chain.chainId] = chainAlias;
119130
}
120131

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+
121151
// lookup rpcUrl, in descending order of priority:
122-
// current -> config (foundry.toml) -> default
123-
function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain)
124-
private
125-
view
126-
returns (Chain memory)
127-
{
152+
// current -> config (foundry.toml) -> environment variable -> default
153+
function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain) private returns (Chain memory) {
128154
if (bytes(chain.rpcUrl).length == 0) {
129155
try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) {
130156
chain.rpcUrl = configRpcUrl;
131157
} catch (bytes memory err) {
132-
chain.rpcUrl = defaultRpcUrls[chainAlias];
158+
chain.rpcUrl =
159+
vm.envOr(string(abi.encodePacked(_toUpper(chainAlias), "_RPC_URL")), defaultRpcUrls[chainAlias]);
133160
// distinguish 'not found' from 'cannot read'
134161
bytes memory notFoundError =
135162
abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
@@ -150,36 +177,43 @@ abstract contract StdChains {
150177
initialized = true;
151178

152179
// If adding an RPC here, make sure to test the default RPC URL in `testRpcs`
153-
setChainWithDefaultRpcUrl("anvil", Chain("Anvil", 31337, "http://127.0.0.1:8545"));
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"));
154193
setChainWithDefaultRpcUrl(
155-
"mainnet", Chain("Mainnet", 1, "https://mainnet.infura.io/v3/6770454bc6ea42c58aac12978531b93f")
194+
"arbitrum_one_goerli", ChainData("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc")
156195
);
196+
setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
197+
setChainWithDefaultRpcUrl("polygon", ChainData("Polygon", 137, "https://polygon-rpc.com"));
157198
setChainWithDefaultRpcUrl(
158-
"goerli", Chain("Goerli", 5, "https://goerli.infura.io/v3/6770454bc6ea42c58aac12978531b93f")
199+
"polygon_mumbai", ChainData("Polygon Mumbai", 80001, "https://rpc-mumbai.maticvigil.com")
159200
);
201+
setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
160202
setChainWithDefaultRpcUrl(
161-
"sepolia", Chain("Sepolia", 11155111, "https://sepolia.infura.io/v3/6770454bc6ea42c58aac12978531b93f")
203+
"avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
162204
);
163-
setChainWithDefaultRpcUrl("optimism", Chain("Optimism", 10, "https://mainnet.optimism.io"));
164-
setChainWithDefaultRpcUrl("optimism_goerli", Chain("Optimism Goerli", 420, "https://goerli.optimism.io"));
165-
setChainWithDefaultRpcUrl("arbitrum_one", Chain("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
166205
setChainWithDefaultRpcUrl(
167-
"arbitrum_one_goerli", Chain("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc")
206+
"bnb_smart_chain", ChainData("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")
168207
);
169-
setChainWithDefaultRpcUrl("arbitrum_nova", Chain("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
170-
setChainWithDefaultRpcUrl("polygon", Chain("Polygon", 137, "https://polygon-rpc.com"));
171-
setChainWithDefaultRpcUrl("polygon_mumbai", Chain("Polygon Mumbai", 80001, "https://rpc-mumbai.maticvigil.com"));
172-
setChainWithDefaultRpcUrl("avalanche", Chain("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
173208
setChainWithDefaultRpcUrl(
174-
"avalanche_fuji", Chain("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
209+
"bnb_smart_chain_testnet",
210+
ChainData("BNB Smart Chain Testnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545")
175211
);
176-
setChainWithDefaultRpcUrl("bnb_smart_chain", Chain("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org"));
177-
setChainWithDefaultRpcUrl("bnb_smart_chain_testnet", Chain("BNB Smart Chain Testnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545"));// forgefmt: disable-line
178-
setChainWithDefaultRpcUrl("gnosis_chain", Chain("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
212+
setChainWithDefaultRpcUrl("gnosis_chain", ChainData("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
179213
}
180214

181215
// set chain info, with priority to chainAlias' rpc url in foundry.toml
182-
function setChainWithDefaultRpcUrl(string memory chainAlias, Chain memory chain) private {
216+
function setChainWithDefaultRpcUrl(string memory chainAlias, ChainData memory chain) private {
183217
string memory rpcUrl = chain.rpcUrl;
184218
defaultRpcUrls[chainAlias] = rpcUrl;
185219
chain.rpcUrl = "";

test/StdChains.t.sol

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ contract StdChainsTest is Test {
1010
assertEq(getChain("optimism_goerli").rpcUrl, "https://goerli.optimism.io/");
1111
assertEq(getChain("arbitrum_one_goerli").rpcUrl, "https://goerli-rollup.arbitrum.io/rpc/");
1212

13+
// Environment variables should be the next fallback
14+
assertEq(getChain("arbitrum_nova").rpcUrl, "https://nova.arbitrum.io/rpc");
15+
vm.setEnv("ARBITRUM_NOVA_RPC_URL", "myoverride");
16+
assertEq(getChain("arbitrum_nova").rpcUrl, "myoverride");
17+
vm.setEnv("ARBITRUM_NOVA_RPC_URL", "https://nova.arbitrum.io/rpc");
18+
19+
// Cannot override RPCs defined in `foundry.toml`
20+
vm.setEnv("MAINNET_RPC_URL", "myoverride2");
21+
assertEq(getChain("mainnet").rpcUrl, "https://mainnet.infura.io/v3/7a8769b798b642f6933f2ed52042bd70");
22+
1323
// Other RPCs should remain unchanged.
1424
assertEq(getChain(31337).rpcUrl, "http://127.0.0.1:8545");
1525
assertEq(getChain("sepolia").rpcUrl, "https://sepolia.infura.io/v3/6770454bc6ea42c58aac12978531b93f");
@@ -45,46 +55,60 @@ contract StdChainsTest is Test {
4555
}
4656

4757
function testSetChainFirstFails() public {
48-
vm.expectRevert("StdChains setChain(string,Chain): Chain ID 31337 already used by \"anvil\".");
49-
setChain("anvil2", Chain("Anvil", 31337, "URL"));
58+
vm.expectRevert("StdChains setChain(string,ChainData): Chain ID 31337 already used by \"anvil\".");
59+
setChain("anvil2", ChainData("Anvil", 31337, "URL"));
5060
}
5161

5262
function testChainBubbleUp() public {
53-
setChain("needs_undefined_env_var", Chain("", 123456789, ""));
63+
setChain("needs_undefined_env_var", ChainData("", 123456789, ""));
5464
vm.expectRevert(
5565
"Failed to resolve env var `UNDEFINED_RPC_URL_PLACEHOLDER` in `${UNDEFINED_RPC_URL_PLACEHOLDER}`: environment variable not found"
5666
);
5767
getChain("needs_undefined_env_var");
5868
}
5969

6070
function testCannotSetChain_ChainIdExists() public {
61-
setChain("custom_chain", Chain("Custom Chain", 123456789, "https://custom.chain/"));
71+
setChain("custom_chain", ChainData("Custom Chain", 123456789, "https://custom.chain/"));
6272

63-
vm.expectRevert('StdChains setChain(string,Chain): Chain ID 123456789 already used by "custom_chain".');
73+
vm.expectRevert('StdChains setChain(string,ChainData): Chain ID 123456789 already used by "custom_chain".');
6474

65-
setChain("another_custom_chain", Chain("", 123456789, ""));
75+
setChain("another_custom_chain", ChainData("", 123456789, ""));
6676
}
6777

6878
function testSetChain() public {
69-
setChain("custom_chain", Chain("Custom Chain", 123456789, "https://custom.chain/"));
79+
setChain("custom_chain", ChainData("Custom Chain", 123456789, "https://custom.chain/"));
7080
Chain memory customChain = getChain("custom_chain");
7181
assertEq(customChain.name, "Custom Chain");
7282
assertEq(customChain.chainId, 123456789);
83+
assertEq(customChain.chainAlias, "custom_chain");
7384
assertEq(customChain.rpcUrl, "https://custom.chain/");
7485
Chain memory chainById = getChain(123456789);
7586
assertEq(chainById.name, customChain.name);
7687
assertEq(chainById.chainId, customChain.chainId);
88+
assertEq(chainById.chainAlias, customChain.chainAlias);
7789
assertEq(chainById.rpcUrl, customChain.rpcUrl);
90+
customChain.name = "Another Custom Chain";
91+
customChain.chainId = 987654321;
92+
setChain("another_custom_chain", customChain);
93+
Chain memory anotherCustomChain = getChain("another_custom_chain");
94+
assertEq(anotherCustomChain.name, "Another Custom Chain");
95+
assertEq(anotherCustomChain.chainId, 987654321);
96+
assertEq(anotherCustomChain.chainAlias, "another_custom_chain");
97+
assertEq(anotherCustomChain.rpcUrl, "https://custom.chain/");
98+
// Verify the first chain data was not overwritten
99+
chainById = getChain(123456789);
100+
assertEq(chainById.name, "Custom Chain");
101+
assertEq(chainById.chainId, 123456789);
78102
}
79103

80104
function testSetNoEmptyAlias() public {
81-
vm.expectRevert("StdChains setChain(string,Chain): Chain alias cannot be the empty string.");
82-
setChain("", Chain("", 123456789, ""));
105+
vm.expectRevert("StdChains setChain(string,ChainData): Chain alias cannot be the empty string.");
106+
setChain("", ChainData("", 123456789, ""));
83107
}
84108

85109
function testSetNoChainId0() public {
86-
vm.expectRevert("StdChains setChain(string,Chain): Chain ID cannot be 0.");
87-
setChain("alias", Chain("", 0, ""));
110+
vm.expectRevert("StdChains setChain(string,ChainData): Chain ID cannot be 0.");
111+
setChain("alias", ChainData("", 0, ""));
88112
}
89113

90114
function testGetNoChainId0() public {
@@ -108,10 +132,10 @@ contract StdChainsTest is Test {
108132
}
109133

110134
function testSetChain_ExistingOne() public {
111-
setChain("custom_chain", Chain("Custom Chain", 123456789, "https://custom.chain/"));
135+
setChain("custom_chain", ChainData("Custom Chain", 123456789, "https://custom.chain/"));
112136
assertEq(getChain(123456789).chainId, 123456789);
113137

114-
setChain("custom_chain", Chain("Modified Chain", 999999999, "https://modified.chain/"));
138+
setChain("custom_chain", ChainData("Modified Chain", 999999999, "https://modified.chain/"));
115139
vm.expectRevert("StdChains getChain(uint256): Chain with ID 123456789 not found.");
116140
getChain(123456789);
117141

0 commit comments

Comments
 (0)