Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/StdConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ contract StdConfig {
error ChainNotInitialized(uint256 chainId);
error UnableToParseVariable(string key);
error WriteToFileInForbiddenCtxt();
error MissingRpcEndpoint(uint256 chainId);

// -- STORAGE (CACHE FROM CONFIG FILE) ------------------------------------

Expand Down Expand Up @@ -104,13 +105,17 @@ contract StdConfig {
_chainKeys.push(chain_key);

// Cache the configured RPC endpoint for that chain.
// Falls back to `[rpc_endpoints]`. Panics if no rpc endpoint is configured.
// Falls back to `[rpc_endpoints]`. A chain section may exist only to carry
// config values, in which case no endpoint is needed, so a missing one is
// deferred to `getRpcUrl` instead of failing the whole load here.
try vm.parseTomlString(content, string.concat("$.", chain_key, ".endpoint_url")) returns (
string memory url
) {
_rpcOf[chainId] = vm.resolveEnv(url);
} catch {
_rpcOf[chainId] = vm.resolveEnv(vm.rpcUrl(chain_key));
try vm.rpcUrl(chain_key) returns (string memory url) {
_rpcOf[chainId] = vm.resolveEnv(url);
} catch {}
}

// Iterate through all the available `TypeKind`s (except `None`) to create the sub-section paths
Expand Down Expand Up @@ -353,13 +358,18 @@ contract StdConfig {
}

/// @notice Reads the RPC URL for a specific chain id.
/// @dev Reverts with `MissingRpcEndpoint` if the chain was configured without an
/// endpoint. Loading such a chain is allowed, since its config values are
/// still usable; only asking for the endpoint is an error.
function getRpcUrl(uint256 chainId) public view returns (string memory) {
return _rpcOf[chainId];
string memory url = _rpcOf[chainId];
if (bytes(url).length == 0) revert MissingRpcEndpoint(chainId);
return url;
}

/// @notice Reads the RPC URL for the current chain.
function getRpcUrl() public view returns (string memory) {
return _rpcOf[vm.getChainId()];
return getRpcUrl(vm.getChainId());
}

// -- SETTER FUNCTIONS (SINGLE VALUES) -------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions test/Config.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -378,4 +378,51 @@ contract ConfigTest is Test, Config {
new StdConfig(badParseConfig, false);
vm.removeFile(badParseConfig);
}

function test_loadConfigWithoutRpcEndpoint() public {
// A chain section may exist only to carry config values. Loading it should not
// require an endpoint, since nothing on the `_loadConfig` path ever reads one.
string memory configOnly = "./test/fixtures/config_no_endpoint.toml";
vm.writeFile(
configOnly,
string.concat(
"[mainnet]\n",
"endpoint_url = \"https://ethereum.reth.rs/rpc\"\n",
"\n",
"# No endpoint_url, and no matching entry in `[rpc_endpoints]`.\n",
"[optimism.uint]\n",
"some_number = 7\n"
)
);

_loadConfig(configOnly, false);

assertEq(config.getChainIds().length, 2, "both chains should load");
assertEq(config.get(10, "some_number").toUint256(), 7, "config values stay readable");
assertEq(config.getRpcUrl(1), "https://ethereum.reth.rs/rpc", "configured endpoints still resolve");

vm.removeFile(configOnly);
}

function testRevert_MissingRpcEndpoint() public {
// Loading a chain without an endpoint is fine; asking for the endpoint is not.
string memory configOnly = "./test/fixtures/config_no_endpoint_revert.toml";
vm.writeFile(
configOnly,
string.concat(
"[mainnet]\n",
"endpoint_url = \"https://ethereum.reth.rs/rpc\"\n",
"\n",
"[optimism.uint]\n",
"some_number = 7\n"
)
);

_loadConfig(configOnly, false);

vm.expectRevert(abi.encodeWithSelector(StdConfig.MissingRpcEndpoint.selector, uint256(10)));
config.getRpcUrl(10);

vm.removeFile(configOnly);
}
}