Source Code
Overview
ETH Balance
0 ETH
More Info
ContractCreator
Multichain Info
N/A
Latest 1 from a total of 1 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Deploy Clone | 2310033 | 93 days ago | IN | 0 ETH | 0.00009257 |
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block |
From
|
To
|
|||
---|---|---|---|---|---|---|
2310033 | 93 days ago | Contract Creation | 0 ETH |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x1Ee49D60...128233bae The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
DepositCollectFactory
Compiler Version
v0.8.22+commit.4fc1097e
Optimization Enabled:
Yes with 20000 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {DepositCollect} from "./DepositCollect.sol"; import {VerifySignatureUtil as SigUtil} from "./libraries/VerifySignatureUtil.sol"; import {LibString} from "solady/utils/LibString.sol"; /// @title Deposit Collect Factory /// @author Prometheum Inc. /// @notice This contract is responsible for deploying the implementation and clone contracts for DepositCollect. Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Deposit-Collect-Contracts)</b> for more concept details. contract DepositCollectFactory { /// @notice The **DeployedAddr** event is emitted at the end of the [`deployClone()`](#deployclone) function /// @dev **addr** : address new deployed contract address (this is for getting return address from hardhat side since they are non-view functions) event DeployedAddr(address addr); /// @notice This function is for deploying clone contract with deployed implementation contract and salt /// @param _implementation address of the deposit collect implementation contract /// @param salt unique digest required to generate a unique contract address /// @return address of the newly deployed deposit collect clone contract function deployClone(address _implementation, bytes32 salt) public returns (address) { // Deploy minimal clone with CREAT2 address payable cloneDepositCollect = payable(Clones.cloneDeterministic(_implementation, salt)); DepositCollect(cloneDepositCollect).initClone( DepositCollect(payable(_implementation)).getCowsManagement() ); emit DeployedAddr(cloneDepositCollect); return cloneDepositCollect; } /// @notice This function is for deploying multiple clone contract with deployed implementation contract and a range of salts /// @param _implementation address of the deposit contract implementation contract /// @param _minSalt deploying multiple clone contracts starts with the minimum salt /// @param _maxSalt deploying multiple clone contracts ends with the maximum salt /// @return list of newly deployed deposit collect clone addresses function deployClones( address _implementation, uint256 _minSalt, uint256 _maxSalt ) external returns (address[] memory) { uint256 len = (_maxSalt - _minSalt) + 1; // In the local node test, it'll run out of gas when the len hit 449 require(len < 441, "over uplimit"); address[] memory deployedClones = new address[](len); for (uint256 i = 0; i < len; ) { deployedClones[i] = deployClone( _implementation, SigUtil.stringToBytes32(LibString.toString(_minSalt + i)) ); unchecked { ++i; } } return deployedClones; } /// @notice This function is for calculate address of clone contract with deployed implementation contract and salt /// @param _implementation address of the deposit collect implementation contract /// @param salt unique digest required to generate a unique contract address /// @return address of the predicted deposit collect clone function predictCloneAddr(address _implementation, bytes32 salt) external view returns (address) { // Computes the address of a clone deployed (include deployer: this factory contract) return Clones.predictDeterministicAddress(_implementation, salt); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol) pragma solidity ^0.8.20; import {Errors} from "../utils/Errors.sol"; /** * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for * deploying minimal proxy contracts, also known as "clones". * * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies * > a minimal bytecode implementation that delegates all calls to a known, fixed address. * * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the * deterministic method. */ library Clones { /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * * This function uses the create opcode, which should never revert. */ function clone(address implementation) internal returns (address instance) { return clone(implementation, 0); } /** * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency * to the new contract. * * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) * to always have enough balance for new deployments. Consider exposing this function under a payable method. */ function clone(address implementation, uint256 value) internal returns (address instance) { if (address(this).balance < value) { revert Errors.InsufficientBalance(address(this).balance, value); } /// @solidity memory-safe-assembly assembly { // Stores the bytecode after address mstore(0x20, 0x5af43d82803e903d91602b57fd5bf3) // implementation address mstore(0x11, implementation) // Packs the first 3 bytes of the `implementation` address with the bytecode before the address. mstore(0x00, or(shr(0x88, implementation), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) instance := create(value, 0x09, 0x37) } if (instance == address(0)) { revert Errors.FailedDeployment(); } } /** * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`. * * This function uses the create2 opcode and a `salt` to deterministically deploy * the clone. Using the same `implementation` and `salt` multiple time will revert, since * the clones cannot be deployed twice at the same address. */ function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) { return cloneDeterministic(implementation, salt, 0); } /** * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with * a `value` parameter to send native currency to the new contract. * * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory) * to always have enough balance for new deployments. Consider exposing this function under a payable method. */ function cloneDeterministic( address implementation, bytes32 salt, uint256 value ) internal returns (address instance) { if (address(this).balance < value) { revert Errors.InsufficientBalance(address(this).balance, value); } /// @solidity memory-safe-assembly assembly { // Stores the bytecode after address mstore(0x20, 0x5af43d82803e903d91602b57fd5bf3) // implementation address mstore(0x11, implementation) // Packs the first 3 bytes of the `implementation` address with the bytecode before the address. mstore(0x00, or(shr(0x88, implementation), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000)) instance := create2(value, 0x09, 0x37, salt) } if (instance == address(0)) { revert Errors.FailedDeployment(); } } /** * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. */ function predictDeterministicAddress( address implementation, bytes32 salt, address deployer ) internal pure returns (address predicted) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(add(ptr, 0x38), deployer) mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff) mstore(add(ptr, 0x14), implementation) mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73) mstore(add(ptr, 0x58), salt) mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37)) predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff) } } /** * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}. */ function predictDeterministicAddress( address implementation, bytes32 salt ) internal view returns (address predicted) { return predictDeterministicAddress(implementation, salt, address(this)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.20; /** * @dev Interface of the ERC-20 standard as defined in the ERC. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the value of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the value of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves a `value` amount of tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 value) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets a `value` amount of tokens as the allowance of `spender` over the * caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 value) external returns (bool); /** * @dev Moves a `value` amount of tokens from `from` to `to` using the * allowance mechanism. `value` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 value) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) pragma solidity ^0.8.20; /** * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. * * These functions can be used to verify that a message was signed by the holder * of the private keys of a given address. */ library ECDSA { enum RecoverError { NoError, InvalidSignature, InvalidSignatureLength, InvalidSignatureS } /** * @dev The signature derives the `address(0)`. */ error ECDSAInvalidSignature(); /** * @dev The signature has an invalid length. */ error ECDSAInvalidSignatureLength(uint256 length); /** * @dev The signature has an S value that is in the upper half order. */ error ECDSAInvalidSignatureS(bytes32 s); /** * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not * return address(0) without also returning an error description. Errors are documented using an enum (error type) * and a bytes32 providing additional information about the error. * * If no error is returned, then the address can be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. * * Documentation for signature generation: * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] */ function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) { if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. /// @solidity memory-safe-assembly assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } return tryRecover(hash, v, r, s); } else { return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); } } /** * @dev Returns the address that signed a hashed message (`hash`) with * `signature`. This address can then be used for verification purposes. * * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: * this function rejects them by requiring the `s` value to be in the lower * half order, and the `v` value to be either 27 or 28. * * IMPORTANT: `hash` _must_ be the result of a hash operation for the * verification to be secure: it is possible to craft signatures that * recover to arbitrary addresses for non-hashed data. A safe way to ensure * this is by receiving a hash of the original message (which may otherwise * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. */ function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. * * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures] */ function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) { unchecked { bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); // We do not check for an overflow here since the shift operation results in 0 or 1. uint8 v = uint8((uint256(vs) >> 255) + 27); return tryRecover(hash, v, r, s); } } /** * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. */ function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); _throwError(error, errorArg); return recovered; } /** * @dev Overload of {ECDSA-tryRecover} that receives the `v`, * `r` and `s` signature fields separately. */ function tryRecover( bytes32 hash, uint8 v, bytes32 r, bytes32 s ) internal pure returns (address, RecoverError, bytes32) { // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most // signatures from current libraries generate a unique signature with an s-value in the lower half order. // // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept // these malleable signatures as well. if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { return (address(0), RecoverError.InvalidSignatureS, s); } // If the signature is valid (and not malleable), return the signer address address signer = ecrecover(hash, v, r, s); if (signer == address(0)) { return (address(0), RecoverError.InvalidSignature, bytes32(0)); } return (signer, RecoverError.NoError, bytes32(0)); } /** * @dev Overload of {ECDSA-recover} that receives the `v`, * `r` and `s` signature fields separately. */ function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); _throwError(error, errorArg); return recovered; } /** * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. */ function _throwError(RecoverError error, bytes32 errorArg) private pure { if (error == RecoverError.NoError) { return; // no error: do nothing } else if (error == RecoverError.InvalidSignature) { revert ECDSAInvalidSignature(); } else if (error == RecoverError.InvalidSignatureLength) { revert ECDSAInvalidSignatureLength(uint256(errorArg)); } else if (error == RecoverError.InvalidSignatureS) { revert ECDSAInvalidSignatureS(errorArg); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol) pragma solidity ^0.8.20; import {Strings} from "../Strings.sol"; /** * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing. * * The library provides methods for generating a hash of a message that conforms to the * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712] * specifications. */ library MessageHashUtils { /** * @dev Returns the keccak256 digest of an ERC-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing a bytes32 `messageHash` with * `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with * keccak256, although any bytes32 value can be safely used because the final digest will * be re-hashed. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20) } } /** * @dev Returns the keccak256 digest of an ERC-191 signed data with version * `0x45` (`personal_sign` messages). * * The digest is calculated by prefixing an arbitrary `message` with * `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the * hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method. * * See {ECDSA-recover}. */ function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) { return keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message)); } /** * @dev Returns the keccak256 digest of an ERC-191 signed data with version * `0x00` (data with intended validator). * * The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended * `validator` address. Then hashing the result. * * See {ECDSA-recover}. */ function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) { return keccak256(abi.encodePacked(hex"19_00", validator, data)); } /** * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`). * * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with * `\x19\x01` and hashing the result. It corresponds to the hash signed by the * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712. * * See {ECDSA-recover}. */ function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) { /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) mstore(ptr, hex"19_01") mstore(add(ptr, 0x02), domainSeparator) mstore(add(ptr, 0x22), structHash) digest := keccak256(ptr, 0x42) } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @dev Collection of common custom errors used in multiple contracts * * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library. * It is recommended to avoid relying on the error API for critical functionality. */ library Errors { /** * @dev The ETH balance of the account is not enough to perform the operation. */ error InsufficientBalance(uint256 balance, uint256 needed); /** * @dev A call to an address target failed. The target may have reverted. */ error FailedCall(); /** * @dev The deployment failed. */ error FailedDeployment(); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol) pragma solidity ^0.8.20; import {Panic} from "../Panic.sol"; import {SafeCast} from "./SafeCast.sol"; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { enum Rounding { Floor, // Toward negative infinity Ceil, // Toward positive infinity Trunc, // Toward zero Expand // Away from zero } /** * @dev Returns the addition of two unsigned integers, with an success flag (no overflow). */ function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { uint256 c = a + b; if (c < a) return (false, 0); return (true, c); } } /** * @dev Returns the subtraction of two unsigned integers, with an success flag (no overflow). */ function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b > a) return (false, 0); return (true, a - b); } } /** * @dev Returns the multiplication of two unsigned integers, with an success flag (no overflow). */ function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) return (true, 0); uint256 c = a * b; if (c / a != b) return (false, 0); return (true, c); } } /** * @dev Returns the division of two unsigned integers, with a success flag (no division by zero). */ function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b == 0) return (false, 0); return (true, a / b); } } /** * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). */ function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) { unchecked { if (b == 0) return (false, 0); return (true, a % b); } } /** * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. * * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute * one branch when needed, making this function more expensive. */ function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) { unchecked { // branchless ternary works because: // b ^ (a ^ b) == a // b ^ 0 == b return b ^ ((a ^ b) * SafeCast.toUint(condition)); } } /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return ternary(a > b, a, b); } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return ternary(a < b, a, b); } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; } /** * @dev Returns the ceiling of the division of two numbers. * * This differs from standard division with `/` in that it rounds towards infinity instead * of rounding towards zero. */ function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) { if (b == 0) { // Guarantee the same behavior as in a regular Solidity division. Panic.panic(Panic.DIVISION_BY_ZERO); } // The following calculation ensures accurate ceiling division without overflow. // Since a is non-zero, (a - 1) / b will not overflow. // The largest possible result occurs when (a - 1) / b is type(uint256).max, // but the largest value we can obtain is type(uint256).max - 1, which happens // when a = type(uint256).max and b = 1. unchecked { return SafeCast.toUint(a > 0) * ((a - 1) / b + 1); } } /** * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or * denominator == 0. * * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by * Uniswap Labs also under MIT license. */ function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) { unchecked { // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256 // variables such that product = prod1 * 2²⁵⁶ + prod0. uint256 prod0 = x * y; // Least significant 256 bits of the product uint256 prod1; // Most significant 256 bits of the product assembly { let mm := mulmod(x, y, not(0)) prod1 := sub(sub(mm, prod0), lt(mm, prod0)) } // Handle non-overflow cases, 256 by 256 division. if (prod1 == 0) { // Solidity will revert if denominator == 0, unlike the div opcode on its own. // The surrounding unchecked block does not change this fact. // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic. return prod0 / denominator; } // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0. if (denominator <= prod1) { Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW)); } /////////////////////////////////////////////// // 512 by 256 division. /////////////////////////////////////////////// // Make division exact by subtracting the remainder from [prod1 prod0]. uint256 remainder; assembly { // Compute remainder using mulmod. remainder := mulmod(x, y, denominator) // Subtract 256 bit number from 512 bit number. prod1 := sub(prod1, gt(remainder, prod0)) prod0 := sub(prod0, remainder) } // Factor powers of two out of denominator and compute largest power of two divisor of denominator. // Always >= 1. See https://cs.stackexchange.com/q/138556/92363. uint256 twos = denominator & (0 - denominator); assembly { // Divide denominator by twos. denominator := div(denominator, twos) // Divide [prod1 prod0] by twos. prod0 := div(prod0, twos) // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one. twos := add(div(sub(0, twos), twos), 1) } // Shift in bits from prod1 into prod0. prod0 |= prod1 * twos; // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for // four bits. That is, denominator * inv ≡ 1 mod 2⁴. uint256 inverse = (3 * denominator) ^ 2; // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also // works in modular arithmetic, doubling the correct bits in each step. inverse *= 2 - denominator * inverse; // inverse mod 2⁸ inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶ inverse *= 2 - denominator * inverse; // inverse mod 2³² inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴ inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸ inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶ // Because the division is now exact we can divide by multiplying with the modular inverse of denominator. // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and prod1 // is no longer required. result = prod0 * inverse; return result; } } /** * @dev Calculates x * y / denominator with full precision, following the selected rounding direction. */ function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) { return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0); } /** * @dev Calculate the modular multiplicative inverse of a number in Z/nZ. * * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, expect 0. * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. * * If the input value is not inversible, 0 is returned. * * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Ferma's little theorem and get the * inverse using `Math.modExp(a, n - 2, n)`. */ function invMod(uint256 a, uint256 n) internal pure returns (uint256) { unchecked { if (n == 0) return 0; // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version) // Used to compute integers x and y such that: ax + ny = gcd(a, n). // When the gcd is 1, then the inverse of a modulo n exists and it's x. // ax + ny = 1 // ax = 1 + (-y)n // ax ≡ 1 (mod n) # x is the inverse of a modulo n // If the remainder is 0 the gcd is n right away. uint256 remainder = a % n; uint256 gcd = n; // Therefore the initial coefficients are: // ax + ny = gcd(a, n) = n // 0a + 1n = n int256 x = 0; int256 y = 1; while (remainder != 0) { uint256 quotient = gcd / remainder; (gcd, remainder) = ( // The old remainder is the next gcd to try. remainder, // Compute the next remainder. // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd // where gcd is at most n (capped to type(uint256).max) gcd - remainder * quotient ); (x, y) = ( // Increment the coefficient of a. y, // Decrement the coefficient of n. // Can overflow, but the result is casted to uint256 so that the // next value of y is "wrapped around" to a value between 0 and n - 1. x - y * int256(quotient) ); } if (gcd != 1) return 0; // No inverse exists. return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative. } } /** * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) * * Requirements: * - modulus can't be zero * - underlying staticcall to precompile must succeed * * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make * sure the chain you're using it on supports the precompiled contract for modular exponentiation * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, * the underlying function will succeed given the lack of a revert, but the result may be incorrectly * interpreted as 0. */ function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) { (bool success, uint256 result) = tryModExp(b, e, m); if (!success) { Panic.panic(Panic.DIVISION_BY_ZERO); } return result; } /** * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). * It includes a success flag indicating if the operation succeeded. Operation will be marked has failed if trying * to operate modulo 0 or if the underlying precompile reverted. * * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack * of a revert, but the result may be incorrectly interpreted as 0. */ function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) { if (m == 0) return (false, 0); /// @solidity memory-safe-assembly assembly { let ptr := mload(0x40) // | Offset | Content | Content (Hex) | // |-----------|------------|--------------------------------------------------------------------| // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 | // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 | // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 | // | 0x60:0x7f | value of b | 0x<.............................................................b> | // | 0x80:0x9f | value of e | 0x<.............................................................e> | // | 0xa0:0xbf | value of m | 0x<.............................................................m> | mstore(ptr, 0x20) mstore(add(ptr, 0x20), 0x20) mstore(add(ptr, 0x40), 0x20) mstore(add(ptr, 0x60), b) mstore(add(ptr, 0x80), e) mstore(add(ptr, 0xa0), m) // Given the result < m, it's guaranteed to fit in 32 bytes, // so we can use the memory scratch space located at offset 0. success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20) result := mload(0x00) } } /** * @dev Variant of {modExp} that supports inputs of arbitrary length. */ function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) { (bool success, bytes memory result) = tryModExp(b, e, m); if (!success) { Panic.panic(Panic.DIVISION_BY_ZERO); } return result; } /** * @dev Variant of {tryModExp} that supports inputs of arbitrary length. */ function tryModExp( bytes memory b, bytes memory e, bytes memory m ) internal view returns (bool success, bytes memory result) { if (_zeroBytes(m)) return (false, new bytes(0)); uint256 mLen = m.length; // Encode call args in result and move the free memory pointer result = abi.encodePacked(b.length, e.length, mLen, b, e, m); /// @solidity memory-safe-assembly assembly { let dataPtr := add(result, 0x20) // Write result on top of args to avoid allocating extra memory. success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen) // Overwrite the length. // result.length > returndatasize() is guaranteed because returndatasize() == m.length mstore(result, mLen) // Set the memory pointer after the returned data. mstore(0x40, add(dataPtr, mLen)) } } /** * @dev Returns whether the provided byte array is zero. */ function _zeroBytes(bytes memory byteArray) private pure returns (bool) { for (uint256 i = 0; i < byteArray.length; ++i) { if (byteArray[i] != 0) { return false; } } return true; } /** * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded * towards zero. * * This method is based on Newton's method for computing square roots; the algorithm is restricted to only * using integer operations. */ function sqrt(uint256 a) internal pure returns (uint256) { unchecked { // Take care of easy edge cases when a == 0 or a == 1 if (a <= 1) { return a; } // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between // the current value as `ε_n = | x_n - sqrt(a) |`. // // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is // bigger than any uint256. // // By noticing that // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)` // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar // to the msb function. uint256 aa = a; uint256 xn = 1; if (aa >= (1 << 128)) { aa >>= 128; xn <<= 64; } if (aa >= (1 << 64)) { aa >>= 64; xn <<= 32; } if (aa >= (1 << 32)) { aa >>= 32; xn <<= 16; } if (aa >= (1 << 16)) { aa >>= 16; xn <<= 8; } if (aa >= (1 << 8)) { aa >>= 8; xn <<= 4; } if (aa >= (1 << 4)) { aa >>= 4; xn <<= 2; } if (aa >= (1 << 2)) { xn <<= 1; } // We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1). // // We can refine our estimation by noticing that the middle of that interval minimizes the error. // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2). // This is going to be our x_0 (and ε_0) xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2) // From here, Newton's method give us: // x_{n+1} = (x_n + a / x_n) / 2 // // One should note that: // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a // = ((x_n² + a) / (2 * x_n))² - a // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²) // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²) // = (x_n² - a)² / (2 * x_n)² // = ((x_n² - a) / (2 * x_n))² // ≥ 0 // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n // // This gives us the proof of quadratic convergence of the sequence: // ε_{n+1} = | x_{n+1} - sqrt(a) | // = | (x_n + a / x_n) / 2 - sqrt(a) | // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) | // = | (x_n - sqrt(a))² / (2 * x_n) | // = | ε_n² / (2 * x_n) | // = ε_n² / | (2 * x_n) | // // For the first iteration, we have a special case where x_0 is known: // ε_1 = ε_0² / | (2 * x_0) | // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2))) // ≤ 2**(2*e-4) / (3 * 2**(e-1)) // ≤ 2**(e-3) / 3 // ≤ 2**(e-3-log2(3)) // ≤ 2**(e-4.5) // // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n: // ε_{n+1} = ε_n² / | (2 * x_n) | // ≤ (2**(e-k))² / (2 * 2**(e-1)) // ≤ 2**(2*e-2*k) / 2**e // ≤ 2**(e-2*k) xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5 xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9 xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18 xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36 xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72 // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either // sqrt(a) or sqrt(a) + 1. return xn - SafeCast.toUint(xn > a / xn); } } /** * @dev Calculates sqrt(a), following the selected rounding direction. */ function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = sqrt(a); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a); } } /** * @dev Return the log in base 2 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log2(uint256 value) internal pure returns (uint256) { uint256 result = 0; uint256 exp; unchecked { exp = 128 * SafeCast.toUint(value > (1 << 128) - 1); value >>= exp; result += exp; exp = 64 * SafeCast.toUint(value > (1 << 64) - 1); value >>= exp; result += exp; exp = 32 * SafeCast.toUint(value > (1 << 32) - 1); value >>= exp; result += exp; exp = 16 * SafeCast.toUint(value > (1 << 16) - 1); value >>= exp; result += exp; exp = 8 * SafeCast.toUint(value > (1 << 8) - 1); value >>= exp; result += exp; exp = 4 * SafeCast.toUint(value > (1 << 4) - 1); value >>= exp; result += exp; exp = 2 * SafeCast.toUint(value > (1 << 2) - 1); value >>= exp; result += exp; result += SafeCast.toUint(value > 1); } return result; } /** * @dev Return the log in base 2, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log2(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log2(value); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value); } } /** * @dev Return the log in base 10 of a positive value rounded towards zero. * Returns 0 if given 0. */ function log10(uint256 value) internal pure returns (uint256) { uint256 result = 0; unchecked { if (value >= 10 ** 64) { value /= 10 ** 64; result += 64; } if (value >= 10 ** 32) { value /= 10 ** 32; result += 32; } if (value >= 10 ** 16) { value /= 10 ** 16; result += 16; } if (value >= 10 ** 8) { value /= 10 ** 8; result += 8; } if (value >= 10 ** 4) { value /= 10 ** 4; result += 4; } if (value >= 10 ** 2) { value /= 10 ** 2; result += 2; } if (value >= 10 ** 1) { result += 1; } } return result; } /** * @dev Return the log in base 10, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log10(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log10(value); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value); } } /** * @dev Return the log in base 256 of a positive value rounded towards zero. * Returns 0 if given 0. * * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. */ function log256(uint256 value) internal pure returns (uint256) { uint256 result = 0; uint256 isGt; unchecked { isGt = SafeCast.toUint(value > (1 << 128) - 1); value >>= isGt * 128; result += isGt * 16; isGt = SafeCast.toUint(value > (1 << 64) - 1); value >>= isGt * 64; result += isGt * 8; isGt = SafeCast.toUint(value > (1 << 32) - 1); value >>= isGt * 32; result += isGt * 4; isGt = SafeCast.toUint(value > (1 << 16) - 1); value >>= isGt * 16; result += isGt * 2; result += SafeCast.toUint(value > (1 << 8) - 1); } return result; } /** * @dev Return the log in base 256, following the selected rounding direction, of a positive value. * Returns 0 if given 0. */ function log256(uint256 value, Rounding rounding) internal pure returns (uint256) { unchecked { uint256 result = log256(value); return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value); } } /** * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers. */ function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) { return uint8(rounding) % 2 == 1; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol) // This file was procedurally generated from scripts/generate/templates/SafeCast.js. pragma solidity ^0.8.20; /** * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow * checks. * * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can * easily result in undesired exploitation or bugs, since developers usually * assume that overflows raise errors. `SafeCast` restores this intuition by * reverting the transaction when such an operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeCast { /** * @dev Value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value); /** * @dev An int value doesn't fit in an uint of `bits` size. */ error SafeCastOverflowedIntToUint(int256 value); /** * @dev Value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedIntDowncast(uint8 bits, int256 value); /** * @dev An uint value doesn't fit in an int of `bits` size. */ error SafeCastOverflowedUintToInt(uint256 value); /** * @dev Returns the downcasted uint248 from uint256, reverting on * overflow (when the input is greater than largest uint248). * * Counterpart to Solidity's `uint248` operator. * * Requirements: * * - input must fit into 248 bits */ function toUint248(uint256 value) internal pure returns (uint248) { if (value > type(uint248).max) { revert SafeCastOverflowedUintDowncast(248, value); } return uint248(value); } /** * @dev Returns the downcasted uint240 from uint256, reverting on * overflow (when the input is greater than largest uint240). * * Counterpart to Solidity's `uint240` operator. * * Requirements: * * - input must fit into 240 bits */ function toUint240(uint256 value) internal pure returns (uint240) { if (value > type(uint240).max) { revert SafeCastOverflowedUintDowncast(240, value); } return uint240(value); } /** * @dev Returns the downcasted uint232 from uint256, reverting on * overflow (when the input is greater than largest uint232). * * Counterpart to Solidity's `uint232` operator. * * Requirements: * * - input must fit into 232 bits */ function toUint232(uint256 value) internal pure returns (uint232) { if (value > type(uint232).max) { revert SafeCastOverflowedUintDowncast(232, value); } return uint232(value); } /** * @dev Returns the downcasted uint224 from uint256, reverting on * overflow (when the input is greater than largest uint224). * * Counterpart to Solidity's `uint224` operator. * * Requirements: * * - input must fit into 224 bits */ function toUint224(uint256 value) internal pure returns (uint224) { if (value > type(uint224).max) { revert SafeCastOverflowedUintDowncast(224, value); } return uint224(value); } /** * @dev Returns the downcasted uint216 from uint256, reverting on * overflow (when the input is greater than largest uint216). * * Counterpart to Solidity's `uint216` operator. * * Requirements: * * - input must fit into 216 bits */ function toUint216(uint256 value) internal pure returns (uint216) { if (value > type(uint216).max) { revert SafeCastOverflowedUintDowncast(216, value); } return uint216(value); } /** * @dev Returns the downcasted uint208 from uint256, reverting on * overflow (when the input is greater than largest uint208). * * Counterpart to Solidity's `uint208` operator. * * Requirements: * * - input must fit into 208 bits */ function toUint208(uint256 value) internal pure returns (uint208) { if (value > type(uint208).max) { revert SafeCastOverflowedUintDowncast(208, value); } return uint208(value); } /** * @dev Returns the downcasted uint200 from uint256, reverting on * overflow (when the input is greater than largest uint200). * * Counterpart to Solidity's `uint200` operator. * * Requirements: * * - input must fit into 200 bits */ function toUint200(uint256 value) internal pure returns (uint200) { if (value > type(uint200).max) { revert SafeCastOverflowedUintDowncast(200, value); } return uint200(value); } /** * @dev Returns the downcasted uint192 from uint256, reverting on * overflow (when the input is greater than largest uint192). * * Counterpart to Solidity's `uint192` operator. * * Requirements: * * - input must fit into 192 bits */ function toUint192(uint256 value) internal pure returns (uint192) { if (value > type(uint192).max) { revert SafeCastOverflowedUintDowncast(192, value); } return uint192(value); } /** * @dev Returns the downcasted uint184 from uint256, reverting on * overflow (when the input is greater than largest uint184). * * Counterpart to Solidity's `uint184` operator. * * Requirements: * * - input must fit into 184 bits */ function toUint184(uint256 value) internal pure returns (uint184) { if (value > type(uint184).max) { revert SafeCastOverflowedUintDowncast(184, value); } return uint184(value); } /** * @dev Returns the downcasted uint176 from uint256, reverting on * overflow (when the input is greater than largest uint176). * * Counterpart to Solidity's `uint176` operator. * * Requirements: * * - input must fit into 176 bits */ function toUint176(uint256 value) internal pure returns (uint176) { if (value > type(uint176).max) { revert SafeCastOverflowedUintDowncast(176, value); } return uint176(value); } /** * @dev Returns the downcasted uint168 from uint256, reverting on * overflow (when the input is greater than largest uint168). * * Counterpart to Solidity's `uint168` operator. * * Requirements: * * - input must fit into 168 bits */ function toUint168(uint256 value) internal pure returns (uint168) { if (value > type(uint168).max) { revert SafeCastOverflowedUintDowncast(168, value); } return uint168(value); } /** * @dev Returns the downcasted uint160 from uint256, reverting on * overflow (when the input is greater than largest uint160). * * Counterpart to Solidity's `uint160` operator. * * Requirements: * * - input must fit into 160 bits */ function toUint160(uint256 value) internal pure returns (uint160) { if (value > type(uint160).max) { revert SafeCastOverflowedUintDowncast(160, value); } return uint160(value); } /** * @dev Returns the downcasted uint152 from uint256, reverting on * overflow (when the input is greater than largest uint152). * * Counterpart to Solidity's `uint152` operator. * * Requirements: * * - input must fit into 152 bits */ function toUint152(uint256 value) internal pure returns (uint152) { if (value > type(uint152).max) { revert SafeCastOverflowedUintDowncast(152, value); } return uint152(value); } /** * @dev Returns the downcasted uint144 from uint256, reverting on * overflow (when the input is greater than largest uint144). * * Counterpart to Solidity's `uint144` operator. * * Requirements: * * - input must fit into 144 bits */ function toUint144(uint256 value) internal pure returns (uint144) { if (value > type(uint144).max) { revert SafeCastOverflowedUintDowncast(144, value); } return uint144(value); } /** * @dev Returns the downcasted uint136 from uint256, reverting on * overflow (when the input is greater than largest uint136). * * Counterpart to Solidity's `uint136` operator. * * Requirements: * * - input must fit into 136 bits */ function toUint136(uint256 value) internal pure returns (uint136) { if (value > type(uint136).max) { revert SafeCastOverflowedUintDowncast(136, value); } return uint136(value); } /** * @dev Returns the downcasted uint128 from uint256, reverting on * overflow (when the input is greater than largest uint128). * * Counterpart to Solidity's `uint128` operator. * * Requirements: * * - input must fit into 128 bits */ function toUint128(uint256 value) internal pure returns (uint128) { if (value > type(uint128).max) { revert SafeCastOverflowedUintDowncast(128, value); } return uint128(value); } /** * @dev Returns the downcasted uint120 from uint256, reverting on * overflow (when the input is greater than largest uint120). * * Counterpart to Solidity's `uint120` operator. * * Requirements: * * - input must fit into 120 bits */ function toUint120(uint256 value) internal pure returns (uint120) { if (value > type(uint120).max) { revert SafeCastOverflowedUintDowncast(120, value); } return uint120(value); } /** * @dev Returns the downcasted uint112 from uint256, reverting on * overflow (when the input is greater than largest uint112). * * Counterpart to Solidity's `uint112` operator. * * Requirements: * * - input must fit into 112 bits */ function toUint112(uint256 value) internal pure returns (uint112) { if (value > type(uint112).max) { revert SafeCastOverflowedUintDowncast(112, value); } return uint112(value); } /** * @dev Returns the downcasted uint104 from uint256, reverting on * overflow (when the input is greater than largest uint104). * * Counterpart to Solidity's `uint104` operator. * * Requirements: * * - input must fit into 104 bits */ function toUint104(uint256 value) internal pure returns (uint104) { if (value > type(uint104).max) { revert SafeCastOverflowedUintDowncast(104, value); } return uint104(value); } /** * @dev Returns the downcasted uint96 from uint256, reverting on * overflow (when the input is greater than largest uint96). * * Counterpart to Solidity's `uint96` operator. * * Requirements: * * - input must fit into 96 bits */ function toUint96(uint256 value) internal pure returns (uint96) { if (value > type(uint96).max) { revert SafeCastOverflowedUintDowncast(96, value); } return uint96(value); } /** * @dev Returns the downcasted uint88 from uint256, reverting on * overflow (when the input is greater than largest uint88). * * Counterpart to Solidity's `uint88` operator. * * Requirements: * * - input must fit into 88 bits */ function toUint88(uint256 value) internal pure returns (uint88) { if (value > type(uint88).max) { revert SafeCastOverflowedUintDowncast(88, value); } return uint88(value); } /** * @dev Returns the downcasted uint80 from uint256, reverting on * overflow (when the input is greater than largest uint80). * * Counterpart to Solidity's `uint80` operator. * * Requirements: * * - input must fit into 80 bits */ function toUint80(uint256 value) internal pure returns (uint80) { if (value > type(uint80).max) { revert SafeCastOverflowedUintDowncast(80, value); } return uint80(value); } /** * @dev Returns the downcasted uint72 from uint256, reverting on * overflow (when the input is greater than largest uint72). * * Counterpart to Solidity's `uint72` operator. * * Requirements: * * - input must fit into 72 bits */ function toUint72(uint256 value) internal pure returns (uint72) { if (value > type(uint72).max) { revert SafeCastOverflowedUintDowncast(72, value); } return uint72(value); } /** * @dev Returns the downcasted uint64 from uint256, reverting on * overflow (when the input is greater than largest uint64). * * Counterpart to Solidity's `uint64` operator. * * Requirements: * * - input must fit into 64 bits */ function toUint64(uint256 value) internal pure returns (uint64) { if (value > type(uint64).max) { revert SafeCastOverflowedUintDowncast(64, value); } return uint64(value); } /** * @dev Returns the downcasted uint56 from uint256, reverting on * overflow (when the input is greater than largest uint56). * * Counterpart to Solidity's `uint56` operator. * * Requirements: * * - input must fit into 56 bits */ function toUint56(uint256 value) internal pure returns (uint56) { if (value > type(uint56).max) { revert SafeCastOverflowedUintDowncast(56, value); } return uint56(value); } /** * @dev Returns the downcasted uint48 from uint256, reverting on * overflow (when the input is greater than largest uint48). * * Counterpart to Solidity's `uint48` operator. * * Requirements: * * - input must fit into 48 bits */ function toUint48(uint256 value) internal pure returns (uint48) { if (value > type(uint48).max) { revert SafeCastOverflowedUintDowncast(48, value); } return uint48(value); } /** * @dev Returns the downcasted uint40 from uint256, reverting on * overflow (when the input is greater than largest uint40). * * Counterpart to Solidity's `uint40` operator. * * Requirements: * * - input must fit into 40 bits */ function toUint40(uint256 value) internal pure returns (uint40) { if (value > type(uint40).max) { revert SafeCastOverflowedUintDowncast(40, value); } return uint40(value); } /** * @dev Returns the downcasted uint32 from uint256, reverting on * overflow (when the input is greater than largest uint32). * * Counterpart to Solidity's `uint32` operator. * * Requirements: * * - input must fit into 32 bits */ function toUint32(uint256 value) internal pure returns (uint32) { if (value > type(uint32).max) { revert SafeCastOverflowedUintDowncast(32, value); } return uint32(value); } /** * @dev Returns the downcasted uint24 from uint256, reverting on * overflow (when the input is greater than largest uint24). * * Counterpart to Solidity's `uint24` operator. * * Requirements: * * - input must fit into 24 bits */ function toUint24(uint256 value) internal pure returns (uint24) { if (value > type(uint24).max) { revert SafeCastOverflowedUintDowncast(24, value); } return uint24(value); } /** * @dev Returns the downcasted uint16 from uint256, reverting on * overflow (when the input is greater than largest uint16). * * Counterpart to Solidity's `uint16` operator. * * Requirements: * * - input must fit into 16 bits */ function toUint16(uint256 value) internal pure returns (uint16) { if (value > type(uint16).max) { revert SafeCastOverflowedUintDowncast(16, value); } return uint16(value); } /** * @dev Returns the downcasted uint8 from uint256, reverting on * overflow (when the input is greater than largest uint8). * * Counterpart to Solidity's `uint8` operator. * * Requirements: * * - input must fit into 8 bits */ function toUint8(uint256 value) internal pure returns (uint8) { if (value > type(uint8).max) { revert SafeCastOverflowedUintDowncast(8, value); } return uint8(value); } /** * @dev Converts a signed int256 into an unsigned uint256. * * Requirements: * * - input must be greater than or equal to 0. */ function toUint256(int256 value) internal pure returns (uint256) { if (value < 0) { revert SafeCastOverflowedIntToUint(value); } return uint256(value); } /** * @dev Returns the downcasted int248 from int256, reverting on * overflow (when the input is less than smallest int248 or * greater than largest int248). * * Counterpart to Solidity's `int248` operator. * * Requirements: * * - input must fit into 248 bits */ function toInt248(int256 value) internal pure returns (int248 downcasted) { downcasted = int248(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(248, value); } } /** * @dev Returns the downcasted int240 from int256, reverting on * overflow (when the input is less than smallest int240 or * greater than largest int240). * * Counterpart to Solidity's `int240` operator. * * Requirements: * * - input must fit into 240 bits */ function toInt240(int256 value) internal pure returns (int240 downcasted) { downcasted = int240(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(240, value); } } /** * @dev Returns the downcasted int232 from int256, reverting on * overflow (when the input is less than smallest int232 or * greater than largest int232). * * Counterpart to Solidity's `int232` operator. * * Requirements: * * - input must fit into 232 bits */ function toInt232(int256 value) internal pure returns (int232 downcasted) { downcasted = int232(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(232, value); } } /** * @dev Returns the downcasted int224 from int256, reverting on * overflow (when the input is less than smallest int224 or * greater than largest int224). * * Counterpart to Solidity's `int224` operator. * * Requirements: * * - input must fit into 224 bits */ function toInt224(int256 value) internal pure returns (int224 downcasted) { downcasted = int224(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(224, value); } } /** * @dev Returns the downcasted int216 from int256, reverting on * overflow (when the input is less than smallest int216 or * greater than largest int216). * * Counterpart to Solidity's `int216` operator. * * Requirements: * * - input must fit into 216 bits */ function toInt216(int256 value) internal pure returns (int216 downcasted) { downcasted = int216(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(216, value); } } /** * @dev Returns the downcasted int208 from int256, reverting on * overflow (when the input is less than smallest int208 or * greater than largest int208). * * Counterpart to Solidity's `int208` operator. * * Requirements: * * - input must fit into 208 bits */ function toInt208(int256 value) internal pure returns (int208 downcasted) { downcasted = int208(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(208, value); } } /** * @dev Returns the downcasted int200 from int256, reverting on * overflow (when the input is less than smallest int200 or * greater than largest int200). * * Counterpart to Solidity's `int200` operator. * * Requirements: * * - input must fit into 200 bits */ function toInt200(int256 value) internal pure returns (int200 downcasted) { downcasted = int200(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(200, value); } } /** * @dev Returns the downcasted int192 from int256, reverting on * overflow (when the input is less than smallest int192 or * greater than largest int192). * * Counterpart to Solidity's `int192` operator. * * Requirements: * * - input must fit into 192 bits */ function toInt192(int256 value) internal pure returns (int192 downcasted) { downcasted = int192(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(192, value); } } /** * @dev Returns the downcasted int184 from int256, reverting on * overflow (when the input is less than smallest int184 or * greater than largest int184). * * Counterpart to Solidity's `int184` operator. * * Requirements: * * - input must fit into 184 bits */ function toInt184(int256 value) internal pure returns (int184 downcasted) { downcasted = int184(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(184, value); } } /** * @dev Returns the downcasted int176 from int256, reverting on * overflow (when the input is less than smallest int176 or * greater than largest int176). * * Counterpart to Solidity's `int176` operator. * * Requirements: * * - input must fit into 176 bits */ function toInt176(int256 value) internal pure returns (int176 downcasted) { downcasted = int176(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(176, value); } } /** * @dev Returns the downcasted int168 from int256, reverting on * overflow (when the input is less than smallest int168 or * greater than largest int168). * * Counterpart to Solidity's `int168` operator. * * Requirements: * * - input must fit into 168 bits */ function toInt168(int256 value) internal pure returns (int168 downcasted) { downcasted = int168(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(168, value); } } /** * @dev Returns the downcasted int160 from int256, reverting on * overflow (when the input is less than smallest int160 or * greater than largest int160). * * Counterpart to Solidity's `int160` operator. * * Requirements: * * - input must fit into 160 bits */ function toInt160(int256 value) internal pure returns (int160 downcasted) { downcasted = int160(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(160, value); } } /** * @dev Returns the downcasted int152 from int256, reverting on * overflow (when the input is less than smallest int152 or * greater than largest int152). * * Counterpart to Solidity's `int152` operator. * * Requirements: * * - input must fit into 152 bits */ function toInt152(int256 value) internal pure returns (int152 downcasted) { downcasted = int152(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(152, value); } } /** * @dev Returns the downcasted int144 from int256, reverting on * overflow (when the input is less than smallest int144 or * greater than largest int144). * * Counterpart to Solidity's `int144` operator. * * Requirements: * * - input must fit into 144 bits */ function toInt144(int256 value) internal pure returns (int144 downcasted) { downcasted = int144(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(144, value); } } /** * @dev Returns the downcasted int136 from int256, reverting on * overflow (when the input is less than smallest int136 or * greater than largest int136). * * Counterpart to Solidity's `int136` operator. * * Requirements: * * - input must fit into 136 bits */ function toInt136(int256 value) internal pure returns (int136 downcasted) { downcasted = int136(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(136, value); } } /** * @dev Returns the downcasted int128 from int256, reverting on * overflow (when the input is less than smallest int128 or * greater than largest int128). * * Counterpart to Solidity's `int128` operator. * * Requirements: * * - input must fit into 128 bits */ function toInt128(int256 value) internal pure returns (int128 downcasted) { downcasted = int128(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(128, value); } } /** * @dev Returns the downcasted int120 from int256, reverting on * overflow (when the input is less than smallest int120 or * greater than largest int120). * * Counterpart to Solidity's `int120` operator. * * Requirements: * * - input must fit into 120 bits */ function toInt120(int256 value) internal pure returns (int120 downcasted) { downcasted = int120(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(120, value); } } /** * @dev Returns the downcasted int112 from int256, reverting on * overflow (when the input is less than smallest int112 or * greater than largest int112). * * Counterpart to Solidity's `int112` operator. * * Requirements: * * - input must fit into 112 bits */ function toInt112(int256 value) internal pure returns (int112 downcasted) { downcasted = int112(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(112, value); } } /** * @dev Returns the downcasted int104 from int256, reverting on * overflow (when the input is less than smallest int104 or * greater than largest int104). * * Counterpart to Solidity's `int104` operator. * * Requirements: * * - input must fit into 104 bits */ function toInt104(int256 value) internal pure returns (int104 downcasted) { downcasted = int104(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(104, value); } } /** * @dev Returns the downcasted int96 from int256, reverting on * overflow (when the input is less than smallest int96 or * greater than largest int96). * * Counterpart to Solidity's `int96` operator. * * Requirements: * * - input must fit into 96 bits */ function toInt96(int256 value) internal pure returns (int96 downcasted) { downcasted = int96(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(96, value); } } /** * @dev Returns the downcasted int88 from int256, reverting on * overflow (when the input is less than smallest int88 or * greater than largest int88). * * Counterpart to Solidity's `int88` operator. * * Requirements: * * - input must fit into 88 bits */ function toInt88(int256 value) internal pure returns (int88 downcasted) { downcasted = int88(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(88, value); } } /** * @dev Returns the downcasted int80 from int256, reverting on * overflow (when the input is less than smallest int80 or * greater than largest int80). * * Counterpart to Solidity's `int80` operator. * * Requirements: * * - input must fit into 80 bits */ function toInt80(int256 value) internal pure returns (int80 downcasted) { downcasted = int80(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(80, value); } } /** * @dev Returns the downcasted int72 from int256, reverting on * overflow (when the input is less than smallest int72 or * greater than largest int72). * * Counterpart to Solidity's `int72` operator. * * Requirements: * * - input must fit into 72 bits */ function toInt72(int256 value) internal pure returns (int72 downcasted) { downcasted = int72(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(72, value); } } /** * @dev Returns the downcasted int64 from int256, reverting on * overflow (when the input is less than smallest int64 or * greater than largest int64). * * Counterpart to Solidity's `int64` operator. * * Requirements: * * - input must fit into 64 bits */ function toInt64(int256 value) internal pure returns (int64 downcasted) { downcasted = int64(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(64, value); } } /** * @dev Returns the downcasted int56 from int256, reverting on * overflow (when the input is less than smallest int56 or * greater than largest int56). * * Counterpart to Solidity's `int56` operator. * * Requirements: * * - input must fit into 56 bits */ function toInt56(int256 value) internal pure returns (int56 downcasted) { downcasted = int56(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(56, value); } } /** * @dev Returns the downcasted int48 from int256, reverting on * overflow (when the input is less than smallest int48 or * greater than largest int48). * * Counterpart to Solidity's `int48` operator. * * Requirements: * * - input must fit into 48 bits */ function toInt48(int256 value) internal pure returns (int48 downcasted) { downcasted = int48(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(48, value); } } /** * @dev Returns the downcasted int40 from int256, reverting on * overflow (when the input is less than smallest int40 or * greater than largest int40). * * Counterpart to Solidity's `int40` operator. * * Requirements: * * - input must fit into 40 bits */ function toInt40(int256 value) internal pure returns (int40 downcasted) { downcasted = int40(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(40, value); } } /** * @dev Returns the downcasted int32 from int256, reverting on * overflow (when the input is less than smallest int32 or * greater than largest int32). * * Counterpart to Solidity's `int32` operator. * * Requirements: * * - input must fit into 32 bits */ function toInt32(int256 value) internal pure returns (int32 downcasted) { downcasted = int32(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(32, value); } } /** * @dev Returns the downcasted int24 from int256, reverting on * overflow (when the input is less than smallest int24 or * greater than largest int24). * * Counterpart to Solidity's `int24` operator. * * Requirements: * * - input must fit into 24 bits */ function toInt24(int256 value) internal pure returns (int24 downcasted) { downcasted = int24(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(24, value); } } /** * @dev Returns the downcasted int16 from int256, reverting on * overflow (when the input is less than smallest int16 or * greater than largest int16). * * Counterpart to Solidity's `int16` operator. * * Requirements: * * - input must fit into 16 bits */ function toInt16(int256 value) internal pure returns (int16 downcasted) { downcasted = int16(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(16, value); } } /** * @dev Returns the downcasted int8 from int256, reverting on * overflow (when the input is less than smallest int8 or * greater than largest int8). * * Counterpart to Solidity's `int8` operator. * * Requirements: * * - input must fit into 8 bits */ function toInt8(int256 value) internal pure returns (int8 downcasted) { downcasted = int8(value); if (downcasted != value) { revert SafeCastOverflowedIntDowncast(8, value); } } /** * @dev Converts an unsigned uint256 into a signed int256. * * Requirements: * * - input must be less than or equal to maxInt256. */ function toInt256(uint256 value) internal pure returns (int256) { // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive if (value > uint256(type(int256).max)) { revert SafeCastOverflowedUintToInt(value); } return int256(value); } /** * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. */ function toUint(bool b) internal pure returns (uint256 u) { /// @solidity memory-safe-assembly assembly { u := iszero(iszero(b)) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol) pragma solidity ^0.8.20; import {SafeCast} from "./SafeCast.sol"; /** * @dev Standard signed math utilities missing in the Solidity language. */ library SignedMath { /** * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. * * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone. * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute * one branch when needed, making this function more expensive. */ function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) { unchecked { // branchless ternary works because: // b ^ (a ^ b) == a // b ^ 0 == b return b ^ ((a ^ b) * int256(SafeCast.toUint(condition))); } } /** * @dev Returns the largest of two signed numbers. */ function max(int256 a, int256 b) internal pure returns (int256) { return ternary(a > b, a, b); } /** * @dev Returns the smallest of two signed numbers. */ function min(int256 a, int256 b) internal pure returns (int256) { return ternary(a < b, a, b); } /** * @dev Returns the average of two signed numbers without overflow. * The result is rounded towards zero. */ function average(int256 a, int256 b) internal pure returns (int256) { // Formula from the book "Hacker's Delight" int256 x = (a & b) + ((a ^ b) >> 1); return x + (int256(uint256(x) >> 255) & (a ^ b)); } /** * @dev Returns the absolute unsigned value of a signed value. */ function abs(int256 n) internal pure returns (uint256) { unchecked { // Formula from the "Bit Twiddling Hacks" by Sean Eron Anderson. // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift, // taking advantage of the most significant (or "sign" bit) in two's complement representation. // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result, // the mask will either be `bytes(0)` (if n is positive) or `~bytes32(0)` (if n is negative). int256 mask = n >> 255; // A `bytes(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it. return uint256((n + mask) ^ mask); } } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /** * @dev Helper library for emitting standardized panic codes. * * ```solidity * contract Example { * using Panic for uint256; * * // Use any of the declared internal constants * function foo() { Panic.GENERIC.panic(); } * * // Alternatively * function foo() { Panic.panic(Panic.GENERIC); } * } * ``` * * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil]. */ // slither-disable-next-line unused-state library Panic { /// @dev generic / unspecified error uint256 internal constant GENERIC = 0x00; /// @dev used by the assert() builtin uint256 internal constant ASSERT = 0x01; /// @dev arithmetic underflow or overflow uint256 internal constant UNDER_OVERFLOW = 0x11; /// @dev division or modulo by zero uint256 internal constant DIVISION_BY_ZERO = 0x12; /// @dev enum conversion error uint256 internal constant ENUM_CONVERSION_ERROR = 0x21; /// @dev invalid encoding in storage uint256 internal constant STORAGE_ENCODING_ERROR = 0x22; /// @dev empty array pop uint256 internal constant EMPTY_ARRAY_POP = 0x31; /// @dev array out of bounds access uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32; /// @dev resource error (too large allocation or too large array) uint256 internal constant RESOURCE_ERROR = 0x41; /// @dev calling invalid internal function uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51; /// @dev Reverts with a panic code. Recommended to use with /// the internal constants with predefined codes. function panic(uint256 code) internal pure { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x4e487b71) mstore(0x20, code) revert(0x1c, 0x24) } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol) pragma solidity ^0.8.20; import {Math} from "./math/Math.sol"; import {SignedMath} from "./math/SignedMath.sol"; /** * @dev String operations. */ library Strings { bytes16 private constant HEX_DIGITS = "0123456789abcdef"; uint8 private constant ADDRESS_LENGTH = 20; /** * @dev The `value` string doesn't fit in the specified `length`. */ error StringsInsufficientHexLength(uint256 value, uint256 length); /** * @dev Converts a `uint256` to its ASCII `string` decimal representation. */ function toString(uint256 value) internal pure returns (string memory) { unchecked { uint256 length = Math.log10(value) + 1; string memory buffer = new string(length); uint256 ptr; /// @solidity memory-safe-assembly assembly { ptr := add(buffer, add(32, length)) } while (true) { ptr--; /// @solidity memory-safe-assembly assembly { mstore8(ptr, byte(mod(value, 10), HEX_DIGITS)) } value /= 10; if (value == 0) break; } return buffer; } } /** * @dev Converts a `int256` to its ASCII `string` decimal representation. */ function toStringSigned(int256 value) internal pure returns (string memory) { return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value))); } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. */ function toHexString(uint256 value) internal pure returns (string memory) { unchecked { return toHexString(value, Math.log256(value) + 1); } } /** * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. */ function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { uint256 localValue = value; bytes memory buffer = new bytes(2 * length + 2); buffer[0] = "0"; buffer[1] = "x"; for (uint256 i = 2 * length + 1; i > 1; --i) { buffer[i] = HEX_DIGITS[localValue & 0xf]; localValue >>= 4; } if (localValue != 0) { revert StringsInsufficientHexLength(value, length); } return string(buffer); } /** * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal * representation. */ function toHexString(address addr) internal pure returns (string memory) { return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH); } /** * @dev Returns true if the two strings are equal. */ function equal(string memory a, string memory b) internal pure returns (bool) { return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b)); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Library for converting numbers into strings and other string operations. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol) /// /// @dev Note: /// For performance and bytecode compactness, most of the string operations are restricted to /// byte strings (7-bit ASCII), except where otherwise specified. /// Usage of byte string operations on charsets with runes spanning two or more bytes /// can lead to undefined behavior. library LibString { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The length of the output is too small to contain all the hex digits. error HexLengthInsufficient(); /// @dev The length of the string is more than 32 bytes. error TooBigForSmallString(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The constant returned when the `search` is not found in the string. uint256 internal constant NOT_FOUND = type(uint256).max; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* DECIMAL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the base 10 decimal representation of `value`. function toString(uint256 value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // The maximum value of a uint256 contains 78 digits (1 byte per digit), but // we allocate 0xa0 bytes to keep the free memory pointer 32-byte word aligned. // We will need 1 word for the trailing zeros padding, 1 word for the length, // and 3 words for a maximum of 78 digits. str := add(mload(0x40), 0x80) // Update the free memory pointer to allocate. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end of the memory to calculate the length later. let end := str let w := not(0) // Tsk. // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for { let temp := value } 1 {} { str := add(str, w) // `sub(str, 1)`. // Write the character to the pointer. // The ASCII index of the '0' character is 48. mstore8(str, add(48, mod(temp, 10))) // Keep dividing `temp` until zero. temp := div(temp, 10) if iszero(temp) { break } } let length := sub(end, str) // Move the pointer 32 bytes leftwards to make room for the length. str := sub(str, 0x20) // Store the length. mstore(str, length) } } /// @dev Returns the base 10 decimal representation of `value`. function toString(int256 value) internal pure returns (string memory str) { if (value >= 0) { return toString(uint256(value)); } unchecked { str = toString(~uint256(value) + 1); } /// @solidity memory-safe-assembly assembly { // We still have some spare memory space on the left, // as we have allocated 3 words (96 bytes) for up to 78 digits. let length := mload(str) // Load the string length. mstore(str, 0x2d) // Store the '-' character. str := sub(str, 1) // Move back the string pointer by a byte. mstore(str, add(length, 1)) // Update the string length. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HEXADECIMAL OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the hexadecimal representation of `value`, /// left-padded to an input length of `length` bytes. /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2 + 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. function toHexString(uint256 value, uint256 length) internal pure returns (string memory str) { str = toHexStringNoPrefix(value, length); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`, /// left-padded to an input length of `length` bytes. /// The output is prefixed with "0x" encoded using 2 hexadecimal digits per byte, /// giving a total length of `length * 2` bytes. /// Reverts if `length` is too small for the output to contain all the digits. function toHexStringNoPrefix(uint256 value, uint256 length) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, `length * 2` bytes // for the digits, 0x02 bytes for the prefix, and 0x20 bytes for the length. // We add 0x20 to the total and round down to a multiple of 0x20. // (0x20 + 0x20 + 0x02 + 0x20) = 0x62. str := add(mload(0x40), and(add(shl(1, length), 0x42), not(0x1f))) // Allocate the memory. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end to calculate the length later. let end := str // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let start := sub(str, add(length, length)) let w := not(1) // Tsk. let temp := value // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for {} 1 {} { str := add(str, w) // `sub(str, 2)`. mstore8(add(str, 1), mload(and(temp, 15))) mstore8(str, mload(and(shr(4, temp), 15))) temp := shr(8, temp) if iszero(xor(str, start)) { break } } if temp { mstore(0x00, 0x2194895a) // `HexLengthInsufficient()`. revert(0x1c, 0x04) } // Compute the string's length. let strLength := sub(end, str) // Move the pointer and write the length. str := sub(str, 0x20) mstore(str, strLength) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will left-padded to have /// a length of `20 * 2 + 2` bytes. function toHexString(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x". /// The output excludes leading "0" from the `toHexString` output. /// `0x00: "0x0", 0x01: "0x1", 0x12: "0x12", 0x123: "0x123"`. function toMinimalHexString(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. let strLength := add(mload(str), 2) // Compute the length. mstore(add(str, o), 0x3078) // Write the "0x" prefix, accounting for leading zero. str := sub(add(str, o), 2) // Move the pointer, accounting for leading zero. mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. } } /// @dev Returns the hexadecimal representation of `value`. /// The output excludes leading "0" from the `toHexStringNoPrefix` output. /// `0x00: "0", 0x01: "1", 0x12: "12", 0x123: "123"`. function toMinimalHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let o := eq(byte(0, mload(add(str, 0x20))), 0x30) // Whether leading zero is present. let strLength := mload(str) // Get the length. str := add(str, o) // Move the pointer, accounting for leading zero. mstore(str, sub(strLength, o)) // Write the length, accounting for leading zero. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will left-padded to have /// a length of `20 * 2` bytes. function toHexStringNoPrefix(uint256 value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, // 0x02 bytes for the prefix, and 0x40 bytes for the digits. // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x40) is 0xa0. str := add(mload(0x40), 0x80) // Allocate the memory. mstore(0x40, add(str, 0x20)) // Zeroize the slot after the string. mstore(str, 0) // Cache the end to calculate the length later. let end := str // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let w := not(1) // Tsk. // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for { let temp := value } 1 {} { str := add(str, w) // `sub(str, 2)`. mstore8(add(str, 1), mload(and(temp, 15))) mstore8(str, mload(and(shr(4, temp), 15))) temp := shr(8, temp) if iszero(temp) { break } } // Compute the string's length. let strLength := sub(end, str) // Move the pointer and write the length. str := sub(str, 0x20) mstore(str, strLength) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x", encoded using 2 hexadecimal digits per byte, /// and the alphabets are capitalized conditionally according to /// https://eips.ethereum.org/EIPS/eip-55 function toHexStringChecksummed(address value) internal pure returns (string memory str) { str = toHexString(value); /// @solidity memory-safe-assembly assembly { let mask := shl(6, div(not(0), 255)) // `0b010000000100000000 ...` let o := add(str, 0x22) let hashed := and(keccak256(o, 40), mul(34, mask)) // `0b10001000 ... ` let t := shl(240, 136) // `0b10001000 << 240` for { let i := 0 } 1 {} { mstore(add(i, i), mul(t, byte(i, hashed))) i := add(i, 1) if eq(i, 20) { break } } mstore(o, xor(mload(o), shr(1, and(mload(0x00), and(mload(o), mask))))) o := add(o, 0x20) mstore(o, xor(mload(o), shr(1, and(mload(0x20), and(mload(o), mask))))) } } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. function toHexString(address value) internal pure returns (string memory str) { str = toHexStringNoPrefix(value); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hexadecimal representation of `value`. /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix(address value) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { str := mload(0x40) // Allocate the memory. // We need 0x20 bytes for the trailing zeros padding, 0x20 bytes for the length, // 0x02 bytes for the prefix, and 0x28 bytes for the digits. // The next multiple of 0x20 above (0x20 + 0x20 + 0x02 + 0x28) is 0x80. mstore(0x40, add(str, 0x80)) // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) str := add(str, 2) mstore(str, 40) let o := add(str, 0x20) mstore(add(o, 40), 0) value := shl(96, value) // We write the string from rightmost digit to leftmost digit. // The following is essentially a do-while loop that also handles the zero case. for { let i := 0 } 1 {} { let p := add(o, add(i, i)) let temp := byte(i, value) mstore8(add(p, 1), mload(and(temp, 15))) mstore8(p, mload(shr(4, temp))) i := add(i, 1) if eq(i, 20) { break } } } } /// @dev Returns the hex encoded string from the raw bytes. /// The output is encoded using 2 hexadecimal digits per byte. function toHexString(bytes memory raw) internal pure returns (string memory str) { str = toHexStringNoPrefix(raw); /// @solidity memory-safe-assembly assembly { let strLength := add(mload(str), 2) // Compute the length. mstore(str, 0x3078) // Write the "0x" prefix. str := sub(str, 2) // Move the pointer. mstore(str, strLength) // Write the length. } } /// @dev Returns the hex encoded string from the raw bytes. /// The output is encoded using 2 hexadecimal digits per byte. function toHexStringNoPrefix(bytes memory raw) internal pure returns (string memory str) { /// @solidity memory-safe-assembly assembly { let length := mload(raw) str := add(mload(0x40), 2) // Skip 2 bytes for the optional prefix. mstore(str, add(length, length)) // Store the length of the output. // Store "0123456789abcdef" in scratch space. mstore(0x0f, 0x30313233343536373839616263646566) let o := add(str, 0x20) let end := add(raw, length) for {} iszero(eq(raw, end)) {} { raw := add(raw, 1) mstore8(add(o, 1), mload(and(mload(raw), 15))) mstore8(o, mload(and(shr(4, mload(raw)), 15))) o := add(o, 2) } mstore(o, 0) // Zeroize the slot after the string. mstore(0x40, add(o, 0x20)) // Allocate the memory. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* RUNE STRING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the number of UTF characters in the string. function runeCount(string memory s) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { if mload(s) { mstore(0x00, div(not(0), 255)) mstore(0x20, 0x0202020202020202020202020202020202020202020202020303030304040506) let o := add(s, 0x20) let end := add(o, mload(s)) for { result := 1 } 1 { result := add(result, 1) } { o := add(o, byte(0, mload(shr(250, mload(o))))) if iszero(lt(o, end)) { break } } } } } /// @dev Returns if this string is a 7-bit ASCII string. /// (i.e. all characters codes are in [0..127]) function is7BitASCII(string memory s) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let mask := shl(7, div(not(0), 255)) result := 1 let n := mload(s) if n { let o := add(s, 0x20) let end := add(o, n) let last := mload(end) mstore(end, 0) for {} 1 {} { if and(mask, mload(o)) { result := 0 break } o := add(o, 0x20) if iszero(lt(o, end)) { break } } mstore(end, last) } } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* BYTE STRING OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // For performance and bytecode compactness, byte string operations are restricted // to 7-bit ASCII strings. All offsets are byte offsets, not UTF character offsets. // Usage of byte string operations on charsets with runes spanning two or more bytes // can lead to undefined behavior. /// @dev Returns `subject` all occurrences of `search` replaced with `replacement`. function replace(string memory subject, string memory search, string memory replacement) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) let searchLength := mload(search) let replacementLength := mload(replacement) subject := add(subject, 0x20) search := add(search, 0x20) replacement := add(replacement, 0x20) result := add(mload(0x40), 0x20) let subjectEnd := add(subject, subjectLength) if iszero(gt(searchLength, subjectLength)) { let subjectSearchEnd := add(sub(subjectEnd, searchLength), 1) let h := 0 if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } let m := shl(3, sub(0x20, and(searchLength, 0x1f))) let s := mload(search) for {} 1 {} { let t := mload(subject) // Whether the first `searchLength % 32` bytes of // `subject` and `search` matches. if iszero(shr(m, xor(t, s))) { if h { if iszero(eq(keccak256(subject, searchLength), h)) { mstore(result, t) result := add(result, 1) subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } continue } } // Copy the `replacement` one word at a time. for { let o := 0 } 1 {} { mstore(add(result, o), mload(add(replacement, o))) o := add(o, 0x20) if iszero(lt(o, replacementLength)) { break } } result := add(result, replacementLength) subject := add(subject, searchLength) if searchLength { if iszero(lt(subject, subjectSearchEnd)) { break } continue } } mstore(result, t) result := add(result, 1) subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } } } let resultRemainder := result result := add(mload(0x40), 0x20) let k := add(sub(resultRemainder, result), sub(subjectEnd, subject)) // Copy the rest of the string one word at a time. for {} lt(subject, subjectEnd) {} { mstore(resultRemainder, mload(subject)) resultRemainder := add(resultRemainder, 0x20) subject := add(subject, 0x20) } result := sub(result, 0x20) let last := add(add(result, 0x20), k) // Zeroize the slot after the string. mstore(last, 0) mstore(0x40, add(last, 0x20)) // Allocate the memory. mstore(result, k) // Store the length. } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right, starting from `from`. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function indexOf(string memory subject, string memory search, uint256 from) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { for { let subjectLength := mload(subject) } 1 {} { if iszero(mload(search)) { if iszero(gt(from, subjectLength)) { result := from break } result := subjectLength break } let searchLength := mload(search) let subjectStart := add(subject, 0x20) result := not(0) // Initialize to `NOT_FOUND`. subject := add(subjectStart, from) let end := add(sub(add(subjectStart, subjectLength), searchLength), 1) let m := shl(3, sub(0x20, and(searchLength, 0x1f))) let s := mload(add(search, 0x20)) if iszero(and(lt(subject, end), lt(from, subjectLength))) { break } if iszero(lt(searchLength, 0x20)) { for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { if iszero(shr(m, xor(mload(subject), s))) { if eq(keccak256(subject, searchLength), h) { result := sub(subject, subjectStart) break } } subject := add(subject, 1) if iszero(lt(subject, end)) { break } } break } for {} 1 {} { if iszero(shr(m, xor(mload(subject), s))) { result := sub(subject, subjectStart) break } subject := add(subject, 1) if iszero(lt(subject, end)) { break } } break } } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from left to right. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function indexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = indexOf(subject, search, 0); } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left, starting from `from`. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function lastIndexOf(string memory subject, string memory search, uint256 from) internal pure returns (uint256 result) { /// @solidity memory-safe-assembly assembly { for {} 1 {} { result := not(0) // Initialize to `NOT_FOUND`. let searchLength := mload(search) if gt(searchLength, mload(subject)) { break } let w := result let fromMax := sub(mload(subject), searchLength) if iszero(gt(fromMax, from)) { from := fromMax } let end := add(add(subject, 0x20), w) subject := add(add(subject, 0x20), from) if iszero(gt(subject, end)) { break } // As this function is not too often used, // we shall simply use keccak256 for smaller bytecode size. for { let h := keccak256(add(search, 0x20), searchLength) } 1 {} { if eq(keccak256(subject, searchLength), h) { result := sub(subject, add(end, 1)) break } subject := add(subject, w) // `sub(subject, 1)`. if iszero(gt(subject, end)) { break } } break } } } /// @dev Returns the byte index of the first location of `search` in `subject`, /// searching from right to left. /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `search` is not found. function lastIndexOf(string memory subject, string memory search) internal pure returns (uint256 result) { result = lastIndexOf(subject, search, uint256(int256(-1))); } /// @dev Returns true if `search` is found in `subject`, false otherwise. function contains(string memory subject, string memory search) internal pure returns (bool) { return indexOf(subject, search) != NOT_FOUND; } /// @dev Returns whether `subject` starts with `search`. function startsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) // Just using keccak256 directly is actually cheaper. // forgefmt: disable-next-item result := and( iszero(gt(searchLength, mload(subject))), eq( keccak256(add(subject, 0x20), searchLength), keccak256(add(search, 0x20), searchLength) ) ) } } /// @dev Returns whether `subject` ends with `search`. function endsWith(string memory subject, string memory search) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { let searchLength := mload(search) let subjectLength := mload(subject) // Whether `search` is not longer than `subject`. let withinRange := iszero(gt(searchLength, subjectLength)) // Just using keccak256 directly is actually cheaper. // forgefmt: disable-next-item result := and( withinRange, eq( keccak256( // `subject + 0x20 + max(subjectLength - searchLength, 0)`. add(add(subject, 0x20), mul(withinRange, sub(subjectLength, searchLength))), searchLength ), keccak256(add(search, 0x20), searchLength) ) ) } } /// @dev Returns `subject` repeated `times`. function repeat(string memory subject, uint256 times) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) if iszero(or(iszero(times), iszero(subjectLength))) { subject := add(subject, 0x20) result := mload(0x40) let output := add(result, 0x20) for {} 1 {} { // Copy the `subject` one word at a time. for { let o := 0 } 1 {} { mstore(add(output, o), mload(add(subject, o))) o := add(o, 0x20) if iszero(lt(o, subjectLength)) { break } } output := add(output, subjectLength) times := sub(times, 1) if iszero(times) { break } } mstore(output, 0) // Zeroize the slot after the string. let resultLength := sub(output, add(result, 0x20)) mstore(result, resultLength) // Store the length. // Allocate the memory. mstore(0x40, add(result, add(resultLength, 0x20))) } } } /// @dev Returns a copy of `subject` sliced from `start` to `end` (exclusive). /// `start` and `end` are byte offsets. function slice(string memory subject, uint256 start, uint256 end) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) if iszero(gt(subjectLength, end)) { end := subjectLength } if iszero(gt(subjectLength, start)) { start := subjectLength } if lt(start, end) { result := mload(0x40) let resultLength := sub(end, start) mstore(result, resultLength) subject := add(subject, start) let w := not(0x1f) // Copy the `subject` one word at a time, backwards. for { let o := and(add(resultLength, 0x1f), w) } 1 {} { mstore(add(result, o), mload(add(subject, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } // Zeroize the slot after the string. mstore(add(add(result, 0x20), resultLength), 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(result, and(add(resultLength, 0x3f), w))) } } } /// @dev Returns a copy of `subject` sliced from `start` to the end of the string. /// `start` is a byte offset. function slice(string memory subject, uint256 start) internal pure returns (string memory result) { result = slice(subject, start, uint256(int256(-1))); } /// @dev Returns all the indices of `search` in `subject`. /// The indices are byte offsets. function indicesOf(string memory subject, string memory search) internal pure returns (uint256[] memory result) { /// @solidity memory-safe-assembly assembly { let subjectLength := mload(subject) let searchLength := mload(search) if iszero(gt(searchLength, subjectLength)) { subject := add(subject, 0x20) search := add(search, 0x20) result := add(mload(0x40), 0x20) let subjectStart := subject let subjectSearchEnd := add(sub(add(subject, subjectLength), searchLength), 1) let h := 0 if iszero(lt(searchLength, 0x20)) { h := keccak256(search, searchLength) } let m := shl(3, sub(0x20, and(searchLength, 0x1f))) let s := mload(search) for {} 1 {} { let t := mload(subject) // Whether the first `searchLength % 32` bytes of // `subject` and `search` matches. if iszero(shr(m, xor(t, s))) { if h { if iszero(eq(keccak256(subject, searchLength), h)) { subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } continue } } // Append to `result`. mstore(result, sub(subject, subjectStart)) result := add(result, 0x20) // Advance `subject` by `searchLength`. subject := add(subject, searchLength) if searchLength { if iszero(lt(subject, subjectSearchEnd)) { break } continue } } subject := add(subject, 1) if iszero(lt(subject, subjectSearchEnd)) { break } } let resultEnd := result // Assign `result` to the free memory pointer. result := mload(0x40) // Store the length of `result`. mstore(result, shr(5, sub(resultEnd, add(result, 0x20)))) // Allocate memory for result. // We allocate one more word, so this array can be recycled for {split}. mstore(0x40, add(resultEnd, 0x20)) } } } /// @dev Returns a arrays of strings based on the `delimiter` inside of the `subject` string. function split(string memory subject, string memory delimiter) internal pure returns (string[] memory result) { uint256[] memory indices = indicesOf(subject, delimiter); /// @solidity memory-safe-assembly assembly { let w := not(0x1f) let indexPtr := add(indices, 0x20) let indicesEnd := add(indexPtr, shl(5, add(mload(indices), 1))) mstore(add(indicesEnd, w), mload(subject)) mstore(indices, add(mload(indices), 1)) let prevIndex := 0 for {} 1 {} { let index := mload(indexPtr) mstore(indexPtr, 0x60) if iszero(eq(index, prevIndex)) { let element := mload(0x40) let elementLength := sub(index, prevIndex) mstore(element, elementLength) // Copy the `subject` one word at a time, backwards. for { let o := and(add(elementLength, 0x1f), w) } 1 {} { mstore(add(element, o), mload(add(add(subject, prevIndex), o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } // Zeroize the slot after the string. mstore(add(add(element, 0x20), elementLength), 0) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, add(element, and(add(elementLength, 0x3f), w))) // Store the `element` into the array. mstore(indexPtr, element) } prevIndex := add(index, mload(delimiter)) indexPtr := add(indexPtr, 0x20) if iszero(lt(indexPtr, indicesEnd)) { break } } result := indices if iszero(mload(delimiter)) { result := add(indices, 0x20) mstore(result, sub(mload(indices), 2)) } } } /// @dev Returns a concatenated string of `a` and `b`. /// Cheaper than `string.concat()` and does not de-align the free memory pointer. function concat(string memory a, string memory b) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let w := not(0x1f) result := mload(0x40) let aLength := mload(a) // Copy `a` one word at a time, backwards. for { let o := and(add(aLength, 0x20), w) } 1 {} { mstore(add(result, o), mload(add(a, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } let bLength := mload(b) let output := add(result, aLength) // Copy `b` one word at a time, backwards. for { let o := and(add(bLength, 0x20), w) } 1 {} { mstore(add(output, o), mload(add(b, o))) o := add(o, w) // `sub(o, 0x20)`. if iszero(o) { break } } let totalLength := add(aLength, bLength) let last := add(add(result, 0x20), totalLength) // Zeroize the slot after the string. mstore(last, 0) // Stores the length. mstore(result, totalLength) // Allocate memory for the length and the bytes, // rounded up to a multiple of 32. mstore(0x40, and(add(last, 0x1f), w)) } } /// @dev Returns a copy of the string in either lowercase or UPPERCASE. /// WARNING! This function is only compatible with 7-bit ASCII strings. function toCase(string memory subject, bool toUpper) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let length := mload(subject) if length { result := add(mload(0x40), 0x20) subject := add(subject, 1) let flags := shl(add(70, shl(5, toUpper)), 0x3ffffff) let w := not(0) for { let o := length } 1 {} { o := add(o, w) let b := and(0xff, mload(add(subject, o))) mstore8(add(result, o), xor(b, and(shr(b, flags), 0x20))) if iszero(o) { break } } result := mload(0x40) mstore(result, length) // Store the length. let last := add(add(result, 0x20), length) mstore(last, 0) // Zeroize the slot after the string. mstore(0x40, add(last, 0x20)) // Allocate the memory. } } } /// @dev Returns a string from a small bytes32 string. /// `s` must be null-terminated, or behavior will be undefined. function fromSmallString(bytes32 s) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) let n := 0 for {} byte(n, s) { n := add(n, 1) } {} // Scan for '\0'. mstore(result, n) let o := add(result, 0x20) mstore(o, s) mstore(add(o, n), 0) mstore(0x40, add(result, 0x40)) } } /// @dev Returns the small string, with all bytes after the first null byte zeroized. function normalizeSmallString(bytes32 s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { for {} byte(result, s) { result := add(result, 1) } {} // Scan for '\0'. mstore(0x00, s) mstore(result, 0x00) result := mload(0x00) } } /// @dev Returns the string as a normalized null-terminated small string. function toSmallString(string memory s) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { result := mload(s) if iszero(lt(result, 33)) { mstore(0x00, 0xec92f9a3) // `TooBigForSmallString()`. revert(0x1c, 0x04) } result := shl(shl(3, sub(32, result)), mload(add(s, result))) } } /// @dev Returns a lowercased copy of the string. /// WARNING! This function is only compatible with 7-bit ASCII strings. function lower(string memory subject) internal pure returns (string memory result) { result = toCase(subject, false); } /// @dev Returns an UPPERCASED copy of the string. /// WARNING! This function is only compatible with 7-bit ASCII strings. function upper(string memory subject) internal pure returns (string memory result) { result = toCase(subject, true); } /// @dev Escapes the string to be used within HTML tags. function escapeHTML(string memory s) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let end := add(s, mload(s)) result := add(mload(0x40), 0x20) // Store the bytes of the packed offsets and strides into the scratch space. // `packed = (stride << 5) | offset`. Max offset is 20. Max stride is 6. mstore(0x1f, 0x900094) mstore(0x08, 0xc0000000a6ab) // Store ""&'<>" into the scratch space. mstore(0x00, shl(64, 0x2671756f743b26616d703b262333393b266c743b2667743b)) for {} iszero(eq(s, end)) {} { s := add(s, 1) let c := and(mload(s), 0xff) // Not in `["\"","'","&","<",">"]`. if iszero(and(shl(c, 1), 0x500000c400000000)) { mstore8(result, c) result := add(result, 1) continue } let t := shr(248, mload(c)) mstore(result, mload(and(t, 0x1f))) result := add(result, shr(5, t)) } let last := result mstore(last, 0) // Zeroize the slot after the string. result := mload(0x40) mstore(result, sub(last, add(result, 0x20))) // Store the length. mstore(0x40, add(last, 0x20)) // Allocate the memory. } } /// @dev Escapes the string to be used within double-quotes in a JSON. /// If `addDoubleQuotes` is true, the result will be enclosed in double-quotes. function escapeJSON(string memory s, bool addDoubleQuotes) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { let end := add(s, mload(s)) result := add(mload(0x40), 0x20) if addDoubleQuotes { mstore8(result, 34) result := add(1, result) } // Store "\\u0000" in scratch space. // Store "0123456789abcdef" in scratch space. // Also, store `{0x08:"b", 0x09:"t", 0x0a:"n", 0x0c:"f", 0x0d:"r"}`. // into the scratch space. mstore(0x15, 0x5c75303030303031323334353637383961626364656662746e006672) // Bitmask for detecting `["\"","\\"]`. let e := or(shl(0x22, 1), shl(0x5c, 1)) for {} iszero(eq(s, end)) {} { s := add(s, 1) let c := and(mload(s), 0xff) if iszero(lt(c, 0x20)) { if iszero(and(shl(c, 1), e)) { // Not in `["\"","\\"]`. mstore8(result, c) result := add(result, 1) continue } mstore8(result, 0x5c) // "\\". mstore8(add(result, 1), c) result := add(result, 2) continue } if iszero(and(shl(c, 1), 0x3700)) { // Not in `["\b","\t","\n","\f","\d"]`. mstore8(0x1d, mload(shr(4, c))) // Hex value. mstore8(0x1e, mload(and(c, 15))) // Hex value. mstore(result, mload(0x19)) // "\\u00XX". result := add(result, 6) continue } mstore8(result, 0x5c) // "\\". mstore8(add(result, 1), mload(add(c, 8))) result := add(result, 2) } if addDoubleQuotes { mstore8(result, 34) result := add(1, result) } let last := result mstore(last, 0) // Zeroize the slot after the string. result := mload(0x40) mstore(result, sub(last, add(result, 0x20))) // Store the length. mstore(0x40, add(last, 0x20)) // Allocate the memory. } } /// @dev Escapes the string to be used within double-quotes in a JSON. function escapeJSON(string memory s) internal pure returns (string memory result) { result = escapeJSON(s, false); } /// @dev Returns whether `a` equals `b`. function eq(string memory a, string memory b) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { result := eq(keccak256(add(a, 0x20), mload(a)), keccak256(add(b, 0x20), mload(b))) } } /// @dev Returns whether `a` equals `b`, where `b` is a null-terminated small string. function eqs(string memory a, bytes32 b) internal pure returns (bool result) { /// @solidity memory-safe-assembly assembly { // These should be evaluated on compile time, as far as possible. let m := not(shl(7, div(not(iszero(b)), 255))) // `0x7f7f ...`. let x := not(or(m, or(b, add(m, and(b, m))))) let r := shl(7, iszero(iszero(shr(128, x)))) r := or(r, shl(6, iszero(iszero(shr(64, shr(r, x)))))) r := or(r, shl(5, lt(0xffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffff, shr(r, x)))) r := or(r, shl(3, lt(0xff, shr(r, x)))) // forgefmt: disable-next-item result := gt(eq(mload(a), add(iszero(x), xor(31, shr(3, r)))), xor(shr(add(8, r), b), shr(add(8, r), mload(add(a, 0x20))))) } } /// @dev Packs a single string with its length into a single word. /// Returns `bytes32(0)` if the length is zero or greater than 31. function packOne(string memory a) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { // We don't need to zero right pad the string, // since this is our own custom non-standard packing scheme. result := mul( // Load the length and the bytes. mload(add(a, 0x1f)), // `length != 0 && length < 32`. Abuses underflow. // Assumes that the length is valid and within the block gas limit. lt(sub(mload(a), 1), 0x1f) ) } } /// @dev Unpacks a string packed using {packOne}. /// Returns the empty string if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packOne}, the output behavior is undefined. function unpackOne(bytes32 packed) internal pure returns (string memory result) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. result := mload(0x40) // Allocate 2 words (1 for the length, 1 for the bytes). mstore(0x40, add(result, 0x40)) // Zeroize the length slot. mstore(result, 0) // Store the length and bytes. mstore(add(result, 0x1f), packed) // Right pad with zeroes. mstore(add(add(result, 0x20), mload(result)), 0) } } /// @dev Packs two strings with their lengths into a single word. /// Returns `bytes32(0)` if combined length is zero or greater than 30. function packTwo(string memory a, string memory b) internal pure returns (bytes32 result) { /// @solidity memory-safe-assembly assembly { let aLength := mload(a) // We don't need to zero right pad the strings, // since this is our own custom non-standard packing scheme. result := mul( // Load the length and the bytes of `a` and `b`. or( shl(shl(3, sub(0x1f, aLength)), mload(add(a, aLength))), mload(sub(add(b, 0x1e), aLength)) ), // `totalLength != 0 && totalLength < 31`. Abuses underflow. // Assumes that the lengths are valid and within the block gas limit. lt(sub(add(aLength, mload(b)), 1), 0x1e) ) } } /// @dev Unpacks strings packed using {packTwo}. /// Returns the empty strings if `packed` is `bytes32(0)`. /// If `packed` is not an output of {packTwo}, the output behavior is undefined. function unpackTwo(bytes32 packed) internal pure returns (string memory resultA, string memory resultB) { /// @solidity memory-safe-assembly assembly { // Grab the free memory pointer. resultA := mload(0x40) resultB := add(resultA, 0x40) // Allocate 2 words for each string (1 for the length, 1 for the byte). Total 4 words. mstore(0x40, add(resultB, 0x40)) // Zeroize the length slots. mstore(resultA, 0) mstore(resultB, 0) // Store the lengths and bytes. mstore(add(resultA, 0x1f), packed) mstore(add(resultB, 0x1f), mload(add(add(resultA, 0x20), mload(resultA)))) // Right pad with zeroes. mstore(add(add(resultA, 0x20), mload(resultA)), 0) mstore(add(add(resultB, 0x20), mload(resultB)), 0) } } /// @dev Directly returns `a` without copying. function directReturn(string memory a) internal pure { assembly { // Assumes that the string does not start from the scratch space. let retStart := sub(a, 0x20) let retUnpaddedSize := add(mload(a), 0x40) // Right pad with zeroes. Just in case the string is produced // by a method that doesn't zero right pad. mstore(add(retStart, retUnpaddedSize), 0) // Store the return offset. mstore(retStart, 0x20) // End the transaction, returning the string. return(retStart, and(not(0x1f), add(0x1f, retUnpaddedSize))) } } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {VerifySignatureUtil as SigUtil} from "./libraries/VerifySignatureUtil.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IFunctions} from "./interfaces/IDiamond.sol"; import {IGlobalState} from "./interfaces/IGlobalState.sol"; import {IKeyManagement} from "./interfaces/IKeyManagement.sol"; import {GlobalStateFacet} from "./facets/GlobalStateFacet.sol"; import {KeyMgmtUtil} from "./libraries/KeyMgmtUtil.sol"; import {KeyManagementStoreFacet} from "./facets/KeyManagementStoreFacet.sol"; /// @title COWS Management /// @author Prometheum Inc. /// @notice This contract is responsible for managing proxy contract addresses that represent COWS Containers. Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#COWSManagement-Contract)</b> for more concept details. contract COWSManagement { using Strings for *; /// @notice Types of COWS containers enum AccountType { Empty, Omnibus, Investigation, Garbage, Firmwallet } /// @notice Status of COWS management keys enum KeyStatus { Nonexistent, Enabled, Expired, Inactive } /// @notice Contains information about the COWS container /// @dev [**accountType**](#accounttype) : type of the COWS container<br />**contractAddress** : address of proxy contract that represent COWS Container struct ContainerInfo { AccountType accountType; address contractAddress; } /// @notice Given a specific key {address} that is used to deploy this contract and be part of recognizing COWS container, returns its {[KeyStatus](#keystatus)} mapping(address => KeyStatus) public cowsManagementKeys; /// @notice Return number {uint8} of inactive COWS management keys left can be used uint8 public cmgtKeyLeft; /// @notice Given a specific COWS ID {bytes32}, returns its relevant container information {[ContainerInfo](#containerinfo)} mapping(bytes32 => ContainerInfo) internal cowsContainers; /// @notice Given a specific container {address}, returns its COWS ID {bytes32} mapping(address => bytes32) internal containerIds; /// @notice Given a specific COWS ID {bytes32}, returns the signature used to recognize that container {bytes} mapping(bytes32 => bytes) internal recognizeRecords; /// @notice Given a specific request ID {bytes32}, returns boolean value to indicate whether it's used or not mapping(bytes32 => bool) internal cowsMgmtRequests; /// @notice Number of total active containers uint8 public totalActiveContainers; /// @notice The **Recognize** event is emitted at the end of the [`recognize()`](#recognize) function event Recognize(); /// @notice The **COWSAction** event is emitted at the end of the [`pauseCOWS`](#pauseCOWS), and [`unpauseCOWS`](#unpauseCOWS) functions /// @dev **requestId** : unique identifier used for record keeping /// @dev **actionType** : type of action that was taken when this event was emitted /// @dev **cowId** : identifier of the container that was actioned on event COWSAction(bytes32 requestId, string actionType, bytes32 cowId); /// @notice The **SwitchCmgtKey** event is emitted at the end of the [`switchCowsManagementKey()`](#switchCowsManagementKey) function /// @dev **keyLeft** : amount of cows management keys left after the switch function has executed event SwitchCmgtKey(uint8 keyLeft); /// @notice sets `cowsManagementKeys` when the contract is deployed constructor(address[] memory _cowsManagementKeys) { uint8 cowsmanagementKeysLength = uint8(_cowsManagementKeys.length); require(cowsmanagementKeysLength > 0, "no cowsMgmt keys were given"); cowsManagementKeys[_cowsManagementKeys[0]] = KeyStatus.Enabled; uint8 _cmgtKeyLeft; for (uint256 i = 1; i < cowsmanagementKeysLength; ) { require( cowsManagementKeys[_cowsManagementKeys[i]] == KeyStatus.Nonexistent, "duplicate keys are found" ); cowsManagementKeys[_cowsManagementKeys[i]] = KeyStatus.Inactive; unchecked { ++i; ++_cmgtKeyLeft; } } cmgtKeyLeft = _cmgtKeyLeft; } /// @notice returns specific information about a given container related to `cowId` /// @param cowId unique identifier for a specific COWS container /// @return containerInfo information associated to the specific container `cowId` function getContainerInfo( bytes32 cowId ) external view returns (ContainerInfo memory containerInfo) { return cowsContainers[cowId]; } /// @notice returns the 'cowId' of a specified 'container' address /// @param container address of the container /// @return cowId unique identifier to represent the container function getCowId(address container) external view returns (bytes32 cowId) { return containerIds[container]; } /// @notice returns signature used to recognize the given COWS ID /// @param cowId unique identifier for a specific COWS container /// @return signature signature used in `recognize()` call function getRecognizeRecord(bytes32 cowId) external view returns (bytes memory signature) { return recognizeRecords[cowId]; } /// @notice Returns a boolean value whether a specific request ID is used from 'cowsMgmtRequests' /// @param requestId the unique identifier of all kind requests within the `COWSManagement` function isReqIdUsed(bytes32 requestId) external view returns (bool isUsed) { isUsed = cowsMgmtRequests[requestId]; } /// @notice Returns a boolean value whether a specific address is an Enabled cowsManagement key from `cowsManagementKeys` /// @param keyAddress the key address to be checked function isCowsManagementKey(address keyAddress) public view returns (bool isEnabled) { isEnabled = cowsManagementKeys[keyAddress] == KeyStatus.Enabled; } /// @dev Checks whether or not '_contract' is a contract or not /// @param _contract address to check whether it's a contract or not function _isContract(address _contract) internal view returns (bool isContract) { uint256 containerCodeSize; assembly { containerCodeSize := extcodesize(_contract) } return (containerCodeSize > 0); } /// @notice This function is used to verify CASTL and Approver signatures for all of the COWSManagement functions (eg. `pauseCOWS()`, `unpauseCOWS()`, etc...) /// @param cowId unique identifier for COWS container /// @param expiredAt block timestamp to expire each signature /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature signature signed by CASTL using the 'methodType' message format /// @param approvalSignatures signatures from approvers using the 'methodType' message format. Also contains 'userId' /// @param methodType string to dictate the 'methodType' used in the formed message format function _verifyCOWSActionMsgs( bytes32 cowId, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures, string memory methodType ) internal view { IFunctions container = IFunctions(cowsContainers[cowId].contractAddress); uint256 approvalSignaturesLength = approvalSignatures.length; require( approvalSignaturesLength > 2 && approvalSignaturesLength < 6, "invalid signature amount" ); // Form message of common parts string[] memory parts = new string[](4); // Preallocating 4 string slots in memory parts[0] = SigUtil.bytesToHex(cowId); // cowsId parts[1] = methodType; // methodType if ( IFunctions(cowsContainers[cowId].contractAddress).getGlobalState() != IGlobalState.COWSState.ActiveEmergency || castlSignature.length != 0 ) { require(castlSignature.length != 0, "required castl sig"); // Form CASTL message for 3 of 5 parts[2] = approvalSignaturesLength.toString(); // totalSigNum parts[3] = SigUtil.concatApproverSignatures(approvalSignatures); // approvalSignatures // Verify castl key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require( container.getCASTLKey(castlKey) == IKeyManagement.KeyStatus.Enabled, "invalid castl sig" ); } // Check for duplicate approval userIds or signatures SigUtil._checkNoDupes(approvalSignatures); // Prepare the relevant hash raw for each approver message parts[2] = expiredAt.toString(); // expiredAt // If no requestId is passed, remove from message if (requestId == bytes32(0)) { SigUtil.shrinkStrArray(parts, 3); // Shrink 'parts' to 3 // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); } else { parts[3] = SigUtil.bytesToHex(requestId); // requestId // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); } SigUtil.shrinkStrArray(parts, 2); // Shrink 'parts' to 2 // At this point, 'parts' looks like this: // [cowId, methodTypel, hashRaw] // 'cowId' and 'methodType' will be replaced with each approver's 'userId' in 'verifyApproverSignature' within the loop for (uint256 i; i < approvalSignaturesLength; ) { address approvalKey = SigUtil.verifyApproverSignature(parts, approvalSignatures[i]); require( container.isValidApprovalAddress(approvalSignatures[i].userId, approvalKey), "invalid approver sig" ); unchecked { ++i; } } } /// @notice This function is used by COWS management key & CASTL to recognize a deployed COWS contract suite and shows intent to initialize it /// @param cowId unique identifier for COWS container to recognize /// @param accountType account type of the specific COWS container /// @param containerAddress diamond proxy address for the specific COWS container /// @param prevActiveCowId unique identifier for a previous active COWS container. Used to form the message signed in `prevActiveCastlSignature` /// @param expiredAt block timestamp when 'cowsMgmtKeySignature' and 'prevActiveCastlSignature' expires /// @param cowsMgmtKeySignature signature signed by COWS management key with the following message format: `"{cowId}//recognize//{accountType}//{containerAddress}//"` /// @param castlSignature signature signed by CASTL key with the following message format: `"{cowId}//recognize//{accountType}//{containerAddress}//cowsMgmtKeySignature//prevActiveCastlSignature//"` /// @param prevActiveCastlSignature previous active CASTL signature signed by an active CASTL key with the following message format: `"{cowId}//recognize//{accountType}//{containerAddress}//"` function recognize( bytes32 cowId, AccountType accountType, address containerAddress, bytes32 prevActiveCowId, uint256 expiredAt, bytes calldata cowsMgmtKeySignature, bytes calldata castlSignature, bytes calldata prevActiveCastlSignature ) external { require(expiredAt > block.timestamp, "expired request"); // Check if the cowId is already existed in recognizeRecords require(recognizeRecords[cowId].length == 0, "cow id is recognized"); // Check that the container's internal cowId matches the passed in 'cowId' { require(_isContract(containerAddress), "invalid contract"); // a low level call is used to prevent a generic evm revert on uniplemented methods on 'containerAddress' // staticcall is used to prevent any possible state changes (bool success, bytes memory data) = containerAddress.staticcall( abi.encodeWithSelector(IGlobalState.getCowId.selector) ); require(success, "invalid contract"); require(abi.decode(data, (bytes32)) == cowId, "invalid cowId"); } { string[] memory sharedParts = new string[](7); SigUtil.shrinkStrArray(sharedParts, 5); // Shrink to length of 5 to form cowsMgmtSig message // Form signing message for cowsMgmtKeySignature { sharedParts[0] = SigUtil.bytesToHex(cowId); // cowsId sharedParts[1] = "recognize"; // methodType sharedParts[2] = uint256(accountType).toString(); // accountType sharedParts[3] = containerAddress.toHexString(); // containerAddress sharedParts[4] = expiredAt.toString(); // expiredAt // Check if recovered signer is a valid cowsMgmtKeyAddr address cowsMgmtKeyAddr = SigUtil.verifySignature( cowsMgmtKeySignature, SigUtil.formatMessageWithDelimiter(sharedParts) ); require(isCowsManagementKey(cowsMgmtKeyAddr), "invalid cowsMgmt sig"); } // Check if there are any active containers, if none form the CASTL message with cowsMgmtKeySignature if (totalActiveContainers == 0) { // Form signing message for castlSignature assembly { mstore(sharedParts, 6) // Set 'sharedParts' length to 6 } sharedParts[5] = SigUtil.signatureToString(cowsMgmtKeySignature); // cowsMgmtKeySignature } else { // If there is a previous active CASTL Key, use it in the formed message IFunctions prevActiveContainer = IFunctions( cowsContainers[prevActiveCowId].contractAddress ); require(address(prevActiveContainer) != address(0), "invalid prev cowId"); require(prevActiveContainer.getTotalActiveCASTLKeys() > 0, "No Active CASTL Keys."); require( prevActiveContainer.getGlobalState() != IGlobalState.COWSState.Deployed, "invalid prev global state" ); // Check if previously used castlKey is valid address prevCastlKey = SigUtil.verifySignature( prevActiveCastlSignature, SigUtil.formatMessageWithDelimiter(sharedParts) ); require( prevActiveContainer.getCASTLKey(prevCastlKey) == IKeyManagement.KeyStatus.Enabled, "invalid prevActiveCastl sig" ); assembly { mstore(sharedParts, 7) // Set 'parts' length to 7 } sharedParts[5] = SigUtil.signatureToString(cowsMgmtKeySignature); // cowsMgmtKeySignature sharedParts[6] = SigUtil.signatureToString(prevActiveCastlSignature); // previously used castlSignature } // Check if previously used castlKey is valid address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(sharedParts) ); require( KeyManagementStoreFacet(containerAddress).getCASTLKey(castlKey) == IKeyManagement.KeyStatus.Enabled, "invalid castl sig" ); } recognizeRecords[cowId] = castlSignature; ContainerInfo storage container = cowsContainers[cowId]; container.contractAddress = containerAddress; container.accountType = accountType; containerIds[containerAddress] = cowId; // Update totalActiveContainers ++totalActiveContainers; IFunctions(containerAddress).setGlobalState(IGlobalState.COWSState.Recognized); // Emit event Recognize() emit Recognize(); } /// @notice This function is used to pause a COWS container, and temporarily prevent asset/custody actions /// @param cowId unique identifier for COWS container to pause. Used to form the message signed in `castlSignature` /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function pauseCOWS( bytes32 cowId, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { address containerAddress = cowsContainers[cowId].contractAddress; require(containerAddress != address(0), "invalid cowId"); require( GlobalStateFacet(containerAddress).getGlobalState() == IGlobalState.COWSState.ActiveNormal, "invalid global state" ); require(expiredAt > block.timestamp, "expired request"); require(!cowsMgmtRequests[requestId], "request already used"); // Verify CASTL and approver messages using the 'pauseCOWS' message format _verifyCOWSActionMsgs( cowId, expiredAt, requestId, castlSignature, approvalSignatures, "pauseCOWS" ); cowsMgmtRequests[requestId] = true; GlobalStateFacet(containerAddress).setGlobalState(IGlobalState.COWSState.Paused); emit COWSAction(requestId, "Pause", cowId); } /// @notice This function is used to unpause a specific COWS container if it's paused /// @param cowId unique identifier for the COWS container to be unpaused /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function unpauseCOWS( bytes32 cowId, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { address containerAddress = cowsContainers[cowId].contractAddress; require(containerAddress != address(0), "invalid cowId"); require( GlobalStateFacet(containerAddress).getGlobalState() == IGlobalState.COWSState.Paused, "invalid global state" ); require(expiredAt > block.timestamp, "expired request"); require(!cowsMgmtRequests[requestId], "request already used"); // Verify CASTL and approver messages using the 'unpauseCOWS' message format _verifyCOWSActionMsgs( cowId, expiredAt, requestId, castlSignature, approvalSignatures, "unpauseCOWS" ); cowsMgmtRequests[requestId] = true; GlobalStateFacet(containerAddress).setGlobalState(IGlobalState.COWSState.ActiveNormal); emit COWSAction(requestId, "Unpause", cowId); } /// @notice This function is used to expire current active COWS management key and enable 1 inactive COWS management key to be new one if available /// @param cowId unique identifier of COWS container that the CASTL key used to sign 'castlSignature' from /// @param deprecateKey the current active COWS management key is going to be deprecated with this call /// @param enableKey any of inactive COWS management key from 'cowsManagementKeys' is going to be enabled with this call /// @param expiredAt block timestamp when 'castlSignature' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification function switchCowsManagementKey( bytes32 cowId, address deprecateKey, address enableKey, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature ) external { address containerAddress = cowsContainers[cowId].contractAddress; require(containerAddress != address(0), "invalid cowId"); require( GlobalStateFacet(containerAddress).getGlobalState() == IGlobalState.COWSState.ActiveNormal, "invalid global state" ); require(expiredAt > block.timestamp, "expired request"); require(!cowsMgmtRequests[requestId], "request already used"); require(cowsManagementKeys[deprecateKey] == KeyStatus.Enabled, "invalid key status"); // Verify castlSignature { string[] memory parts = new string[](6); parts[0] = SigUtil.bytesToHex(cowId); // cowsId parts[1] = "switchCowsManagementKey"; // methodType parts[2] = deprecateKey.toHexString(); // deprecateKey parts[3] = enableKey.toHexString(); // enableKey parts[4] = expiredAt.toString(); // expiredAt parts[5] = SigUtil.bytesToHex(requestId); // requestId // Check if recovered signer has valid CASTL key; address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require( KeyManagementStoreFacet(containerAddress).getCASTLKey(castlKey) == IKeyManagement.KeyStatus.Enabled, "invalid castl sig" ); } cowsManagementKeys[deprecateKey] = KeyStatus.Expired; // If there's at least 1 available key left if (cmgtKeyLeft != 0) { // Require `enableKey` exists and currently 'Inactive' require(cowsManagementKeys[enableKey] == KeyStatus.Inactive, "invalid key status"); cowsManagementKeys[enableKey] = KeyStatus.Enabled; cmgtKeyLeft--; } cowsMgmtRequests[requestId] = true; emit SwitchCmgtKey(cmgtKeyLeft); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {COWSManagement} from "./COWSManagement.sol"; import {IGlobalState} from "./interfaces/IGlobalState.sol"; import {IKeyManagement} from "./interfaces/IKeyManagement.sol"; import {IDASManagement} from "./interfaces/IDASManagement.sol"; import {GlobalStateFacet} from "./facets/GlobalStateFacet.sol"; import {DASManagementFacet} from "./facets/DASManagementFacet.sol"; import {VerifySignatureUtil as SigUtil} from "./libraries/VerifySignatureUtil.sol"; import {KeyManagementStoreFacet} from "./facets/KeyManagementStoreFacet.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; /// @title Deposit Collect /// @author Prometheum Inc. /// @notice This contract is responsible for collecting deposits from other custodial services and to move them to any COWS Container. Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Deposit-Collect-Contracts)</b> for more concept details. contract DepositCollect { using Strings for *; /// @notice Contains the address of the COWS Management contract. COWSManagement internal cowsManagement; /// @notice Mapping of `depositCollectId` to a boolean to dictate if that request has been used yet. mapping(bytes32 => bool) internal depositCollectRequests; /// @notice The **DepositCollectNotify** event is emitted at the end of the [`depositCollect()`](#depositcollect-1) function /// @dev **depositCollectId** : bytes32 received deposit (request) id from CTM and is used for [`depositCollect()`](#depositcollect-1) call which fund request we moved event DepositCollectNotify(bytes32 indexed depositCollectId); /// @notice This function mainly for clone contracts. Sets cowsManagement after the contract is deployed by factory, only 1 time /// @param _cowsManagement address of the cows management contract function initClone(address _cowsManagement) external { require(address(cowsManagement) == address(0), "init clone failed"); cowsManagement = COWSManagement(_cowsManagement); } /// @notice Returns the address of [COWSManagement](#cowsmanagement) /// @return address of the cows management contract function getCowsManagement() external view returns (address) { return address(cowsManagement); } /// @notice Returns a boolean to dictate whether a given `depositCollectId` has been used yet. /// @param depositCollectId the request to check if it has been used yet. /// @return Boolean dictating whether the given `depositCollectId` has been used yet. function isDepositCollectReqUsed(bytes32 depositCollectId) external view returns (bool) { return depositCollectRequests[depositCollectId]; } /// @notice This function is used by CASTL to move fund from deposit collect contract to any COWS container /// @param cowId unique identifier for COWS container. Used to verify the message signed in 'castlSignature' and assign destination COWS container. Should match `cowId` of destination COWS container in message format for `castlSignature` /// @param depositCollectId the received deposit (request) id from CTM. Used to identify which fund request will be processing. Should match `deposit_collect_id` in message format for `castlSignature` /// @param dasAddress the ERC20 token contract address of DAS for fund moving. Should match `dasAddress` in message format for `castlSignature` /// @param expectedDeposit the amount of fund will be moved. Should match `amount` in message format for `castlSignature` /// @param expiredAt the expiry timestamp for `castlSignature` to move fund. Should match `expire_at_block` in message format for `castlSignature` /// @param castlSignature signature signed by CASTL key with the following message format: `"depositCollect// {deposit_collect_id}// {dasAddress}// {amount}// {cowId}// {expire_at_block}//"` function depositCollect( bytes32 cowId, bytes32 depositCollectId, address dasAddress, uint256 expectedDeposit, uint256 expiredAt, bytes calldata castlSignature ) external { require(block.timestamp < expiredAt, "expired request"); require(!depositCollectRequests[depositCollectId], "request already used"); COWSManagement.ContainerInfo memory containerInfo = cowsManagement.getContainerInfo(cowId); require(containerInfo.contractAddress != address(0), "invalid cowId"); require( GlobalStateFacet(containerInfo.contractAddress).getGlobalState() == IGlobalState.COWSState.ActiveNormal, "invalid destination state" ); if (containerInfo.accountType == COWSManagement.AccountType.Omnibus) { require( DASManagementFacet(containerInfo.contractAddress).getDASStatus(dasAddress) == IDASManagement.DASStatus.Enabled, "invalid DAS status" ); } // Re-form signing message for castlSignature { string[] memory parts = new string[](7); parts[0] = "depositCollect"; // methodType parts[1] = SigUtil.bytesToHex(depositCollectId); // depositCollectId parts[2] = address(this).toHexString(); // cloneAddr parts[3] = dasAddress.toHexString(); // dasAddress parts[4] = expectedDeposit.toString(); // expectedDeposit parts[5] = SigUtil.bytesToHex(cowId); // cowsId parts[6] = expiredAt.toString(); // expiredAt address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require( KeyManagementStoreFacet(containerInfo.contractAddress).getCASTLKey(castlKey) == IKeyManagement.KeyStatus.Enabled, "invalid castl sig" ); } depositCollectRequests[depositCollectId] = true; if (dasAddress == address(0)) { (bool sent, ) = payable(containerInfo.contractAddress).call{value: expectedDeposit}(""); require(sent, "Collect deposit ETH failed"); } else { require( IERC20(dasAddress).transfer(containerInfo.contractAddress, expectedDeposit), "Collect deposit ERC20 failed" ); } emit DepositCollectNotify(depositCollectId); } /// @notice This function is added to receive ETH in this contract receive() external payable {} }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {AppStorage} from "./libraries/AppStorage.sol"; import {LibDiamond} from "./libraries/LibDiamond.sol"; import {IDiamond} from "./interfaces/IDiamond.sol"; /// @title COWS Container /// @author Prometheum Inc. /// @notice This contract is a proxy contract that delegates logic to [Facet](/src/facets) contracts. Each container contract represents a whole COWS container. /// @dev This proxy contract uses a minimal, immutable [Diamond Proxy pattern](https://eips.ethereum.org/EIPS/eip-2535) that cannot be upgraded after this container is deployed. This also uses AppStorage and DiamondStorage as its shared storage methods. contract Diamond is IDiamond { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; /// @notice Initalize all necessary AppStorage and links all Facet contracts in Diamond Storage on deployment. constructor( bytes32 cowId, address cowMgmtAddr, address[] memory castlKeys, FacetCut[] memory initialCuts ) { LibDiamond._setCowId(cowId); // set cowId in AppStorage LibDiamond._setCowsMgmt(cowMgmtAddr); // set cows management in AppStorage LibDiamond._setCastlKeys(castlKeys); // set castl keys in AppStorage s.totalActiveCastlKeys += uint8(castlKeys.length); _cutNewDiamondFacets(initialCuts); // link each 'initialCuts.selector' to 'initialCuts.facetAddress' } /// @notice This is a internal function called within the `constructor` that cuts all of the facet contract into Diamond Storage /// @param cuts List of facets and selectors to include in Diamond Storage function _cutNewDiamondFacets(FacetCut[] memory cuts) internal { // loop through diamond cut uint256 diamondCutsLength = cuts.length; for (uint256 i; i < diamondCutsLength; ) { LibDiamond.addFacetSelectors(cuts[i].facetAddress, cuts[i].functionSelectors); unchecked { ++i; } } } /// @notice This is a special function that is called when trying to call a function that is not declared within the the contract. This function will then:<br/>1. Find the intended function selector in calldata.<br/>2. Find the facet mapped to that intended function selector in Diamond Storage.<br/>3. Use `delegatecall` to forward all calldata and state context to that facet contract fallback() external payable { // get diamond storage LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); address facet = ds.facets[msg.sig]; // get facet from function selector require(facet != address(0), "Diamond: Function does not exist"); // Execute external function from facet using delegatecall and return any value. assembly { calldatacopy(0, 0, calldatasize()) // copy function selector and any arguments let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) // execute function call using the facet returndatacopy(0, 0, returndatasize()) // get any return value switch result // return any return value or error back to the caller case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /// @notice This function is called when trying to call this contract with `msg.value` > 0, but no calldata _(if there's any calldata, it will call `fallback()` instead)_. It currently doesn't do/change anything, but it can be extended further if we'd like to. receive() external payable virtual {} }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {AppStorage} from "../libraries/AppStorage.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; import {KeyMgmtUtil} from "../libraries/KeyMgmtUtil.sol"; import {COWSManagement} from "../COWSManagement.sol"; import {ICustodyWallet} from "../interfaces/ICustodyWallet.sol"; import {IGlobalState} from "../interfaces/IGlobalState.sol"; import {IDASManagement} from "../interfaces/IDASManagement.sol"; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; /// @title Custody Wallet /// @author Prometheum Inc. /// @notice This Facet is responsible for all Custodial actions for DAS such as `dasApprovalCheck()` and `withdraw()`. This is the version used for Omnibus containers (for Investigation/Garbage, see [CustodyWalletUnrestrictedFacet](/src/facets/CustodyWalletFacet.sol/contract.CustodyWalletUnrestrictedFacet.html)) contract CustodyWalletFacet is ICustodyWallet { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; using Strings for *; /// @inheritdoc ICustodyWallet function dasApprovalCheck( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external returns (bool isSuccessful) { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require( s.globalState == IGlobalState.COWSState.ActiveNormal || s.globalState == IGlobalState.COWSState.ActiveEmergency, "invalid global state" ); uint256 approvalSignaturesLength = approvalSignatures.length; require( approvalSignaturesLength > 2 && approvalSignaturesLength < 6, "invalid signature amount" ); // Check for duplicate approval userIds or signatures SigUtil._checkNoDupes(approvalSignatures); { // Form message of common parts string[] memory parts = new string[](5); parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "dasApprovalCheck"; // methodType if ( s.globalState == IGlobalState.COWSState.ActiveNormal || (s.globalState == IGlobalState.COWSState.ActiveEmergency && castlSignature.length != 0) ) { require(castlSignature.length != 0, "required castl sig"); // Form CASTL message for 3 of 5 SigUtil.shrinkStrArray(parts, 4); // Set 'parts' length to 4 to form the CASTL message parts[2] = approvalSignaturesLength.toString(); // totalSigNum parts[3] = SigUtil.concatApproverSignatures(approvalSignatures); // approvalSignatures // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); assembly { mstore(parts, 5) // Set 'parts' length back to 5 } } // Prepare the relevant hash raw for each approver message parts[2] = dasAddress.toHexString(); // dasAddress parts[3] = expiredAt.toString(); // expiredAt parts[4] = SigUtil.bytesToHex(requestId); // requestId // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); SigUtil.shrinkStrArray(parts, 2); // Shrink 'parts' to 2 // At this point, 'parts' looks like this: // cowId//hashRaw // 'cowId' will be replaced with each approver's 'userId' in 'verifyApproverSignature' within the loop for (uint256 i; i < approvalSignaturesLength; ) { address approvalKey = SigUtil.verifyApproverSignature(parts, approvalSignatures[i]); require( KeyMgmtUtil.verifyApprovalAddress(approvalSignatures[i].userId, approvalKey), "invalid approver sig" ); unchecked { ++i; } } } s.requestIds[requestId] = true; // Emit event DASApprovalCheck(requestId, dasAddress, balance) emit DASApprovalCheck( requestId, dasAddress, dasAddress == address(0) ? address(this).balance : IERC20(dasAddress).balanceOf(address(this)) ); return true; } /// @inheritdoc ICustodyWallet function withdraw( address to, uint256 amount, address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(s.globalState == IGlobalState.COWSState.ActiveNormal, "invalid global state"); IDASManagement.DASStatus dasStatus = s.dasStatus[dasAddress]; require( dasStatus == IDASManagement.DASStatus.Enabled || dasStatus == IDASManagement.DASStatus.PauseDeposit, "invalid DAS status" ); require(!s.requestIds[requestId], "request already used"); // If 'to' is a COWS container, if (s.cowsManagement.getCowId(to) != bytes32(0)) { COWSManagement.ContainerInfo memory containerInfo = s.cowsManagement.getContainerInfo( s.cowsManagement.getCowId(to) ); // Check global state for any type of container IGlobalState.COWSState toGlobalState = IGlobalState(containerInfo.contractAddress) .getGlobalState(); require(toGlobalState == IGlobalState.COWSState.ActiveNormal, "invalid destination state"); // Check DAS status only for Omnibus container if (containerInfo.accountType == COWSManagement.AccountType.Omnibus) { require( IDASManagement(containerInfo.contractAddress).getDASStatus(dasAddress) == IDASManagement.DASStatus.Enabled, "DAS must be enabled" ); } } uint256 approvalSignaturesLength = approvalSignatures.length; require( approvalSignaturesLength > 2 && approvalSignaturesLength < 6, "invalid signature amount" ); // Check for duplicate approval userIds or signatures SigUtil._checkNoDupes(approvalSignatures); // Form message of common parts string[] memory parts = new string[](7); parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "approveWithdrawal"; // methodType { // Form CASTL message for 3 of 5 SigUtil.shrinkStrArray(parts, 4); // Set 'parts' length to 4 to form the CASTL message parts[2] = approvalSignaturesLength.toString(); // totalSigNum parts[3] = SigUtil.concatApproverSignatures(approvalSignatures); // approvalSignatures // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); assembly { mstore(parts, 7) // Set 'parts' length back to 7 } } // Prepare the relevant hash raw for each approver message parts[2] = dasAddress.toHexString(); // dasAddress parts[3] = amount.toString(); // amount parts[4] = to.toHexString(); // to parts[5] = expiredAt.toString(); // expiredAt parts[6] = SigUtil.bytesToHex(requestId); // requestId // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); SigUtil.shrinkStrArray(parts, 2); // Shrink 'parts' to length of 2 // At this point, 'parts' looks like this: // cowId//hashRaw// // 'cowId' will be replaced with each approver's 'userId' in 'verifyApproverSignature' within the loop for (uint256 i = 0; i < approvalSignaturesLength; ) { address approvalKey = SigUtil.verifyApproverSignature(parts, approvalSignatures[i]); require( KeyMgmtUtil.verifyApprovalAddress(approvalSignatures[i].userId, approvalKey), "invalid approver sig" ); unchecked { ++i; } } s.requestIds[requestId] = true; if (dasAddress == address(0)) { (bool sent, ) = payable(to).call{value: amount}(""); require(sent, "withdraw ETH failed"); } else { require(IERC20(dasAddress).transfer(to, amount), "withdraw DAS failed"); } emit WithdrawalRequest(requestId); } } /// @title Custody Wallet /// @author Prometheum Inc. /// @notice This Facet is responsible for all Custodial actions for DAS such as `dasApprovalCheck()` and `withdraw()`. This is the version used for Investigation/Garbage containers (for Omnibus, see [CustodyWalletFacet](/src/facets/CustodyWalletFacet.sol/contract.CustodyWalletFacet.html)) contract CustodyWalletUnrestrictedFacet is ICustodyWallet { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; using Strings for *; /// @inheritdoc ICustodyWallet function dasApprovalCheck( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external returns (bool) { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require( s.globalState == IGlobalState.COWSState.ActiveNormal || s.globalState == IGlobalState.COWSState.ActiveEmergency, "invalid global state" ); uint256 approvalSignaturesLength = approvalSignatures.length; require( approvalSignaturesLength > 2 && approvalSignaturesLength < 6, "invalid signature amount" ); // Check for duplicate approval userIds or signatures SigUtil._checkNoDupes(approvalSignatures); { // Form message of common parts string[] memory parts = new string[](5); parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "dasApprovalCheck"; // methodType if ( s.globalState == IGlobalState.COWSState.ActiveNormal || (s.globalState == IGlobalState.COWSState.ActiveEmergency && castlSignature.length != 0) ) { require(castlSignature.length != 0, "required castl sig"); // Form CASTL message for 3 of 5 SigUtil.shrinkStrArray(parts, 4); // Set 'parts' length to 4 to form the CASTL message parts[2] = approvalSignaturesLength.toString(); // totalSigNum parts[3] = SigUtil.concatApproverSignatures(approvalSignatures); // approvalSignatures // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); assembly { mstore(parts, 5) // Set 'parts' length back to 5 } } // Prepare the relevant hash raw for each approver message parts[2] = dasAddress.toHexString(); // dasAddress parts[3] = expiredAt.toString(); // expiredAt parts[4] = SigUtil.bytesToHex(requestId); // requestId // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); SigUtil.shrinkStrArray(parts, 2); // Shrink 'parts' to 2 // At this point, 'parts' looks like this: // cowId//hashRaw// // 'cowId' will be replaced with each approver's 'userId' in 'verifyApproverSignature' within the loop for (uint256 i; i < approvalSignaturesLength; ) { address approvalKey = SigUtil.verifyApproverSignature(parts, approvalSignatures[i]); require( KeyMgmtUtil.verifyApprovalAddress(approvalSignatures[i].userId, approvalKey), "invalid approver sig" ); unchecked { ++i; } } } s.requestIds[requestId] = true; // Emit event DASApprovalCheck(dasAddress, balance) emit DASApprovalCheck( requestId, dasAddress, dasAddress == address(0) ? address(this).balance : IERC20(dasAddress).balanceOf(address(this)) ); return true; } /// @inheritdoc ICustodyWallet function withdraw( address to, uint256 amount, address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require(s.globalState == IGlobalState.COWSState.ActiveNormal, "invalid global state"); // If 'to' is a COWS container, if (s.cowsManagement.getCowId(to) != bytes32(0)) { COWSManagement.ContainerInfo memory containerInfo = s.cowsManagement.getContainerInfo( s.cowsManagement.getCowId(to) ); // Check global state for any type of container IGlobalState.COWSState toGlobalState = IGlobalState(containerInfo.contractAddress) .getGlobalState(); require(toGlobalState == IGlobalState.COWSState.ActiveNormal, "invalid destination state"); // Check DAS status only for Omnibus container if (containerInfo.accountType == COWSManagement.AccountType.Omnibus) { require( IDASManagement(containerInfo.contractAddress).getDASStatus(dasAddress) == IDASManagement.DASStatus.Enabled, "DAS must be enabled" ); } } require( approvalSignatures.length > 2 && approvalSignatures.length < 6, "invalid signature amount" ); // Check for duplicate approval userIds or signatures SigUtil._checkNoDupes(approvalSignatures); // Form message of common parts string[] memory parts = new string[](7); parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "approveWithdrawal"; // methodType { // Form CASTL message for 3 of 5 SigUtil.shrinkStrArray(parts, 4); // Set 'parts' length to 4 to form the CASTL message parts[2] = approvalSignatures.length.toString(); // totalSigNum parts[3] = SigUtil.concatApproverSignatures(approvalSignatures); // approvalSignatures // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); assembly { mstore(parts, 7) // Set 'parts' length back to 7 } } // Prepare the relevant hash raw for each approver message parts[2] = dasAddress.toHexString(); // dasAddress parts[3] = amount.toString(); // amount parts[4] = to.toHexString(); // to parts[5] = expiredAt.toString(); // expiredAt parts[6] = SigUtil.bytesToHex(requestId); // requestId // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); SigUtil.shrinkStrArray(parts, 2); // Shrink 'parts' to length of 2 // At this point, 'parts' looks like this: // cowId//hashRaw// // 'cowId' will be replaced with each approver's 'userId' in 'verifyApproverSignature' within the loop for (uint256 i; i < approvalSignatures.length; ) { require( KeyMgmtUtil.verifyApprovalAddress( approvalSignatures[i].userId, SigUtil.verifyApproverSignature(parts, approvalSignatures[i]) ), "invalid approver sig" ); unchecked { ++i; } } s.requestIds[requestId] = true; if (dasAddress == address(0)) { (bool sent, ) = payable(to).call{value: amount}(""); require(sent, "withdraw ETH failed"); } else { require(IERC20(dasAddress).transfer(to, amount), "withdraw DAS failed"); } emit WithdrawalRequest(requestId); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {AppStorage} from "../libraries/AppStorage.sol"; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; import {IGlobalState} from "../interfaces/IGlobalState.sol"; import {IDASManagement} from "../interfaces/IDASManagement.sol"; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; import {KeyMgmtUtil} from "../libraries/KeyMgmtUtil.sol"; import {COWSManagement} from "../COWSManagement.sol"; /// @title DAS Management /// @author Prometheum Inc. /// @notice This Facet is responsible for the management of DAS using operations such as `addDAS()`, `disableDAS()`, `pauseDAS()`, etc.. /// @dev This Facet is only found in Omnibus container types contract DASManagementFacet is IDASManagement { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; using Strings for *; /// @inheritdoc IDASManagement function getDASStatus(address dasAddress) external view returns (DASStatus info) { return s.dasStatus[dasAddress]; } /// @notice This function is used to verify CASTL and Approver signatures for all of the DAS Status modification functions (eg. `addDAS()`, `disableDAS()`, `pauseDASDeposits()`, etc...) /// @param dasAddress contract address of the DAS /// @param expiredAt block timestamp when `castlSignature` and each `approvalSignatures` expires /// @param requestId unique identifier for `methodType` requests /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param castlSignature signature signed by CASTL using the 'methodType' message format /// @param approvalSignatures signatures from approvers using the 'methodType' message format. Also contains 'userId' /// @param methodType string to dictate the 'methodType' used in the formed message format function _verifyDASStatusMsgs( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures, string memory methodType ) internal view { uint256 approvalSignaturesLength = approvalSignatures.length; require( approvalSignaturesLength > 2 && approvalSignaturesLength < 6, "invalid signature amount" ); // Check for duplicate approval userIds or signatures SigUtil._checkNoDupes(approvalSignatures); // Form message of common parts string[] memory parts = new string[](5); parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = methodType; // methodType if ( s.globalState == IGlobalState.COWSState.ActiveNormal || (s.globalState == IGlobalState.COWSState.ActiveEmergency && castlSignature.length != 0) ) { require(castlSignature.length != 0, "required castl sig"); // Form CASTL message for 3 of 5 SigUtil.shrinkStrArray(parts, 4); // Shrink to length of 4 to form CASTL message parts[2] = approvalSignaturesLength.toString(); // totalSigNum parts[3] = SigUtil.concatApproverSignatures(approvalSignatures); // approvalSignatures // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); assembly { mstore(parts, 5) // Set 'parts' length back to 5 } } // Prepare the relevant hash raw for each approver message parts[2] = dasAddress.toHexString(); // dasAddress parts[3] = expiredAt.toString(); // expiredAt parts[4] = SigUtil.bytesToHex(requestId); // requestId // Form the hash raw, hash it with keccak256, cast it to its string representation, then set it to parts[1] parts[1] = SigUtil.bytesToHex(keccak256(bytes(SigUtil.formatMessageWithDelimiter(parts)))); SigUtil.shrinkStrArray(parts, 2); // Shrink 'parts' to 2 // At this point, 'parts' looks like this: // cowId//hashRaw// // 'cowId' will be replaced with each approver's 'userId' in 'verifyApproverSignature' within the loop for (uint256 i; i < approvalSignaturesLength; ) { address approvalKey = SigUtil.verifyApproverSignature(parts, approvalSignatures[i]); require( KeyMgmtUtil.verifyApprovalAddress(approvalSignatures[i].userId, approvalKey), "invalid approver sig" ); unchecked { ++i; } } } /// @inheritdoc IDASManagement function addDAS( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require(s.dasStatus[dasAddress] == DASStatus.NotAdded, "invalid DAS status"); IGlobalState.COWSState _globalState = s.globalState; // Check signatures by different conditions if (_globalState == IGlobalState.COWSState.Recognized) { // If it's under recognized state, [castl-sig-only] string[] memory parts = new string[](5); { parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "addDAS"; // methodType parts[2] = dasAddress.toHexString(); // dasAddress parts[3] = expiredAt.toString(); // expiredAt parts[4] = SigUtil.bytesToHex(requestId); // requestId } // Check CASTL signature address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); } else if ( // If the container is under ActiveNormal/ActiveEmergency _globalState == IGlobalState.COWSState.ActiveNormal || _globalState == IGlobalState.COWSState.ActiveEmergency ) { // Verify CASTL and approver messages using the 'addDAS' message format _verifyDASStatusMsgs( dasAddress, expiredAt, requestId, castlSignature, approvalSignatures, "addDAS" ); } else { // If the container is not in Recognized or ActiveNormal/ActiveEmergency revert("invalid global state"); } s.dasStatus[dasAddress] = DASStatus.Enabled; s.requestIds[requestId] = true; emit DASManagementAction(requestId, "AddDAS", dasAddress); } /// @inheritdoc IDASManagement function pauseDASDeposits( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require(s.dasStatus[dasAddress] == DASStatus.Enabled, "invalid DAS status"); require( s.globalState == IGlobalState.COWSState.ActiveNormal || s.globalState == IGlobalState.COWSState.ActiveEmergency, "invalid global state" ); // Verify CASTL and approver messages using the 'pauseDASDeposits' message format _verifyDASStatusMsgs( dasAddress, expiredAt, requestId, castlSignature, approvalSignatures, "pauseDASDeposits" ); s.dasStatus[dasAddress] = DASStatus.PauseDeposit; s.requestIds[requestId] = true; emit DASManagementAction(requestId, "PauseDASDeposits", dasAddress); } /// @inheritdoc IDASManagement function pauseDASDepositsAndWithdrawals( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); DASStatus dasStatus = s.dasStatus[dasAddress]; require( dasStatus == DASStatus.Enabled || dasStatus == DASStatus.PauseDeposit, "invalid DAS status" ); require( s.globalState == IGlobalState.COWSState.ActiveNormal || s.globalState == IGlobalState.COWSState.ActiveEmergency, "invalid global state" ); // Verify CASTL and approver messages using the 'pauseDASDepositsAndWithdrawals' message format _verifyDASStatusMsgs( dasAddress, expiredAt, requestId, castlSignature, approvalSignatures, "pauseDASDepositsAndWithdrawals" ); s.dasStatus[dasAddress] = DASStatus.PauseDepositAndWithdraw; s.requestIds[requestId] = true; emit DASManagementAction(requestId, "PauseDASDepositsAndWithdrawals", dasAddress); } /// @inheritdoc IDASManagement function disableDAS( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require( s.globalState == IGlobalState.COWSState.ActiveNormal || s.globalState == IGlobalState.COWSState.ActiveEmergency, "invalid global state" ); // Require that the das status has been added require(s.dasStatus[dasAddress] != DASStatus.NotAdded, "invalid DAS status"); // Verify CASTL and approver messages using the 'disableDAS' message format _verifyDASStatusMsgs( dasAddress, expiredAt, requestId, castlSignature, approvalSignatures, "disableDAS" ); s.dasStatus[dasAddress] = DASStatus.Disabled; s.requestIds[requestId] = true; emit DASManagementAction(requestId, "DisableDAS", dasAddress); } /// @inheritdoc IDASManagement function enableDAS( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require( s.globalState == IGlobalState.COWSState.ActiveNormal || s.globalState == IGlobalState.COWSState.ActiveEmergency, "invalid global state" ); DASStatus dasStatus = s.dasStatus[dasAddress]; require( dasStatus != DASStatus.NotAdded && dasStatus != DASStatus.Enabled, "invalid DAS status" ); // Verify CASTL and approver messages using the 'enableDAS' message format _verifyDASStatusMsgs( dasAddress, expiredAt, requestId, castlSignature, approvalSignatures, "enableDAS" ); s.dasStatus[dasAddress] = DASStatus.Enabled; s.requestIds[requestId] = true; emit DASManagementAction(requestId, "EnableDAS", dasAddress); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {AppStorage} from "../libraries/AppStorage.sol"; import {IDirectDeposit} from "../interfaces/IDirectDeposit.sol"; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; import {IGlobalState} from "../interfaces/IGlobalState.sol"; import {IDASManagement} from "../interfaces/IDASManagement.sol"; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; /// @title Direct Deposit /// @author Prometheum Inc. /// @notice This is the Facet to allow users to deposit directly to COWS Containers using their own wallet (eg. Metamask) and a previously approved CASTL signature. /// @dev This Facet is only found in Omnibus container types contract DirectDepositFacet is IDirectDeposit { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; using Strings for *; /// @inheritdoc IDirectDeposit function getDirectDeposits(bytes32 depositId) external view returns (bytes memory signature) { return s.directDeposits[depositId]; } /// @inheritdoc IDirectDeposit function directDeposit( bytes32 depositDirectId, address dasAddress, uint256 amount, uint256 expiredAt, bytes calldata castlSignature ) external payable { require(s.globalState == IGlobalState.COWSState.ActiveNormal, "invalid global state"); require(block.timestamp < expiredAt, "expired request"); require(!s.requestIds[depositDirectId], "request already used"); require(s.dasStatus[dasAddress] == IDASManagement.DASStatus.Enabled, "invalid DAS status"); { // Form CASTL only message string[] memory parts = new string[](7); parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "directDeposit"; // methodType parts[2] = SigUtil.bytesToHex(depositDirectId); // depositDirectId parts[3] = msg.sender.toHexString(); // source parts[4] = dasAddress.toHexString(); // dasAddress parts[5] = amount.toString(); // amount parts[6] = expiredAt.toString(); // expiredAt // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); } if (dasAddress == address(0)) { require(amount == msg.value, "invalid ETH amount sent"); } s.directDeposits[depositDirectId] = castlSignature; s.requestIds[depositDirectId] = true; if (dasAddress != address(0)) { // Transfer ERC20 fund to the custody wallet within the same COW require( IERC20(dasAddress).transferFrom(msg.sender, address(this), amount), "Direct deposit ERC20 failed" ); } emit DirectDepositRequest(depositDirectId, dasAddress, amount, msg.sender.toHexString()); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {AppStorage} from "../libraries/AppStorage.sol"; import {IGlobalState} from "../interfaces/IGlobalState.sol"; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; import {KeyMgmtUtil} from "../libraries/KeyMgmtUtil.sol"; import {COWSManagement} from "../COWSManagement.sol"; /// @title Global State /// @author Prometheum Inc. /// @notice This is the Facet used to maintain the COWS Container statuses (eg. global states). contract GlobalStateFacet is IGlobalState { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; using Strings for *; /// @inheritdoc IGlobalState function getCowId() external view returns (bytes32 cowId) { return s.cowId; } /// @inheritdoc IGlobalState function getGlobalState() external view returns (COWSState state) { return s.globalState; } /// @inheritdoc IGlobalState function isKeyUsed(address keyAddress) external view returns (bool isUsed) { isUsed = s.isUserKeyUsed[keyAddress]; } /// @inheritdoc IGlobalState function isReqIdUsed(bytes32 requestId) external view returns (bool isUsed) { isUsed = s.requestIds[requestId]; } /// @inheritdoc IGlobalState function setGlobalState(COWSState state) external { require(msg.sender == address(s.cowsManagement), "caller must be cows management"); s.globalState = state; emit SetGlobalState(state); } /// @inheritdoc IGlobalState function confirmApproversKeys( uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo calldata approvalSignature ) external { require(expiredAt > block.timestamp, "expired request"); require(!s.requestIds[requestId], "request already used"); require(s.globalState == COWSState.ActiveNormal, "invalid global state"); IKeyManagement.EmergencyReplacement storage emergencyReplacement = s.emergencyKeyReplacements[ s.activeEmgRequestId ]; require(approvalSignature.userId == emergencyReplacement.userId, "invalid user"); // Form CASTL message string[] memory parts = new string[](5); SigUtil.shrinkStrArray(parts, 3); // Shrink to length of 3 to form CASTL message parts[0] = SigUtil.bytesToHex(s.cowId); // cowsId parts[1] = "confirmApproversKeys"; // methodType parts[2] = SigUtil.signatureToString(approvalSignature.signature); // approvalSignature // Verify CASTL key address castlKey = SigUtil.verifySignature( castlSignature, SigUtil.formatMessageWithDelimiter(parts) ); require(s.castlKeys[castlKey] == IKeyManagement.KeyStatus.Enabled, "invalid castl sig"); // Extend 'parts' to length of 4 for approver message assembly { mstore(parts, 5) // Normally, it's not advised not to enlarge a memory array, but the 4th slot was already preallocated when 'parts' was first instantiated } // Form approver message parts[2] = emergencyReplacement.newKeys.emergencyKey.toHexString(); // new replaced emergencyKey parts[3] = expiredAt.toString(); // expiredAt parts[4] = SigUtil.bytesToHex(requestId); // requestId // Verify new replaced approval key address approvalKey = SigUtil.verifySignature( approvalSignature.signature, SigUtil.formatMessageWithDelimiter(parts) ); require( KeyMgmtUtil.verifyApprovalAddress(approvalSignature.userId, approvalKey), "invalid approver sig" ); s.requestIds[requestId] = true; emit ConfirmApproversKeys(requestId); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {AppStorage} from "../libraries/AppStorage.sol"; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; import {KeyMgmtUtil} from "../libraries/KeyMgmtUtil.sol"; import {IKeyManagementStore, IKeyManagement} from "../interfaces/IKeyManagement.sol"; import {GlobalStateFacet} from "../facets/GlobalStateFacet.sol"; import {COWSManagement} from "../COWSManagement.sol"; import {KeyMgmtUtil} from "../libraries/KeyMgmtUtil.sol"; import {KeyMgmtUtil} from "../libraries/KeyMgmtUtil.sol"; /// @title Key Management Store /// @author Prometheum Inc. /// @notice This is the Facet that is used to return all Key Management related storage. contract KeyManagementStoreFacet is IKeyManagementStore { /// @notice This is a variable that contains all data used across the entire COWS Container (See [AppStorage](/src/libraries/AppStorage.sol/struct.AppStorage.html)). Data can be accessed by prefixing it s. (eg. bytes32 cowId = s.cowId;) AppStorage internal s; /// @inheritdoc IKeyManagementStore function getCASTLKey(address key) external view returns (KeyStatus) { return s.castlKeys[key]; } /// @inheritdoc IKeyManagementStore function getInterventionKey(address key) external view returns (KeyStatus) { return s.interventionKeys[key]; } /// @inheritdoc IKeyManagementStore function getTotalActiveCASTLKeys() external view returns (uint8) { return s.totalActiveCastlKeys; } /// @inheritdoc IKeyManagementStore function getInitiator() external view returns (bytes memory) { return s.initiator; } /// @inheritdoc IKeyManagementStore function getUserKeys(bytes32 userId) external view returns (UserKeys memory) { return s.complianceUserKeys[userId]; } /// @inheritdoc IKeyManagementStore function getTotalActiveInterventionKeys() external view returns (uint256) { return s.totalActiveInterventionKeys; } /// @inheritdoc IKeyManagementStore function getEmergencyReplacements( bytes32 requestId ) external view returns (EmergencyReplacement memory) { return s.emergencyKeyReplacements[requestId]; } /// @inheritdoc IKeyManagementStore function getUserBlocks(bytes32 userId) external view returns (UserBlocks) { return s.complianceUserKeys[userId].userBlocks; } /// @inheritdoc IKeyManagementStore function getActiveEmgRequestId() external view returns (bytes32) { return s.activeEmgRequestId; } /// @inheritdoc IKeyManagementStore function getTotalActiveUsers() external view returns (uint256) { return s.totalActiveUsers; } /// @inheritdoc IKeyManagementStore function isUserKeyUsed(address keyAddress) external view returns (bool) { return s.isUserKeyUsed[keyAddress]; } /// @inheritdoc IKeyManagementStore function isValidApprovalAddress( bytes32 userId, address recoveredAddress ) external view returns (bool) { return KeyMgmtUtil.verifyApprovalAddress(userId, recoveredAddress); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; /// @title Custody Wallet Interface /// @author Prometheum Inc. /// @notice This interface is for the [CustodyWalletFacet](/src/facets/CustodyWalletFacet.sol/contract.CustodyWalletFacet.html) and [CustodyWalletUnrestrictedFacet](/src/facets/CustodyWalletFacet.sol/contract.CustodyWalletUnrestrictedFacet.html) contract. /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Facet-Contracts)</b> for more concept details. interface ICustodyWallet { /// @notice The **DASApprovalCheck** event is emitted at the end of the [`dasApprovalCheck()`](#dasapprovalcheck) function. /// @dev **requestId** : unique identifier used for record keeping /// @dev **dasAddress** : contract address of the DAS to return the current balance of /// @dev **balance** : the balance of the checked DAS held by the container<br /> event DASApprovalCheck(bytes32 requestId, address dasAddress, uint256 balance); /// @notice The **WithdrawalRequest** event is emitted at the end of the [`withdraw()`](#withdraw) function. /// @dev **requestId** : unique identifier used for record keeping event WithdrawalRequest(bytes32 requestId); /// @notice Emits DASApprovalCheck event to check current balance for a specific DAS. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140046/CustodyWalletFacet#dasApprovalCheck)</b> /// @dev Uses the <b>[dasApprovalCheck()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#dasApprovalCheck)</b> message format. /// @param dasAddress contract of the DAS to return the current balance of /// @param expiredAt block timestamp when `castlSignature` and each `approvalSignatures` expires /// @param requestId unique identifier used for record keeping in `requestIds` mapping /// @param castlSignature signature signed by CASTL using the `dasApprovalCheck` message format /// @param approvalSignatures signatures from approvers using the `dasApprovalCheck` message format. Also contains `userId` /// @return isSuccessful true if the call was successful function dasApprovalCheck( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external returns (bool isSuccessful); /// @notice Withdraw DAS for customer by valid approvals and CASTL signature. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140046/CustodyWalletFacet#withdraw)</b> /// @dev Uses the <b>[withdraw()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#withdraw)</b> message format. /// @param to address to withdraw DAS to /// @param amount amount of DAS to withdraw /// @param dasAddress contract address of DAS to withdraw /// @param expiredAt block timestamp when `castlSignature` and each `approvalSignatures` expires /// @param requestId unique identifier used to differentiate between withdraw requests /// @param castlSignature signature signed by CASTL using the `withdraw` message format /// @param approvalSignatures signatures from approvers using the `withdraw` message format. Also contains `userId` function withdraw( address to, uint256 amount, address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; /// @title DAS Management Interface /// @author Prometheum Inc. /// @notice This interface is for the [DASManagement](/src/facets/DASManagementFacet.sol/contract.DASManagementFacet.html) contract. /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Facet-Contracts)</b> for more concept details. interface IDASManagement { /// @notice Representation of the varying statuses for DAS we support. enum DASStatus { NotAdded, // an empty status Enabled, Disabled, PauseDeposit, PauseDepositAndWithdraw } /// @notice Emitted when a DASManagement action is invoked successfully /// @dev **requestId** : unique identifier used for record keeping <br/> /// @dev **dasAction**: DAS action being executed (`addDAS()`, `pauseDASDeposits()`, `pauseDASDepositsAndWithdrawals()`, `disableDAS`, `enableDAS()`) <br /> /// @dev **dasAddress** the token contract address of the DAS to be operated <br /> event DASManagementAction(bytes32 requestId, string dasAction, address indexed dasAddress); /// @notice Returns the DASStatus of `dasAddress` in the `dasStatus` mapping within AppStorage /// @param dasAddress the address of the DAS to check the status of /// @return info DASStatus of the `dasAddress` function getDASStatus(address dasAddress) external view returns (DASStatus info); /// @notice Adds support for new DAS for `directDeposit()` or `depositCollect()`. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140128/DASManagementFacet#addDAS())</b> /// @dev Uses the <b>[addDAS()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#addDAS)</b> message format. /// @param dasAddress contract address of the DAS to add /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier for this specific 'addDAS' call /// @param castlSignature signature signed by CASTL using the 'addDAS()' message format /// @param approvalSignatures signatures from approvers using the 'addDAS()' message format. Also contains 'userId' function addDAS( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Pauses DAS deposits from `directDeposit()` and `depositCollect()` to the container specified. <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140128/DASManagementFacet#pauseDASDeposits())</b> /// @dev Uses the <b>[pauseDASDeposits()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#pauseDASDeposits)</b> message format. /// @param dasAddress token contract address of the DAS to pause /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier for this specific 'pauseDASDeposits()' call /// @param castlSignature signature signed by CASTL using the `pauseDASDeposits()` message format /// @param approvalSignatures signatures from approvers using the `pauseDASDeposits()` message format. Also contains `userId` function pauseDASDeposits( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Pauses DAS deposits and withdrawals from the container specified. <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140128/DASManagementFacet#pauseDASDepositsAndWithdrawals())</b> /// @dev Uses the <b>[pauseDASDepositsAndWithdrawals()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#pauseDASDepositsAndWithdrawals)</b> message format. /// @param dasAddress token contract address of the DAS to pause /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature signature signed by CASTL using the `pauseDASDepositsAndWithdrawals()` message format /// @param approvalSignatures signatures from approvers using the `pauseDASDepositsAndWithdrawals()` message format. Also contains `userId` function pauseDASDepositsAndWithdrawals( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Disable DAS for any transfer to the container specified. <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140128/DASManagementFacet#disableDAS())</b> /// @dev Uses the <b>[disableDAS()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#disableDAS)</b> message format. /// @param dasAddress token contract address of the DAS to disable /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier for this specific 'disableDAS' call /// @param castlSignature signature signed by CASTL using the `disableDAS()` message format /// @param approvalSignatures signatures from approvers using the `disableDAS()` message format. Also contains `userId` function disableDAS( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Enable DAS for any transfer within the container specified. <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140128/DASManagementFacet#enableDAS())</b> /// @dev Uses the <b>[enableDAS()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#enableDAS)</b> message format. /// @param dasAddress token contract address of the DAS to enable /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier for this specific 'enableDAS' call /// @param castlSignature signature signed by CASTL using the `enableDAS` message format /// @param approvalSignatures signatures from approvers using the `enableDAS` message format. Also contains `userId` function enableDAS( address dasAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {ICustodyWallet} from "./ICustodyWallet.sol"; import {IDASManagement} from "./IDASManagement.sol"; import {IDirectDeposit} from "./IDirectDeposit.sol"; import {IGlobalState} from "./IGlobalState.sol"; import {IKeyManagementEmg, IKeyManagementInit, IKeyManagementStore, IKeyManagementOthers} from "./IKeyManagement.sol"; /// @title Diamond Interface /// @author Prometheum Inc. /// @notice This interface is for the [Diamond](/src/Diamond.sol/contract.Diamond.html) contract. /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Container-Contracts)</b> for more concept details. interface IDiamond { /// @notice Represents a single Facet's address alongside all of its available external function selectors. /// @dev **facetAddress**: contract address of the Facet contract<br />**functionSelectors**: list of function selectors accessable through the `facetAddress` struct FacetCut { address facetAddress; bytes4[] functionSelectors; } } /// @title Diamond Interface /// @author Prometheum Inc. /// @notice This interface inherits all possible [Facet](/src/facets/) interfaces to create an ABI that represents the capabilities of the Diamond proxy. interface IFunctions is ICustodyWallet, IDASManagement, IDirectDeposit, IGlobalState, IKeyManagementStore, IKeyManagementEmg, IKeyManagementInit, IKeyManagementOthers {}
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; /// @title Direct Deposit Interface /// @author Prometheum Inc. /// @notice This interface is for the [DirectDeposit](/src/facets/DirectDepositFacet.sol/contract.DirectDepositFacet.html) contract. /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Facet-Contracts)</b> for more concept details. interface IDirectDeposit { /// @notice Emitted when [`directDeposit()`](#directdeposit) is invoked and executed successfully /// @dev **directDepositId**: unique identifier for a specific direct deposit request<br /> **dasAddress**: contract address of the DAS to deposit<br /> **amount**: amount of DAS to deposit<br /> **from**: address where the DAS was deposited from event DirectDepositRequest( bytes32 indexed directDepositId, address indexed dasAddress, uint256 amount, string from ); /// @notice Returns the signature used for a specific depositId used for directDeposit(). /// @param depositId specific direct deposit id to query /// @return signature castlSignature used in the specific direct deposit function getDirectDeposits(bytes32 depositId) external view returns (bytes memory signature); /// @notice Allows verified users to user their personal wallet to deposit DAS directly to container. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326140020/DirectDepositFacet#directDeposit)</b> /// @dev Uses the <b>[directDeposit()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#directDeposit)</b> message format /// @param depositDirectId unique identifier used to record keeping /// @param dasAddress contract address of DAS to deposit (can be 0 to target ETH) /// @param amount amount of DAS to deposit /// @param expiredAt block timestamp of when to expire `castlSignature` /// @param castlSignature signature signed by CASTL using the `directDeposit` message format function directDeposit( bytes32 depositDirectId, address dasAddress, uint256 amount, uint256 expiredAt, bytes calldata castlSignature ) external payable; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; /// @title Global State Interface /// @author Prometheum Inc. /// @notice This interface is for the [GlobalState](/src/facets/GlobalStateFacet.sol/contract.GlobalStateFacet.html) contract. /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Facet-Contracts)</b> for more concept details. interface IGlobalState { /// @notice Representation of varying global states of a COWS Container enum COWSState { Deployed, Recognized, ActiveNormal, ActiveEmergency, Paused } /// @notice Emitted when [`confirmApproversKeys()`](#confirmApproversKeys) is invoked and executed successfully /// @dev **requestId** : unique identifier used for uniqueness event ConfirmApproversKeys(bytes32 requestId); /// @notice Emitted when [`setGlobalState()`](#setGlobalState) is invoked and executed successfully /// @dev **newGlobalState** : the global state that the container has been set to event SetGlobalState(COWSState newGlobalState); /// @notice Returns this container’s COW identifier from AppStorage /// @return cowId of the container function getCowId() external view returns (bytes32 cowId); /// @notice Returns this container’s current global state /// @return globalState the global state of the container function getGlobalState() external view returns (COWSState globalState); /// @notice Sets the container’s global state to a valid global state value /// @param state the states to set the container's global state to function setGlobalState(COWSState state) external; /// @notice Returns a boolean value whether a specific requestId is used from 'requestIds' /// @param requestId the id of all kind requests within the same cow container function isReqIdUsed(bytes32 requestId) external view returns (bool isUsed); /// @notice Returns a boolean value whether a specific key is used from 'keyAddress' /// @param keyAddress address of the key to be checked function isKeyUsed(address keyAddress) external view returns (bool isUsed); /// @notice Void transaction to show we have replaced key set up properly before resume normal. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139980/GlobalStateFacet#confirmApproversKeys)</b> /// @dev Uses the <b>[confirmApproversKeys()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#confirmApproversKeys)</b> message format. /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignature' expires /// @param requestId unique identifier for this specific confirmApproversKeys call /// @param castlSignature signature signed by CASTL using the 'confirmApproversKeys' message format /// @param approvalSignature signature from the approver that just replaced new keys via emergency replacement using the 'confirmApproversKeys' message format. Also contains 'userId' function confirmApproversKeys( uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo calldata approvalSignature ) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; /// @title Key Management Interface /// @author Prometheum Inc. /// @notice This interface is for the Key Management related contracts. These contracts include: [KeyManagementStore](/src/facets/KeyManagementStoreFacet.sol/contract.KeyManagementStoreFacet.html)<br /> [KeyManagementInit](/src/facets/KeyManagementInitFacet.sol/contract.KeyManagementInitFacet.html)<br /> [KeyManagementEmg](/src/facets/KeyManagementEmgFacet.sol/contract.KeyManagementEmgFacet.html) /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139919/Structure+Overview#Facet-Contracts)</b> for more concept details. interface IKeyManagement { /// @notice Represents the varying states that shared with both CASTL and Intervention key can be in. enum KeyStatus { Nonexistent, Enabled, Expired } /// @notice Represents the varying states that approver and its keys attached to it can be in. enum UserStatus { Nonexistent, Enabled, Disabled, Expired } /// @notice Represents the varying blocking states that a user can be in. enum UserBlocks { None, Approval, All } /// @notice Represents the varying states that an emergency request status can be in. enum EmergencyRequestStatus { Nonexistent, Started, Executed, Rejected } /// @notice Contains all key information for each user inside the `complianceUserKeys` mapping in AppStorage /// @dev **delayTo**: block timestamp to delay the emergency key’s functionality to<br/> **emergencyKey**: address of the user’s emergency key<br/> **approvalKeys**: list of approval key addresses owned by the user<br/> **status**: status that dictates whether this user is nonexistent/ enabled/ disabled/ expired<br/> **userBlocks**: dictates if this user if block from any functionality or not<br/> struct UserKeys { uint96 delayTo; address emergencyKey; address[] approvalKeys; UserStatus status; UserBlocks userBlocks; } /// @notice Contains data relevant for each EmergencyReplacement inside the `emergencyKeyReplacements` mapping in AppStorage /// @dev **gasWallet**: msg.sender of the emergency replacement transaction<br /> **userId**: identifier of the user who initiated this emergency replacement<br /> **oldKeys**: the old keys associated to the user before this emergency replacement<br /> **newKeys**: the new keys to associate with this user after this emergency replacement<br /> **signedEmergencySignature**: signature used to start this emergency replacement<br /> **signedRejectionSignature**: signature used to interrupt this emergency replacement<br /> **delayTo**: block timestamp of when this emergency replacement can be executed<br /> **isRejected**: boolean that dictates whether this emergency replacement has been interrupted or not<br /> **status**: dictates the current status of this emergency replacement struct EmergencyReplacement { address gasWallet; bytes32 userId; UserKeys oldKeys; UserKeys newKeys; uint256 delayTo; bool isRejected; EmergencyRequestStatus status; } /// @notice Contains data tied to a specific user when initializing a container /// @dev This is used in the input of `init()` struct InputUserKey { bytes32 userId; address approvalKey; address emgKey; } /// @notice Emitted at the end of `init()` event Init(); /// @notice Emitted at the end of `addApprovalKey() and 'removeApprovalKey()` /// @dev **requestId** : unique identifier used for record keeping <br/> **actionType**: string that dictates what action was used for the approval key (eg. “Add” or "Remove") event ApprovalKeyAction(bytes32 requestId, string actionType); /// @notice Emitted at the end of `addUser()` and `removeUser()` /// @dev **requestId** : unique identifier used for record keeping <br/> **actionType**: string that dictates what action was used for the user (eg. Add, “Disable”) <br /> **userId**: userId of the User event UserAction(bytes32 requestId, string actionType, bytes32 userId); /// @notice Emitted at every step of the EmergencyReplaceUserKeys flow /// @dev **requestId** : unique identifier used for record keeping <br/> **userId** : unique identifier of the user starts emergency replacement <br/> **actionType**: string that dictates what action was used in the emergency replace flow key (eg. “Start”, “Intervene”, “Execute”) event EmergencyReplaceUserKeysRequest(bytes32 requestId, bytes32 userId, string actionType); /// @notice Emitted at the end of `interventionKeyCheck()` /// @dev **requestId** : unique identifier used for record keeping <br/>**keyAddress**: intervention key’s address that is going to be verified event InterventionKeyCheck(bytes32 requestId, address keyAddress); /// @notice Emitted at the end of `addInterventionKey()` & `removeInterventionKey()` /// @dev **requestId** : unique identifier used for record keeping <br/> **actionType**: string that dictates what action was used for the intervention key (eg. “Add” or "Remove"). **keyAddress**: intervention key’s address that is going to be Added or Removed event InterventionKey(bytes32 requestId, string actionType, address keyAddress); /// @notice Emitted at the end of `addCASTLKey()` and `expireCASTLKey()` /// @dev **requestId** : unique identifier used for record keeping <br/> **actionType**: string that dictates whether a CASTL key was Added or Expired (eg. “Add”, "Expire") <br /> **keyAddress**: CASTL key’s address that is going to be Added or Expired event CASTLKey(bytes32 requestId, string actionType, address keyAddress); } /// @title Key Management Store Interface /// @author Prometheum Inc. /// @notice This interface is for the [KeyManagementStore](/src/facets/KeyManagementStoreFacet.sol/contract.KeyManagementStoreFacet.html) contract. interface IKeyManagementStore is IKeyManagement { /// @notice Returns the KeyStatus of the CASTL key /// @param key address of the CASTL key to check its KeyStatus /// @return status returns the status of the passed in key function getCASTLKey(address key) external view returns (KeyStatus); /// @notice Returns the KeyStatus of the intervention key /// @param key address of the intervention key to check its KeyStatus /// @return status returns the status of the passed in key function getInterventionKey(address key) external view returns (KeyStatus); /// @notice Returns the total amount of active CASTL keys registered in this container /// @return totalCASTLKeys total number of CASTLKeys function getTotalActiveCASTLKeys() external view returns (uint8 totalCASTLKeys); /// @notice Returns `initiator` from storage /// @return signature used in the `init()` call to Initialize this container function getInitiator() external view returns (bytes memory signature); /// @notice Returns user keys associated to `userId` from `complianceUserKeys` mapping /// @param userId identifier of the user to fetch the keys of /// @return userKeys keys associated to the `userId` function getUserKeys( bytes32 userId ) external view returns (IKeyManagement.UserKeys memory userKeys); /// @notice Returns the total amount of interventions keys registered in this container /// @return totalActiveInterventionKeys total amount of intervention keys registered in the container function getTotalActiveInterventionKeys() external view returns (uint256 totalActiveInterventionKeys); /// @notice Returns the EmergencyReplacement associated to requestId from ‘emergencyKeyReplacements’ mapping in storage /// @param requestId identifier of the request to fetch the emergency replacement info for /// @return emergencyReplacement associated to the `requestId` function getEmergencyReplacements( bytes32 requestId ) external view returns (IKeyManagement.EmergencyReplacement memory emergencyReplacement); /// @notice Returns UserBlock status of the user /// @param userId userId of the user to check the blocked status of function getUserBlocks(bytes32 userId) external view returns (UserBlocks); /// @notice Returns boolean to check if the passed in key is currently being used /// @param keyAddress keyAddress of the user to check if it is used or not function isUserKeyUsed(address keyAddress) external view returns (bool); /// @notice Returns bytes32 of request id that indicates which emergency replacement is active now /// @return requestId identifier of the request to fetch the emergency replacement info for function getActiveEmgRequestId() external view returns (bytes32); /// @notice Returns the total amount of total active users in this container /// @return totalActiveUsers total amount of active users in the container function getTotalActiveUsers() external view returns (uint256 totalActiveUsers); /// @notice This confirms whether the specified 'userId' owns the specified 'recoveredAddress'. It also checks if 'userId' is unblocked and if they're enabled. /// @param userId user identifier to check if they own the 'recoveredAddress' or not /// @param recoveredAddress address to check if 'userId' owns it or not. It also checks if they're unblocked and enabled /// @return isValid boolean that dictates if 'userId' owns 'recoveredAddress' and if they're unblocked and enabled function isValidApprovalAddress( bytes32 userId, address recoveredAddress ) external view returns (bool isValid); } /// @title Key Management Init Interface /// @author Prometheum Inc. /// @notice This interface is for the [KeyManagementInit](/src/facets/KeyManagementInitFacet.sol/contract.KeyManagementInitFacet.html) contract. interface IKeyManagementInit is IKeyManagement { /// @notice Initialize intervention keys and user’s approval and emergency key for the container. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127233/KeyManagementInitFacet#init)</b> /// @dev Message format is in <b>[init()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#init)</b> format. /// @param userKeys user keys to initialize the container with /// @param interventionKeys intervention keys to initialize the container with /// @param expiredAt block timestamp when 'castlSignature' expires /// @param castlSignature castl siganture used for verification for this initialization process function init( InputUserKey[] calldata userKeys, address[] calldata interventionKeys, uint256 expiredAt, bytes calldata castlSignature ) external; /// @notice Add a new approval key for specified user. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127233/KeyManagementInitFacet#addApprovalKey)</b> /// @dev Message format is in <b>[addApprovalKey()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#addApprovalKey)</b> format and requires 3 of 5 approvals with 1 CASTL signature. Currently, only support adding 1 new key at a time. /// @param userId user id to add the approval key for /// @param keyAddress address of the approval key to add /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function addApprovalKey( bytes32 userId, address keyAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Removes an approval key for specified user. /// @dev Message format is in `removeApprovalKey` format and requires 3 of 5 approvals with 1 CASTL signature. Currently, only support adding 1 new key at a time. /// @param userId user id to remove the approval key for /// @param keyAddress address of the approval key to remove /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function removeApprovalKey( bytes32 userId, address keyAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Add keys for a new user. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127233/KeyManagementInitFacet#addUser)</b> /// @dev Message format is in <b>[addUser()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#addUser)</b> format and requires 3 of 5 approvals with 1 CASTL signature. /// @param userKey user keys to add to the container /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function addUser( InputUserKey calldata userKey, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Remove an existed user with its approval & emergency keys. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127233/KeyManagementInitFacet#removeUser)</b> /// @dev Message format is in <b>[removeUser()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#removeUser)</b> format and requires 3 of 5 approvals with 1 CASTL signature. /// @param userId user id to remove the approval key for /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function removeUser( bytes32 userId, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Enables an existing user (aka approver) and its keys /// @dev Message format is in enableUser() format and requires 3 of 5 approvals with 1 CASTL signature /// @param userId user id to enable the approver and its keys for /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function enableUser( bytes32 userId, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Disables an existing user (aka approver) and its keys /// @dev Message format is in disableUser() format and requires 3 of 5 approvals with 1 CASTL signature /// @param userId user id to disable the approver and its keys for /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function disableUser( bytes32 userId, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; } /// @title Key Management Emg Interface /// @author Prometheum Inc. /// @notice This interface is for the [KeyManagementEmg](/src/facets/KeyManagementEmgFacet.sol/contract.KeyManagementEmgFacet.html) contract. interface IKeyManagementEmg is IKeyManagement { /// @notice Begins the emergency replace user keys process. This updates the globalState to ActiveEmergency. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127247/KeyManagementEmgFacet#emergencyReplaceUserKeysStart)</b> /// @dev Message format is in <b>[emergencyReplaceUserKeysStart()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#emergencyReplaceUserKeysStart)</b> format. /// @param approvalKey new approval key to replace for user /// @param emergencyKey new emergency key to replace for user /// @param expiredAt block timestamp when 'emergencySignature' expires /// @param requestId unique identifier to link to each emergencyReplaceUserKeys request /// @param emergencySignature signature from user's emergency key using the 'emergencyReplaceUserKeysStart' message format. Also contains 'userId' function emergencyReplaceUserKeysStart( address approvalKey, address emergencyKey, uint256 expiredAt, bytes32 requestId, SigUtil.ApproverInfo calldata emergencySignature ) external; /// @notice If a specific emergency replace user keys request is deemed as non-valid, an intervention key can be used to interrupt a request using this function. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127247/KeyManagementEmgFacet#emergencyReplaceUserKeysIntervene)</b> /// @dev Uses the <b>[emergencyReplaceUserKeysIntervene()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#emergencyReplaceUserKeysIntervene)</b> message format. /// @param expiredAt block timestamp when 'signedRejectApproval' expires /// @param signedRejectApproval signature from a valid intervention key /// @return isRejected true if the call was successful function emergencyReplaceUserKeysIntervene( uint256 expiredAt, bytes calldata signedRejectApproval ) external returns (bool isRejected); /// @notice Execute emergency key replacement request to complete replacement after the delay block time. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/348127247/KeyManagementEmgFacet#emergencyReplaceUserKeysExecute)</b> /// @dev Uses the <b>[emergencyReplaceUserKeysExecute()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#emergencyReplaceUserKeysExecute)</b> message format. /// @param expiredAt block timestamp when 'approvalSignature' & 'emergencySignature' expires /// @param requestId unique identifier to link to each emergencyReplaceUserKeys request /// @param approvalSignature signature from user's new replaced approval key using the 'emergencyReplaceUserKeysExecute' message format. /// @param emergencySignature signature from user's new replaced emergency key using the 'emergencyReplaceUserKeysExecute' message format. function emergencyReplaceUserKeysExecute( uint256 expiredAt, bytes32 requestId, bytes calldata approvalSignature, bytes calldata emergencySignature ) external; } /// @title Key Management Others Interface /// @author Prometheum Inc. /// @notice This interface is for the [KeyManagementOthers](/src/facets/KeyManagementOthersFacet.sol/contract.KeyManagementOthersFacet.html) contract. interface IKeyManagementOthers is IKeyManagement { /// @notice Emits InterventionKeyCheck event to check if it's Enabled for a specific intervention key. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/373260289/KeyManagementOthersFacet#interventionKeyCheck)</b> /// @dev Message format is in <b>[interventionKeyCheck()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#interventionKeyCheck)</b> format. /// @param expiredAt block timestamp when 'interventionSignature' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param interventionSignature intervention signature for verification /// @return isSuccessful true if the call was successful function interventionKeyCheck( uint256 expiredAt, bytes32 requestId, bytes calldata interventionSignature ) external returns (bool isSuccessful); /// @notice Add a new intervention key. This is a function we will use to update intervention key after init(). Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/373260289/KeyManagementOthersFacet#addInterventionKey)</b> /// @dev Message format is in <b>[addInterventionKey()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#addInterventionKey)</b> format. /// @param keyAddress address of the intervention key to add /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function addInterventionKey( address keyAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Removes intervention key. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/373260289/KeyManagementOthersFacet#removeInterventionKey)</b> /// @dev Message format is in <b>[removeInterventionKey()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#removeInterventionKey)</b> format. /// @param keyAddress address of the approval key to remove /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function removeInterventionKey( address keyAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Add a new CASTL key. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/373260289/KeyManagementOthersFacet#addCASTLKey)</b> /// @dev Message format is in <b>[addCASTLKey()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#addCASTLKey)</b> format. /// @param keyAddress address of the CASTL key to add /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification /// @param approvalSignatures 3 of 5 approval signatures for verification function addCASTLKey( address keyAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; /// @notice Expires an existing CASTL key. Contract spec can be found <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/373260289/KeyManagementOthersFacet#expireCASTLKey)</b> /// @dev Message format is in <b>[expireCASTLKey()](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#expireCASTLKey)</b> format. /// @param keyAddress address of the CASTL key to expire /// @param expiredAt block timestamp when 'castlSignature' and each 'approvalSignatures' expires /// @param requestId unique identifier used to form messages and prevent replays /// @param castlSignature castl signature for verification **(NOTE: This cannot be signed by `keyAddress`)** /// @param approvalSignatures 3 of 5 approval signatures for verification function expireCASTLKey( address keyAddress, uint256 expiredAt, bytes32 requestId, bytes calldata castlSignature, SigUtil.ApproverInfo[] calldata approvalSignatures ) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {VerifySignatureUtil as SigUtil} from "../libraries/VerifySignatureUtil.sol"; import {Diamond} from "../Diamond.sol"; import {COWSManagement} from "../COWSManagement.sol"; import {IGlobalState} from "../interfaces/IGlobalState.sol"; import {GlobalStateFacet} from "../facets/GlobalStateFacet.sol"; import {ICustodyWallet} from "../interfaces/ICustodyWallet.sol"; import {CustodyWalletFacet} from "../facets/CustodyWalletFacet.sol"; import {IDASManagement} from "../interfaces/IDASManagement.sol"; import {DASManagementFacet} from "../facets/DASManagementFacet.sol"; import {IDirectDeposit} from "../interfaces/IDirectDeposit.sol"; import {DirectDepositFacet} from "../facets/DirectDepositFacet.sol"; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; /// @notice This struct is used to hold all COWS container related storage to be used by all facets. This struct must always be put at slot 1 in storage to allow the use of shared storage. /// @dev These are are the values included within the shared storage for containers*: /// - **cowsManagement** _{COWSManagement}_: address of the COWSManagement contract registers this COWS container /// - **globalState** _{IGlobalState.COWSState}_: state of the whole container _(eg. `ActiveNormal` or `Recognized`)_ /// - **cowId** _{byte32}_: unique identifier that represents this specific container /// - **requestIds** _{mapping(bytes32 => bool)}_: mapping of `requestId` to boolean to dictate if the request has been used yet /// - **dasStatus** _{mapping(address => IDASManagement.DASStatus)}_: mapping of `dasAddress` to DASStatus /// - **directDeposits** _{mapping(bytes32 => bytes)}_: mapping of `requestId` to signature used to call `directDeposit()` /// - **complianceUserKeys** _{mapping(bytes32 => IKeyManagement.UserKeys)}_: mapping of userId to UserKeys /// - **totalActiveUsers** _{uint256}_: total amount of approvers _(cannot be less than **5**)_ /// - **interventionKeys** _{mapping(address => IKeyManagement.KeyStatus)}_: mapping to dictate what `KeyStatus` a specific `interventionKey` is in /// - **totalActiveInterventionKeys** _{uint8}_: total amount of intervention keys _(cannot be less than **1**)_ /// - **castlKeys** _{mapping(address => IKeyManagement.KeyStatus)}_: mapping to dictate what `KeyStatus` a specific `castlKey` is in /// - **isUserKeyUsed** _{mapping(address => bool)}_: mapping to dictate if a user key has been used or not /// - **emergencyKeyReplacements** _{mapping(bytes32 => IKeyManagement.EmergencyReplacement)}_: mapping of `requestId` to EmergencyReplacement /// - **activeEmgRequestId** _{bytes32}_: unique identifier that represents current active emergency replacement request /// - **initiator** _{bytes}_: signature used when `init()` was called /// - **totalActiveCastlKeys** _{uint8}_: total amount of active castl keys /// /// <br /><br />*See [Diamond](/src/Diamond.sol/contract.Diamond.html) for more information on shared storage and the Diamond proxy pattern. struct AppStorage { /** ---- External Contracts ---- **/ COWSManagement cowsManagement; /** ------------------------- **/ /** ---- GlobalState ---- **/ IGlobalState.COWSState globalState; bytes32 cowId; mapping(bytes32 => bool) requestIds; /** --------------------- **/ /** ---- DASManagement ---- **/ mapping(address => IDASManagement.DASStatus) dasStatus; /** ----------------------- **/ /** ---- DirectDeposit ---- **/ mapping(bytes32 => bytes) directDeposits; /** ----------------------- **/ /** ---- KeyManagement ---- **/ mapping(bytes32 => IKeyManagement.UserKeys) complianceUserKeys; uint256 totalActiveUsers; mapping(address => bool) isUserKeyUsed; mapping(address => IKeyManagement.KeyStatus) interventionKeys; uint8 totalActiveInterventionKeys; mapping(address => IKeyManagement.KeyStatus) castlKeys; mapping(bytes32 => IKeyManagement.EmergencyReplacement) emergencyKeyReplacements; bytes32 activeEmgRequestId; bytes initiator; uint8 totalActiveCastlKeys; /** ----------------------- **/ }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; import {AppStorage} from "./AppStorage.sol"; import {LibDiamond} from "./LibDiamond.sol"; /// @title Key Management Util Library /// @author Prometheum Inc. /// @notice This library is used to hold extra utility for the Key Management Facets. library KeyMgmtUtil { /// @notice This verifies that `recoveredAddress` is a valid approver key that belongs to `userId`. It also checks if the address is unblocked and if it's enabled. /// @param userId user identifier to check if they own `recoveredAddress` or not /// @param recoveredAddress address to check if `userId` owns it or not. It is also checked if the address is unblocked or enabled /// @return isValid boolean that dictates if `userId` owns the approval key `recoveredAddress`, if it's unblocked, and if it's enabled function verifyApprovalAddress( bytes32 userId, address recoveredAddress ) internal view returns (bool isValid) { AppStorage storage s = LibDiamond.appStorage(); address[] memory _approvalKeys = s.complianceUserKeys[userId].approvalKeys; bool isBlocked = s.complianceUserKeys[userId].userBlocks > IKeyManagement.UserBlocks.None; bool isEnabled = s.complianceUserKeys[userId].status == IKeyManagement.UserStatus.Enabled; bool isVerified; for (uint256 k; k < _approvalKeys.length; ) { if (_approvalKeys[k] == recoveredAddress) { isVerified = true; break; } unchecked { ++k; } } return (!isBlocked && isEnabled && isVerified); } /// @notice Sets all user related info in related to `userId` in storage /// @param userId `userId` to adjust the storage for /// @param delayTo `delayTo` to set in storage for `userId` /// @param emergencyKey `emergencyKey` to set in storage for `userId` /// @param approvalKeys `approvalKeys` to set in storage for `userId` /// @param status `status` to set in storage for `userId` /// @param userBlocks `userBlocks` to set in storage for `userId` /// @custom:reverts if 'emergencyKey' or each 'approvalKey' isn't unique to this container function setUserKeys( bytes32 userId, uint96 delayTo, address emergencyKey, address[] memory approvalKeys, IKeyManagement.UserStatus status, IKeyManagement.UserBlocks userBlocks ) internal { AppStorage storage s = LibDiamond.appStorage(); IKeyManagement.UserKeys storage _userKeys = s.complianceUserKeys[userId]; require(isKeyUnique(emergencyKey), "key already used"); _userKeys.status = status; _userKeys.userBlocks = userBlocks; _userKeys.delayTo = delayTo; _userKeys.emergencyKey = emergencyKey; // if approval key array isn't empty, overwrite to update it uint256 approvalKeysLength = approvalKeys.length; if (approvalKeysLength != 0) { // for replacing all keys attach to a user (also apply to init or emergency replacement) address[] memory addedApprovalKeys = new address[](approvalKeysLength); for (uint256 i = 0; i < approvalKeysLength; ) { require(isKeyUnique(approvalKeys[i]), "key already used"); addedApprovalKeys[i] = approvalKeys[i]; unchecked { ++i; } } _userKeys.approvalKeys = addedApprovalKeys; } } /// @notice Finds and returns the index of 'addressToFind' in 'addressArray'. /// If 'addressToFind' isn't found, '-1' is returned. /// @param addressArray array used to find 'addressToFind's index within. /// @param addressToFind address to find within 'addressArray'. /// @return index the index within the 'addressArray' array that 'address' is located at. /// Returns '-1' if 'addressToFind' was not found. function findAddress( address[] memory addressArray, address addressToFind ) internal pure returns (int256 index) { for (uint256 i; i < addressArray.length; ) { if (addressArray[i] == addressToFind) return int256(i); unchecked { ++i; } } index = -1; } /// @notice Checks 'key' against all other existing keys on this /// container to ensure that this key is entirely unique /// @param key address to check for uniqueness /// @return isUnique boolean to dictate if 'key' is unique or not function isKeyUnique(address key) internal view returns (bool isUnique) { AppStorage storage s = LibDiamond.appStorage(); bool isUsedUserKey = s.isUserKeyUsed[key]; if (isUsedUserKey) return false; bool isUsedCastlKey = s.castlKeys[key] != IKeyManagement.KeyStatus.Nonexistent; if (isUsedCastlKey) return false; bool isUsedInterventionKey = s.interventionKeys[key] != IKeyManagement.KeyStatus.Nonexistent; if (isUsedInterventionKey) return false; return true; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {AppStorage} from "./AppStorage.sol"; import {Diamond} from "../Diamond.sol"; import {COWSManagement} from "../COWSManagement.sol"; import {GlobalStateFacet} from "../facets/GlobalStateFacet.sol"; import {CustodyWalletFacet} from "../facets/CustodyWalletFacet.sol"; import {DASManagementFacet} from "../facets/DASManagementFacet.sol"; import {DirectDepositFacet} from "../facets/DirectDepositFacet.sol"; import {IKeyManagement} from "../interfaces/IKeyManagement.sol"; /// @title Diamond Library /// @author Prometheum Inc. /// @notice This is the contract used to contain functionality related to the Diamond Proxy. This includes facet cutting, AppStorage, and DiamondStorage. For more information on the Diamond Proxy, see [Diamond.sol](/src/Diamond.sol/contract.Diamond.html). library LibDiamond { /*******************************/ /* APP STORAGE */ /*******************************/ /// @notice This function returns a storage pointer at slot 0. This mimics the normal AppStorage behavior of declaring the variable at the beginning of each facet contract. /// @return s storage pointer at slot 0. function appStorage() internal pure returns (AppStorage storage s) { assembly { s.slot := 0 } } /// @notice sets cowId in AppStorage. Used in the constructor of Diamond.sol /// @param cowId identifier of this container function _setCowId(bytes32 cowId) internal { AppStorage storage s = appStorage(); s.cowId = cowId; } /// @notice sets COWS management address in AppStorage. Used in the constructor of Diamond.sol /// @param cowMgmtAddr address of the COWSManagement contract function _setCowsMgmt(address cowMgmtAddr) internal { AppStorage storage s = appStorage(); s.cowsManagement = COWSManagement(cowMgmtAddr); } /// @notice sets castl keys to `KeyStatus.Enabled` in AppStorage. Used in the constructor of Diamond.sol /// @param castlKeys castl keys to set in app storage function _setCastlKeys(address[] memory castlKeys) internal { AppStorage storage s = appStorage(); uint256 castlKeysLength = castlKeys.length; require(castlKeysLength > 0, "no castl keys were given"); for (uint256 i; i < castlKeysLength; ) { s.castlKeys[castlKeys[i]] = IKeyManagement.KeyStatus.Enabled; unchecked { ++i; } } } /***********************************/ /* DIAMOND STORAGE */ /***********************************/ /// @notice This determines the slot position of diamond storage. This is mainly used for organization and storage collision protection. bytes32 private constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); /// @notice Contains facet linking information by mapping function selectors to the facet addresses they're associated /// @dev **facets** {mapping(bytes4 => address)}: maps function selectors to the facet addresses that execute the functions struct DiamondStorage { mapping(bytes4 => address) facets; } /// @notice This function returns a storage pointer at slot DIAMOND_STORAGE_POSITION. This is only used to store that selector to facet address mapping at the moment, but this can easily be expanded to store more diamond related information. /// @return ds storage pointer located at `DIAMOND_STORAGE_POSITION` function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } } /// @notice Links all `_selectors` to the `_newFacetAddress` in DiamondStorage /// @param _newFacetAddress address of the facet address to link to `_selectors` /// @param _selectors bytes4 selectors to link up to the `_newFacetAddress` function addFacetSelectors(address _newFacetAddress, bytes4[] memory _selectors) internal { DiamondStorage storage ds = diamondStorage(); require(_selectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); require(_newFacetAddress != address(0), "facet cannot be address(0)"); enforceHasContractCode(_newFacetAddress, "facet has no code"); uint256 selectorsLength = _selectors.length; for (uint256 i; i < selectorsLength; ) { // add facet for selector ds.facets[_selectors[i]] = _newFacetAddress; unchecked { ++i; } } } /// @notice Checks `_contract` address for bytecode. If no bytecode is found, `_errorMessage` is thrown /// @param _contract address to check for bytecode /// @param _errorMessage error message to throw if no bytecode is found function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { uint256 contractSize; assembly { contractSize := extcodesize(_contract) } require(contractSize > 0, _errorMessage); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; import {LibString} from "solady/utils/LibString.sol"; import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; /// @notice Check <b>[HERE](https://prometheum.atlassian.net/wiki/spaces/PE/pages/326139938/VerifySignatureUtil#COWS-Function-Message-formats)</b> for all signature format of each function. library VerifySignatureUtil { /// @notice representation of a single approver along with the `userId` and the `signature` for the current function call. /// @dev **userId**: associated to the signer of the signed message<br />**resulting**: signature from signed message struct ApproverInfo { bytes32 userId; bytes signature; } /// @dev returns recovered address when calling ECDSA.recover() on `signature` and `message` /// @param signature resulting signature from signed message /// @param message signed message used to create `signature` /// @return recovered address of signer function verifySignature( bytes memory signature, string memory message ) internal pure returns (address) { // convert to Ethereum Signed Message bytes32 messageHash = MessageHashUtils.toEthSignedMessageHash(bytes(message)); // start recover signer from signatrue and hash of message return ECDSA.recover(messageHash, signature); } /// @notice forms approver message and returns recovered signing address /// @dev this is an overloaded function that accepts `approverInfo` and `hashRaw` /// @param approverInfo relevant approver info used to form message /// @param hashRaw holds the hash of all static values used across all approvers /// @return recoveredAddress address of ecrecovered signer function verifyApproverSignature( ApproverInfo memory approverInfo, string memory hashRaw ) internal pure returns (address recoveredAddress) { string[] memory parts = new string[](2); parts[0] = bytesToHex(approverInfo.userId); parts[1] = hashRaw; return verifySignature(approverInfo.signature, formatMessageWithDelimiter(parts)); } /// @notice forms approver message and returns recovered signing address /// @dev this is an overloaded function that accepts `parts` and `approverInfo` /// @param parts string array to be reused across all validated approvers. Must be size of 3 /// @param approverInfo holds relevant approver info to create approver message /// @return recoveredAddress address of ecrecovered signer function verifyApproverSignature( string[] memory parts, ApproverInfo memory approverInfo ) internal pure returns (address) { parts[0] = bytesToHex(approverInfo.userId); return verifySignature(approverInfo.signature, formatMessageWithDelimiter(parts)); } /// @notice shrinks string array `parts` to specified size `newSize` /// @param parts array strings to be shrunk /// @param newSize new length to assign to `parts` /// @custom:reverts if `newSize` is not less than current length of `parts` function shrinkStrArray(string[] memory parts, uint256 newSize) internal pure { require(newSize < parts.length, "`newSize` must be smaller"); /// @solidity memory-safe-assembly assembly { mstore(parts, newSize) // change length of parts to `newSize` } } /// @notice checks if there are any duplicate signatures or userId found within `approvers` /// @dev this could be optimized by loading them into a memory "hashmap" /// @param approvers array of `ApproverInfo` to check for duplicates /// @custom:reverts if any duplicate signatures/userIds are found function _checkNoDupes(ApproverInfo[] memory approvers) internal pure { uint256 approversLength = approvers.length; for (uint256 i; i < approversLength; ) { for (uint256 j = i + 1; j < approversLength; ) { require( keccak256(approvers[i].signature) != keccak256(approvers[j].signature), "duplicate apr signatures found" ); require(approvers[i].userId != approvers[j].userId, "duplicate apr user ids found"); unchecked { ++j; } } unchecked { ++i; } } } /// @dev Returns formatted string using `source` string array /// @param source string array to format into a single delimited string /// @param withEndingDelimiter boolean to dictate whether to include "//" at the end of the resulting string or not /// @return result single delimited string function _formatMessageWithDelimiter( string[] memory source, bool withEndingDelimiter ) internal pure returns (string memory result) { assembly ("memory-safe") { // Set result's offset to the inital memory pointer location result := mload(0x40) mstore(0x40, add(result, 0x20)) // This marks the beginning of where the result's contents start let totalLen // Total length of final string /* * The following lines convert to the following in solidity code: * for (uint256 i = 1; i < (source.length + 1); ) {} */ let i := 1 // Loop inital index let sourceLen := add(1, mload(source)) // grab amount of strings in source array and add 1 for { } lt(i, sourceLen) { } { // Get memory location of string let memLoc := mload(add(source, mul(i, 0x20))) // Get string's length let len := mload(memLoc) // This allows an additional 2 bytes for 0x2f2f or "//" switch gt(len, 30) // If shorter than 30 bytes case 0 { // get string content let contents := mload(add(memLoc, 0x20)) // calculates the amount of 'dirt' found on the stack slot unrelated to the 'contents' let freeBits := mul(sub(30, len), 8) // cleans up contents by creating a mask to clean the 'dirt' in the position where we put '//' in the next line contents := and(contents, not(shl(freeBits, 0xffff))) // get 0x2f2f set at the next available position let delimiter := shl(freeBits, 0x2f2f) // add delimiter to the end of contents contents := add(contents, delimiter) // set contents at next safe memory location mstore(mload(0x40), contents) // Get full length of added contents using additional bytes for "//" let fullContentLen := add(len, 2) // Add length to finalized totalLen totalLen := add(totalLen, fullContentLen) // Update memory pointer to after stored string mstore(0x40, add(mload(0x40), fullContentLen)) } // If longer than 30 bytes case 1 { let j := 1 // Index for which 32 bytes of string the loop is currently on let remainingLongStrLoops := add(div(len, 32), 1) // Get total amount of loops to perform for { } lt(j, remainingLongStrLoops) { } { // Load 32 bytes of the string from memory let contents := mload(add(memLoc, mul(0x20, j))) // Store loaded 32 bytes of the string at the memory pointer's location mstore(mload(0x40), contents) // Move memory pointer forward 32 bytes mstore(0x40, add(mload(0x40), 0x20)) // Increment j by 1 j := add(j, 1) } // Load remaining contents from memory let remainingContents := mload(add(memLoc, mul(0x20, j))) // If there are any contents remaining if remainingContents { // Store loaded 32 bytes of the string at the memory pointer's location mstore(mload(0x40), remainingContents) // Move memory pointer forward by the remaining amount of characters remaining mstore(0x40, add(mload(0x40), mod(len, 32))) } // Put right padded "//" at the next available positions mstore(mload(0x40), 0x2f2f000000000000000000000000000000000000000000000000000000000000) // Move memory pointer forward 2 bytes mstore(0x40, add(mload(0x40), 0x02)) // Add length (+2 for "//") to finalized totalLen totalLen := add(totalLen, add(len, 2)) } // Increment i by 1 i := add(i, 1) } // If 'withEndingDelimiter' is false if iszero(withEndingDelimiter) { // Chops off last 2 bytes totalLen := sub(totalLen, 2) } // Store the final string's length at result's offset mstore(result, totalLen) } } /// @notice Returns formatted string using `source` string array. Includes "//" at the end /// @dev Uses '_formatMessageWithDelimiter()' under the hood /// @param source string array to format into a single delimited string /// @return target delimited string (eg. {cows_id}// ... // {request_id} // ) function formatMessageWithDelimiter( string[] memory source ) internal pure returns (string memory target) { return _formatMessageWithDelimiter(source, true); } /// @dev Returns formatted string using `source` string array without the ending "//" /// @dev Uses '_formatMessageWithDelimiter()' under the hood /// @param source string array to format into a single delimited string /// @return target delimited string (eg. {cows_id}// ... // {request_id} ) function formatMessageWithDelimiterNoEnd( string[] memory source ) internal pure returns (string memory target) { return _formatMessageWithDelimiter(source, false); } /// @dev Returns the hexadecimal representation of `value`. /// The output is prefixed with "0x" and encoded using 2 hexadecimal digits per byte. /// As address are 20 bytes long, the output will be left-padded to have a length of `20 * 2 + 2` bytes. /// @param value bytes32 value to cast into a string /// @return valueStr representation of `value` as a string function bytesToHex(bytes32 value) internal pure returns (string memory valueStr) { return LibString.toHexString(uint256(value), 32); // Adding '32' fixes the final output to the length of 32 bytes. This fixes the bug that removes preceding 0's } /// @dev Returns the hex encoded string from the raw bytes. /// The output is prefixed with "0x" and is encoded using 2 hexadecimal digits per byte. function signatureToString(bytes memory buffer) internal pure returns (string memory str) { return LibString.toHexString(buffer); } /// @dev Formats each `approverInfo.signature` into single string similar to `formatMessageWithDelimiterNoEnd()` /// @param approverInfo array of `ApproverInfo` /// @return target delimited string using each `approverInfo.signature` function concatApproverSignatures( ApproverInfo[] memory approverInfo ) internal pure returns (string memory target) { uint256 approverInfoLength = approverInfo.length; for (uint256 i; i < approverInfoLength; ) { if (i == approverInfoLength - 1) { target = string(abi.encodePacked(target, signatureToString(approverInfo[i].signature))); break; } target = string(abi.encodePacked(target, signatureToString(approverInfo[i].signature), "//")); unchecked { ++i; } } } /// @dev Convert string array into single hash string /// @param parts array of sub message strings will be used to form hash raw string /// @return hashStr string representation of the hashed delimited string formed from `parts` function hashRawPartsToHexString( string[] memory parts ) internal pure returns (string memory hashStr) { return bytesToHex(keccak256(bytes(formatMessageWithDelimiter(parts)))); } /// @dev Convert string into bytes32 /// @param source string value to cast into a bytes32 /// @return result representation of `value` as a bytes32 function stringToBytes32(string memory source) internal pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { return 0x0; } assembly { result := mload(add(source, 32)) } } }
{ "optimizer": { "enabled": true, "runs": 20000 }, "viaIR": false, "evmVersion": "paris", "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
[{"inputs":[],"name":"FailedDeployment","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"InsufficientBalance","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"}],"name":"DeployedAddr","type":"event"},{"inputs":[{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"deployClone","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"uint256","name":"_minSalt","type":"uint256"},{"internalType":"uint256","name":"_maxSalt","type":"uint256"}],"name":"deployClones","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_implementation","type":"address"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"name":"predictCloneAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
Deployed Bytecode
0x608060405234801561001057600080fd5b50600436106100415760003560e01c806318c8423714610046578063bc17faa01461006f578063e54bbe4b146100a7575b600080fd5b610059610054366004610573565b6100ba565b60405161006691906105a8565b60405180910390f35b61008261007d366004610602565b610200565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610066565b6100826100b5366004610602565b61036b565b606060006100c8848461065d565b6100d3906001610670565b90506101b98110610145576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600c60248201527f6f7665722075706c696d6974000000000000000000000000000000000000000060448201526064015b60405180910390fd5b60008167ffffffffffffffff81111561016057610160610683565b604051908082528060200260200182016040528015610189578160200160208202803683370190505b50905060005b828110156101f4576101b58761007d6101b06101ab858b610670565b610377565b6103d9565b8282815181106101c7576101c76106b2565b73ffffffffffffffffffffffffffffffffffffffff9092166020928302919091019091015260010161018f565b509150505b9392505050565b60008061020d84846103f8565b90508073ffffffffffffffffffffffffffffffffffffffff166309a2aec58573ffffffffffffffffffffffffffffffffffffffff1663fef712de6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610276573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061029a91906106e1565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e084901b16815273ffffffffffffffffffffffffffffffffffffffff9091166004820152602401600060405180830381600087803b15801561030057600080fd5b505af1158015610314573d6000803e3d6000fd5b505060405173ffffffffffffffffffffffffffffffffffffffff841681527f841233ecc3bf1472a3fd3c210732e65bcd2e25561d2edbf7ba698cd2cf03f3b69250602001905060405180910390a190505b92915050565b60006101f98383610406565b60606080604051019050602081016040526000815280600019835b928101926030600a8206018453600a9004806103925750508190037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909101908152919050565b8051600090829082036103ef5750600092915050565b50506020015190565b60006101f98383600061047c565b6040513060388201526f5af43d82803e903d91602b57fd5bf3ff602482015260148101839052733d602d80600a3d3981f3363d3d373d3d3d363d738152605881018290526037600c8201206078820152605560439091012060009073ffffffffffffffffffffffffffffffffffffffff166101f9565b6000814710156104c1576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810183905260440161013c565b6e5af43d82803e903d91602b57fd5bf360205283601152763d602d80600a3d3981f3363d3d373d3d3d363d730000008460881c17600052826037600984f5905073ffffffffffffffffffffffffffffffffffffffff81166101f9576040517fb06ebf3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116811461057057600080fd5b50565b60008060006060848603121561058857600080fd5b83356105938161054e565b95602085013595506040909401359392505050565b6020808252825182820181905260009190848201906040850190845b818110156105f657835173ffffffffffffffffffffffffffffffffffffffff16835292840192918401916001016105c4565b50909695505050505050565b6000806040838503121561061557600080fd5b82356106208161054e565b946020939093013593505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156103655761036561062e565b808201808211156103655761036561062e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156106f357600080fd5b81516101f98161054e56fea2646970667358221220ee9691fd896213190439db2b041b2f4b86c062f5064af4099315b612adb8505464736f6c63430008160033
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.