mike dupont
init: retro-sync API server + viewer + 71 Bach tiles + catalog
1295969
// 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 (values[i] > type(uint128).max) {
revert UnsafeCast("value in array does not fit in 'uint128'");
}
result[i] = uint128(values[i]);
}
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 (values[i] > type(uint64).max) {
revert UnsafeCast("value in array does not fit in 'uint64'");
}
result[i] = uint64(values[i]);
}
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 (values[i] > type(uint32).max) {
revert UnsafeCast("value in array does not fit in 'uint32'");
}
result[i] = uint32(values[i]);
}
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 (values[i] > type(uint16).max) {
revert UnsafeCast("value in array does not fit in 'uint16'");
}
result[i] = uint16(values[i]);
}
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 (values[i] > type(uint8).max) {
revert UnsafeCast("value in array does not fit in 'uint8'");
}
result[i] = uint8(values[i]);
}
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 (values[i] > type(int128).max || values[i] < type(int128).min) {
revert UnsafeCast("value in array does not fit in 'int128'");
}
result[i] = int128(values[i]);
}
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 (values[i] > type(int64).max || values[i] < type(int64).min) {
revert UnsafeCast("value in array does not fit in 'int64'");
}
result[i] = int64(values[i]);
}
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 (values[i] > type(int32).max || values[i] < type(int32).min) {
revert UnsafeCast("value in array does not fit in 'int32'");
}
result[i] = int32(values[i]);
}
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 (values[i] > type(int16).max || values[i] < type(int16).min) {
revert UnsafeCast("value in array does not fit in 'int16'");
}
result[i] = int16(values[i]);
}
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 (values[i] > type(int8).max || values[i] < type(int8).min) {
revert UnsafeCast("value in array does not fit in 'int8'");
}
result[i] = int8(values[i]);
}
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[]));
}
}