Spaces:
Building
Building
| // SPDX-License-Identifier: MIT OR Apache-2.0 | |
| pragma solidity ^0.8.13; | |
| // Enable globally. | |
| using LibVariable for Variable global; | |
| struct Variable { | |
| Type ty; | |
| bytes data; | |
| } | |
| struct Type { | |
| TypeKind kind; | |
| bool isArray; | |
| } | |
| enum TypeKind { | |
| None, | |
| Bool, | |
| Address, | |
| Bytes32, | |
| Uint256, | |
| Int256, | |
| String, | |
| Bytes | |
| } | |
| /// @notice Library for type-safe coercion of the `Variable` struct to concrete types. | |
| /// | |
| /// @dev Ensures that when a `Variable` is cast to a concrete Solidity type, the operation is safe and the | |
| /// underlying type matches what is expected. | |
| /// Provides functions to check types, convert them to strings, and coerce `Variable` instances into | |
| /// both single values and arrays of various types. | |
| /// | |
| /// Usage example: | |
| /// ```solidity | |
| /// import {LibVariable} from "./LibVariable.sol"; | |
| /// | |
| /// contract MyContract { | |
| /// using LibVariable for Variable; | |
| /// StdConfig config; // Assume 'config' is an instance of `StdConfig` and has already been loaded. | |
| /// | |
| /// function readValues() public { | |
| /// // Retrieve a 'uint256' value from the config. | |
| /// uint256 myNumber = config.get("important_number").toUint256(); | |
| /// | |
| /// // Would revert with `TypeMismatch` as 'important_number' isn't a `uint256` in the config file. | |
| /// // string memory notANumber = config.get("important_number").toString(); | |
| /// | |
| /// // Retrieve a address array from the config. | |
| /// address[] memory admins = config.get("whitelisted_admins").toAddressArray(); | |
| /// } | |
| /// } | |
| /// ``` | |
| library LibVariable { | |
| error NotInitialized(); | |
| error TypeMismatch(string expected, string actual); | |
| error UnsafeCast(string message); | |
| // -- TYPE HELPERS ---------------------------------------------------- | |
| /// @notice Compares two Type instances for equality. | |
| function isEqual(Type memory self, Type memory other) internal pure returns (bool) { | |
| return self.kind == other.kind && self.isArray == other.isArray; | |
| } | |
| /// @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)); | |
| } | |
| } | |
| /// @notice Converts a Type struct to its full string representation (i.e. "uint256[]"). | |
| function toString(Type memory self) internal pure returns (string memory) { | |
| string memory tyStr = toString(self.kind); | |
| if (!self.isArray || self.kind == TypeKind.None) { | |
| return tyStr; | |
| } else { | |
| return string.concat(tyStr, "[]"); | |
| } | |
| } | |
| /// @dev Converts a `TypeKind` enum to its base string representation. | |
| function toString(TypeKind self) internal pure returns (string memory) { | |
| if (self == TypeKind.Bool) return "bool"; | |
| if (self == TypeKind.Address) return "address"; | |
| if (self == TypeKind.Bytes32) return "bytes32"; | |
| if (self == TypeKind.Uint256) return "uint256"; | |
| if (self == TypeKind.Int256) return "int256"; | |
| if (self == TypeKind.String) return "string"; | |
| if (self == TypeKind.Bytes) return "bytes"; | |
| return "none"; | |
| } | |
| /// @dev Converts a `TypeKind` enum to its base string representation. | |
| function toTomlKey(TypeKind self) internal pure returns (string memory) { | |
| if (self == TypeKind.Bool) return "bool"; | |
| if (self == TypeKind.Address) return "address"; | |
| if (self == TypeKind.Bytes32) return "bytes32"; | |
| if (self == TypeKind.Uint256) return "uint"; | |
| if (self == TypeKind.Int256) return "int"; | |
| if (self == TypeKind.String) return "string"; | |
| if (self == TypeKind.Bytes) return "bytes"; | |
| return "none"; | |
| } | |
| // -- VARIABLE HELPERS ---------------------------------------------------- | |
| /// @dev Checks if a `Variable` has been initialized and matches the expected type reverting if not. | |
| modifier check(Variable memory self, Type memory expected) { | |
| assertExists(self); | |
| assertEq(self.ty, expected); | |
| _; | |
| } | |
| /// @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(); | |
| } | |
| } | |
| // -- VARIABLE COERCION FUNCTIONS (SINGLE VALUES) -------------------------- | |
| /// @notice Coerces a `Variable` to a `bool` value. | |
| function toBool(Variable memory self) internal pure check(self, Type(TypeKind.Bool, false)) returns (bool) { | |
| return abi.decode(self.data, (bool)); | |
| } | |
| /// @notice Coerces a `Variable` to an `address` value. | |
| function toAddress(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Address, false)) | |
| returns (address) | |
| { | |
| return abi.decode(self.data, (address)); | |
| } | |
| /// @notice Coerces a `Variable` to a `bytes32` value. | |
| function toBytes32(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Bytes32, false)) | |
| returns (bytes32) | |
| { | |
| return abi.decode(self.data, (bytes32)); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint256` value. | |
| function toUint256(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Uint256, false)) | |
| returns (uint256) | |
| { | |
| return abi.decode(self.data, (uint256)); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint128` value, checking for overflow. | |
| function toUint128(Variable memory self) internal pure returns (uint128) { | |
| uint256 value = self.toUint256(); | |
| if (value > type(uint128).max) { | |
| revert UnsafeCast("value does not fit in 'uint128'"); | |
| } | |
| return uint128(value); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint64` value, checking for overflow. | |
| function toUint64(Variable memory self) internal pure returns (uint64) { | |
| uint256 value = self.toUint256(); | |
| if (value > type(uint64).max) { | |
| revert UnsafeCast("value does not fit in 'uint64'"); | |
| } | |
| return uint64(value); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint32` value, checking for overflow. | |
| function toUint32(Variable memory self) internal pure returns (uint32) { | |
| uint256 value = self.toUint256(); | |
| if (value > type(uint32).max) { | |
| revert UnsafeCast("value does not fit in 'uint32'"); | |
| } | |
| return uint32(value); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint16` value, checking for overflow. | |
| function toUint16(Variable memory self) internal pure returns (uint16) { | |
| uint256 value = self.toUint256(); | |
| if (value > type(uint16).max) { | |
| revert UnsafeCast("value does not fit in 'uint16'"); | |
| } | |
| return uint16(value); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint8` value, checking for overflow. | |
| function toUint8(Variable memory self) internal pure returns (uint8) { | |
| uint256 value = self.toUint256(); | |
| if (value > type(uint8).max) { | |
| revert UnsafeCast("value does not fit in 'uint8'"); | |
| } | |
| return uint8(value); | |
| } | |
| /// @notice Coerces a `Variable` to an `int256` value. | |
| function toInt256(Variable memory self) internal pure check(self, Type(TypeKind.Int256, false)) returns (int256) { | |
| return abi.decode(self.data, (int256)); | |
| } | |
| /// @notice Coerces a `Variable` to an `int128` value, checking for overflow/underflow. | |
| function toInt128(Variable memory self) internal pure returns (int128) { | |
| int256 value = self.toInt256(); | |
| if (value > type(int128).max || value < type(int128).min) { | |
| revert UnsafeCast("value does not fit in 'int128'"); | |
| } | |
| return int128(value); | |
| } | |
| /// @notice Coerces a `Variable` to an `int64` value, checking for overflow/underflow. | |
| function toInt64(Variable memory self) internal pure returns (int64) { | |
| int256 value = self.toInt256(); | |
| if (value > type(int64).max || value < type(int64).min) { | |
| revert UnsafeCast("value does not fit in 'int64'"); | |
| } | |
| return int64(value); | |
| } | |
| /// @notice Coerces a `Variable` to an `int32` value, checking for overflow/underflow. | |
| function toInt32(Variable memory self) internal pure returns (int32) { | |
| int256 value = self.toInt256(); | |
| if (value > type(int32).max || value < type(int32).min) { | |
| revert UnsafeCast("value does not fit in 'int32'"); | |
| } | |
| return int32(value); | |
| } | |
| /// @notice Coerces a `Variable` to an `int16` value, checking for overflow/underflow. | |
| function toInt16(Variable memory self) internal pure returns (int16) { | |
| int256 value = self.toInt256(); | |
| if (value > type(int16).max || value < type(int16).min) { | |
| revert UnsafeCast("value does not fit in 'int16'"); | |
| } | |
| return int16(value); | |
| } | |
| /// @notice Coerces a `Variable` to an `int8` value, checking for overflow/underflow. | |
| function toInt8(Variable memory self) internal pure returns (int8) { | |
| int256 value = self.toInt256(); | |
| if (value > type(int8).max || value < type(int8).min) { | |
| revert UnsafeCast("value does not fit in 'int8'"); | |
| } | |
| return int8(value); | |
| } | |
| /// @notice Coerces a `Variable` to a `string` value. | |
| function toString(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.String, false)) | |
| returns (string memory) | |
| { | |
| return abi.decode(self.data, (string)); | |
| } | |
| /// @notice Coerces a `Variable` to a `bytes` value. | |
| function toBytes(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Bytes, false)) | |
| returns (bytes memory) | |
| { | |
| return abi.decode(self.data, (bytes)); | |
| } | |
| // -- VARIABLE COERCION FUNCTIONS (ARRAYS) --------------------------------- | |
| /// @notice Coerces a `Variable` to a `bool` array. | |
| function toBoolArray(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Bool, true)) | |
| returns (bool memory) | |
| { | |
| return abi.decode(self.data, (bool)); | |
| } | |
| /// @notice Coerces a `Variable` to an `address` array. | |
| function toAddressArray(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Address, true)) | |
| returns (address memory) | |
| { | |
| return abi.decode(self.data, (address)); | |
| } | |
| /// @notice Coerces a `Variable` to a `bytes32` array. | |
| function toBytes32Array(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Bytes32, true)) | |
| returns (bytes32 memory) | |
| { | |
| return abi.decode(self.data, (bytes32)); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint256` array. | |
| function toUint256Array(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Uint256, true)) | |
| returns (uint256 memory) | |
| { | |
| return abi.decode(self.data, (uint256)); | |
| } | |
| /// @notice Coerces a `Variable` to a `uint128` array, checking for overflow. | |
| function toUint128Array(Variable memory self) internal pure returns (uint128 memory) { | |
| uint256 memory values = self.toUint256Array(); | |
| uint128 memory result = new uint128(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(uint128).max) { | |
| revert UnsafeCast("value in array does not fit in 'uint128'"); | |
| } | |
| resulti = uint128(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `uint64` array, checking for overflow. | |
| function toUint64Array(Variable memory self) internal pure returns (uint64 memory) { | |
| uint256 memory values = self.toUint256Array(); | |
| uint64 memory result = new uint64(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(uint64).max) { | |
| revert UnsafeCast("value in array does not fit in 'uint64'"); | |
| } | |
| resulti = uint64(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `uint32` array, checking for overflow. | |
| function toUint32Array(Variable memory self) internal pure returns (uint32 memory) { | |
| uint256 memory values = self.toUint256Array(); | |
| uint32 memory result = new uint32(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(uint32).max) { | |
| revert UnsafeCast("value in array does not fit in 'uint32'"); | |
| } | |
| resulti = uint32(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `uint16` array, checking for overflow. | |
| function toUint16Array(Variable memory self) internal pure returns (uint16 memory) { | |
| uint256 memory values = self.toUint256Array(); | |
| uint16 memory result = new uint16(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(uint16).max) { | |
| revert UnsafeCast("value in array does not fit in 'uint16'"); | |
| } | |
| resulti = uint16(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `uint8` array, checking for overflow. | |
| function toUint8Array(Variable memory self) internal pure returns (uint8 memory) { | |
| uint256 memory values = self.toUint256Array(); | |
| uint8 memory result = new uint8(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(uint8).max) { | |
| revert UnsafeCast("value in array does not fit in 'uint8'"); | |
| } | |
| resulti = uint8(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to an `int256` array. | |
| function toInt256Array(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Int256, true)) | |
| returns (int256 memory) | |
| { | |
| return abi.decode(self.data, (int256)); | |
| } | |
| /// @notice Coerces a `Variable` to a `int128` array, checking for overflow/underflow. | |
| function toInt128Array(Variable memory self) internal pure returns (int128 memory) { | |
| int256 memory values = self.toInt256Array(); | |
| int128 memory result = new int128(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(int128).max || valuesi < type(int128).min) { | |
| revert UnsafeCast("value in array does not fit in 'int128'"); | |
| } | |
| resulti = int128(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `int64` array, checking for overflow/underflow. | |
| function toInt64Array(Variable memory self) internal pure returns (int64 memory) { | |
| int256 memory values = self.toInt256Array(); | |
| int64 memory result = new int64(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(int64).max || valuesi < type(int64).min) { | |
| revert UnsafeCast("value in array does not fit in 'int64'"); | |
| } | |
| resulti = int64(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `int32` array, checking for overflow/underflow. | |
| function toInt32Array(Variable memory self) internal pure returns (int32 memory) { | |
| int256 memory values = self.toInt256Array(); | |
| int32 memory result = new int32(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(int32).max || valuesi < type(int32).min) { | |
| revert UnsafeCast("value in array does not fit in 'int32'"); | |
| } | |
| resulti = int32(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `int16` array, checking for overflow/underflow. | |
| function toInt16Array(Variable memory self) internal pure returns (int16 memory) { | |
| int256 memory values = self.toInt256Array(); | |
| int16 memory result = new int16(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(int16).max || valuesi < type(int16).min) { | |
| revert UnsafeCast("value in array does not fit in 'int16'"); | |
| } | |
| resulti = int16(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `int8` array, checking for overflow/underflow. | |
| function toInt8Array(Variable memory self) internal pure returns (int8 memory) { | |
| int256 memory values = self.toInt256Array(); | |
| int8 memory result = new int8(values.length); | |
| for (uint256 i = 0; i < values.length; i++) { | |
| if (valuesi > type(int8).max || valuesi < type(int8).min) { | |
| revert UnsafeCast("value in array does not fit in 'int8'"); | |
| } | |
| resulti = int8(valuesi); | |
| } | |
| return result; | |
| } | |
| /// @notice Coerces a `Variable` to a `string` array. | |
| function toStringArray(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.String, true)) | |
| returns (string memory) | |
| { | |
| return abi.decode(self.data, (string)); | |
| } | |
| /// @notice Coerces a `Variable` to a `bytes` array. | |
| function toBytesArray(Variable memory self) | |
| internal | |
| pure | |
| check(self, Type(TypeKind.Bytes, true)) | |
| returns (bytes memory) | |
| { | |
| return abi.decode(self.data, (bytes)); | |
| } | |
| } | |