Skip to content
Merged
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
4 changes: 3 additions & 1 deletion src/Config.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;

import {console} from "./console.sol";
import {StdConfig} from "./StdConfig.sol";
import {CommonBase} from "./Base.sol";
import {StdConfig, Variable, LibVariable} from "./StdConfig.sol";

/// @notice Boilerplate to streamline the setup of multi-chain environments.
abstract contract Config is CommonBase {
using LibVariable for Variable;

// -- STORAGE (CONFIG + CHAINS + FORKS) ------------------------------------

/// @notice Contract instance holding the data from the TOML config file.
Expand Down
20 changes: 14 additions & 6 deletions src/LibVariable.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2 <0.9.0;

using LibVariable for Variable global;

struct Variable {
Type ty;
bytes data;
Expand Down Expand Up @@ -51,8 +49,6 @@ enum TypeKind {
/// }
/// ```
library LibVariable {
error NotInitialized();
error TypeMismatch(string expected, string actual);

// -- TYPE HELPERS ----------------------------------------------------

Expand All @@ -64,7 +60,7 @@ library LibVariable {
/// @notice Compares two Type instances for equality. Reverts if they are not equal.
function assertEq(Type memory self, Type memory other) internal pure {
if (!isEqual(self, other)) {
revert TypeMismatch(toString(other), toString(self));
revert(_concat("type mismatch: expected '", toString(other), _concat("', got '", toString(self), "'")));
}
}

Expand Down Expand Up @@ -112,7 +108,7 @@ library LibVariable {
/// @dev Checks if a `Variable` has been initialized, reverting if not.
function assertExists(Variable memory self) public pure {
if (self.ty.kind == TypeKind.None) {
revert NotInitialized();
revert("variable not initialized");
}
}

Expand Down Expand Up @@ -229,4 +225,16 @@ library LibVariable {
{
return abi.decode(self.data, (bytes[]));
}

// -- UTILITY HELPERS ------------------------------------------------------

/// @dev concatenates two strings
function _concat(string memory s1, string memory s2) private pure returns (string memory) {
return string(abi.encodePacked(s1, s2));
}

/// @dev concatenates three strings
function _concat(string memory s1, string memory s2, string memory s3) private pure returns (string memory) {
return string(abi.encodePacked(s1, s2, s3));
}
}
15 changes: 4 additions & 11 deletions src/StdConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ contract StdConfig {
VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
uint8 private constant NUM_TYPES = 7;

// -- ERRORS ---------------------------------------------------------------

error AlreadyInitialized(string key);
error InvalidChainKey(string aliasOrId);
error ChainIdNotFound(uint256 chainId);
error UnableToParseVariable(string key);

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

/// @notice Path to the loaded TOML configuration file.
Expand Down Expand Up @@ -112,7 +105,7 @@ contract StdConfig {
if (_typeOf[chainId][key].kind == TypeKind.None) {
_loadAndCacheValue(content, _concat(typePath, ".", key), chainId, key, ty);
} else {
revert AlreadyInitialized(key);
revert(_concat("already initialized: '", key, "'"));
}
}
} catch {} // Section does not exist, ignore.
Expand Down Expand Up @@ -203,7 +196,7 @@ contract StdConfig {
}

if (!success) {
revert UnableToParseVariable(key);
revert(_concat("unable to parse variable: '", key, "'"));
}
}

Expand All @@ -226,7 +219,7 @@ contract StdConfig {
try vm.getChain(aliasOrId) returns (VmSafe.Chain memory chainInfo) {
return chainInfo.chainId;
} catch {
revert InvalidChainKey(aliasOrId);
revert(_concat("invalid chain key: '", aliasOrId, "' is not a valid alias nor a number"));
}
}
}
Expand All @@ -238,7 +231,7 @@ contract StdConfig {
return _chainKeys[i];
}
}
revert ChainIdNotFound(chainId);
revert(_concat("chain id: '", vm.toString(chainId), "' not found in configuration"));
}

/// @dev Ensures type consistency when setting a value - prevents changing types unless uninitialized.
Expand Down
67 changes: 37 additions & 30 deletions test/Config.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;

import {Test} from "../src/Test.sol";
import {Config} from "../src/Config.sol";
import {StdConfig} from "../src/StdConfig.sol";
import {Variable, LibVariable} from "../src/LibVariable.sol";
import {Config, StdConfig, Variable, LibVariable} from "../src/Config.sol";

contract ConfigTest is Test, Config {
using LibVariable for Variable;

function test_loadConfig() public {
// Deploy the config contract with the test fixture.
_loadConfig("./test/fixtures/config.toml");
Expand Down Expand Up @@ -201,7 +201,7 @@ contract ConfigTest is Test, Config {
}

function testRevert_InvalidChainKey() public {
// Create a fixture with an invalid chain key
// Create a temporary fixture with an invalid chain key
string memory invalidChainConfig = "./test/fixtures/config_invalid_chain.toml";
vm.writeFile(
invalidChainConfig,
Expand All @@ -223,21 +223,23 @@ contract ConfigTest is Test, Config {
)
);

vm.expectRevert(abi.encodeWithSelector(StdConfig.InvalidChainKey.selector, "invalid_chain"));
vm.expectRevert("invalid chain key: 'invalid_chain' is not a valid alias nor a number");
new StdConfig(invalidChainConfig);

// Clean up the temporary file.
vm.removeFile(invalidChainConfig);
}

function testRevert_ChainIdNotFound() public {
_loadConfig("./test/fixtures/config.toml");

// Try to write a value for a non-existent chain ID
vm.expectRevert(abi.encodeWithSelector(StdConfig.ChainIdNotFound.selector, uint256(999999)));
vm.expectRevert("chain id: '999999' not found in configuration");
config.set(999999, "some_key", uint256(123), true);
}

function testRevert_UnableToParseVariable() public {
// Create a fixture with an unparseable variable
// Create a temprorary fixture with an unparseable variable
string memory badParseConfig = "./test/fixtures/config_bad_parse.toml";
vm.writeFile(
badParseConfig,
Expand All @@ -252,15 +254,18 @@ contract ConfigTest is Test, Config {
)
);

vm.expectRevert(abi.encodeWithSelector(StdConfig.UnableToParseVariable.selector, "bad_value"));
vm.expectRevert("unable to parse variable: 'bad_value'");
new StdConfig(badParseConfig);

// Clean up the temporary file.
vm.removeFile(badParseConfig);
}
}

/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`,
/// as direct library calls are inlined by the compiler, causing call depth issues.
contract LibVariableTest is Test, Config {
using LibVariable for Variable;
LibVariableHelper helper;

function setUp() public {
Expand All @@ -273,41 +278,41 @@ contract LibVariableTest is Test, Config {
Variable memory notInit = config.get(1, "non_existent_key");

// Test single value types - should revert with NotInitialized
vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toBool(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toUint(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toAddress(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toBytes32(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toString(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toBytes(notInit);

// Test array types - should also revert with NotInitialized
vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toBoolArray(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toUintArray(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toAddressArray(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toBytes32Array(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toStringArray(notInit);

vm.expectRevert(LibVariable.NotInitialized.selector);
vm.expectRevert("variable not initialized");
helper.toBytesArray(notInit);
}

Expand All @@ -316,53 +321,55 @@ contract LibVariableTest is Test, Config {
Variable memory boolVar = config.get(1, "is_live");

// Try to coerce it to wrong single value types - should revert with TypeMismatch
vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256", "bool"));
vm.expectRevert("type mismatch: expected 'uint256', got 'bool'");
helper.toUint(boolVar);

vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "bool"));
vm.expectRevert("type mismatch: expected 'address', got 'bool'");
helper.toAddress(boolVar);

vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes32", "bool"));
vm.expectRevert("type mismatch: expected 'bytes32', got 'bool'");
helper.toBytes32(boolVar);

vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "string", "bool"));
vm.expectRevert("type mismatch: expected 'string', got 'bool'");
helper.toString(boolVar);

vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bytes", "bool"));
vm.expectRevert("type mismatch: expected 'bytes', got 'bool'");
helper.toBytes(boolVar);

// Get a uint variable
Variable memory uintVar = config.get(1, "number");

// Try to coerce it to wrong types
vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "uint256"));
vm.expectRevert("type mismatch: expected 'bool', got 'uint256'");
helper.toBool(uintVar);

vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "address", "uint256"));
vm.expectRevert("type mismatch: expected 'address', got 'uint256'");
helper.toAddress(uintVar);

// Get an array variable
Variable memory boolArrayVar = config.get(1, "bool_array");

// Try to coerce array to single value - should revert with TypeMismatch
vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool", "bool[]"));
vm.expectRevert("type mismatch: expected 'bool', got 'bool[]'");
helper.toBool(boolArrayVar);

// Try to coerce array to wrong array type
vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "uint256[]", "bool[]"));
vm.expectRevert("type mismatch: expected 'uint256[]', got 'bool[]'");
helper.toUintArray(boolArrayVar);

// Get a single value and try to coerce to array
Variable memory singleBoolVar = config.get(1, "is_live");

vm.expectRevert(abi.encodeWithSelector(LibVariable.TypeMismatch.selector, "bool[]", "bool"));
vm.expectRevert("type mismatch: expected 'bool[]', got 'bool'");
helper.toBoolArray(singleBoolVar);
}
}

/// @dev We must use an external helper contract to ensure proper call depth for `vm.expectRevert`,
/// as direct library calls are inlined by the compiler, causing call depth issues.
contract LibVariableHelper {
using LibVariable for Variable;

function toBool(Variable memory v) external pure returns (bool) {
return v.toBool();
}
Expand Down
Loading