upsideos_evm_rwa_artifacts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +95 -0
- data/data/recallable-payment/abi/AccessControl.json +1 -0
- data/data/recallable-payment/abi/RecallablePayment.json +1 -0
- data/data/recallable-payment/manifest.json +1 -0
- data/data/recallable-payment/verification-source-codes.json +1 -0
- data/data/v5/abi/AccessControl.json +1 -0
- data/data/v5/abi/ERC2771CustomForwarder.json +1 -0
- data/data/v5/abi/IdentityRegistry.json +1 -0
- data/data/v5/abi/InterestPayment.json +1 -0
- data/data/v5/abi/PurchaseContract.json +1 -0
- data/data/v5/abi/RestrictedLockupToken.json +1 -0
- data/data/v5/abi/RestrictedLockupTokenExtension.json +1 -0
- data/data/v5/abi/RestrictedLockupTokenManagementExtension.json +1 -0
- data/data/v5/abi/RestrictedSwap.json +1 -0
- data/data/v5/abi/SnapshotPeriods.json +1 -0
- data/data/v5/abi/TransferRules.json +1 -0
- data/data/v5/abi/merged/RestrictedLockupToken.json +1 -0
- data/data/v5/manifest.json +1 -0
- data/data/v5/verification-source-codes.json +1 -0
- data/data/v5.1/abi/AccessControl.json +1 -0
- data/data/v5.1/abi/ERC2771CustomForwarder.json +1 -0
- data/data/v5.1/abi/IdentityRegistry.json +1 -0
- data/data/v5.1/abi/InterestPayment.json +1 -0
- data/data/v5.1/abi/PurchaseContract.json +1 -0
- data/data/v5.1/abi/RestrictedLockupToken.json +1 -0
- data/data/v5.1/abi/RestrictedLockupTokenExtension.json +1 -0
- data/data/v5.1/abi/RestrictedLockupTokenManagementExtension.json +1 -0
- data/data/v5.1/abi/RestrictedLockupTokenStandardsExtension.json +1 -0
- data/data/v5.1/abi/RestrictedSwap.json +1 -0
- data/data/v5.1/abi/SnapshotPeriods.json +1 -0
- data/data/v5.1/abi/TransferRules.json +1 -0
- data/data/v5.1/abi/merged/RestrictedLockupToken.json +1 -0
- data/data/v5.1/manifest.json +1 -0
- data/data/v5.1/verification-source-codes.json +1 -0
- data/lib/upsideos_evm_rwa_artifacts/version.rb +5 -0
- data/lib/upsideos_evm_rwa_artifacts.rb +118 -0
- metadata +81 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"_format":"hh-sol-build-info-1","id":"63852114cc88cb29563152a201c8fdbd","input":{"language":"Solidity","settings":{"evmVersion":"paris","metadata":{"useLiteralContent":true},"optimizer":{"enabled":true,"runs":200},"outputSelection":{"*":{"":["ast"],"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata","storageLayout","devdoc","userdoc","evm.gasEstimates"]}},"viaIR":true},"sources":{"@openzeppelin/contracts/access/Ownable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n /**\n * @dev The caller account is not authorized to perform an operation.\n */\n error OwnableUnauthorizedAccount(address account);\n\n /**\n * @dev The owner is not a valid owner account. (eg. `address(0)`)\n */\n error OwnableInvalidOwner(address owner);\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n */\n constructor(address initialOwner) {\n if (initialOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(initialOwner);\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n if (owner() != _msgSender()) {\n revert OwnableUnauthorizedAccount(_msgSender());\n }\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby disabling any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n if (newOwner == address(0)) {\n revert OwnableInvalidOwner(address(0));\n }\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"},"@openzeppelin/contracts/interfaces/IERC1363.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @title IERC1363\n * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].\n *\n * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract\n * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.\n */\ninterface IERC1363 is IERC20, IERC165 {\n /*\n * Note: the ERC-165 identifier for this interface is 0xb0202a11.\n * 0xb0202a11 ===\n * bytes4(keccak256('transferAndCall(address,uint256)')) ^\n * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^\n * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256)')) ^\n * bytes4(keccak256('approveAndCall(address,uint256,bytes)'))\n */\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism\n * and then calls {IERC1363Receiver-onTransferReceived} on `to`.\n * @param from The address which you want to send tokens from.\n * @param to The address which you want to transfer to.\n * @param value The amount of tokens to be transferred.\n * @param data Additional data with no specified format, sent in call to `to`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.\n * @param spender The address which will spend the funds.\n * @param value The amount of tokens to be spent.\n * @param data Additional data with no specified format, sent in call to `spender`.\n * @return A boolean value indicating whether the operation succeeded unless throwing.\n */\n function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);\n}\n"},"@openzeppelin/contracts/interfaces/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"../utils/introspection/IERC165.sol\";\n"},"@openzeppelin/contracts/interfaces/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../token/ERC20/IERC20.sol\";\n"},"@openzeppelin/contracts/interfaces/IERC5267.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC5267.sol)\n\npragma solidity ^0.8.20;\n\ninterface IERC5267 {\n /**\n * @dev MAY be emitted to signal that the domain could have changed.\n */\n event EIP712DomainChanged();\n\n /**\n * @dev returns the fields and values that describe the domain separator used by this contract for EIP-712\n * signature.\n */\n function eip712Domain()\n external\n view\n returns (\n bytes1 fields,\n string memory name,\n string memory version,\n uint256 chainId,\n address verifyingContract,\n bytes32 salt,\n uint256[] memory extensions\n );\n}\n"},"@openzeppelin/contracts/interfaces/draft-IERC6093.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/draft-IERC6093.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Standard ERC-20 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-20 tokens.\n */\ninterface IERC20Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC20InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC20InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n * @param allowance Amount of tokens a `spender` is allowed to operate with.\n * @param needed Minimum amount required to perform a transfer.\n */\n error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC20InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `spender` to be approved. Used in approvals.\n * @param spender Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC20InvalidSpender(address spender);\n}\n\n/**\n * @dev Standard ERC-721 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-721 tokens.\n */\ninterface IERC721Errors {\n /**\n * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-20.\n * Used in balance queries.\n * @param owner Address of the current owner of a token.\n */\n error ERC721InvalidOwner(address owner);\n\n /**\n * @dev Indicates a `tokenId` whose `owner` is the zero address.\n * @param tokenId Identifier number of a token.\n */\n error ERC721NonexistentToken(uint256 tokenId);\n\n /**\n * @dev Indicates an error related to the ownership over a particular token. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param tokenId Identifier number of a token.\n * @param owner Address of the current owner of a token.\n */\n error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC721InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC721InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param tokenId Identifier number of a token.\n */\n error ERC721InsufficientApproval(address operator, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC721InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC721InvalidOperator(address operator);\n}\n\n/**\n * @dev Standard ERC-1155 Errors\n * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC-1155 tokens.\n */\ninterface IERC1155Errors {\n /**\n * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n * @param balance Current balance for the interacting account.\n * @param needed Minimum amount required to perform a transfer.\n * @param tokenId Identifier number of a token.\n */\n error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);\n\n /**\n * @dev Indicates a failure with the token `sender`. Used in transfers.\n * @param sender Address whose tokens are being transferred.\n */\n error ERC1155InvalidSender(address sender);\n\n /**\n * @dev Indicates a failure with the token `receiver`. Used in transfers.\n * @param receiver Address to which tokens are being transferred.\n */\n error ERC1155InvalidReceiver(address receiver);\n\n /**\n * @dev Indicates a failure with the `operator`’s approval. Used in transfers.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n * @param owner Address of the current owner of a token.\n */\n error ERC1155MissingApprovalForAll(address operator, address owner);\n\n /**\n * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.\n * @param approver Address initiating an approval operation.\n */\n error ERC1155InvalidApprover(address approver);\n\n /**\n * @dev Indicates a failure with the `operator` to be approved. Used in approvals.\n * @param operator Address that may be allowed to operate on tokens without being their owner.\n */\n error ERC1155InvalidOperator(address operator);\n\n /**\n * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.\n * Used in batch transfers.\n * @param idsLength Length of the array of token identifiers\n * @param valuesLength Length of the array of token amounts\n */\n error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);\n}\n"},"@openzeppelin/contracts/metatx/ERC2771Context.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (metatx/ERC2771Context.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Context variant with ERC-2771 support.\n *\n * WARNING: Avoid using this pattern in contracts that rely in a specific calldata length as they'll\n * be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC-2771\n * specification adding the address size in bytes (20) to the calldata size. An example of an unexpected\n * behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive`\n * function only accessible if `msg.data.length == 0`.\n *\n * WARNING: The usage of `delegatecall` in this contract is dangerous and may result in context corruption.\n * Any forwarded request to this contract triggering a `delegatecall` to itself will result in an invalid {_msgSender}\n * recovery.\n */\nabstract contract ERC2771Context is Context {\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address private immutable _trustedForwarder;\n\n /**\n * @dev Initializes the contract with a trusted forwarder, which will be able to\n * invoke functions on this contract on behalf of other accounts.\n *\n * NOTE: The trusted forwarder can be replaced by overriding {trustedForwarder}.\n */\n /// @custom:oz-upgrades-unsafe-allow constructor\n constructor(address trustedForwarder_) {\n _trustedForwarder = trustedForwarder_;\n }\n\n /**\n * @dev Returns the address of the trusted forwarder.\n */\n function trustedForwarder() public view virtual returns (address) {\n return _trustedForwarder;\n }\n\n /**\n * @dev Indicates whether any particular address is the trusted forwarder.\n */\n function isTrustedForwarder(address forwarder) public view virtual returns (bool) {\n return forwarder == trustedForwarder();\n }\n\n /**\n * @dev Override for `msg.sender`. Defaults to the original `msg.sender` whenever\n * a call is not performed by the trusted forwarder or the calldata length is less than\n * 20 bytes (an address length).\n */\n function _msgSender() internal view virtual override returns (address) {\n uint256 calldataLength = msg.data.length;\n uint256 contextSuffixLength = _contextSuffixLength();\n if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) {\n return address(bytes20(msg.data[calldataLength - contextSuffixLength:]));\n } else {\n return super._msgSender();\n }\n }\n\n /**\n * @dev Override for `msg.data`. Defaults to the original `msg.data` whenever\n * a call is not performed by the trusted forwarder or the calldata length is less than\n * 20 bytes (an address length).\n */\n function _msgData() internal view virtual override returns (bytes calldata) {\n uint256 calldataLength = msg.data.length;\n uint256 contextSuffixLength = _contextSuffixLength();\n if (isTrustedForwarder(msg.sender) && calldataLength >= contextSuffixLength) {\n return msg.data[:calldataLength - contextSuffixLength];\n } else {\n return super._msgData();\n }\n }\n\n /**\n * @dev ERC-2771 specifies the context as being a single address (20 bytes).\n */\n function _contextSuffixLength() internal view virtual override returns (uint256) {\n return 20;\n }\n}\n"},"@openzeppelin/contracts/metatx/ERC2771Forwarder.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (metatx/ERC2771Forwarder.sol)\n\npragma solidity ^0.8.20;\n\nimport {ERC2771Context} from \"./ERC2771Context.sol\";\nimport {ECDSA} from \"../utils/cryptography/ECDSA.sol\";\nimport {EIP712} from \"../utils/cryptography/EIP712.sol\";\nimport {Nonces} from \"../utils/Nonces.sol\";\nimport {Address} from \"../utils/Address.sol\";\nimport {Errors} from \"../utils/Errors.sol\";\n\n/**\n * @dev A forwarder compatible with ERC-2771 contracts. See {ERC2771Context}.\n *\n * This forwarder operates on forward requests that include:\n *\n * * `from`: An address to operate on behalf of. It is required to be equal to the request signer.\n * * `to`: The address that should be called.\n * * `value`: The amount of native token to attach with the requested call.\n * * `gas`: The amount of gas limit that will be forwarded with the requested call.\n * * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation.\n * * `deadline`: A timestamp after which the request is not executable anymore.\n * * `data`: Encoded `msg.data` to send with the requested call.\n *\n * Relayers are able to submit batches if they are processing a high volume of requests. With high\n * throughput, relayers may run into limitations of the chain such as limits on the number of\n * transactions in the mempool. In these cases the recommendation is to distribute the load among\n * multiple accounts.\n *\n * NOTE: Batching requests includes an optional refund for unused `msg.value` that is achieved by\n * performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance,\n * if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly\n * handle `msg.data.length == 0`. `ERC2771Context` in OpenZeppelin Contracts versions prior to 4.9.3\n * do not handle this properly.\n *\n * ==== Security Considerations\n *\n * If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount\n * specified in the request. This contract does not implement any kind of retribution for this gas,\n * and it is assumed that there is an out of band incentive for relayers to pay for execution on\n * behalf of signers. Often, the relayer is operated by a project that will consider it a user\n * acquisition cost.\n *\n * By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward\n * some other purpose that is not aligned with the expected out of band incentives. If you operate a\n * relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or\n * ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be\n * used to execute arbitrary code.\n */\ncontract ERC2771Forwarder is EIP712, Nonces {\n using ECDSA for bytes32;\n\n struct ForwardRequestData {\n address from;\n address to;\n uint256 value;\n uint256 gas;\n uint48 deadline;\n bytes data;\n bytes signature;\n }\n\n bytes32 internal constant _FORWARD_REQUEST_TYPEHASH =\n keccak256(\n \"ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)\"\n );\n\n /**\n * @dev Emitted when a `ForwardRequest` is executed.\n *\n * NOTE: An unsuccessful forward request could be due to an invalid signature, an expired deadline,\n * or simply a revert in the requested call. The contract guarantees that the relayer is not able to force\n * the requested call to run out of gas.\n */\n event ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success);\n\n /**\n * @dev The request `from` doesn't match with the recovered `signer`.\n */\n error ERC2771ForwarderInvalidSigner(address signer, address from);\n\n /**\n * @dev The `requestedValue` doesn't match with the available `msgValue`.\n */\n error ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue);\n\n /**\n * @dev The request `deadline` has expired.\n */\n error ERC2771ForwarderExpiredRequest(uint48 deadline);\n\n /**\n * @dev The request target doesn't trust the `forwarder`.\n */\n error ERC2771UntrustfulTarget(address target, address forwarder);\n\n /**\n * @dev See {EIP712-constructor}.\n */\n constructor(string memory name) EIP712(name, \"1\") {}\n\n /**\n * @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp.\n *\n * A transaction is considered valid when the target trusts this forwarder, the request hasn't expired\n * (deadline is not met), and the signer matches the `from` parameter of the signed request.\n *\n * NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund\n * receiver is provided.\n */\n function verify(ForwardRequestData calldata request) public view virtual returns (bool) {\n (bool isTrustedForwarder, bool active, bool signerMatch, ) = _validate(request);\n return isTrustedForwarder && active && signerMatch;\n }\n\n /**\n * @dev Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas\n * provided to the requested call may not be exactly the amount requested, but the call will not run\n * out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed.\n *\n * Requirements:\n *\n * - The request value should be equal to the provided `msg.value`.\n * - The request should be valid according to {verify}.\n */\n function execute(ForwardRequestData calldata request) public payable virtual {\n // We make sure that msg.value and request.value match exactly.\n // If the request is invalid or the call reverts, this whole function\n // will revert, ensuring value isn't stuck.\n if (msg.value != request.value) {\n revert ERC2771ForwarderMismatchedValue(request.value, msg.value);\n }\n\n if (!_execute(request, true)) {\n revert Errors.FailedCall();\n }\n }\n\n /**\n * @dev Batch version of {execute} with optional refunding and atomic execution.\n *\n * In case a batch contains at least one invalid request (see {verify}), the\n * request will be skipped and the `refundReceiver` parameter will receive back the\n * unused requested value at the end of the execution. This is done to prevent reverting\n * the entire batch when a request is invalid or has already been submitted.\n *\n * If the `refundReceiver` is the `address(0)`, this function will revert when at least\n * one of the requests was not valid instead of skipping it. This could be useful if\n * a batch is required to get executed atomically (at least at the top-level). For example,\n * refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids\n * including reverted transactions.\n *\n * Requirements:\n *\n * - The sum of the requests' values should be equal to the provided `msg.value`.\n * - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address.\n *\n * NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for\n * the first-level forwarded calls. In case a forwarded request calls to a contract with another\n * subcall, the second-level call may revert without the top-level call reverting.\n */\n function executeBatch(\n ForwardRequestData[] calldata requests,\n address payable refundReceiver\n ) public payable virtual {\n bool atomic = refundReceiver == address(0);\n\n uint256 requestsValue;\n uint256 refundValue;\n\n for (uint256 i; i < requests.length; ++i) {\n requestsValue += requests[i].value;\n bool success = _execute(requests[i], atomic);\n if (!success) {\n refundValue += requests[i].value;\n }\n }\n\n // The batch should revert if there's a mismatched msg.value provided\n // to avoid request value tampering\n if (requestsValue != msg.value) {\n revert ERC2771ForwarderMismatchedValue(requestsValue, msg.value);\n }\n\n // Some requests with value were invalid (possibly due to frontrunning).\n // To avoid leaving ETH in the contract this value is refunded.\n if (refundValue != 0) {\n // We know refundReceiver != address(0) && requestsValue == msg.value\n // meaning we can ensure refundValue is not taken from the original contract's balance\n // and refundReceiver is a known account.\n Address.sendValue(refundReceiver, refundValue);\n }\n }\n\n /**\n * @dev Validates if the provided request can be executed at current block timestamp with\n * the given `request.signature` on behalf of `request.signer`.\n */\n function _validate(\n ForwardRequestData calldata request\n ) internal view virtual returns (bool isTrustedForwarder, bool active, bool signerMatch, address signer) {\n (bool isValid, address recovered) = _recoverForwardRequestSigner(request);\n\n return (\n _isTrustedByTarget(request.to),\n request.deadline >= block.timestamp,\n isValid && recovered == request.from,\n recovered\n );\n }\n\n /**\n * @dev Returns a tuple with the recovered the signer of an EIP712 forward request message hash\n * and a boolean indicating if the signature is valid.\n *\n * NOTE: The signature is considered valid if {ECDSA-tryRecover} indicates no recover error for it.\n */\n function _recoverForwardRequestSigner(\n ForwardRequestData calldata request\n ) internal view virtual returns (bool isValid, address signer) {\n (address recovered, ECDSA.RecoverError err, ) = _hashTypedDataV4(\n keccak256(\n abi.encode(\n _FORWARD_REQUEST_TYPEHASH,\n request.from,\n request.to,\n request.value,\n request.gas,\n nonces(request.from),\n request.deadline,\n keccak256(request.data)\n )\n )\n ).tryRecover(request.signature);\n\n return (err == ECDSA.RecoverError.NoError, recovered);\n }\n\n /**\n * @dev Validates and executes a signed request returning the request call `success` value.\n *\n * Internal function without msg.value validation.\n *\n * Requirements:\n *\n * - The caller must have provided enough gas to forward with the call.\n * - The request must be valid (see {verify}) if the `requireValidRequest` is true.\n *\n * Emits an {ExecutedForwardRequest} event.\n *\n * IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially\n * leaving value stuck in the contract.\n */\n function _execute(\n ForwardRequestData calldata request,\n bool requireValidRequest\n ) internal virtual returns (bool success) {\n (bool isTrustedForwarder, bool active, bool signerMatch, address signer) = _validate(request);\n\n // Need to explicitly specify if a revert is required since non-reverting is default for\n // batches and reversion is opt-in since it could be useful in some scenarios\n if (requireValidRequest) {\n if (!isTrustedForwarder) {\n revert ERC2771UntrustfulTarget(request.to, address(this));\n }\n\n if (!active) {\n revert ERC2771ForwarderExpiredRequest(request.deadline);\n }\n\n if (!signerMatch) {\n revert ERC2771ForwarderInvalidSigner(signer, request.from);\n }\n }\n\n // Ignore an invalid request because requireValidRequest = false\n if (isTrustedForwarder && signerMatch && active) {\n // Nonce should be used before the call to prevent reusing by reentrancy\n uint256 currentNonce = _useNonce(signer);\n\n uint256 reqGas = request.gas;\n address to = request.to;\n uint256 value = request.value;\n bytes memory data = abi.encodePacked(request.data, request.from);\n\n uint256 gasLeft;\n\n assembly (\"memory-safe\") {\n success := call(reqGas, to, value, add(data, 0x20), mload(data), 0, 0)\n gasLeft := gas()\n }\n\n _checkForwardedGas(gasLeft, request);\n\n emit ExecutedForwardRequest(signer, currentNonce, success);\n }\n }\n\n /**\n * @dev Returns whether the target trusts this forwarder.\n *\n * This function performs a static call to the target contract calling the\n * {ERC2771Context-isTrustedForwarder} function.\n *\n * NOTE: Consider the execution of this forwarder is permissionless. Without this check, anyone may transfer assets\n * that are owned by, or are approved to this forwarder.\n */\n function _isTrustedByTarget(address target) internal view virtual returns (bool) {\n bytes memory encodedParams = abi.encodeCall(ERC2771Context.isTrustedForwarder, (address(this)));\n\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n // Perform the staticcall and save the result in the scratch space.\n // | Location | Content | Content (Hex) |\n // |-----------|----------|--------------------------------------------------------------------|\n // | | | result ↓ |\n // | 0x00:0x1F | selector | 0x0000000000000000000000000000000000000000000000000000000000000001 |\n success := staticcall(gas(), target, add(encodedParams, 0x20), mload(encodedParams), 0, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n\n return success && returnSize >= 0x20 && returnValue > 0;\n }\n\n /**\n * @dev Checks if the requested gas was correctly forwarded to the callee.\n *\n * As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]:\n * - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee.\n * - At least `floor(gasleft() / 64)` is kept in the caller.\n *\n * It reverts consuming all the available gas if the forwarded gas is not the requested gas.\n *\n * IMPORTANT: The `gasLeft` parameter should be measured exactly at the end of the forwarded call.\n * Any gas consumed in between will make room for bypassing this check.\n */\n function _checkForwardedGas(uint256 gasLeft, ForwardRequestData calldata request) private pure {\n // To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/\n //\n // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas\n // but the forwarding itself still succeeds. In order to make sure that the subcall received sufficient gas,\n // we will inspect gasleft() after the forwarding.\n //\n // Let X be the gas available before the subcall, such that the subcall gets at most X * 63 / 64.\n // We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= req.gas.\n // Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall will be gasleft() = X - Y.\n // If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64.\n // Under this assumption req.gas / 63 > gasleft() is true if and only if\n // req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64.\n // This means that if the subcall runs out of gas we are able to detect that insufficient gas was passed.\n //\n // We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64.\n // The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas.\n // - req.gas / 63 > gasleft()\n // - req.gas / 63 >= X - req.gas\n // - req.gas >= X * 63 / 64\n // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly\n // the forwarding does not revert.\n if (gasLeft < request.gas / 63) {\n // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since\n // neither revert or assert consume all gas since Solidity 0.8.20\n // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require\n assembly (\"memory-safe\") {\n invalid()\n }\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/ERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"./IERC20.sol\";\nimport {IERC20Metadata} from \"./extensions/IERC20Metadata.sol\";\nimport {Context} from \"../../utils/Context.sol\";\nimport {IERC20Errors} from \"../../interfaces/draft-IERC6093.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {\n mapping(address account => uint256) private _balances;\n\n mapping(address account => mapping(address spender => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n _totalSupply += value;\n } else {\n uint256 fromBalance = _balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n _balances[from] = fromBalance - value;\n }\n }\n\n if (to == address(0)) {\n unchecked {\n // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.\n _totalSupply -= value;\n }\n } else {\n unchecked {\n // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.\n _balances[to] += value;\n }\n }\n\n emit Transfer(from, to, value);\n }\n\n /**\n * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).\n * Relies on the `_update` mechanism\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _mint(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(address(0), account, value);\n }\n\n /**\n * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.\n * Relies on the `_update` mechanism.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead\n */\n function _burn(address account, uint256 value) internal {\n if (account == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n _update(account, address(0), value);\n }\n\n /**\n * @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n *\n * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.\n */\n function _approve(address owner, address spender, uint256 value) internal {\n _approve(owner, spender, value, true);\n }\n\n /**\n * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.\n *\n * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by\n * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any\n * `Approval` event during `transferFrom` operations.\n *\n * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to\n * true using the following override:\n *\n * ```solidity\n * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {\n * super._approve(owner, spender, value, true);\n * }\n * ```\n *\n * Requirements are the same as {_approve}.\n */\n function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {\n if (owner == address(0)) {\n revert ERC20InvalidApprover(address(0));\n }\n if (spender == address(0)) {\n revert ERC20InvalidSpender(address(0));\n }\n _allowances[owner][spender] = value;\n if (emitEvent) {\n emit Approval(owner, spender, value);\n }\n }\n\n /**\n * @dev Updates `owner`'s allowance for `spender` based on spent `value`.\n *\n * Does not update the allowance value in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Does not emit an {Approval} event.\n */\n function _spendAllowance(address owner, address spender, uint256 value) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance < type(uint256).max) {\n if (currentAllowance < value) {\n revert ERC20InsufficientAllowance(spender, currentAllowance, value);\n }\n unchecked {\n _approve(owner, spender, currentAllowance - value, false);\n }\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-20 standard as defined in the ERC.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the value of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the value of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves a `value` amount of tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 value) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets a `value` amount of tokens as the allowance of `spender` over the\n * caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 value) external returns (bool);\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to` using the\n * allowance mechanism. `value` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(address from, address to, uint256 value) external returns (bool);\n}\n"},"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\n\n/**\n * @dev Interface for the optional metadata functions from the ERC-20 standard.\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n"},"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"../IERC20.sol\";\nimport {IERC1363} from \"../../../interfaces/IERC1363.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC-20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n /**\n * @dev An operation with an ERC-20 token failed.\n */\n error SafeERC20FailedOperation(address token);\n\n /**\n * @dev Indicates a failed `decreaseAllowance` request.\n */\n error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);\n\n /**\n * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n */\n function safeTransfer(IERC20 token, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the\n * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.\n */\n function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));\n }\n\n /**\n * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.\n */\n function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {\n return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));\n }\n\n /**\n * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n uint256 oldAllowance = token.allowance(address(this), spender);\n forceApprove(token, spender, oldAllowance + value);\n }\n\n /**\n * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no\n * value, non-reverting calls are assumed to be successful.\n *\n * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the \"client\"\n * smart contract uses ERC-7674 to set temporary allowances, then the \"client\" smart contract should avoid using\n * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract\n * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.\n */\n function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {\n unchecked {\n uint256 currentAllowance = token.allowance(address(this), spender);\n if (currentAllowance < requestedDecrease) {\n revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);\n }\n forceApprove(token, spender, currentAllowance - requestedDecrease);\n }\n }\n\n /**\n * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,\n * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval\n * to be set to zero before setting it to a non-zero value, such as USDT.\n *\n * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function\n * only sets the \"standard\" allowance. Any temporary allowance will remain active, in addition to the value being\n * set here.\n */\n function forceApprove(IERC20 token, address spender, uint256 value) internal {\n bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));\n\n if (!_callOptionalReturnBool(token, approvalCall)) {\n _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));\n _callOptionalReturn(token, approvalCall);\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n safeTransfer(token, to, value);\n } else if (!token.transferAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target\n * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * Reverts if the returned value is other than `true`.\n */\n function transferFromAndCallRelaxed(\n IERC1363 token,\n address from,\n address to,\n uint256 value,\n bytes memory data\n ) internal {\n if (to.code.length == 0) {\n safeTransferFrom(token, from, to, value);\n } else if (!token.transferFromAndCall(from, to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no\n * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when\n * targeting contracts.\n *\n * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.\n * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}\n * once without retrying, and relies on the returned value to be true.\n *\n * Reverts if the returned value is other than `true`.\n */\n function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {\n if (to.code.length == 0) {\n forceApprove(token, to, value);\n } else if (!token.approveAndCall(to, value, data)) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n // bubble errors\n if iszero(success) {\n let ptr := mload(0x40)\n returndatacopy(ptr, 0, returndatasize())\n revert(ptr, returndatasize())\n }\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n\n if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {\n revert SafeERC20FailedOperation(address(token));\n }\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n *\n * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.\n */\n function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n assembly (\"memory-safe\") {\n success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);\n }\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.2.0) (utils/Address.sol)\n\npragma solidity ^0.8.20;\n\nimport {Errors} from \"./Errors.sol\";\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev There's no code at `target` (it is not a contract).\n */\n error AddressEmptyCode(address target);\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n if (address(this).balance < amount) {\n revert Errors.InsufficientBalance(address(this).balance, amount);\n }\n\n (bool success, bytes memory returndata) = recipient.call{value: amount}(\"\");\n if (!success) {\n _revert(returndata);\n }\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason or custom error, it is bubbled\n * up by this function (like regular Solidity function calls). However, if\n * the call reverted with no returned reason, this function reverts with a\n * {Errors.FailedCall} error.\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n */\n function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {\n if (address(this).balance < value) {\n revert Errors.InsufficientBalance(address(this).balance, value);\n }\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target\n * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case\n * of an unsuccessful call.\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata\n ) internal view returns (bytes memory) {\n if (!success) {\n _revert(returndata);\n } else {\n // only check if target is a contract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n if (returndata.length == 0 && target.code.length == 0) {\n revert AddressEmptyCode(target);\n }\n return returndata;\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the\n * revert reason or with a default {Errors.FailedCall} error.\n */\n function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {\n if (!success) {\n _revert(returndata);\n } else {\n return returndata;\n }\n }\n\n /**\n * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.\n */\n function _revert(bytes memory returndata) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n assembly (\"memory-safe\") {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert Errors.FailedCall();\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Context.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n\n function _contextSuffixLength() internal view virtual returns (uint256) {\n return 0;\n }\n}\n"},"@openzeppelin/contracts/utils/Errors.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Collection of common custom errors used in multiple contracts\n *\n * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.\n * It is recommended to avoid relying on the error API for critical functionality.\n *\n * _Available since v5.1._\n */\nlibrary Errors {\n /**\n * @dev The ETH balance of the account is not enough to perform the operation.\n */\n error InsufficientBalance(uint256 balance, uint256 needed);\n\n /**\n * @dev A call to an address target failed. The target may have reverted.\n */\n error FailedCall();\n\n /**\n * @dev The deployment failed.\n */\n error FailedDeployment();\n\n /**\n * @dev A necessary precompile is missing.\n */\n error MissingPrecompile(address);\n}\n"},"@openzeppelin/contracts/utils/Nonces.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (utils/Nonces.sol)\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides tracking nonces for addresses. Nonces will only increment.\n */\nabstract contract Nonces {\n /**\n * @dev The nonce used for an `account` is not the expected current nonce.\n */\n error InvalidAccountNonce(address account, uint256 currentNonce);\n\n mapping(address account => uint256) private _nonces;\n\n /**\n * @dev Returns the next unused nonce for an address.\n */\n function nonces(address owner) public view virtual returns (uint256) {\n return _nonces[owner];\n }\n\n /**\n * @dev Consumes a nonce.\n *\n * Returns the current value and increments nonce.\n */\n function _useNonce(address owner) internal virtual returns (uint256) {\n // For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be\n // decremented or reset. This guarantees that the nonce never overflows.\n unchecked {\n // It is important to do x++ and not ++x here.\n return _nonces[owner]++;\n }\n }\n\n /**\n * @dev Same as {_useNonce} but checking that `nonce` is the next valid for `owner`.\n */\n function _useCheckedNonce(address owner, uint256 nonce) internal virtual {\n uint256 current = _useNonce(owner);\n if (nonce != current) {\n revert InvalidAccountNonce(owner, current);\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Panic.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Helper library for emitting standardized panic codes.\n *\n * ```solidity\n * contract Example {\n * using Panic for uint256;\n *\n * // Use any of the declared internal constants\n * function foo() { Panic.GENERIC.panic(); }\n *\n * // Alternatively\n * function foo() { Panic.panic(Panic.GENERIC); }\n * }\n * ```\n *\n * Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].\n *\n * _Available since v5.1._\n */\n// slither-disable-next-line unused-state\nlibrary Panic {\n /// @dev generic / unspecified error\n uint256 internal constant GENERIC = 0x00;\n /// @dev used by the assert() builtin\n uint256 internal constant ASSERT = 0x01;\n /// @dev arithmetic underflow or overflow\n uint256 internal constant UNDER_OVERFLOW = 0x11;\n /// @dev division or modulo by zero\n uint256 internal constant DIVISION_BY_ZERO = 0x12;\n /// @dev enum conversion error\n uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;\n /// @dev invalid encoding in storage\n uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;\n /// @dev empty array pop\n uint256 internal constant EMPTY_ARRAY_POP = 0x31;\n /// @dev array out of bounds access\n uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;\n /// @dev resource error (too large allocation or too large array)\n uint256 internal constant RESOURCE_ERROR = 0x41;\n /// @dev calling invalid internal function\n uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;\n\n /// @dev Reverts with a panic code. Recommended to use with\n /// the internal constants with predefined codes.\n function panic(uint256 code) internal pure {\n assembly (\"memory-safe\") {\n mstore(0x00, 0x4e487b71)\n mstore(0x20, code)\n revert(0x1c, 0x24)\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Pausable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n bool private _paused;\n\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n /**\n * @dev The operation failed because the contract is paused.\n */\n error EnforcedPause();\n\n /**\n * @dev The operation failed because the contract is not paused.\n */\n error ExpectedPause();\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n _requireNotPaused();\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n _requirePaused();\n _;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Throws if the contract is paused.\n */\n function _requireNotPaused() internal view virtual {\n if (paused()) {\n revert EnforcedPause();\n }\n }\n\n /**\n * @dev Throws if the contract is not paused.\n */\n function _requirePaused() internal view virtual {\n if (!paused()) {\n revert ExpectedPause();\n }\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n"},"@openzeppelin/contracts/utils/ReentrancyGuard.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,\n * consider using {ReentrancyGuardTransient} instead.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant NOT_ENTERED = 1;\n uint256 private constant ENTERED = 2;\n\n uint256 private _status;\n\n /**\n * @dev Unauthorized reentrant call.\n */\n error ReentrancyGuardReentrantCall();\n\n constructor() {\n _status = NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be NOT_ENTERED\n if (_status == ENTERED) {\n revert ReentrancyGuardReentrantCall();\n }\n\n // Any calls to nonReentrant after this point will fail\n _status = ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = NOT_ENTERED;\n }\n\n /**\n * @dev Returns true if the reentrancy guard is currently set to \"entered\", which indicates there is a\n * `nonReentrant` function in the call stack.\n */\n function _reentrancyGuardEntered() internal view returns (bool) {\n return _status == ENTERED;\n }\n}\n"},"@openzeppelin/contracts/utils/ShortStrings.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/ShortStrings.sol)\n\npragma solidity ^0.8.20;\n\nimport {StorageSlot} from \"./StorageSlot.sol\";\n\n// | string | 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA |\n// | length | 0x BB |\ntype ShortString is bytes32;\n\n/**\n * @dev This library provides functions to convert short memory strings\n * into a `ShortString` type that can be used as an immutable variable.\n *\n * Strings of arbitrary length can be optimized using this library if\n * they are short enough (up to 31 bytes) by packing them with their\n * length (1 byte) in a single EVM word (32 bytes). Additionally, a\n * fallback mechanism can be used for every other case.\n *\n * Usage example:\n *\n * ```solidity\n * contract Named {\n * using ShortStrings for *;\n *\n * ShortString private immutable _name;\n * string private _nameFallback;\n *\n * constructor(string memory contractName) {\n * _name = contractName.toShortStringWithFallback(_nameFallback);\n * }\n *\n * function name() external view returns (string memory) {\n * return _name.toStringWithFallback(_nameFallback);\n * }\n * }\n * ```\n */\nlibrary ShortStrings {\n // Used as an identifier for strings longer than 31 bytes.\n bytes32 private constant FALLBACK_SENTINEL = 0x00000000000000000000000000000000000000000000000000000000000000FF;\n\n error StringTooLong(string str);\n error InvalidShortString();\n\n /**\n * @dev Encode a string of at most 31 chars into a `ShortString`.\n *\n * This will trigger a `StringTooLong` error is the input string is too long.\n */\n function toShortString(string memory str) internal pure returns (ShortString) {\n bytes memory bstr = bytes(str);\n if (bstr.length > 31) {\n revert StringTooLong(str);\n }\n return ShortString.wrap(bytes32(uint256(bytes32(bstr)) | bstr.length));\n }\n\n /**\n * @dev Decode a `ShortString` back to a \"normal\" string.\n */\n function toString(ShortString sstr) internal pure returns (string memory) {\n uint256 len = byteLength(sstr);\n // using `new string(len)` would work locally but is not memory safe.\n string memory str = new string(32);\n assembly (\"memory-safe\") {\n mstore(str, len)\n mstore(add(str, 0x20), sstr)\n }\n return str;\n }\n\n /**\n * @dev Return the length of a `ShortString`.\n */\n function byteLength(ShortString sstr) internal pure returns (uint256) {\n uint256 result = uint256(ShortString.unwrap(sstr)) & 0xFF;\n if (result > 31) {\n revert InvalidShortString();\n }\n return result;\n }\n\n /**\n * @dev Encode a string into a `ShortString`, or write it to storage if it is too long.\n */\n function toShortStringWithFallback(string memory value, string storage store) internal returns (ShortString) {\n if (bytes(value).length < 32) {\n return toShortString(value);\n } else {\n StorageSlot.getStringSlot(store).value = value;\n return ShortString.wrap(FALLBACK_SENTINEL);\n }\n }\n\n /**\n * @dev Decode a string that was encoded to `ShortString` or written to storage using {toShortStringWithFallback}.\n */\n function toStringWithFallback(ShortString value, string storage store) internal pure returns (string memory) {\n if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {\n return toString(value);\n } else {\n return store;\n }\n }\n\n /**\n * @dev Return the length of a string that was encoded to `ShortString` or written to storage using\n * {toShortStringWithFallback}.\n *\n * WARNING: This will return the \"byte length\" of the string. This may not reflect the actual length in terms of\n * actual characters as the UTF-8 encoding of a single character can span over multiple bytes.\n */\n function byteLengthWithFallback(ShortString value, string storage store) internal view returns (uint256) {\n if (ShortString.unwrap(value) != FALLBACK_SENTINEL) {\n return byteLength(value);\n } else {\n return bytes(store).length;\n }\n }\n}\n"},"@openzeppelin/contracts/utils/StorageSlot.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)\n// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC-1967 implementation slot:\n * ```solidity\n * contract ERC1967 {\n * // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(newImplementation.code.length > 0);\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * TIP: Consider using this library along with {SlotDerivation}.\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n struct Int256Slot {\n int256 value;\n }\n\n struct StringSlot {\n string value;\n }\n\n struct BytesSlot {\n bytes value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `Int256Slot` with member `value` located at `slot`.\n */\n function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns a `StringSlot` with member `value` located at `slot`.\n */\n function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `StringSlot` representation of the string storage pointer `store`.\n */\n function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := store.slot\n }\n }\n\n /**\n * @dev Returns a `BytesSlot` with member `value` located at `slot`.\n */\n function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.\n */\n function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {\n assembly (\"memory-safe\") {\n r.slot := store.slot\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Strings.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)\n\npragma solidity ^0.8.20;\n\nimport {Math} from \"./math/Math.sol\";\nimport {SafeCast} from \"./math/SafeCast.sol\";\nimport {SignedMath} from \"./math/SignedMath.sol\";\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n using SafeCast for *;\n\n bytes16 private constant HEX_DIGITS = \"0123456789abcdef\";\n uint8 private constant ADDRESS_LENGTH = 20;\n uint256 private constant SPECIAL_CHARS_LOOKUP =\n (1 << 0x08) | // backspace\n (1 << 0x09) | // tab\n (1 << 0x0a) | // newline\n (1 << 0x0c) | // form feed\n (1 << 0x0d) | // carriage return\n (1 << 0x22) | // double quote\n (1 << 0x5c); // backslash\n\n /**\n * @dev The `value` string doesn't fit in the specified `length`.\n */\n error StringsInsufficientHexLength(uint256 value, uint256 length);\n\n /**\n * @dev The string being parsed contains characters that are not in scope of the given base.\n */\n error StringsInvalidChar();\n\n /**\n * @dev The string being parsed is not a properly formatted address.\n */\n error StringsInvalidAddressFormat();\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n assembly (\"memory-safe\") {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n assembly (\"memory-safe\") {\n mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `int256` to its ASCII `string` decimal representation.\n */\n function toStringSigned(int256 value) internal pure returns (string memory) {\n return string.concat(value < 0 ? \"-\" : \"\", toString(SignedMath.abs(value)));\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n uint256 localValue = value;\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = HEX_DIGITS[localValue & 0xf];\n localValue >>= 4;\n }\n if (localValue != 0) {\n revert StringsInsufficientHexLength(value, length);\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal\n * representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal\n * representation, according to EIP-55.\n */\n function toChecksumHexString(address addr) internal pure returns (string memory) {\n bytes memory buffer = bytes(toHexString(addr));\n\n // hash the hex part of buffer (skip length + 2 bytes, length 40)\n uint256 hashValue;\n assembly (\"memory-safe\") {\n hashValue := shr(96, keccak256(add(buffer, 0x22), 40))\n }\n\n for (uint256 i = 41; i > 1; --i) {\n // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)\n if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {\n // case shift by xoring with 0x20\n buffer[i] ^= 0x20;\n }\n hashValue >>= 4;\n }\n return string(buffer);\n }\n\n /**\n * @dev Returns true if the two strings are equal.\n */\n function equal(string memory a, string memory b) internal pure returns (bool) {\n return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));\n }\n\n /**\n * @dev Parse a decimal string and returns the value as a `uint256`.\n *\n * Requirements:\n * - The string must be formatted as `[0-9]*`\n * - The result must fit into an `uint256` type\n */\n function parseUint(string memory input) internal pure returns (uint256) {\n return parseUint(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `[0-9]*`\n * - The result must fit into an `uint256` type\n */\n function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {\n (bool success, uint256 value) = tryParseUint(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {\n return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid\n * character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseUint(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, uint256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseUintUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseUintUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, uint256 value) {\n bytes memory buffer = bytes(input);\n\n uint256 result = 0;\n for (uint256 i = begin; i < end; ++i) {\n uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));\n if (chr > 9) return (false, 0);\n result *= 10;\n result += chr;\n }\n return (true, result);\n }\n\n /**\n * @dev Parse a decimal string and returns the value as a `int256`.\n *\n * Requirements:\n * - The string must be formatted as `[-+]?[0-9]*`\n * - The result must fit in an `int256` type.\n */\n function parseInt(string memory input) internal pure returns (int256) {\n return parseInt(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `[-+]?[0-9]*`\n * - The result must fit in an `int256` type.\n */\n function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {\n (bool success, int256 value) = tryParseInt(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if\n * the result does not fit in a `int256`.\n *\n * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.\n */\n function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {\n return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);\n }\n\n uint256 private constant ABS_MIN_INT256 = 2 ** 255;\n\n /**\n * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid\n * character or if the result does not fit in a `int256`.\n *\n * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.\n */\n function tryParseInt(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, int256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseIntUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseIntUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, int256 value) {\n bytes memory buffer = bytes(input);\n\n // Check presence of a negative sign.\n bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n bool positiveSign = sign == bytes1(\"+\");\n bool negativeSign = sign == bytes1(\"-\");\n uint256 offset = (positiveSign || negativeSign).toUint();\n\n (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);\n\n if (absSuccess && absValue < ABS_MIN_INT256) {\n return (true, negativeSign ? -int256(absValue) : int256(absValue));\n } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {\n return (true, type(int256).min);\n } else return (false, 0);\n }\n\n /**\n * @dev Parse a hexadecimal string (with or without \"0x\" prefix), and returns the value as a `uint256`.\n *\n * Requirements:\n * - The string must be formatted as `(0x)?[0-9a-fA-F]*`\n * - The result must fit in an `uint256` type.\n */\n function parseHexUint(string memory input) internal pure returns (uint256) {\n return parseHexUint(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`\n * - The result must fit in an `uint256` type.\n */\n function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {\n (bool success, uint256 value) = tryParseHexUint(input, begin, end);\n if (!success) revert StringsInvalidChar();\n return value;\n }\n\n /**\n * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {\n return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an\n * invalid character.\n *\n * NOTE: This function will revert if the result does not fit in a `uint256`.\n */\n function tryParseHexUint(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, uint256 value) {\n if (end > bytes(input).length || begin > end) return (false, 0);\n return _tryParseHexUintUncheckedBounds(input, begin, end);\n }\n\n /**\n * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that\n * `begin <= end <= input.length`. Other inputs would result in undefined behavior.\n */\n function _tryParseHexUintUncheckedBounds(\n string memory input,\n uint256 begin,\n uint256 end\n ) private pure returns (bool success, uint256 value) {\n bytes memory buffer = bytes(input);\n\n // skip 0x prefix if present\n bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(buffer, begin)) == bytes2(\"0x\"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n uint256 offset = hasPrefix.toUint() * 2;\n\n uint256 result = 0;\n for (uint256 i = begin + offset; i < end; ++i) {\n uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));\n if (chr > 15) return (false, 0);\n result *= 16;\n unchecked {\n // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).\n // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.\n result += chr;\n }\n }\n return (true, result);\n }\n\n /**\n * @dev Parse a hexadecimal string (with or without \"0x\" prefix), and returns the value as an `address`.\n *\n * Requirements:\n * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`\n */\n function parseAddress(string memory input) internal pure returns (address) {\n return parseAddress(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and\n * `end` (excluded).\n *\n * Requirements:\n * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`\n */\n function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {\n (bool success, address value) = tryParseAddress(input, begin, end);\n if (!success) revert StringsInvalidAddressFormat();\n return value;\n }\n\n /**\n * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly\n * formatted address. See {parseAddress-string} requirements.\n */\n function tryParseAddress(string memory input) internal pure returns (bool success, address value) {\n return tryParseAddress(input, 0, bytes(input).length);\n }\n\n /**\n * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly\n * formatted address. See {parseAddress-string-uint256-uint256} requirements.\n */\n function tryParseAddress(\n string memory input,\n uint256 begin,\n uint256 end\n ) internal pure returns (bool success, address value) {\n if (end > bytes(input).length || begin > end) return (false, address(0));\n\n bool hasPrefix = (end > begin + 1) && bytes2(_unsafeReadBytesOffset(bytes(input), begin)) == bytes2(\"0x\"); // don't do out-of-bound (possibly unsafe) read if sub-string is empty\n uint256 expectedLength = 40 + hasPrefix.toUint() * 2;\n\n // check that input is the correct length\n if (end - begin == expectedLength) {\n // length guarantees that this does not overflow, and value is at most type(uint160).max\n (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);\n return (s, address(uint160(v)));\n } else {\n return (false, address(0));\n }\n }\n\n function _tryParseChr(bytes1 chr) private pure returns (uint8) {\n uint8 value = uint8(chr);\n\n // Try to parse `chr`:\n // - Case 1: [0-9]\n // - Case 2: [a-f]\n // - Case 3: [A-F]\n // - otherwise not supported\n unchecked {\n if (value > 47 && value < 58) value -= 48;\n else if (value > 96 && value < 103) value -= 87;\n else if (value > 64 && value < 71) value -= 55;\n else return type(uint8).max;\n }\n\n return value;\n }\n\n /**\n * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.\n *\n * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.\n *\n * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of\n * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode\n * characters that are not in this range, but other tooling may provide different results.\n */\n function escapeJSON(string memory input) internal pure returns (string memory) {\n bytes memory buffer = bytes(input);\n bytes memory output = new bytes(2 * buffer.length); // worst case scenario\n uint256 outputLength = 0;\n\n for (uint256 i; i < buffer.length; ++i) {\n bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));\n if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {\n output[outputLength++] = \"\\\\\";\n if (char == 0x08) output[outputLength++] = \"b\";\n else if (char == 0x09) output[outputLength++] = \"t\";\n else if (char == 0x0a) output[outputLength++] = \"n\";\n else if (char == 0x0c) output[outputLength++] = \"f\";\n else if (char == 0x0d) output[outputLength++] = \"r\";\n else if (char == 0x5c) output[outputLength++] = \"\\\\\";\n else if (char == 0x22) {\n // solhint-disable-next-line quotes\n output[outputLength++] = '\"';\n }\n } else {\n output[outputLength++] = char;\n }\n }\n // write the actual length and deallocate unused memory\n assembly (\"memory-safe\") {\n mstore(output, outputLength)\n mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))\n }\n\n return string(output);\n }\n\n /**\n * @dev Reads a bytes32 from a bytes array without bounds checking.\n *\n * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the\n * assembly block as such would prevent some optimizations.\n */\n function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {\n // This is not memory safe in the general case, but all calls to this private function are within bounds.\n assembly (\"memory-safe\") {\n value := mload(add(buffer, add(0x20, offset)))\n }\n }\n}\n"},"@openzeppelin/contracts/utils/cryptography/ECDSA.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS\n }\n\n /**\n * @dev The signature derives the `address(0)`.\n */\n error ECDSAInvalidSignature();\n\n /**\n * @dev The signature has an invalid length.\n */\n error ECDSAInvalidSignatureLength(uint256 length);\n\n /**\n * @dev The signature has an S value that is in the upper half order.\n */\n error ECDSAInvalidSignatureS(bytes32 s);\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not\n * return address(0) without also returning an error description. Errors are documented using an enum (error type)\n * and a bytes32 providing additional information about the error.\n *\n * If no error is returned, then the address can be used for verification purposes.\n *\n * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n */\n function tryRecover(\n bytes32 hash,\n bytes memory signature\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly (\"memory-safe\") {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[ERC-2098 short signatures]\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n unchecked {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n // We do not check for an overflow here since the shift operation results in 0 or 1.\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n */\n function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address recovered, RecoverError err, bytes32 errArg) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS, s);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature, bytes32(0));\n }\n\n return (signer, RecoverError.NoError, bytes32(0));\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {\n (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);\n _throwError(error, errorArg);\n return recovered;\n }\n\n /**\n * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.\n */\n function _throwError(RecoverError error, bytes32 errorArg) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert ECDSAInvalidSignature();\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert ECDSAInvalidSignatureLength(uint256(errorArg));\n } else if (error == RecoverError.InvalidSignatureS) {\n revert ECDSAInvalidSignatureS(errorArg);\n }\n }\n}\n"},"@openzeppelin/contracts/utils/cryptography/EIP712.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/EIP712.sol)\n\npragma solidity ^0.8.20;\n\nimport {MessageHashUtils} from \"./MessageHashUtils.sol\";\nimport {ShortStrings, ShortString} from \"../ShortStrings.sol\";\nimport {IERC5267} from \"../../interfaces/IERC5267.sol\";\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP-712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose\n * encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract\n * does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to\n * produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP-712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain\n * separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the\n * separator from the immutable values, which is cheaper than accessing a cached version in cold storage.\n *\n * @custom:oz-upgrades-unsafe-allow state-variable-immutable\n */\nabstract contract EIP712 is IERC5267 {\n using ShortStrings for *;\n\n bytes32 private constant TYPE_HASH =\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\");\n\n // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to\n // invalidate the cached domain separator if the chain id changes.\n bytes32 private immutable _cachedDomainSeparator;\n uint256 private immutable _cachedChainId;\n address private immutable _cachedThis;\n\n bytes32 private immutable _hashedName;\n bytes32 private immutable _hashedVersion;\n\n ShortString private immutable _name;\n ShortString private immutable _version;\n // slither-disable-next-line constable-states\n string private _nameFallback;\n // slither-disable-next-line constable-states\n string private _versionFallback;\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP-712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n _name = name.toShortStringWithFallback(_nameFallback);\n _version = version.toShortStringWithFallback(_versionFallback);\n _hashedName = keccak256(bytes(name));\n _hashedVersion = keccak256(bytes(version));\n\n _cachedChainId = block.chainid;\n _cachedDomainSeparator = _buildDomainSeparator();\n _cachedThis = address(this);\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view returns (bytes32) {\n if (address(this) == _cachedThis && block.chainid == _cachedChainId) {\n return _cachedDomainSeparator;\n } else {\n return _buildDomainSeparator();\n }\n }\n\n function _buildDomainSeparator() private view returns (bytes32) {\n return keccak256(abi.encode(TYPE_HASH, _hashedName, _hashedVersion, block.chainid, address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);\n }\n\n /**\n * @inheritdoc IERC5267\n */\n function eip712Domain()\n public\n view\n virtual\n returns (\n bytes1 fields,\n string memory name,\n string memory version,\n uint256 chainId,\n address verifyingContract,\n bytes32 salt,\n uint256[] memory extensions\n )\n {\n return (\n hex\"0f\", // 01111\n _EIP712Name(),\n _EIP712Version(),\n block.chainid,\n address(this),\n bytes32(0),\n new uint256[](0)\n );\n }\n\n /**\n * @dev The name parameter for the EIP712 domain.\n *\n * NOTE: By default this function reads _name which is an immutable value.\n * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).\n */\n // solhint-disable-next-line func-name-mixedcase\n function _EIP712Name() internal view returns (string memory) {\n return _name.toStringWithFallback(_nameFallback);\n }\n\n /**\n * @dev The version parameter for the EIP712 domain.\n *\n * NOTE: By default this function reads _version which is an immutable value.\n * It only reads from storage if necessary (in case the value is too large to fit in a ShortString).\n */\n // solhint-disable-next-line func-name-mixedcase\n function _EIP712Version() internal view returns (string memory) {\n return _version.toStringWithFallback(_versionFallback);\n }\n}\n"},"@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/cryptography/MessageHashUtils.sol)\n\npragma solidity ^0.8.20;\n\nimport {Strings} from \"../Strings.sol\";\n\n/**\n * @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.\n *\n * The library provides methods for generating a hash of a message that conforms to the\n * https://eips.ethereum.org/EIPS/eip-191[ERC-191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]\n * specifications.\n */\nlibrary MessageHashUtils {\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x45` (`personal_sign` messages).\n *\n * The digest is calculated by prefixing a bytes32 `messageHash` with\n * `\"\\x19Ethereum Signed Message:\\n32\"` and hashing the result. It corresponds with the\n * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.\n *\n * NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with\n * keccak256, although any bytes32 value can be safely used because the final digest will\n * be re-hashed.\n *\n * See {ECDSA-recover}.\n */\n function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n mstore(0x00, \"\\x19Ethereum Signed Message:\\n32\") // 32 is the bytes-length of messageHash\n mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix\n digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)\n }\n }\n\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x45` (`personal_sign` messages).\n *\n * The digest is calculated by prefixing an arbitrary `message` with\n * `\"\\x19Ethereum Signed Message:\\n\" + len(message)` and hashing the result. It corresponds with the\n * hash signed when using the https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign[`eth_sign`] JSON-RPC method.\n *\n * See {ECDSA-recover}.\n */\n function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {\n return\n keccak256(bytes.concat(\"\\x19Ethereum Signed Message:\\n\", bytes(Strings.toString(message.length)), message));\n }\n\n /**\n * @dev Returns the keccak256 digest of an ERC-191 signed data with version\n * `0x00` (data with intended validator).\n *\n * The digest is calculated by prefixing an arbitrary `data` with `\"\\x19\\x00\"` and the intended\n * `validator` address. Then hashing the result.\n *\n * See {ECDSA-recover}.\n */\n function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(hex\"19_00\", validator, data));\n }\n\n /**\n * @dev Variant of {toDataWithIntendedValidatorHash-address-bytes} optimized for cases where `data` is a bytes32.\n */\n function toDataWithIntendedValidatorHash(\n address validator,\n bytes32 messageHash\n ) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n mstore(0x00, hex\"19_00\")\n mstore(0x02, shl(96, validator))\n mstore(0x16, messageHash)\n digest := keccak256(0x00, 0x36)\n }\n }\n\n /**\n * @dev Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`).\n *\n * The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with\n * `\\x19\\x01` and hashing the result. It corresponds to the hash signed by the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.\n *\n * See {ECDSA-recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n mstore(ptr, hex\"19_01\")\n mstore(add(ptr, 0x02), domainSeparator)\n mstore(add(ptr, 0x22), structHash)\n digest := keccak256(ptr, 0x42)\n }\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/ERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC165} from \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n */\n abstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Interface of the ERC-165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[ERC].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"@openzeppelin/contracts/utils/math/Math.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)\n\npragma solidity ^0.8.20;\n\nimport {Panic} from \"../Panic.sol\";\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Floor, // Toward negative infinity\n Ceil, // Toward positive infinity\n Trunc, // Toward zero\n Expand // Away from zero\n }\n\n /**\n * @dev Return the 512-bit addition of two uint256.\n *\n * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.\n */\n function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n assembly (\"memory-safe\") {\n low := add(a, b)\n high := lt(low, a)\n }\n }\n\n /**\n * @dev Return the 512-bit multiplication of two uint256.\n *\n * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.\n */\n function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {\n // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use\n // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = high * 2²⁵⁶ + low.\n assembly (\"memory-safe\") {\n let mm := mulmod(a, b, not(0))\n low := mul(a, b)\n high := sub(sub(mm, low), lt(mm, low))\n }\n }\n\n /**\n * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).\n */\n function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a + b;\n success = c >= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).\n */\n function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a - b;\n success = c <= a;\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).\n */\n function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n uint256 c = a * b;\n assembly (\"memory-safe\") {\n // Only true when the multiplication doesn't overflow\n // (c / a == b) || (a == 0)\n success := or(eq(div(c, a), b), iszero(a))\n }\n // equivalent to: success ? c : 0\n result = c * SafeCast.toUint(success);\n }\n }\n\n /**\n * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).\n */\n function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `DIV` opcode returns zero when the denominator is 0.\n result := div(a, b)\n }\n }\n }\n\n /**\n * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).\n */\n function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {\n unchecked {\n success = b > 0;\n assembly (\"memory-safe\") {\n // The `MOD` opcode returns zero when the denominator is 0.\n result := mod(a, b)\n }\n }\n }\n\n /**\n * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryAdd(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.\n */\n function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {\n (, uint256 result) = trySub(a, b);\n return result;\n }\n\n /**\n * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.\n */\n function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {\n (bool success, uint256 result) = tryMul(a, b);\n return ternary(success, result, type(uint256).max);\n }\n\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * SafeCast.toUint(condition));\n }\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds towards infinity instead\n * of rounding towards zero.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n if (b == 0) {\n // Guarantee the same behavior as in a regular Solidity division.\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n\n // The following calculation ensures accurate ceiling division without overflow.\n // Since a is non-zero, (a - 1) / b will not overflow.\n // The largest possible result occurs when (a - 1) / b is type(uint256).max,\n // but the largest value we can obtain is type(uint256).max - 1, which happens\n // when a = type(uint256).max and b = 1.\n unchecked {\n return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);\n }\n }\n\n /**\n * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or\n * denominator == 0.\n *\n * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by\n * Uniswap Labs also under MIT license.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n\n // Handle non-overflow cases, 256 by 256 division.\n if (high == 0) {\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\n // The surrounding unchecked block does not change this fact.\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\n return low / denominator;\n }\n\n // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.\n if (denominator <= high) {\n Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));\n }\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [high low].\n uint256 remainder;\n assembly (\"memory-safe\") {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n high := sub(high, gt(remainder, low))\n low := sub(low, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator.\n // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.\n\n uint256 twos = denominator & (0 - denominator);\n assembly (\"memory-safe\") {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [high low] by twos.\n low := div(low, twos)\n\n // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from high into low.\n low |= high * twos;\n\n // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such\n // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv ≡ 1 mod 2⁴.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also\n // works in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶\n inverse *= 2 - denominator * inverse; // inverse mod 2³²\n inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴\n inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸\n inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is\n // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high\n // is no longer required.\n result = low * inverse;\n return result;\n }\n }\n\n /**\n * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\n return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);\n }\n\n /**\n * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.\n */\n function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {\n unchecked {\n (uint256 high, uint256 low) = mul512(x, y);\n if (high >= 1 << n) {\n Panic.panic(Panic.UNDER_OVERFLOW);\n }\n return (high << (256 - n)) | (low >> n);\n }\n }\n\n /**\n * @dev Calculates x * y >> n with full precision, following the selected rounding direction.\n */\n function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {\n return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);\n }\n\n /**\n * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.\n *\n * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.\n * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.\n *\n * If the input value is not inversible, 0 is returned.\n *\n * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the\n * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.\n */\n function invMod(uint256 a, uint256 n) internal pure returns (uint256) {\n unchecked {\n if (n == 0) return 0;\n\n // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)\n // Used to compute integers x and y such that: ax + ny = gcd(a, n).\n // When the gcd is 1, then the inverse of a modulo n exists and it's x.\n // ax + ny = 1\n // ax = 1 + (-y)n\n // ax ≡ 1 (mod n) # x is the inverse of a modulo n\n\n // If the remainder is 0 the gcd is n right away.\n uint256 remainder = a % n;\n uint256 gcd = n;\n\n // Therefore the initial coefficients are:\n // ax + ny = gcd(a, n) = n\n // 0a + 1n = n\n int256 x = 0;\n int256 y = 1;\n\n while (remainder != 0) {\n uint256 quotient = gcd / remainder;\n\n (gcd, remainder) = (\n // The old remainder is the next gcd to try.\n remainder,\n // Compute the next remainder.\n // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd\n // where gcd is at most n (capped to type(uint256).max)\n gcd - remainder * quotient\n );\n\n (x, y) = (\n // Increment the coefficient of a.\n y,\n // Decrement the coefficient of n.\n // Can overflow, but the result is casted to uint256 so that the\n // next value of y is \"wrapped around\" to a value between 0 and n - 1.\n x - y * int256(quotient)\n );\n }\n\n if (gcd != 1) return 0; // No inverse exists.\n return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.\n }\n }\n\n /**\n * @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.\n *\n * From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is\n * prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that\n * `a**(p-2)` is the modular multiplicative inverse of a in Fp.\n *\n * NOTE: this function does NOT check that `p` is a prime greater than `2`.\n */\n function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {\n unchecked {\n return Math.modExp(a, p - 2, p);\n }\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)\n *\n * Requirements:\n * - modulus can't be zero\n * - underlying staticcall to precompile must succeed\n *\n * IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make\n * sure the chain you're using it on supports the precompiled contract for modular exponentiation\n * at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,\n * the underlying function will succeed given the lack of a revert, but the result may be incorrectly\n * interpreted as 0.\n */\n function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {\n (bool success, uint256 result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).\n * It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying\n * to operate modulo 0 or if the underlying precompile reverted.\n *\n * IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain\n * you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in\n * https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack\n * of a revert, but the result may be incorrectly interpreted as 0.\n */\n function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {\n if (m == 0) return (false, 0);\n assembly (\"memory-safe\") {\n let ptr := mload(0x40)\n // | Offset | Content | Content (Hex) |\n // |-----------|------------|--------------------------------------------------------------------|\n // | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |\n // | 0x60:0x7f | value of b | 0x<.............................................................b> |\n // | 0x80:0x9f | value of e | 0x<.............................................................e> |\n // | 0xa0:0xbf | value of m | 0x<.............................................................m> |\n mstore(ptr, 0x20)\n mstore(add(ptr, 0x20), 0x20)\n mstore(add(ptr, 0x40), 0x20)\n mstore(add(ptr, 0x60), b)\n mstore(add(ptr, 0x80), e)\n mstore(add(ptr, 0xa0), m)\n\n // Given the result < m, it's guaranteed to fit in 32 bytes,\n // so we can use the memory scratch space located at offset 0.\n success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)\n result := mload(0x00)\n }\n }\n\n /**\n * @dev Variant of {modExp} that supports inputs of arbitrary length.\n */\n function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {\n (bool success, bytes memory result) = tryModExp(b, e, m);\n if (!success) {\n Panic.panic(Panic.DIVISION_BY_ZERO);\n }\n return result;\n }\n\n /**\n * @dev Variant of {tryModExp} that supports inputs of arbitrary length.\n */\n function tryModExp(\n bytes memory b,\n bytes memory e,\n bytes memory m\n ) internal view returns (bool success, bytes memory result) {\n if (_zeroBytes(m)) return (false, new bytes(0));\n\n uint256 mLen = m.length;\n\n // Encode call args in result and move the free memory pointer\n result = abi.encodePacked(b.length, e.length, mLen, b, e, m);\n\n assembly (\"memory-safe\") {\n let dataPtr := add(result, 0x20)\n // Write result on top of args to avoid allocating extra memory.\n success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)\n // Overwrite the length.\n // result.length > returndatasize() is guaranteed because returndatasize() == m.length\n mstore(result, mLen)\n // Set the memory pointer after the returned data.\n mstore(0x40, add(dataPtr, mLen))\n }\n }\n\n /**\n * @dev Returns whether the provided byte array is zero.\n */\n function _zeroBytes(bytes memory byteArray) private pure returns (bool) {\n for (uint256 i = 0; i < byteArray.length; ++i) {\n if (byteArray[i] != 0) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded\n * towards zero.\n *\n * This method is based on Newton's method for computing square roots; the algorithm is restricted to only\n * using integer operations.\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n unchecked {\n // Take care of easy edge cases when a == 0 or a == 1\n if (a <= 1) {\n return a;\n }\n\n // In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a\n // sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between\n // the current value as `ε_n = | x_n - sqrt(a) |`.\n //\n // For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root\n // of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is\n // bigger than any uint256.\n //\n // By noticing that\n // `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`\n // we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar\n // to the msb function.\n uint256 aa = a;\n uint256 xn = 1;\n\n if (aa >= (1 << 128)) {\n aa >>= 128;\n xn <<= 64;\n }\n if (aa >= (1 << 64)) {\n aa >>= 64;\n xn <<= 32;\n }\n if (aa >= (1 << 32)) {\n aa >>= 32;\n xn <<= 16;\n }\n if (aa >= (1 << 16)) {\n aa >>= 16;\n xn <<= 8;\n }\n if (aa >= (1 << 8)) {\n aa >>= 8;\n xn <<= 4;\n }\n if (aa >= (1 << 4)) {\n aa >>= 4;\n xn <<= 2;\n }\n if (aa >= (1 << 2)) {\n xn <<= 1;\n }\n\n // 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).\n //\n // We can refine our estimation by noticing that the middle of that interval minimizes the error.\n // If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).\n // This is going to be our x_0 (and ε_0)\n xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)\n\n // From here, Newton's method give us:\n // x_{n+1} = (x_n + a / x_n) / 2\n //\n // One should note that:\n // x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a\n // = ((x_n² + a) / (2 * x_n))² - a\n // = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a\n // = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)\n // = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)\n // = (x_n² - a)² / (2 * x_n)²\n // = ((x_n² - a) / (2 * x_n))²\n // ≥ 0\n // Which proves that for all n ≥ 1, sqrt(a) ≤ x_n\n //\n // This gives us the proof of quadratic convergence of the sequence:\n // ε_{n+1} = | x_{n+1} - sqrt(a) |\n // = | (x_n + a / x_n) / 2 - sqrt(a) |\n // = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |\n // = | (x_n - sqrt(a))² / (2 * x_n) |\n // = | ε_n² / (2 * x_n) |\n // = ε_n² / | (2 * x_n) |\n //\n // For the first iteration, we have a special case where x_0 is known:\n // ε_1 = ε_0² / | (2 * x_0) |\n // ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))\n // ≤ 2**(2*e-4) / (3 * 2**(e-1))\n // ≤ 2**(e-3) / 3\n // ≤ 2**(e-3-log2(3))\n // ≤ 2**(e-4.5)\n //\n // For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:\n // ε_{n+1} = ε_n² / | (2 * x_n) |\n // ≤ (2**(e-k))² / (2 * 2**(e-1))\n // ≤ 2**(2*e-2*k) / 2**e\n // ≤ 2**(e-2*k)\n xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above\n xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5\n xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9\n xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18\n xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36\n xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72\n\n // Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision\n // ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either\n // sqrt(a) or sqrt(a) + 1.\n return xn - SafeCast.toUint(xn > a / xn);\n }\n }\n\n /**\n * @dev Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);\n }\n }\n\n /**\n * @dev Return the log in base 2 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log2(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // If upper 8 bits of 16-bit half set, add 8 to result\n r |= SafeCast.toUint((x >> r) > 0xff) << 3;\n // If upper 4 bits of 8-bit half set, add 4 to result\n r |= SafeCast.toUint((x >> r) > 0xf) << 2;\n\n // Shifts value right by the current result and use it as an index into this lookup table:\n //\n // | x (4 bits) | index | table[index] = MSB position |\n // |------------|---------|-----------------------------|\n // | 0000 | 0 | table[0] = 0 |\n // | 0001 | 1 | table[1] = 0 |\n // | 0010 | 2 | table[2] = 1 |\n // | 0011 | 3 | table[3] = 1 |\n // | 0100 | 4 | table[4] = 2 |\n // | 0101 | 5 | table[5] = 2 |\n // | 0110 | 6 | table[6] = 2 |\n // | 0111 | 7 | table[7] = 2 |\n // | 1000 | 8 | table[8] = 3 |\n // | 1001 | 9 | table[9] = 3 |\n // | 1010 | 10 | table[10] = 3 |\n // | 1011 | 11 | table[11] = 3 |\n // | 1100 | 12 | table[12] = 3 |\n // | 1101 | 13 | table[13] = 3 |\n // | 1110 | 14 | table[14] = 3 |\n // | 1111 | 15 | table[15] = 3 |\n //\n // The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.\n assembly (\"memory-safe\") {\n r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))\n }\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);\n }\n }\n\n /**\n * @dev Return the log in base 10 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10 ** 64) {\n value /= 10 ** 64;\n result += 64;\n }\n if (value >= 10 ** 32) {\n value /= 10 ** 32;\n result += 32;\n }\n if (value >= 10 ** 16) {\n value /= 10 ** 16;\n result += 16;\n }\n if (value >= 10 ** 8) {\n value /= 10 ** 8;\n result += 8;\n }\n if (value >= 10 ** 4) {\n value /= 10 ** 4;\n result += 4;\n }\n if (value >= 10 ** 2) {\n value /= 10 ** 2;\n result += 2;\n }\n if (value >= 10 ** 1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);\n }\n }\n\n /**\n * @dev Return the log in base 256 of a positive value rounded towards zero.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 x) internal pure returns (uint256 r) {\n // If value has upper 128 bits set, log2 result is at least 128\n r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;\n // If upper 64 bits of 128-bit half set, add 64 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;\n // If upper 32 bits of 64-bit half set, add 32 to result\n r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;\n // If upper 16 bits of 32-bit half set, add 16 to result\n r |= SafeCast.toUint((x >> r) > 0xffff) << 4;\n // Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8\n return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);\n }\n\n /**\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);\n }\n }\n\n /**\n * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.\n */\n function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {\n return uint8(rounding) % 2 == 1;\n }\n}\n"},"@openzeppelin/contracts/utils/math/SafeCast.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)\n// This file was procedurally generated from scripts/generate/templates/SafeCast.js.\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow\n * checks.\n *\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\n * easily result in undesired exploitation or bugs, since developers usually\n * assume that overflows raise errors. `SafeCast` restores this intuition by\n * reverting the transaction when such an operation overflows.\n *\n * Using this library instead of the unchecked operations eliminates an entire\n * class of bugs, so it's recommended to use it always.\n */\nlibrary SafeCast {\n /**\n * @dev Value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);\n\n /**\n * @dev An int value doesn't fit in an uint of `bits` size.\n */\n error SafeCastOverflowedIntToUint(int256 value);\n\n /**\n * @dev Value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);\n\n /**\n * @dev An uint value doesn't fit in an int of `bits` size.\n */\n error SafeCastOverflowedUintToInt(uint256 value);\n\n /**\n * @dev Returns the downcasted uint248 from uint256, reverting on\n * overflow (when the input is greater than largest uint248).\n *\n * Counterpart to Solidity's `uint248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toUint248(uint256 value) internal pure returns (uint248) {\n if (value > type(uint248).max) {\n revert SafeCastOverflowedUintDowncast(248, value);\n }\n return uint248(value);\n }\n\n /**\n * @dev Returns the downcasted uint240 from uint256, reverting on\n * overflow (when the input is greater than largest uint240).\n *\n * Counterpart to Solidity's `uint240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toUint240(uint256 value) internal pure returns (uint240) {\n if (value > type(uint240).max) {\n revert SafeCastOverflowedUintDowncast(240, value);\n }\n return uint240(value);\n }\n\n /**\n * @dev Returns the downcasted uint232 from uint256, reverting on\n * overflow (when the input is greater than largest uint232).\n *\n * Counterpart to Solidity's `uint232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toUint232(uint256 value) internal pure returns (uint232) {\n if (value > type(uint232).max) {\n revert SafeCastOverflowedUintDowncast(232, value);\n }\n return uint232(value);\n }\n\n /**\n * @dev Returns the downcasted uint224 from uint256, reverting on\n * overflow (when the input is greater than largest uint224).\n *\n * Counterpart to Solidity's `uint224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toUint224(uint256 value) internal pure returns (uint224) {\n if (value > type(uint224).max) {\n revert SafeCastOverflowedUintDowncast(224, value);\n }\n return uint224(value);\n }\n\n /**\n * @dev Returns the downcasted uint216 from uint256, reverting on\n * overflow (when the input is greater than largest uint216).\n *\n * Counterpart to Solidity's `uint216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toUint216(uint256 value) internal pure returns (uint216) {\n if (value > type(uint216).max) {\n revert SafeCastOverflowedUintDowncast(216, value);\n }\n return uint216(value);\n }\n\n /**\n * @dev Returns the downcasted uint208 from uint256, reverting on\n * overflow (when the input is greater than largest uint208).\n *\n * Counterpart to Solidity's `uint208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toUint208(uint256 value) internal pure returns (uint208) {\n if (value > type(uint208).max) {\n revert SafeCastOverflowedUintDowncast(208, value);\n }\n return uint208(value);\n }\n\n /**\n * @dev Returns the downcasted uint200 from uint256, reverting on\n * overflow (when the input is greater than largest uint200).\n *\n * Counterpart to Solidity's `uint200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toUint200(uint256 value) internal pure returns (uint200) {\n if (value > type(uint200).max) {\n revert SafeCastOverflowedUintDowncast(200, value);\n }\n return uint200(value);\n }\n\n /**\n * @dev Returns the downcasted uint192 from uint256, reverting on\n * overflow (when the input is greater than largest uint192).\n *\n * Counterpart to Solidity's `uint192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toUint192(uint256 value) internal pure returns (uint192) {\n if (value > type(uint192).max) {\n revert SafeCastOverflowedUintDowncast(192, value);\n }\n return uint192(value);\n }\n\n /**\n * @dev Returns the downcasted uint184 from uint256, reverting on\n * overflow (when the input is greater than largest uint184).\n *\n * Counterpart to Solidity's `uint184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toUint184(uint256 value) internal pure returns (uint184) {\n if (value > type(uint184).max) {\n revert SafeCastOverflowedUintDowncast(184, value);\n }\n return uint184(value);\n }\n\n /**\n * @dev Returns the downcasted uint176 from uint256, reverting on\n * overflow (when the input is greater than largest uint176).\n *\n * Counterpart to Solidity's `uint176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toUint176(uint256 value) internal pure returns (uint176) {\n if (value > type(uint176).max) {\n revert SafeCastOverflowedUintDowncast(176, value);\n }\n return uint176(value);\n }\n\n /**\n * @dev Returns the downcasted uint168 from uint256, reverting on\n * overflow (when the input is greater than largest uint168).\n *\n * Counterpart to Solidity's `uint168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toUint168(uint256 value) internal pure returns (uint168) {\n if (value > type(uint168).max) {\n revert SafeCastOverflowedUintDowncast(168, value);\n }\n return uint168(value);\n }\n\n /**\n * @dev Returns the downcasted uint160 from uint256, reverting on\n * overflow (when the input is greater than largest uint160).\n *\n * Counterpart to Solidity's `uint160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toUint160(uint256 value) internal pure returns (uint160) {\n if (value > type(uint160).max) {\n revert SafeCastOverflowedUintDowncast(160, value);\n }\n return uint160(value);\n }\n\n /**\n * @dev Returns the downcasted uint152 from uint256, reverting on\n * overflow (when the input is greater than largest uint152).\n *\n * Counterpart to Solidity's `uint152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toUint152(uint256 value) internal pure returns (uint152) {\n if (value > type(uint152).max) {\n revert SafeCastOverflowedUintDowncast(152, value);\n }\n return uint152(value);\n }\n\n /**\n * @dev Returns the downcasted uint144 from uint256, reverting on\n * overflow (when the input is greater than largest uint144).\n *\n * Counterpart to Solidity's `uint144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toUint144(uint256 value) internal pure returns (uint144) {\n if (value > type(uint144).max) {\n revert SafeCastOverflowedUintDowncast(144, value);\n }\n return uint144(value);\n }\n\n /**\n * @dev Returns the downcasted uint136 from uint256, reverting on\n * overflow (when the input is greater than largest uint136).\n *\n * Counterpart to Solidity's `uint136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toUint136(uint256 value) internal pure returns (uint136) {\n if (value > type(uint136).max) {\n revert SafeCastOverflowedUintDowncast(136, value);\n }\n return uint136(value);\n }\n\n /**\n * @dev Returns the downcasted uint128 from uint256, reverting on\n * overflow (when the input is greater than largest uint128).\n *\n * Counterpart to Solidity's `uint128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toUint128(uint256 value) internal pure returns (uint128) {\n if (value > type(uint128).max) {\n revert SafeCastOverflowedUintDowncast(128, value);\n }\n return uint128(value);\n }\n\n /**\n * @dev Returns the downcasted uint120 from uint256, reverting on\n * overflow (when the input is greater than largest uint120).\n *\n * Counterpart to Solidity's `uint120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toUint120(uint256 value) internal pure returns (uint120) {\n if (value > type(uint120).max) {\n revert SafeCastOverflowedUintDowncast(120, value);\n }\n return uint120(value);\n }\n\n /**\n * @dev Returns the downcasted uint112 from uint256, reverting on\n * overflow (when the input is greater than largest uint112).\n *\n * Counterpart to Solidity's `uint112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toUint112(uint256 value) internal pure returns (uint112) {\n if (value > type(uint112).max) {\n revert SafeCastOverflowedUintDowncast(112, value);\n }\n return uint112(value);\n }\n\n /**\n * @dev Returns the downcasted uint104 from uint256, reverting on\n * overflow (when the input is greater than largest uint104).\n *\n * Counterpart to Solidity's `uint104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toUint104(uint256 value) internal pure returns (uint104) {\n if (value > type(uint104).max) {\n revert SafeCastOverflowedUintDowncast(104, value);\n }\n return uint104(value);\n }\n\n /**\n * @dev Returns the downcasted uint96 from uint256, reverting on\n * overflow (when the input is greater than largest uint96).\n *\n * Counterpart to Solidity's `uint96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toUint96(uint256 value) internal pure returns (uint96) {\n if (value > type(uint96).max) {\n revert SafeCastOverflowedUintDowncast(96, value);\n }\n return uint96(value);\n }\n\n /**\n * @dev Returns the downcasted uint88 from uint256, reverting on\n * overflow (when the input is greater than largest uint88).\n *\n * Counterpart to Solidity's `uint88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toUint88(uint256 value) internal pure returns (uint88) {\n if (value > type(uint88).max) {\n revert SafeCastOverflowedUintDowncast(88, value);\n }\n return uint88(value);\n }\n\n /**\n * @dev Returns the downcasted uint80 from uint256, reverting on\n * overflow (when the input is greater than largest uint80).\n *\n * Counterpart to Solidity's `uint80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toUint80(uint256 value) internal pure returns (uint80) {\n if (value > type(uint80).max) {\n revert SafeCastOverflowedUintDowncast(80, value);\n }\n return uint80(value);\n }\n\n /**\n * @dev Returns the downcasted uint72 from uint256, reverting on\n * overflow (when the input is greater than largest uint72).\n *\n * Counterpart to Solidity's `uint72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toUint72(uint256 value) internal pure returns (uint72) {\n if (value > type(uint72).max) {\n revert SafeCastOverflowedUintDowncast(72, value);\n }\n return uint72(value);\n }\n\n /**\n * @dev Returns the downcasted uint64 from uint256, reverting on\n * overflow (when the input is greater than largest uint64).\n *\n * Counterpart to Solidity's `uint64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toUint64(uint256 value) internal pure returns (uint64) {\n if (value > type(uint64).max) {\n revert SafeCastOverflowedUintDowncast(64, value);\n }\n return uint64(value);\n }\n\n /**\n * @dev Returns the downcasted uint56 from uint256, reverting on\n * overflow (when the input is greater than largest uint56).\n *\n * Counterpart to Solidity's `uint56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toUint56(uint256 value) internal pure returns (uint56) {\n if (value > type(uint56).max) {\n revert SafeCastOverflowedUintDowncast(56, value);\n }\n return uint56(value);\n }\n\n /**\n * @dev Returns the downcasted uint48 from uint256, reverting on\n * overflow (when the input is greater than largest uint48).\n *\n * Counterpart to Solidity's `uint48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toUint48(uint256 value) internal pure returns (uint48) {\n if (value > type(uint48).max) {\n revert SafeCastOverflowedUintDowncast(48, value);\n }\n return uint48(value);\n }\n\n /**\n * @dev Returns the downcasted uint40 from uint256, reverting on\n * overflow (when the input is greater than largest uint40).\n *\n * Counterpart to Solidity's `uint40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toUint40(uint256 value) internal pure returns (uint40) {\n if (value > type(uint40).max) {\n revert SafeCastOverflowedUintDowncast(40, value);\n }\n return uint40(value);\n }\n\n /**\n * @dev Returns the downcasted uint32 from uint256, reverting on\n * overflow (when the input is greater than largest uint32).\n *\n * Counterpart to Solidity's `uint32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toUint32(uint256 value) internal pure returns (uint32) {\n if (value > type(uint32).max) {\n revert SafeCastOverflowedUintDowncast(32, value);\n }\n return uint32(value);\n }\n\n /**\n * @dev Returns the downcasted uint24 from uint256, reverting on\n * overflow (when the input is greater than largest uint24).\n *\n * Counterpart to Solidity's `uint24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toUint24(uint256 value) internal pure returns (uint24) {\n if (value > type(uint24).max) {\n revert SafeCastOverflowedUintDowncast(24, value);\n }\n return uint24(value);\n }\n\n /**\n * @dev Returns the downcasted uint16 from uint256, reverting on\n * overflow (when the input is greater than largest uint16).\n *\n * Counterpart to Solidity's `uint16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toUint16(uint256 value) internal pure returns (uint16) {\n if (value > type(uint16).max) {\n revert SafeCastOverflowedUintDowncast(16, value);\n }\n return uint16(value);\n }\n\n /**\n * @dev Returns the downcasted uint8 from uint256, reverting on\n * overflow (when the input is greater than largest uint8).\n *\n * Counterpart to Solidity's `uint8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toUint8(uint256 value) internal pure returns (uint8) {\n if (value > type(uint8).max) {\n revert SafeCastOverflowedUintDowncast(8, value);\n }\n return uint8(value);\n }\n\n /**\n * @dev Converts a signed int256 into an unsigned uint256.\n *\n * Requirements:\n *\n * - input must be greater than or equal to 0.\n */\n function toUint256(int256 value) internal pure returns (uint256) {\n if (value < 0) {\n revert SafeCastOverflowedIntToUint(value);\n }\n return uint256(value);\n }\n\n /**\n * @dev Returns the downcasted int248 from int256, reverting on\n * overflow (when the input is less than smallest int248 or\n * greater than largest int248).\n *\n * Counterpart to Solidity's `int248` operator.\n *\n * Requirements:\n *\n * - input must fit into 248 bits\n */\n function toInt248(int256 value) internal pure returns (int248 downcasted) {\n downcasted = int248(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(248, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int240 from int256, reverting on\n * overflow (when the input is less than smallest int240 or\n * greater than largest int240).\n *\n * Counterpart to Solidity's `int240` operator.\n *\n * Requirements:\n *\n * - input must fit into 240 bits\n */\n function toInt240(int256 value) internal pure returns (int240 downcasted) {\n downcasted = int240(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(240, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int232 from int256, reverting on\n * overflow (when the input is less than smallest int232 or\n * greater than largest int232).\n *\n * Counterpart to Solidity's `int232` operator.\n *\n * Requirements:\n *\n * - input must fit into 232 bits\n */\n function toInt232(int256 value) internal pure returns (int232 downcasted) {\n downcasted = int232(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(232, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int224 from int256, reverting on\n * overflow (when the input is less than smallest int224 or\n * greater than largest int224).\n *\n * Counterpart to Solidity's `int224` operator.\n *\n * Requirements:\n *\n * - input must fit into 224 bits\n */\n function toInt224(int256 value) internal pure returns (int224 downcasted) {\n downcasted = int224(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(224, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int216 from int256, reverting on\n * overflow (when the input is less than smallest int216 or\n * greater than largest int216).\n *\n * Counterpart to Solidity's `int216` operator.\n *\n * Requirements:\n *\n * - input must fit into 216 bits\n */\n function toInt216(int256 value) internal pure returns (int216 downcasted) {\n downcasted = int216(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(216, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int208 from int256, reverting on\n * overflow (when the input is less than smallest int208 or\n * greater than largest int208).\n *\n * Counterpart to Solidity's `int208` operator.\n *\n * Requirements:\n *\n * - input must fit into 208 bits\n */\n function toInt208(int256 value) internal pure returns (int208 downcasted) {\n downcasted = int208(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(208, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int200 from int256, reverting on\n * overflow (when the input is less than smallest int200 or\n * greater than largest int200).\n *\n * Counterpart to Solidity's `int200` operator.\n *\n * Requirements:\n *\n * - input must fit into 200 bits\n */\n function toInt200(int256 value) internal pure returns (int200 downcasted) {\n downcasted = int200(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(200, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int192 from int256, reverting on\n * overflow (when the input is less than smallest int192 or\n * greater than largest int192).\n *\n * Counterpart to Solidity's `int192` operator.\n *\n * Requirements:\n *\n * - input must fit into 192 bits\n */\n function toInt192(int256 value) internal pure returns (int192 downcasted) {\n downcasted = int192(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(192, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int184 from int256, reverting on\n * overflow (when the input is less than smallest int184 or\n * greater than largest int184).\n *\n * Counterpart to Solidity's `int184` operator.\n *\n * Requirements:\n *\n * - input must fit into 184 bits\n */\n function toInt184(int256 value) internal pure returns (int184 downcasted) {\n downcasted = int184(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(184, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int176 from int256, reverting on\n * overflow (when the input is less than smallest int176 or\n * greater than largest int176).\n *\n * Counterpart to Solidity's `int176` operator.\n *\n * Requirements:\n *\n * - input must fit into 176 bits\n */\n function toInt176(int256 value) internal pure returns (int176 downcasted) {\n downcasted = int176(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(176, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int168 from int256, reverting on\n * overflow (when the input is less than smallest int168 or\n * greater than largest int168).\n *\n * Counterpart to Solidity's `int168` operator.\n *\n * Requirements:\n *\n * - input must fit into 168 bits\n */\n function toInt168(int256 value) internal pure returns (int168 downcasted) {\n downcasted = int168(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(168, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int160 from int256, reverting on\n * overflow (when the input is less than smallest int160 or\n * greater than largest int160).\n *\n * Counterpart to Solidity's `int160` operator.\n *\n * Requirements:\n *\n * - input must fit into 160 bits\n */\n function toInt160(int256 value) internal pure returns (int160 downcasted) {\n downcasted = int160(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(160, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int152 from int256, reverting on\n * overflow (when the input is less than smallest int152 or\n * greater than largest int152).\n *\n * Counterpart to Solidity's `int152` operator.\n *\n * Requirements:\n *\n * - input must fit into 152 bits\n */\n function toInt152(int256 value) internal pure returns (int152 downcasted) {\n downcasted = int152(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(152, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int144 from int256, reverting on\n * overflow (when the input is less than smallest int144 or\n * greater than largest int144).\n *\n * Counterpart to Solidity's `int144` operator.\n *\n * Requirements:\n *\n * - input must fit into 144 bits\n */\n function toInt144(int256 value) internal pure returns (int144 downcasted) {\n downcasted = int144(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(144, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int136 from int256, reverting on\n * overflow (when the input is less than smallest int136 or\n * greater than largest int136).\n *\n * Counterpart to Solidity's `int136` operator.\n *\n * Requirements:\n *\n * - input must fit into 136 bits\n */\n function toInt136(int256 value) internal pure returns (int136 downcasted) {\n downcasted = int136(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(136, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int128 from int256, reverting on\n * overflow (when the input is less than smallest int128 or\n * greater than largest int128).\n *\n * Counterpart to Solidity's `int128` operator.\n *\n * Requirements:\n *\n * - input must fit into 128 bits\n */\n function toInt128(int256 value) internal pure returns (int128 downcasted) {\n downcasted = int128(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(128, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int120 from int256, reverting on\n * overflow (when the input is less than smallest int120 or\n * greater than largest int120).\n *\n * Counterpart to Solidity's `int120` operator.\n *\n * Requirements:\n *\n * - input must fit into 120 bits\n */\n function toInt120(int256 value) internal pure returns (int120 downcasted) {\n downcasted = int120(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(120, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int112 from int256, reverting on\n * overflow (when the input is less than smallest int112 or\n * greater than largest int112).\n *\n * Counterpart to Solidity's `int112` operator.\n *\n * Requirements:\n *\n * - input must fit into 112 bits\n */\n function toInt112(int256 value) internal pure returns (int112 downcasted) {\n downcasted = int112(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(112, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int104 from int256, reverting on\n * overflow (when the input is less than smallest int104 or\n * greater than largest int104).\n *\n * Counterpart to Solidity's `int104` operator.\n *\n * Requirements:\n *\n * - input must fit into 104 bits\n */\n function toInt104(int256 value) internal pure returns (int104 downcasted) {\n downcasted = int104(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(104, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int96 from int256, reverting on\n * overflow (when the input is less than smallest int96 or\n * greater than largest int96).\n *\n * Counterpart to Solidity's `int96` operator.\n *\n * Requirements:\n *\n * - input must fit into 96 bits\n */\n function toInt96(int256 value) internal pure returns (int96 downcasted) {\n downcasted = int96(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(96, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int88 from int256, reverting on\n * overflow (when the input is less than smallest int88 or\n * greater than largest int88).\n *\n * Counterpart to Solidity's `int88` operator.\n *\n * Requirements:\n *\n * - input must fit into 88 bits\n */\n function toInt88(int256 value) internal pure returns (int88 downcasted) {\n downcasted = int88(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(88, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int80 from int256, reverting on\n * overflow (when the input is less than smallest int80 or\n * greater than largest int80).\n *\n * Counterpart to Solidity's `int80` operator.\n *\n * Requirements:\n *\n * - input must fit into 80 bits\n */\n function toInt80(int256 value) internal pure returns (int80 downcasted) {\n downcasted = int80(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(80, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int72 from int256, reverting on\n * overflow (when the input is less than smallest int72 or\n * greater than largest int72).\n *\n * Counterpart to Solidity's `int72` operator.\n *\n * Requirements:\n *\n * - input must fit into 72 bits\n */\n function toInt72(int256 value) internal pure returns (int72 downcasted) {\n downcasted = int72(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(72, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int64 from int256, reverting on\n * overflow (when the input is less than smallest int64 or\n * greater than largest int64).\n *\n * Counterpart to Solidity's `int64` operator.\n *\n * Requirements:\n *\n * - input must fit into 64 bits\n */\n function toInt64(int256 value) internal pure returns (int64 downcasted) {\n downcasted = int64(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(64, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int56 from int256, reverting on\n * overflow (when the input is less than smallest int56 or\n * greater than largest int56).\n *\n * Counterpart to Solidity's `int56` operator.\n *\n * Requirements:\n *\n * - input must fit into 56 bits\n */\n function toInt56(int256 value) internal pure returns (int56 downcasted) {\n downcasted = int56(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(56, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int48 from int256, reverting on\n * overflow (when the input is less than smallest int48 or\n * greater than largest int48).\n *\n * Counterpart to Solidity's `int48` operator.\n *\n * Requirements:\n *\n * - input must fit into 48 bits\n */\n function toInt48(int256 value) internal pure returns (int48 downcasted) {\n downcasted = int48(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(48, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int40 from int256, reverting on\n * overflow (when the input is less than smallest int40 or\n * greater than largest int40).\n *\n * Counterpart to Solidity's `int40` operator.\n *\n * Requirements:\n *\n * - input must fit into 40 bits\n */\n function toInt40(int256 value) internal pure returns (int40 downcasted) {\n downcasted = int40(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(40, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int32 from int256, reverting on\n * overflow (when the input is less than smallest int32 or\n * greater than largest int32).\n *\n * Counterpart to Solidity's `int32` operator.\n *\n * Requirements:\n *\n * - input must fit into 32 bits\n */\n function toInt32(int256 value) internal pure returns (int32 downcasted) {\n downcasted = int32(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(32, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int24 from int256, reverting on\n * overflow (when the input is less than smallest int24 or\n * greater than largest int24).\n *\n * Counterpart to Solidity's `int24` operator.\n *\n * Requirements:\n *\n * - input must fit into 24 bits\n */\n function toInt24(int256 value) internal pure returns (int24 downcasted) {\n downcasted = int24(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(24, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int16 from int256, reverting on\n * overflow (when the input is less than smallest int16 or\n * greater than largest int16).\n *\n * Counterpart to Solidity's `int16` operator.\n *\n * Requirements:\n *\n * - input must fit into 16 bits\n */\n function toInt16(int256 value) internal pure returns (int16 downcasted) {\n downcasted = int16(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(16, value);\n }\n }\n\n /**\n * @dev Returns the downcasted int8 from int256, reverting on\n * overflow (when the input is less than smallest int8 or\n * greater than largest int8).\n *\n * Counterpart to Solidity's `int8` operator.\n *\n * Requirements:\n *\n * - input must fit into 8 bits\n */\n function toInt8(int256 value) internal pure returns (int8 downcasted) {\n downcasted = int8(value);\n if (downcasted != value) {\n revert SafeCastOverflowedIntDowncast(8, value);\n }\n }\n\n /**\n * @dev Converts an unsigned uint256 into a signed int256.\n *\n * Requirements:\n *\n * - input must be less than or equal to maxInt256.\n */\n function toInt256(uint256 value) internal pure returns (int256) {\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n if (value > uint256(type(int256).max)) {\n revert SafeCastOverflowedUintToInt(value);\n }\n return int256(value);\n }\n\n /**\n * @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.\n */\n function toUint(bool b) internal pure returns (uint256 u) {\n assembly (\"memory-safe\") {\n u := iszero(iszero(b))\n }\n }\n}\n"},"@openzeppelin/contracts/utils/math/SignedMath.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SignedMath.sol)\n\npragma solidity ^0.8.20;\n\nimport {SafeCast} from \"./SafeCast.sol\";\n\n/**\n * @dev Standard signed math utilities missing in the Solidity language.\n */\nlibrary SignedMath {\n /**\n * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.\n *\n * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.\n * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute\n * one branch when needed, making this function more expensive.\n */\n function ternary(bool condition, int256 a, int256 b) internal pure returns (int256) {\n unchecked {\n // branchless ternary works because:\n // b ^ (a ^ b) == a\n // b ^ 0 == b\n return b ^ ((a ^ b) * int256(SafeCast.toUint(condition)));\n }\n }\n\n /**\n * @dev Returns the largest of two signed numbers.\n */\n function max(int256 a, int256 b) internal pure returns (int256) {\n return ternary(a > b, a, b);\n }\n\n /**\n * @dev Returns the smallest of two signed numbers.\n */\n function min(int256 a, int256 b) internal pure returns (int256) {\n return ternary(a < b, a, b);\n }\n\n /**\n * @dev Returns the average of two signed numbers without overflow.\n * The result is rounded towards zero.\n */\n function average(int256 a, int256 b) internal pure returns (int256) {\n // Formula from the book \"Hacker's Delight\"\n int256 x = (a & b) + ((a ^ b) >> 1);\n return x + (int256(uint256(x) >> 255) & (a ^ b));\n }\n\n /**\n * @dev Returns the absolute unsigned value of a signed value.\n */\n function abs(int256 n) internal pure returns (uint256) {\n unchecked {\n // Formula from the \"Bit Twiddling Hacks\" by Sean Eron Anderson.\n // Since `n` is a signed integer, the generated bytecode will use the SAR opcode to perform the right shift,\n // taking advantage of the most significant (or \"sign\" bit) in two's complement representation.\n // This opcode adds new most significant bits set to the value of the previous most significant bit. As a result,\n // the mask will either be `bytes32(0)` (if n is positive) or `~bytes32(0)` (if n is negative).\n int256 mask = n >> 255;\n\n // A `bytes32(0)` mask leaves the input unchanged, while a `~bytes32(0)` mask complements it.\n return uint256((n + mask) ^ mask);\n }\n }\n}\n"},"@solidity-bits/contracts/BitScan.sol":{"content":"// SPDX-License-Identifier: MIT\n/**\n _____ ___ ___ __ ____ _ __ \n / ___/____ / (_)___/ (_) /___ __ / __ )(_) /______\n \\__ \\/ __ \\/ / / __ / / __/ / / / / __ / / __/ ___/\n ___/ / /_/ / / / /_/ / / /_/ /_/ / / /_/ / / /_(__ ) \n/____/\\____/_/_/\\__,_/_/\\__/\\__, / /_____/_/\\__/____/ \n /____/ \n\n- npm: https://www.npmjs.com/package/solidity-bits\n- github: https://github.com/estarriolvetch/solidity-bits\n\n */\n\npragma solidity ^0.8.0;\n\n\nlibrary BitScan {\n uint256 constant private DEBRUIJN_256 = 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff;\n bytes constant private LOOKUP_TABLE_256 = hex\"0001020903110a19042112290b311a3905412245134d2a550c5d32651b6d3a7506264262237d468514804e8d2b95569d0d495ea533a966b11c886eb93bc176c9071727374353637324837e9b47af86c7155181ad4fd18ed32c9096db57d59ee30e2e4a6a5f92a6be3498aae067ddb2eb1d5989b56fd7baf33ca0c2ee77e5caf7ff0810182028303840444c545c646c7425617c847f8c949c48a4a8b087b8c0c816365272829aaec650acd0d28fdad4e22d6991bd97dfdcea58b4d6f29fede4f6fe0f1f2f3f4b5b6b607b8b93a3a7b7bf357199c5abcfd9e168bcdee9b3f1ecf5fd1e3e5a7a8aa2b670c4ced8bbe8f0f4fc3d79a1c3cde7effb78cce6facbf9f8\";\n\n /**\n @dev Isolate the least significant set bit.\n */ \n function isolateLS1B256(uint256 bb) pure internal returns (uint256) {\n require(bb > 0);\n unchecked {\n return bb & (0 - bb);\n }\n } \n\n /**\n @dev Isolate the most significant set bit.\n */ \n function isolateMS1B256(uint256 bb) pure internal returns (uint256) {\n require(bb > 0);\n unchecked {\n bb |= bb >> 128;\n bb |= bb >> 64;\n bb |= bb >> 32;\n bb |= bb >> 16;\n bb |= bb >> 8;\n bb |= bb >> 4;\n bb |= bb >> 2;\n bb |= bb >> 1;\n \n return (bb >> 1) + 1;\n }\n } \n\n /**\n @dev Find the index of the lest significant set bit. (trailing zero count)\n */ \n function bitScanForward256(uint256 bb) pure internal returns (uint8) {\n unchecked {\n return uint8(LOOKUP_TABLE_256[(isolateLS1B256(bb) * DEBRUIJN_256) >> 248]);\n } \n }\n\n /**\n @dev Find the index of the most significant set bit.\n */ \n function bitScanReverse256(uint256 bb) pure internal returns (uint8) {\n unchecked {\n return 255 - uint8(LOOKUP_TABLE_256[((isolateMS1B256(bb) * DEBRUIJN_256) >> 248)]);\n } \n }\n\n function log2(uint256 bb) pure internal returns (uint8) {\n unchecked {\n return uint8(LOOKUP_TABLE_256[(isolateMS1B256(bb) * DEBRUIJN_256) >> 248]);\n } \n }\n}\n"},"@solidity-bits/contracts/Popcount.sol":{"content":"// SPDX-License-Identifier: MIT\n/**\n _____ ___ ___ __ ____ _ __ \n / ___/____ / (_)___/ (_) /___ __ / __ )(_) /______\n \\__ \\/ __ \\/ / / __ / / __/ / / / / __ / / __/ ___/\n ___/ / /_/ / / / /_/ / / /_/ /_/ / / /_/ / / /_(__ ) \n/____/\\____/_/_/\\__,_/_/\\__/\\__, / /_____/_/\\__/____/ \n /____/ \n\n- npm: https://www.npmjs.com/package/solidity-bits\n- github: https://github.com/estarriolvetch/solidity-bits\n\n */\n\npragma solidity ^0.8.0;\n\nlibrary Popcount {\n uint256 private constant m1 = 0x5555555555555555555555555555555555555555555555555555555555555555;\n uint256 private constant m2 = 0x3333333333333333333333333333333333333333333333333333333333333333;\n uint256 private constant m4 = 0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f;\n uint256 private constant h01 = 0x0101010101010101010101010101010101010101010101010101010101010101;\n\n function popcount256A(uint256 x) internal pure returns (uint256 count) {\n unchecked{\n for (count=0; x!=0; count++)\n x &= x - 1;\n }\n }\n\n function popcount256B(uint256 x) internal pure returns (uint256) {\n if (x == type(uint256).max) {\n return 256;\n }\n unchecked {\n x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits\n x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits \n x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits \n x = (x * h01) >> 248; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... \n }\n return x;\n }\n}"},"contracts/AccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {EasyAccessControl} from \"./EasyAccessControl.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport \"./AccessControlErrors.sol\";\n\n\n/**\n * @title AccessControl (external)\n * @notice Standalone access control with bitmask roles. Intended to be shared by multiple contracts.\n */\ncontract AccessControl is\n IAccessControl,\n EasyAccessControl,\n ERC2771Context\n{\n constructor(\n address contractAdmin_,\n address reserveAdmin_,\n address transferAdmin_,\n address trustedForwarder_\n )\n EasyAccessControl(contractAdmin_)\n ERC2771Context(trustedForwarder_)\n validAddress(reserveAdmin_)\n validAddress(transferAdmin_)\n validAddress(trustedForwarder_)\n {\n _grantRole(reserveAdmin_, RESERVE_ADMIN_ROLE);\n _grantRole(transferAdmin_, TRANSFER_ADMIN_ROLE);\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n}\n"},"contracts/AccessControlErrors.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n// Centralized error definitions for AccessControl\n// These errors are used by EasyAccessControl, AccessControl, and contracts that use access control\n\nerror EasyAccessControl_InvalidZeroAddress();\nerror EasyAccessControl_InvalidRole();\nerror EasyAccessControl_DoesNotHaveContractOrTransferAdminRole(address addr);\nerror EasyAccessControl_DoesNotHaveContractAdminRole(address addr);\nerror EasyAccessControl_DoesNotHaveTransferAdminRole(address addr);\nerror EasyAccessControl_DoesNotHaveReserveAdminRole(address addr);\nerror EasyAccessControl_DoesNotHaveWalletsAdminRole(address addr);\nerror EasyAccessControl_DoesNotHaveSoftBurnAdminRole(address addr);\nerror EasyAccessControl_DoesNotHaveAdminRole(address addr);\nerror EasyAccessControl_AlreadyHasRole();\nerror EasyAccessControl_CannotRevokeRole();\nerror EasyAccessControl_AtLeastOneContractAdminRequired();\nerror EasyAccessControl_ArraysMustBeSameLength();\n"},"contracts/EasyAccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport \"./AccessControlErrors.sol\";\n\n/**\n * @title Binary Access control\n * @author By CoMakery, Inc., Upside, Republic\n * @dev Binary equivalent to OpenZeppelin/AccessControl\n * Uses bits for storing user roles, minify gas cost and contract size\n */\ncontract EasyAccessControl is IAccessControl, Context {\n uint8 public constant CONTRACT_ADMIN_ROLE = 1; // 0000 0001\n uint8 public constant RESERVE_ADMIN_ROLE = 2; // 0000 0010\n uint8 public constant WALLETS_ADMIN_ROLE = 4; // 0000 0100\n uint8 public constant TRANSFER_ADMIN_ROLE = 8; // 0000 1000\n uint8 public constant SOFT_BURN_ADMIN_ROLE = 16;// 0001 0000\n uint8 public constant MINT_ADMIN_ROLE = 32; // 0010 0000\n\n // MAX_ROLE_BIT is the bitmask with all defined roles set (1|2|4|8|16|32 = 63)\n uint8 internal constant MAX_ROLE_BIT = \n CONTRACT_ADMIN_ROLE |\n RESERVE_ADMIN_ROLE |\n WALLETS_ADMIN_ROLE |\n TRANSFER_ADMIN_ROLE |\n SOFT_BURN_ADMIN_ROLE |\n MINT_ADMIN_ROLE;\n\n mapping(address => uint8) private _roles; // address => binary roles\n uint8 public contractAdminCount; // counter of contract admins to keep at least one\n\n // Note: Events and errors are now defined in IAccessControl interface\n\n modifier validAddress(address addr_) {\n if (addr_ == address(0)) {\n revert EasyAccessControl_InvalidZeroAddress();\n }\n _;\n }\n\n modifier validRole(uint8 role) {\n if (role == 0 || role > MAX_ROLE_BIT) {\n revert EasyAccessControl_InvalidRole();\n }\n _;\n }\n\n modifier onlyContractAdminOrTransferAdmin() {\n _onlyContractAdminOrTransferAdmin();\n _;\n }\n function _onlyContractAdminOrTransferAdmin() internal view {\n if (\n !hasRole(_msgSender(), CONTRACT_ADMIN_ROLE) &&\n !hasRole(_msgSender(), TRANSFER_ADMIN_ROLE)\n ) {\n revert EasyAccessControl_DoesNotHaveContractOrTransferAdminRole(_msgSender());\n }\n }\n\n modifier onlyContractAdmin() {\n _onlyContractAdmin();\n _;\n }\n function _onlyContractAdmin() internal view {\n if (!hasRole(_msgSender(), CONTRACT_ADMIN_ROLE)) {\n revert EasyAccessControl_DoesNotHaveContractAdminRole(_msgSender());\n }\n }\n\n modifier onlyTransferAdmin() {\n _onlyTransferAdmin();\n _;\n }\n function _onlyTransferAdmin() internal view {\n if (!hasRole(_msgSender(), TRANSFER_ADMIN_ROLE)) {\n revert EasyAccessControl_DoesNotHaveTransferAdminRole(_msgSender());\n }\n }\n\n modifier onlyReserveAdmin() {\n _onlyReserveAdmin();\n _;\n }\n function _onlyReserveAdmin() internal view {\n if (!hasRole(_msgSender(), RESERVE_ADMIN_ROLE)) {\n revert EasyAccessControl_DoesNotHaveReserveAdminRole(_msgSender());\n }\n }\n\n modifier onlyWalletsAdmin() {\n _onlyWalletsAdmin();\n _;\n }\n function _onlyWalletsAdmin() internal view {\n if (!hasRole(_msgSender(), WALLETS_ADMIN_ROLE)) {\n revert EasyAccessControl_DoesNotHaveWalletsAdminRole(_msgSender());\n }\n }\n\n modifier onlyWalletsAdminOrTransferAdmin() {\n _onlyWalletsAdminOrTransferAdmin();\n _;\n }\n function _onlyWalletsAdminOrTransferAdmin() internal view {\n if (\n !hasRole(_msgSender(), WALLETS_ADMIN_ROLE) &&\n !hasRole(_msgSender(), TRANSFER_ADMIN_ROLE)\n ) {\n revert EasyAccessControl_DoesNotHaveAdminRole(_msgSender());\n }\n }\n\n modifier anyAdmin() {\n _anyAdmin();\n _;\n }\n function _anyAdmin() internal view {\n if (\n !hasRole(_msgSender(), RESERVE_ADMIN_ROLE) &&\n !hasRole(_msgSender(), WALLETS_ADMIN_ROLE) &&\n !hasRole(_msgSender(), TRANSFER_ADMIN_ROLE) &&\n !hasRole(_msgSender(), CONTRACT_ADMIN_ROLE)\n ) {\n revert EasyAccessControl_DoesNotHaveAdminRole(_msgSender());\n }\n }\n\n /**\n * @notice Constructor, init base role\n * @param contractAdmin_ address of contract admin\n */\n constructor(address contractAdmin_) validAddress(contractAdmin_) {\n _grantRole(contractAdmin_, CONTRACT_ADMIN_ROLE);\n }\n\n /**\n * @notice Grant roles to addresses using role bitmasks\n * @param addresses to grant roles\n * @param roles_ bitmasks array\n */\n function batchGrantRoles(\n address[] calldata addresses,\n uint8[] calldata roles_\n ) public onlyContractAdmin {\n uint256 _len = addresses.length;\n if (_len != roles_.length) {\n revert EasyAccessControl_ArraysMustBeSameLength();\n }\n for (uint256 i; i < _len; i++) {\n grantRole(addresses[i], roles_[i]);\n }\n }\n\n /**\n * @notice Revoke roles to addresses using role bitmasks\n * @param addresses to revoke roles\n * @param roles_ bitmasks array\n */\n function batchRevokeRoles(\n address[] calldata addresses,\n uint8[] calldata roles_\n ) public onlyContractAdmin {\n uint256 _len = addresses.length;\n if (_len != roles_.length) {\n revert EasyAccessControl_ArraysMustBeSameLength();\n }\n for (uint256 i; i < _len; i++) {\n revokeRole(addresses[i], roles_[i]);\n }\n }\n\n /**\n * @notice Grant role/roles to address using role bitmask\n * @param addr to grant role\n * @param role bitmask of role/roles to grant\n */\n function grantRole(\n address addr,\n uint8 role\n ) public validRole(role) validAddress(addr) onlyContractAdmin {\n _grantRole(addr, role);\n }\n\n /**\n * @notice Grant role/roles to address using role bitmask\n * @param addr to grant role\n * @param role bitmask of role/roles to grant\n */\n function _grantRole(address addr, uint8 role) internal virtual {\n if (hasRole(addr, role)) {\n revert EasyAccessControl_AlreadyHasRole();\n }\n if (\n _roles[addr] & CONTRACT_ADMIN_ROLE == 0 &&\n role & CONTRACT_ADMIN_ROLE > 0\n ) contractAdminCount++;\n _roles[addr] |= role;\n emit RoleChange(_msgSender(), addr, role, true);\n }\n\n /**\n * @notice Revoke role/roles from address using role bitmask\n * @param addr to revoke role\n * @param role bitmask of role/roles to revoke\n */\n function revokeRole(\n address addr,\n uint8 role\n ) public validRole(role) validAddress(addr) onlyContractAdmin {\n if (!hasRole(addr, role)) {\n revert EasyAccessControl_CannotRevokeRole();\n }\n if (role & CONTRACT_ADMIN_ROLE > 0) {\n if (contractAdminCount == 1) {\n revert EasyAccessControl_AtLeastOneContractAdminRequired();\n }\n contractAdminCount--;\n }\n _roles[addr] ^= role;\n emit RoleChange(_msgSender(), addr, role, false);\n }\n\n /**\n * @notice Check role/roles availability at address\n * @param addr to check role\n * @param role bitmask of role/roles to check\n * @return bool true or false\n */\n function hasRole(\n address addr,\n uint8 role\n ) public view validRole(role) validAddress(addr) returns (bool) {\n return _roles[addr] & role == role;\n }\n\n /**\n * @notice Get the roles for an address (equivalent to admins mapping)\n * @param addr The address to check\n * @return uint8 The bitmask of roles for the address\n */\n function roles(address addr) public view returns (uint8) {\n return _roles[addr];\n }\n}\n"},"contracts/IdentityRegistry.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {RestrictedLockupToken} from \"./RestrictedLockupToken.sol\";\nimport {IIdentityRegistry} from \"./interfaces/IIdentityRegistry.sol\";\nimport {ERC165} from \"@openzeppelin/contracts/utils/introspection/ERC165.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {EasyAccessControl} from \"./EasyAccessControl.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\n\ncontract IdentityRegistry is\n IIdentityRegistry,\n ERC165,\n EasyAccessControl,\n ERC2771Context\n{\n uint256 constant MAX_REGIONS_COUNT = 10;\n uint256 constant MAX_VALIDITY_DURATION = 100 * 365 days;\n bytes4 public immutable INTERFACE_ID;\n uint256 private constant NO_ACCREDITATION = 0;\n\n mapping(address wallet => IdentityInfo info) private _identity;\n uint256 private _amlKycValidityDurationSeconds;\n\n error IdentityRegistry_InvalidWalletsAdmin();\n error IdentityRegistry_InvalidTrustedForwarder();\n error IdentityRegistry_WalletHasNoIdentityToRemove();\n error IdentityRegistry_WalletAlreadyHasSameRegions();\n error IdentityRegistry_WalletAlreadyHasRegion();\n error IdentityRegistry_WalletDoesNotHaveRegion();\n error IdentityRegistry_WalletAlreadyHasSameAmlKycStatus();\n error IdentityRegistry_WalletAlreadyHasSameAccreditationLevel();\n error IdentityRegistry_WalletAlreadyHasNoAccreditation();\n error IdentityRegistry_AlreadyHasSameAmlKycValidityDuration();\n error IdentityRegistry_EmptyRegionsArray();\n error IdentityRegistry_InvalidRegion();\n error IdentityRegistry_RegionsExceedsMaxCount();\n error IdentityRegistry_InvalidAmlKycValidityDuration();\n error IdentityRegistry_TimestampInTheFuture();\n\n constructor(\n address contractAdmin_,\n address walletsAdmin_,\n address trustedForwarder_,\n uint256 amlKycValidityDuration_\n ) EasyAccessControl(contractAdmin_) ERC2771Context(trustedForwarder_) {\n // contractAdmin verification is done in EasyAccessControl constructor\n // and reverts with EasyAccessControl_InvalidZeroAddress\n if (walletsAdmin_ == address(0)) {\n revert IdentityRegistry_InvalidWalletsAdmin();\n }\n if (trustedForwarder_ == address(0)) {\n revert IdentityRegistry_InvalidTrustedForwarder();\n }\n\n INTERFACE_ID = type(IIdentityRegistry).interfaceId;\n _grantRole(walletsAdmin_, WALLETS_ADMIN_ROLE);\n _updateAmlKycValidityDuration(amlKycValidityDuration_);\n }\n\n /**\n * Support of ERC165\n * @dev See https://eips.ethereum.org/EIPS/eip-165\n * @param interfaceId The interface identifier, as specified in ERC-165\n */\n function supportsInterface(\n bytes4 interfaceId\n ) public view override returns (bool) {\n return\n interfaceId == INTERFACE_ID || super.supportsInterface(interfaceId);\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n\n /**\n * @dev Helper function to check if two arrays are equal\n */\n function _arraysEqual(uint256[] memory a, uint256[] memory b) private pure returns (bool) {\n if (a.length != b.length) {\n return false;\n }\n for (uint256 i = 0; i < a.length; i++) {\n if (a[i] != b[i]) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * @dev Helper function to check if an array contains a specific value\n */\n function _arrayContains(uint256[] memory array, uint256 value) private pure returns (bool) {\n for (uint256 i = 0; i < array.length; i++) {\n if (array[i] == value) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @dev Helper function to remove a value from an array\n */\n function _removeFromArray(uint256[] storage array, uint256 value) private returns (bool) {\n for (uint256 i = 0; i < array.length; i++) {\n if (array[i] == value) {\n // Move the last element to the position of the element to remove\n array[i] = array[array.length - 1];\n array.pop();\n return true;\n }\n }\n return false;\n }\n\n /**\n * @dev Updates the regions for a wallet if they're different from the current values\n * @param owner The wallet address\n * @param newRegions The new regions array\n * @return changed Whether the regions were actually updated\n */\n function _updateRegions(address owner, uint256[] memory newRegions) private returns (bool changed) {\n if (newRegions.length == 0) {\n revert IdentityRegistry_EmptyRegionsArray();\n }\n if (newRegions.length > MAX_REGIONS_COUNT) {\n revert IdentityRegistry_RegionsExceedsMaxCount();\n }\n \n // Check for duplicates in the new regions array\n for (uint256 i = 0; i < newRegions.length; i++) {\n for (uint256 j = i + 1; j < newRegions.length; j++) {\n if (newRegions[i] == newRegions[j]) {\n revert IdentityRegistry_InvalidRegion();\n }\n }\n }\n \n if (!_arraysEqual(_identity[owner].regions, newRegions)) {\n _identity[owner].regions = newRegions;\n emit RegionsSet(_msgSender(), owner, newRegions);\n return true;\n }\n return false;\n }\n\n /**\n * @dev Updates the AML/KYC status for a wallet if it's different from the current value\n * @param owner The wallet address\n * @param passed Whether AML/KYC is passed\n * @param timestamp The timestamp for the change (0 means use block.timestamp)\n * @return changed Whether the AML/KYC status was actually updated\n */\n function _updateAmlKyc(address owner, bool passed, uint256 timestamp) private returns (bool changed) {\n if (timestamp > block.timestamp) {\n revert IdentityRegistry_TimestampInTheFuture();\n }\n uint256 changeTimestamp = timestamp == 0 ? block.timestamp : timestamp;\n \n if (_identity[owner].amlKycPassed != passed || _identity[owner].lastAmlKycChangeTimestamp != changeTimestamp) {\n _identity[owner].amlKycPassed = passed;\n _identity[owner].lastAmlKycChangeTimestamp = changeTimestamp;\n \n if (passed) {\n emit AmlKycPassed(_msgSender(), owner, changeTimestamp);\n } else {\n emit AmlKycFailed(_msgSender(), owner, changeTimestamp);\n }\n return true;\n }\n return false;\n }\n\n /**\n * @dev Updates the accreditation type for a wallet if it's different from the current value\n * @param owner The wallet address\n * @param newAccreditationType The new accreditation type\n * @param timestamp The timestamp for the change (0 means use block.timestamp)\n * @return changed Whether the accreditation was actually updated\n */\n function _updateAccreditation(address owner, uint256 newAccreditationType, uint256 timestamp) private returns (bool changed) {\n if (timestamp > block.timestamp) {\n revert IdentityRegistry_TimestampInTheFuture();\n }\n uint256 changeTimestamp = timestamp == 0 ? block.timestamp : timestamp;\n \n if (_identity[owner].accreditationType != newAccreditationType || _identity[owner].lastAccreditationChangeTimestamp != changeTimestamp) {\n uint256 oldAccreditationType = _identity[owner].accreditationType;\n _identity[owner].accreditationType = newAccreditationType;\n _identity[owner].lastAccreditationChangeTimestamp = changeTimestamp;\n \n if (newAccreditationType == NO_ACCREDITATION) {\n emit AccreditationRevoked(_msgSender(), owner, oldAccreditationType, changeTimestamp);\n } else {\n emit AccreditationGranted(_msgSender(), owner, newAccreditationType, changeTimestamp);\n }\n return true;\n }\n return false;\n }\n\n function _updateAmlKycValidityDuration(uint256 amlKycValidityDuration_) private {\n if (amlKycValidityDuration_ > MAX_VALIDITY_DURATION) {\n revert IdentityRegistry_InvalidAmlKycValidityDuration();\n }\n uint256 oldAmlKycValidityDuration = _amlKycValidityDurationSeconds;\n _amlKycValidityDurationSeconds = amlKycValidityDuration_;\n emit AmlKycValidityDurationSet(_msgSender(), oldAmlKycValidityDuration, amlKycValidityDuration_);\n }\n\n /// @inheritdoc IIdentityRegistry\n function setAmlKycValidityDuration(uint256 amlKycValidityDuration_) external onlyContractAdmin {\n if (amlKycValidityDuration_ == _amlKycValidityDurationSeconds) {\n revert IdentityRegistry_AlreadyHasSameAmlKycValidityDuration();\n }\n _updateAmlKycValidityDuration(amlKycValidityDuration_);\n }\n\n /// @inheritdoc IIdentityRegistry\n function setIdentity(\n address owner,\n IdentityInfo memory info\n ) public override onlyWalletsAdmin {\n IdentityInfo memory oldIdentity = _identity[owner];\n \n bool isNewIdentity = (oldIdentity.regions.length == 0 && \n oldIdentity.accreditationType == NO_ACCREDITATION && \n !oldIdentity.amlKycPassed && \n oldIdentity.lastAmlKycChangeTimestamp == 0 && \n oldIdentity.lastAccreditationChangeTimestamp == 0);\n\n if (isNewIdentity) {\n emit IdentityCreated(_msgSender(), owner);\n }\n\n // Update each field\n _updateRegions(owner, info.regions);\n _updateAmlKyc(owner, info.amlKycPassed, info.lastAmlKycChangeTimestamp);\n _updateAccreditation(owner, info.accreditationType, info.lastAccreditationChangeTimestamp);\n }\n\n /// @inheritdoc IIdentityRegistry\n function batchSetIdentity(address[] memory owners, IdentityInfo memory info) external override onlyWalletsAdmin {\n for (uint256 i = 0; i < owners.length; i++) {\n setIdentity(owners[i], info);\n }\n }\n\n /// @inheritdoc IIdentityRegistry\n function removeIdentity(address owner) external override onlyWalletsAdmin {\n IdentityInfo memory info = _identity[owner];\n if (info.accreditationType == NO_ACCREDITATION\n && info.regions.length == 0\n && !info.amlKycPassed\n && info.lastAmlKycChangeTimestamp == 0\n && info.lastAccreditationChangeTimestamp == 0)\n {\n revert IdentityRegistry_WalletHasNoIdentityToRemove();\n }\n delete _identity[owner];\n emit IdentityRemoved(_msgSender(), owner);\n }\n\n /// @inheritdoc IIdentityRegistry\n function identity(\n address owner\n ) external view override returns (IdentityInfo memory) {\n return _identity[owner];\n }\n\n /// @inheritdoc IIdentityRegistry\n function setRegions(\n address owner,\n uint256[] memory newRegions\n ) external override onlyWalletsAdmin {\n if (!_updateRegions(owner, newRegions)) {\n revert IdentityRegistry_WalletAlreadyHasSameRegions();\n }\n }\n\n /// @inheritdoc IIdentityRegistry\n function addRegion(\n address owner,\n uint256 region\n ) external override onlyWalletsAdmin {\n if (_arrayContains(_identity[owner].regions, region)) {\n revert IdentityRegistry_WalletAlreadyHasRegion();\n }\n if (_identity[owner].regions.length >= MAX_REGIONS_COUNT) {\n revert IdentityRegistry_RegionsExceedsMaxCount();\n }\n \n _identity[owner].regions.push(region);\n emit RegionAdded(_msgSender(), owner, region);\n }\n\n /// @inheritdoc IIdentityRegistry\n function removeRegion(\n address owner,\n uint256 region\n ) external override onlyWalletsAdmin {\n if (!_removeFromArray(_identity[owner].regions, region)) {\n revert IdentityRegistry_WalletDoesNotHaveRegion();\n }\n \n emit RegionRemoved(_msgSender(), owner, region);\n }\n\n /// @inheritdoc IIdentityRegistry\n function grantAmlKyc(address owner, uint256 amlKycTimestamp) external override onlyWalletsAdmin {\n if (!_updateAmlKyc(owner, true, amlKycTimestamp)) {\n revert IdentityRegistry_WalletAlreadyHasSameAmlKycStatus();\n }\n }\n\n /// @inheritdoc IIdentityRegistry\n function revokeAmlKyc(address owner, uint256 amlKycTimestamp) external override onlyWalletsAdmin {\n if (!_updateAmlKyc(owner, false, amlKycTimestamp)) {\n revert IdentityRegistry_WalletAlreadyHasSameAmlKycStatus();\n }\n }\n\n /// @inheritdoc IIdentityRegistry\n function grantAccreditation(\n address owner,\n uint256 _accreditationType,\n uint256 accreditationTimestamp\n ) external override onlyWalletsAdmin {\n if (!_updateAccreditation(owner, _accreditationType, accreditationTimestamp)) {\n revert IdentityRegistry_WalletAlreadyHasSameAccreditationLevel();\n }\n }\n\n /// @inheritdoc IIdentityRegistry\n function revokeAccreditation(\n address owner,\n uint256 accreditationTimestamp\n ) external override onlyWalletsAdmin {\n if (!_updateAccreditation(owner, NO_ACCREDITATION, accreditationTimestamp)) {\n revert IdentityRegistry_WalletAlreadyHasNoAccreditation();\n }\n }\n\n /// @inheritdoc IIdentityRegistry\n function regions(\n address owner\n ) external view override returns (uint256[] memory) {\n return _identity[owner].regions;\n }\n\n /// @inheritdoc IIdentityRegistry\n function hasRegion(\n address owner,\n uint256 region\n ) external view override returns (bool) {\n return _arrayContains(_identity[owner].regions, region);\n }\n\n /// @inheritdoc IIdentityRegistry\n function accreditationType(address owner) external view override returns (uint256) {\n return _identity[owner].accreditationType;\n }\n\n /// @inheritdoc IIdentityRegistry\n function isAmlKycPassed(\n address owner\n ) external view override returns (bool) {\n bool isAmlKycValid = _amlKycValidityDurationSeconds == 0 ? true : (_identity[owner].lastAmlKycChangeTimestamp + _amlKycValidityDurationSeconds) >= block.timestamp;\n\n return _identity[owner].amlKycPassed && isAmlKycValid;\n }\n\n /// @inheritdoc IIdentityRegistry\n function amlKycValidityDuration() external view override returns (uint256) {\n return _amlKycValidityDurationSeconds;\n }\n}\n"},"contracts/InterestPayment.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport {ERC20, IERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {Pausable} from \"@openzeppelin/contracts/utils/Pausable.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {IRestrictedLockupToken} from \"./interfaces/IRestrictedLockupToken.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport {ISnapshotPeriods} from \"./interfaces/ISnapshotPeriods.sol\";\nimport {IInterestPayment} from \"./interfaces/IInterestPayment.sol\";\nimport {IDividends} from \"./interfaces/IDividends.sol\";\nimport \"./AccessControlErrors.sol\";\n\n/**\n * @title InterestPayment\n * @notice Contract for managing interest payments for loans backed by RestrictedLockupToken\n * @dev Tracks interest accrual based on token holdings and allows claiming of interest payments\n */\ncontract InterestPayment is\n IInterestPayment,\n IDividends,\n ReentrancyGuard,\n Pausable,\n ERC2771Context\n{\n // Role constants\n uint8 public constant CONTRACT_ADMIN_ROLE = 1;\n uint8 public constant RESERVE_ADMIN_ROLE = 2;\n uint8 public constant WALLETS_ADMIN_ROLE = 4;\n uint8 public constant TRANSFER_ADMIN_ROLE = 8;\n\n uint256 public constant INTEREST_RATE_PRECISION_FACTOR = 1_000_000_000;\n uint256 private constant BIPS_PRECISION = 10_000;\n \n // Valid duration periods in seconds (360, 365, 366 days)\n uint256 private constant DURATION_360_DAYS = 360 * 24 * 60 * 60; // 31,104,000 seconds\n uint256 private constant DURATION_365_DAYS = 365 * 24 * 60 * 60; // 31,536,000 seconds\n uint256 private constant DURATION_366_DAYS = 366 * 24 * 60 * 60; // 31,622,400 seconds\n\n /// @dev Struct to hold constructor parameters to avoid stack too deep issues\n struct ConstructorParams {\n address accessControl; // external access control address\n address restrictedLockupTokenAddress;\n address trustedForwarder;\n address paymentToken;\n uint256 paymentPeriodSeconds;\n uint256 principalAmountPerToken;\n uint256 interestAccrualStartTimestamp;\n uint256 interestAccrualEndTimestamp;\n uint256 maxInterestRate;\n }\n\n IRestrictedLockupToken private immutable restrictedLockupToken_;\n ISnapshotPeriods public immutable snapshotPeriods;\n IERC20 private immutable paymentToken_;\n uint256 public maxInterestRate;\n uint256 private immutable principalAmountPerToken_;\n uint256 public interestRatePeriodSeconds;\n uint256 public interestAccrualStartTimestamp;\n uint256 public interestAccrualEndTimestamp;\n uint256 public paymentPausedAfterTimestamp;\n address public reclaimerAddress;\n using SafeERC20 for IERC20;\n IAccessControl public accessControl;\n\n struct PaymentPeriod {\n uint256 startTimestamp;\n uint256 endTimestamp;\n uint256 interestRatePerSecond;\n uint256 totalClaimedInterest;\n uint256 totalReclaimedInterest;\n uint256 interestRate;\n bool paused;\n }\n\n struct AccountInfo {\n uint256 claimedInterest;\n uint256 reclaimedInterest;\n }\n\n /// @dev Dividend-related structures (adapted from Dividends contract)\n struct FundedDividends {\n uint256 totalFunds; // Total dividend tokens\n uint256 unusedFunds; // Unused dividend tokens\n uint256 dividendPerSecurity; // Dividend token per security token\n }\n\n uint256 private _fundedPrincipalAmount;\n uint256 private _totalPrincipalAmountClaimed;\n uint256 private _totalPrincipalAmountReclaimed;\n uint256 private _totalPrincipalAmountUnused;\n\n uint256 private _totalInterestAmountFunded;\n uint256 private _totalInterestAmountUnused;\n uint256 private _totalInterestAmountClaimed;\n uint256 private _totalInterestAmountReclaimed;\n\n PaymentPeriod[] public paymentPeriods;\n\n // @dev periodIdx => receiver => amount of claimed and reclaimed interest funds\n mapping(uint256 => mapping(address => AccountInfo))\n private accountInfoByPeriods; // claimed interest funds of ERC20\n\n /// @dev Dividend-related mappings (adapted from Dividends contract)\n /// @dev timestamp => token => receiver => amount of claimed dividend funds\n mapping(uint256 => mapping(address => mapping(address => uint256)))\n internal dividendClaimedFunds; // claimed dividend funds of ERC20\n \n /// @dev timestamp => token addr => FundedDividends struct\n mapping(uint256 => mapping(address => FundedDividends))\n internal fundedDividends;\n\n // Errors\n error InterestPayment_InvalidRestrictedLockupTokenAddress();\n error InterestPayment_InvalidPaymentPeriodSeconds();\n error InterestPayment_InvalidAmount();\n error InterestPayment_TokenSupplyIsZero();\n error InterestPayment_PrincipalAmountNotDivisibleByTokenSupply();\n error InterestPayment_InvalidFeeApplied();\n error InterestPayment_InvalidUnclaimedAmount(\n uint256 claimableFunds,\n uint256 claimedFunds\n );\n error InterestPayment_InvalidPeriod();\n error InterestPayment_InvalidPeriodIndex();\n error InterestPayment_StartTimestampGreaterThanEndTimestamp();\n error InterestPayment_StartTimestampBeforeAccrualStartTimestamp();\n error InterestPayment_EndTimestampGreaterThanAccrualEndTimestamp();\n error InterestPayment_PeriodDurationNotMultipleOfInterestRatePeriod();\n error InterestPayment_PeriodAlreadyExists();\n error InterestPayment_PeriodNotNext();\n error InterestPayment_NoPaymentPeriods();\n error InterestPayment_NoFundsToClaim();\n error InterestPayment_NotEnoughFundsToClaim();\n error InterestPayment_NotEnoughFundedPrincipal();\n error InterestPayment_PaymentNotPausedAfter();\n error InterestPayment_CannotUnpauseAfterMaturity();\n error InterestPayment_CannotReclaimAllForOngoingPeriod();\n error InterestPayment_InvalidTimestamp();\n error InterestPayment_InvalidInterestAccrualStartTimestamp();\n error InterestPayment_InvalidInterestAccrualEndTimestamp();\n error InterestPayment_InvalidInterestAccrualPeriod();\n error InterestPayment_InvalidPrincipalAmountPerToken();\n error InterestPayment_InvalidReclaimerAddress();\n error InterestPayment_PaymentPeriodPaused(uint256 periodIdx);\n error InterestPayment_MaturityNotReached();\n error InterestPayment_InvalidPaymentToken();\n error InterestPayment_InterestRateGreaterThanMaxInterestRate();\n error InterestPayment_PeriodAlreadyStarted();\n error InterestPayment_InterestRateNotChanged();\n error InterestPayment_MaxInterestRateNotChanged();\n error InterestPayment_InvalidInterestRatePeriodDuration();\n \n // Dividend-related errors (adapted from Dividends contract)\n error InterestPayment_InvalidDividendSnapshotId();\n error InterestPayment_NoRemainingUnclaimedDividendBalance();\n error InterestPayment_InvalidDividendTokenAddress();\n error InterestPayment_InvalidTokenDecimals(uint8 restrictedLockupTokenDecimals);\n error InterestPayment_IndivisibleAmount(\n uint256 paymentTokenAmount,\n uint256 totalSupplyAt\n );\n error InterestPayment_InvalidUnclaimedDividendBalance(\n uint256 claimableFunds,\n uint256 claimedFunds\n );\n error InterestPayment_ZeroTotalSupply();\n error InterestPayment_DividendsAlreadyClaimed();\n error InterestPayment_InvalidArrayLengths();\n\n modifier onlyValidPeriod(uint256 periodIdx_) {\n if (periodIdx_ >= paymentPeriods.length) {\n revert InterestPayment_InvalidPeriodIndex();\n }\n _;\n }\n\n modifier onlyValidReclaimerAddress(address reclaimerAddress_) {\n if (reclaimerAddress_ == address(0)) {\n revert InterestPayment_InvalidReclaimerAddress();\n }\n _;\n }\n\n modifier whenPeriodPaymentNotPaused(uint256 periodIdx) {\n if (paymentPeriodPaused(periodIdx)) {\n revert InterestPayment_PaymentPeriodPaused(periodIdx);\n }\n _;\n }\n\n modifier onlyValidAmount(uint256 amount) {\n if (amount == 0) {\n revert InterestPayment_InvalidAmount();\n }\n _;\n }\n\n modifier onlyValidDividendSnapshotId(uint256 timestamp_) {\n if (timestamp_ == 0 || timestamp_ >= block.timestamp) {\n revert InterestPayment_InvalidDividendSnapshotId();\n }\n _;\n }\n\n modifier onlyValidInterestRatePeriodDuration(uint256 interestRatePeriodDuration_) {\n if (interestRatePeriodDuration_ != DURATION_360_DAYS && \n interestRatePeriodDuration_ != DURATION_365_DAYS && \n interestRatePeriodDuration_ != DURATION_366_DAYS) {\n revert InterestPayment_InvalidInterestRatePeriodDuration();\n }\n _;\n }\n\n constructor(\n ConstructorParams memory params\n ) ReentrancyGuard() ERC2771Context(params.trustedForwarder) {\n if (params.restrictedLockupTokenAddress == address(0)) {\n revert InterestPayment_InvalidRestrictedLockupTokenAddress();\n }\n if (params.paymentPeriodSeconds == 0) {\n revert InterestPayment_InvalidPaymentPeriodSeconds();\n }\n if (params.interestAccrualStartTimestamp == 0) {\n revert InterestPayment_InvalidInterestAccrualStartTimestamp();\n }\n if (params.interestAccrualEndTimestamp == 0) {\n revert InterestPayment_InvalidInterestAccrualEndTimestamp();\n }\n if (\n params.interestAccrualStartTimestamp >=\n params.interestAccrualEndTimestamp\n ) {\n revert InterestPayment_InvalidInterestAccrualPeriod();\n }\n if (params.principalAmountPerToken == 0) {\n revert InterestPayment_InvalidPrincipalAmountPerToken();\n }\n if (params.paymentToken == address(0)) {\n revert InterestPayment_InvalidPaymentToken();\n }\n\n // set external access control\n if (params.accessControl == address(0)) {\n revert EasyAccessControl_InvalidZeroAddress();\n }\n\n accessControl = IAccessControl(params.accessControl);\n interestAccrualStartTimestamp = params.interestAccrualStartTimestamp;\n interestAccrualEndTimestamp = params.interestAccrualEndTimestamp;\n interestRatePeriodSeconds = params.paymentPeriodSeconds;\n maxInterestRate = params.maxInterestRate;\n principalAmountPerToken_ = params.principalAmountPerToken;\n\n restrictedLockupToken_ = IRestrictedLockupToken(\n params.restrictedLockupTokenAddress\n );\n snapshotPeriods = ISnapshotPeriods(\n restrictedLockupToken_.snapshotPeriodsAddress()\n );\n paymentToken_ = IERC20(params.paymentToken);\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n\n /// @inheritdoc IInterestPayment\n function paymentPeriodPaused(\n uint256 periodIdx\n ) public view override returns (bool) {\n return paymentPeriods[periodIdx].paused;\n }\n\n // Access control modifiers (using external accessControl)\n modifier onlyContractAdminOrTransferAdmin() {\n _onlyContractAdminOrTransferAdmin();\n _;\n }\n modifier onlyContractAdmin() {\n _onlyContractAdmin();\n _;\n }\n modifier onlyTransferAdmin() {\n _onlyTransferAdmin();\n _;\n }\n function _onlyTransferAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), TRANSFER_ADMIN_ROLE)) {\n revert EasyAccessControl_DoesNotHaveTransferAdminRole(_msgSender());\n }\n }\n function _onlyContractAdminOrTransferAdmin() internal view {\n if (\n !accessControl.hasRole(_msgSender(), CONTRACT_ADMIN_ROLE) &&\n !accessControl.hasRole(_msgSender(), TRANSFER_ADMIN_ROLE)\n ) {\n revert EasyAccessControl_DoesNotHaveContractOrTransferAdminRole(_msgSender());\n }\n }\n function _onlyContractAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), CONTRACT_ADMIN_ROLE)) {\n revert EasyAccessControl_DoesNotHaveContractAdminRole(_msgSender());\n }\n }\n\n /// @inheritdoc IInterestPayment\n function pausePaymentPeriod(\n uint256 periodIdx\n )\n external\n override\n onlyContractAdminOrTransferAdmin\n onlyValidPeriod(periodIdx)\n {\n paymentPeriods[periodIdx].paused = true;\n emit PeriodPaused(_msgSender(), periodIdx);\n }\n\n /// @inheritdoc IInterestPayment\n function unpausePaymentPeriod(\n uint256 periodIdx\n )\n external\n override\n onlyContractAdminOrTransferAdmin\n onlyValidPeriod(periodIdx)\n {\n paymentPeriods[periodIdx].paused = false;\n emit PeriodUnpaused(_msgSender(), periodIdx);\n }\n\n /**\n * @dev pause contract. Only contract or transfer admin.\n */\n function pause(bool isPaused_) external onlyContractAdminOrTransferAdmin {\n if (isPaused_) {\n _pause();\n } else {\n _unpause();\n }\n }\n\n function _pausePaymentAfterTimestamp(uint256 timestamp) internal {\n if (timestamp == 0 || timestamp < block.timestamp) {\n revert InterestPayment_InvalidTimestamp();\n }\n if (timestamp < interestAccrualStartTimestamp) {\n revert InterestPayment_InvalidTimestamp();\n }\n paymentPausedAfterTimestamp = timestamp;\n\n emit PaymentPausedAfter(timestamp);\n }\n\n /**\n * @dev pause contract. Only contract or transfer admin.\n */\n function pausePaymentAfter(\n uint256 timestamp\n ) external onlyContractAdminOrTransferAdmin {\n _pausePaymentAfterTimestamp(timestamp);\n }\n\n function unpausePaymentAfter() external onlyContractAdminOrTransferAdmin {\n if (paymentPausedAfterTimestamp == 0) {\n revert InterestPayment_PaymentNotPausedAfter();\n }\n if (block.timestamp > interestAccrualEndTimestamp) {\n revert InterestPayment_CannotUnpauseAfterMaturity();\n }\n paymentPausedAfterTimestamp = 0;\n\n emit PaymentUnpausedAfter();\n }\n\n /// @inheritdoc IInterestPayment\n function setReclaimerAddress(\n address newReclaimerAddress\n ) external onlyContractAdmin {\n reclaimerAddress = newReclaimerAddress;\n\n emit ReclaimerAddressChanged(_msgSender(), newReclaimerAddress);\n }\n\n function _updateInterestAccrualEnd(uint256 newInterestAccrualEndTimestamp) internal {\n interestAccrualEndTimestamp = newInterestAccrualEndTimestamp;\n\n emit InterestAccrualEndShifted(\n _msgSender(),\n newInterestAccrualEndTimestamp\n );\n }\n\n /// @inheritdoc IInterestPayment\n function shiftInterestAccrualEnd(\n uint256 newInterestAccrualEndTimestamp\n ) external onlyContractAdminOrTransferAdmin {\n if (newInterestAccrualEndTimestamp <= interestAccrualEndTimestamp) {\n revert InterestPayment_InvalidInterestAccrualEndTimestamp();\n }\n _updateInterestAccrualEnd(newInterestAccrualEndTimestamp);\n }\n\n function setMaxInterestRate(\n uint256 maxInterestRate_\n ) external onlyContractAdmin {\n if (maxInterestRate_ == maxInterestRate) {\n revert InterestPayment_MaxInterestRateNotChanged();\n }\n maxInterestRate = maxInterestRate_;\n\n emit SetMaxInterestRate(_msgSender(), maxInterestRate_);\n }\n\n function updateInterestRateForPeriod(\n uint256 periodIdx,\n uint256 interestRate_,\n uint256 interestRatePeriodDuration\n )\n external\n nonReentrant\n onlyTransferAdmin\n whenNotPaused\n onlyValidPeriod(periodIdx)\n onlyValidInterestRatePeriodDuration(interestRatePeriodDuration)\n {\n if (interestRate_ > maxInterestRate && maxInterestRate != 0) {\n revert InterestPayment_InterestRateGreaterThanMaxInterestRate();\n }\n if (block.timestamp > paymentPeriods[periodIdx].startTimestamp) {\n revert InterestPayment_PeriodAlreadyStarted();\n }\n if (interestRate_ == paymentPeriods[periodIdx].interestRate) {\n revert InterestPayment_InterestRateNotChanged();\n }\n uint256 interestRatePerSecond = calculateInterestRatePerSecond(\n interestRate_,\n interestRatePeriodDuration\n );\n paymentPeriods[periodIdx].interestRatePerSecond = interestRatePerSecond;\n paymentPeriods[periodIdx].interestRate = interestRate_;\n\n emit InterestRateUpdated(_msgSender(), periodIdx, interestRate_);\n }\n\n function totalInterestAmountFunded() public view returns (uint256) {\n return _totalInterestAmountFunded;\n }\n\n function totalInterestAmountUnused() public view returns (uint256) {\n return _totalInterestAmountUnused;\n }\n\n function totalInterestAmountClaimed() public view returns (uint256) {\n return _totalInterestAmountClaimed;\n }\n\n function totalInterestAmountReclaimed() public view returns (uint256) {\n return _totalInterestAmountReclaimed;\n }\n\n /// @inheritdoc IInterestPayment\n function accruedInterest(\n address account\n ) public view override returns (uint256) {\n return accruedInterestAt(account, block.timestamp);\n }\n\n /// @inheritdoc IInterestPayment\n function accruedInterestAt(\n address account,\n uint256 timestamp\n ) public view override returns (uint256) {\n uint256 endTimestamp = nearestInterestPaymentTimestampAt(timestamp);\n if (endTimestamp <= interestAccrualStartTimestamp) {\n return 0;\n }\n\n uint256 accruedInterestAmount = 0;\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n PaymentPeriod memory period = paymentPeriods[i];\n uint256 accountOwnershipForPeriod = 0;\n if (endTimestamp >= period.endTimestamp) {\n // fully accrued period\n accountOwnershipForPeriod = snapshotPeriods.ownershipForPeriod(\n address(restrictedLockupToken_),\n account,\n period.startTimestamp,\n period.endTimestamp\n );\n } else if (endTimestamp > period.startTimestamp) {\n accountOwnershipForPeriod = snapshotPeriods.ownershipForPeriod(\n address(restrictedLockupToken_),\n account,\n period.startTimestamp,\n endTimestamp\n );\n }\n accruedInterestAmount += _calculateInterest(\n period.interestRatePerSecond * accountOwnershipForPeriod\n );\n if (endTimestamp <= period.startTimestamp) {\n break;\n }\n }\n\n return accruedInterestAmount;\n }\n\n // it can be calculated\n // as totalAccruedInterestAt(period.endTimestamp) - totalAccruedInterestAt(period.startTimestamp)\n // or as is ownershipForPeriod * period.interestRatePerSecond * principalAmountPerToken / PRECISION_FACTOR / BIPS_PRECISION\n function accruedInterestForPeriod(\n address account,\n uint256 periodIdx\n ) public view returns (uint256) {\n uint256 endTimestamp = nearestInterestPaymentTimestampAt(\n block.timestamp\n );\n PaymentPeriod memory period = paymentPeriods[periodIdx];\n if (endTimestamp <= period.startTimestamp) {\n return 0;\n }\n if (endTimestamp >= period.endTimestamp) {\n endTimestamp = period.endTimestamp;\n }\n uint256 ownershipForPeriodAmount = snapshotPeriods.ownershipForPeriod(\n address(restrictedLockupToken_),\n account,\n period.startTimestamp,\n endTimestamp\n );\n\n return\n _calculateInterest(\n period.interestRatePerSecond * ownershipForPeriodAmount\n );\n }\n\n /// @inheritdoc IInterestPayment\n function totalAccruedInterest() external view returns (uint256) {\n return totalAccruedInterestAt(block.timestamp);\n }\n\n /// @inheritdoc IInterestPayment\n function totalAccruedInterestAt(\n uint256 timestamp\n ) public view override returns (uint256 totalAmount) {\n uint256 endTimestamp = timestamp > block.timestamp\n ? nearestInterestPaymentTimestampAt(block.timestamp)\n : nearestInterestPaymentTimestampAt(timestamp);\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n PaymentPeriod memory period = paymentPeriods[i];\n uint256 totalOwnershipForPeriod = 0;\n if (endTimestamp >= period.endTimestamp) {\n // fully accrued period\n totalOwnershipForPeriod = snapshotPeriods\n .totalOwnershipForPeriod(\n address(restrictedLockupToken_),\n period.startTimestamp,\n period.endTimestamp\n );\n } else if (endTimestamp > period.startTimestamp) {\n totalOwnershipForPeriod = snapshotPeriods\n .totalOwnershipForPeriod(\n address(restrictedLockupToken_),\n period.startTimestamp,\n endTimestamp\n );\n }\n totalAmount += _calculateInterest(\n period.interestRatePerSecond * totalOwnershipForPeriod\n );\n if (endTimestamp <= period.startTimestamp) {\n break;\n }\n }\n }\n\n function _calculateInterest(\n uint256 interestOwnershipPerSecond\n ) internal view returns (uint256) {\n return\n (interestOwnershipPerSecond * principalAmountPerToken_) /\n INTEREST_RATE_PRECISION_FACTOR /\n BIPS_PRECISION;\n }\n\n // @inheritdoc IInterestPayment\n function nearestInterestPaymentTimestampAt(\n uint256 timestamp\n ) public view returns (uint256) {\n if (timestamp < interestAccrualStartTimestamp) {\n return interestAccrualStartTimestamp;\n }\n uint256 effectiveTimestamp = timestamp;\n if (\n paymentPausedAfterTimestamp > 0 &&\n timestamp > paymentPausedAfterTimestamp\n ) {\n effectiveTimestamp = paymentPausedAfterTimestamp;\n }\n if (effectiveTimestamp >= interestAccrualEndTimestamp) {\n return interestAccrualEndTimestamp;\n }\n\n uint256 paymentTimestamp = effectiveTimestamp -\n ((effectiveTimestamp - interestAccrualStartTimestamp) %\n interestRatePeriodSeconds);\n\n return paymentTimestamp;\n }\n\n // @inheritdoc IInterestPayment\n function findPaymentPeriodIndex(\n uint256 startTimestamp,\n uint256 endTimestamp\n ) public view returns (uint256) {\n if (paymentPeriods.length == 0) {\n return type(uint256).max;\n }\n\n uint256 left = 0;\n uint256 right = paymentPeriods.length;\n\n while (left < right) {\n uint256 mid = left + (right - left) / 2;\n if (paymentPeriods[mid].startTimestamp < startTimestamp) {\n left = mid + 1;\n } else {\n right = mid;\n }\n }\n if (left == paymentPeriods.length) {\n return type(uint256).max;\n }\n\n if (\n paymentPeriods[left].startTimestamp == startTimestamp &&\n paymentPeriods[left].endTimestamp == endTimestamp\n ) {\n return left;\n }\n\n return type(uint256).max; // Return max if not found\n }\n\n /// @dev fund interest for a period\n /// @param startTimestamp start timestamp\n /// @param endTimestamp end timestamp\n /// @param interestRate_ interest rate in BIPS\n /// @param interestRatePeriodDuration interest rate period duration\n function createPaymentPeriod(\n uint256 startTimestamp,\n uint256 endTimestamp,\n uint256 interestRate_,\n uint256 interestRatePeriodDuration\n ) external nonReentrant onlyTransferAdmin whenNotPaused onlyValidInterestRatePeriodDuration(interestRatePeriodDuration) {\n if (startTimestamp >= endTimestamp) {\n revert InterestPayment_StartTimestampGreaterThanEndTimestamp();\n }\n if (startTimestamp < interestAccrualStartTimestamp) {\n revert InterestPayment_StartTimestampBeforeAccrualStartTimestamp();\n }\n if (endTimestamp > interestAccrualEndTimestamp) {\n revert InterestPayment_EndTimestampGreaterThanAccrualEndTimestamp();\n }\n if ((endTimestamp - startTimestamp) % interestRatePeriodSeconds != 0) {\n if (endTimestamp != interestAccrualEndTimestamp) {\n revert InterestPayment_PeriodDurationNotMultipleOfInterestRatePeriod();\n }\n }\n\n uint256 accrualPeriodIdx = findPaymentPeriodIndex(\n startTimestamp,\n endTimestamp\n );\n if (accrualPeriodIdx != type(uint256).max) {\n revert InterestPayment_PeriodAlreadyExists();\n }\n // no period found -> create new period\n // but check if it is next period after last funded period or startTimestamp is accrual start timestamp\n if (paymentPeriods.length > 0) {\n if (\n startTimestamp !=\n paymentPeriods[paymentPeriods.length - 1].endTimestamp\n ) {\n revert InterestPayment_PeriodNotNext();\n }\n } else {\n if (startTimestamp != interestAccrualStartTimestamp) {\n revert InterestPayment_PeriodNotNext();\n }\n }\n\n _createPaymentPeriod(\n startTimestamp,\n endTimestamp,\n interestRate_,\n interestRatePeriodDuration\n );\n }\n\n function _createPaymentPeriod(\n uint256 startTimestamp,\n uint256 endTimestamp,\n uint256 interestRate_,\n uint256 interestRatePeriodDuration\n ) internal {\n if (interestRate_ > maxInterestRate && maxInterestRate != 0) {\n revert InterestPayment_InterestRateGreaterThanMaxInterestRate();\n }\n uint256 accrualPeriodIdx = paymentPeriods.length;\n uint256 interestRatePerSecond = calculateInterestRatePerSecond(\n interestRate_,\n interestRatePeriodDuration\n );\n\n paymentPeriods.push(\n PaymentPeriod(\n startTimestamp,\n endTimestamp,\n interestRatePerSecond,\n 0,\n 0,\n interestRate_,\n false\n )\n );\n\n emit PaymentPeriodCreated(\n _msgSender(),\n accrualPeriodIdx,\n startTimestamp,\n endTimestamp,\n interestRate_\n );\n }\n\n function calculateInterestRatePerSecond(\n uint256 interestRate_,\n uint256 interestRatePeriodDuration\n ) internal pure returns (uint256) {\n return\n (interestRate_ * INTEREST_RATE_PRECISION_FACTOR) /\n interestRatePeriodDuration;\n }\n\n /// @inheritdoc IInterestPayment\n function fundInterest(\n uint256 amount\n )\n external\n nonReentrant\n whenNotPaused\n onlyValidAmount(amount)\n {\n _totalInterestAmountFunded += amount;\n _totalInterestAmountUnused += amount;\n\n uint256 _balanceBefore = paymentToken_.balanceOf(address(this));\n paymentToken_.safeTransferFrom(_msgSender(), address(this), amount);\n uint256 _balanceAfter = paymentToken_.balanceOf(address(this));\n if (_balanceBefore + amount != _balanceAfter) {\n revert InterestPayment_InvalidFeeApplied();\n }\n\n emit Funded(_msgSender(), amount);\n }\n\n /// @inheritdoc IInterestPayment\n function claimInterestForPeriod(\n uint256 paymentPeriodIdx,\n uint256 amount\n )\n public\n nonReentrant\n onlyValidPeriod(paymentPeriodIdx)\n whenPeriodPaymentNotPaused(paymentPeriodIdx)\n whenNotPaused\n {\n _claimInterestForPeriod(_msgSender(), paymentPeriodIdx, amount, false);\n }\n\n // @inheritdoc IInterestPayment\n function batchClaimInterestForPeriods(\n uint256[] memory paymentPeriodIdxs,\n uint256 amount\n ) external whenNotPaused {\n if (paymentPeriodIdxs.length == 0) {\n revert InterestPayment_InvalidPeriod();\n }\n if (\n amount > _totalInterestAmountUnused ||\n _totalInterestAmountUnused == 0\n ) {\n revert InterestPayment_NoFundsToClaim();\n }\n uint256 totalClaimableAmount = 0;\n for (uint256 i = 0; i < paymentPeriodIdxs.length; i++) {\n if (paymentPeriodIdxs[i] >= paymentPeriods.length) {\n revert InterestPayment_InvalidPeriodIndex();\n }\n if (paymentPeriodPaused(paymentPeriodIdxs[i])) {\n revert InterestPayment_PaymentPeriodPaused(\n paymentPeriodIdxs[i]\n );\n }\n uint256 claimableAmount = unclaimedAmountForPeriod(\n _msgSender(),\n paymentPeriodIdxs[i]\n );\n if (claimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n\n totalClaimableAmount += claimableAmount;\n if (amount > 0 && totalClaimableAmount > amount) {\n uint256 adjustment = totalClaimableAmount - amount;\n claimableAmount -= adjustment;\n totalClaimableAmount = amount;\n }\n claimInterestForPeriod(paymentPeriodIdxs[i], claimableAmount);\n if (amount > 0 && totalClaimableAmount >= amount) {\n break;\n }\n }\n }\n\n function _claimInterest(\n address target,\n uint256 amount,\n bool isForceClaimByAdmin\n ) internal {\n uint256 currentTimestamp = block.timestamp;\n uint256 currentPaymentTimestamp = nearestInterestPaymentTimestampAt(\n currentTimestamp\n );\n\n uint256 totalClaimableAmount = 0;\n\n // Check if there are any funded periods\n if (paymentPeriods.length == 0) {\n revert InterestPayment_NoPaymentPeriods();\n }\n\n // Process each funded period\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n PaymentPeriod storage period = paymentPeriods[i];\n // Check if period is accrued (fully or partially)\n if (currentPaymentTimestamp <= period.startTimestamp) {\n // Period has not started accruing yet\n // we can break here because all subsequent periods will not be accrued\n break;\n }\n uint256 claimableAmount = unclaimedAmountForPeriod(target, i);\n\n // Skip periods with no unused funds if payment period is paused\n if (claimableAmount == 0 || (!isForceClaimByAdmin && paymentPeriodPaused(i))) {\n continue;\n }\n\n totalClaimableAmount += claimableAmount;\n\n // Check if we've reached the amount cap (if provided)\n if (amount > 0 && totalClaimableAmount > amount) {\n // Adjust the claimable amount for this period to respect the cap\n uint256 adjustment = totalClaimableAmount - amount;\n claimableAmount -= adjustment;\n totalClaimableAmount = amount;\n }\n\n // Update claimed funds and unused funds\n period.totalClaimedInterest += claimableAmount;\n accountInfoByPeriods[i][target]\n .claimedInterest += claimableAmount;\n\n if (isForceClaimByAdmin) {\n emit ForceClaimed(_msgSender(), target, claimableAmount, i);\n } else {\n emit Claimed(target, claimableAmount, i);\n }\n\n // If we've reached the amount cap, exit the loop\n if (amount > 0 && totalClaimableAmount >= amount) {\n break;\n }\n }\n\n if (amount > 0 && totalClaimableAmount < amount) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n // Check if we have any funds to claim after processing all periods\n if (totalClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (totalClaimableAmount > _totalInterestAmountUnused) {\n revert InterestPayment_NoFundsToClaim();\n }\n _totalInterestAmountUnused -= totalClaimableAmount;\n _totalInterestAmountClaimed += totalClaimableAmount;\n\n // Transfer the total claimable amount\n paymentToken_.safeTransfer(target, totalClaimableAmount);\n }\n\n function _claimInterestForPeriod(\n address target,\n uint256 paymentPeriodIdx,\n uint256 amount,\n bool isForceClaimByAdmin\n ) internal {\n if (\n amount > _totalInterestAmountUnused ||\n _totalInterestAmountUnused == 0\n ) {\n revert InterestPayment_NoFundsToClaim();\n }\n uint256 availableClaimableAmount = unclaimedAmountForPeriod(\n target,\n paymentPeriodIdx\n );\n if (availableClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (availableClaimableAmount < amount) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n if (amount > 0) {\n // claim specific amount for period (not all funds)\n availableClaimableAmount = amount;\n }\n\n accountInfoByPeriods[paymentPeriodIdx][target]\n .claimedInterest += availableClaimableAmount;\n paymentPeriods[paymentPeriodIdx]\n .totalClaimedInterest += availableClaimableAmount;\n _totalInterestAmountClaimed += availableClaimableAmount;\n _totalInterestAmountUnused -= availableClaimableAmount;\n \n if (isForceClaimByAdmin) {\n emit ForceClaimed(_msgSender(), target, availableClaimableAmount, paymentPeriodIdx);\n } else {\n emit Claimed(target, availableClaimableAmount, paymentPeriodIdx);\n }\n \n paymentToken_.safeTransfer(target, availableClaimableAmount);\n }\n\n /// @dev claim interest for all periods\n /// @notice this function can run out of gas if there are a lot of periods\n /// so claim each period separately\n function claimInterest(uint256 amount) public nonReentrant whenNotPaused {\n _claimInterest(_msgSender(), amount, false);\n }\n\n /// @notice Force claim interest for a specific wallet address\n /// @dev Only callable by Transfer Admin. Similar to claimInterest but claims on behalf of a wallet address and with amount cap\n /// @param wallet The wallet address to claim interest for\n /// @param amount The maximum amount to claim, or 0 for all available funds\n function forceClaim(\n address wallet,\n uint256 amount\n ) external nonReentrant onlyTransferAdmin whenNotPaused {\n _claimInterest(wallet, amount, true);\n }\n\n function paymentPeriodsCount() public view returns (uint256) {\n return paymentPeriods.length;\n }\n\n function periodTotalInterest(\n uint256 periodIdx\n ) public view returns (uint256) {\n uint256 endTimestamp = nearestInterestPaymentTimestampAt(\n block.timestamp\n );\n PaymentPeriod memory period = paymentPeriods[periodIdx];\n if (endTimestamp <= period.startTimestamp) {\n return 0;\n }\n if (endTimestamp >= period.endTimestamp) {\n endTimestamp = period.endTimestamp;\n }\n uint256 totalOwnershipForPeriod = snapshotPeriods\n .totalOwnershipForPeriod(\n address(restrictedLockupToken_),\n period.startTimestamp,\n endTimestamp\n );\n\n return\n _calculateInterest(\n period.interestRatePerSecond * totalOwnershipForPeriod\n );\n }\n\n function periodTotalClaimedInterest(\n uint256 periodIdx\n ) public view returns (uint256) {\n return paymentPeriods[periodIdx].totalClaimedInterest;\n }\n\n function periodTotalReclaimedInterest(\n uint256 periodIdx\n ) public view returns (uint256) {\n return paymentPeriods[periodIdx].totalReclaimedInterest;\n }\n\n /// @inheritdoc IInterestPayment\n function periodAvailableInterest(\n uint256 periodIdx\n ) public view returns (uint256) {\n uint256 _totalAccruedInterestForPeriod = periodTotalInterest(periodIdx);\n\n if (\n _totalAccruedInterestForPeriod <=\n paymentPeriods[periodIdx].totalClaimedInterest +\n paymentPeriods[periodIdx].totalReclaimedInterest\n ) {\n return 0;\n }\n\n return\n _totalAccruedInterestForPeriod -\n paymentPeriods[periodIdx].totalClaimedInterest -\n paymentPeriods[periodIdx].totalReclaimedInterest;\n }\n\n function periodDuration(uint256 periodIdx) public view returns (uint256) {\n return\n paymentPeriods[periodIdx].endTimestamp -\n paymentPeriods[periodIdx].startTimestamp;\n }\n\n function periodStartTimestamp(\n uint256 periodIdx\n ) public view returns (uint256) {\n return paymentPeriods[periodIdx].startTimestamp;\n }\n\n function periodEndTimestamp(\n uint256 periodIdx\n ) public view returns (uint256) {\n return paymentPeriods[periodIdx].endTimestamp;\n }\n\n function unclaimedAmountAt(\n address receiver_,\n uint256 timestamp\n ) public view returns (uint256 totalClaimableAmount) {\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n if (paymentPeriods[i].startTimestamp > timestamp) {\n break;\n }\n totalClaimableAmount += unclaimedAmountForPeriod(receiver_, i);\n }\n }\n\n /// @inheritdoc IInterestPayment\n /// @dev Cap available claimable amount to unused funds in the period or total unused funds\n function unclaimedAmountForPeriod(\n address receiver_,\n uint256 paymentPeriodIdx_\n ) public view returns (uint256 _unclaimedAmountForPeriod) {\n uint256 _claimableFunds = accruedInterestForPeriod(\n receiver_,\n paymentPeriodIdx_\n );\n uint256 _usedFunds = usedAmountForPeriod(receiver_, paymentPeriodIdx_);\n // should not happen because we check for available funds to claim first but just in case\n if (_usedFunds > _claimableFunds) {\n revert InterestPayment_InvalidUnclaimedAmount(\n _claimableFunds,\n _usedFunds\n );\n }\n\n _unclaimedAmountForPeriod = _claimableFunds - _usedFunds;\n // if reclaimed all funds for period, we can't change unclaimed balance for specific user\n uint256 totalPeriodUnusedInterest = periodAvailableInterest(\n paymentPeriodIdx_\n );\n // cover reclaimAll for period case\n if (_unclaimedAmountForPeriod > totalPeriodUnusedInterest) {\n _unclaimedAmountForPeriod = totalPeriodUnusedInterest;\n }\n // cover reclaim all contract funds case\n if (_unclaimedAmountForPeriod > _totalInterestAmountUnused) {\n _unclaimedAmountForPeriod = _totalInterestAmountUnused;\n }\n }\n\n // @inheritdoc IInterestPayment\n function claimedAmountForPeriod(\n address receiver_,\n uint256 periodIdx_\n ) public view returns (uint256) {\n return accountInfoByPeriods[periodIdx_][receiver_].claimedInterest;\n }\n\n function accountTotalClaimedAmount(\n address receiver_\n ) public view returns (uint256 totalClaimed) {\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n totalClaimed += accountInfoByPeriods[i][receiver_].claimedInterest;\n }\n return totalClaimed;\n }\n\n function accountTotalReclaimedAmount(\n address receiver_\n ) public view returns (uint256 totalReclaimed) {\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n totalReclaimed += accountInfoByPeriods[i][receiver_]\n .reclaimedInterest;\n }\n return totalReclaimed;\n }\n\n function reclaimedAmountForPeriod(\n address receiver_,\n uint256 periodIdx_\n ) public view returns (uint256) {\n return accountInfoByPeriods[periodIdx_][receiver_].reclaimedInterest;\n }\n\n function usedAmountForPeriod(\n address receiver_,\n uint256 periodIdx_\n ) public view returns (uint256) {\n return\n accountInfoByPeriods[periodIdx_][receiver_].claimedInterest +\n accountInfoByPeriods[periodIdx_][receiver_].reclaimedInterest;\n }\n\n /// @inheritdoc IInterestPayment\n function reclaimInterestForPeriod(\n address wallet,\n uint256 paymentPeriodIdx,\n uint256 amount\n )\n public\n nonReentrant\n onlyTransferAdmin\n onlyValidPeriod(paymentPeriodIdx)\n onlyValidReclaimerAddress(reclaimerAddress)\n whenNotPaused\n {\n if (\n amount > _totalInterestAmountUnused ||\n _totalInterestAmountUnused == 0\n ) {\n revert InterestPayment_NoFundsToClaim();\n }\n\n // Cap available claimable amount to unused funds in the period or total unused funds\n uint256 availableClaimableAmount = unclaimedAmountForPeriod(\n wallet,\n paymentPeriodIdx\n );\n if (availableClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (availableClaimableAmount < amount) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n if (amount > 0) {\n // claim specific amount for period (not all funds)\n availableClaimableAmount = amount;\n }\n\n accountInfoByPeriods[paymentPeriodIdx][wallet]\n .reclaimedInterest += availableClaimableAmount;\n paymentPeriods[paymentPeriodIdx]\n .totalReclaimedInterest += availableClaimableAmount;\n _totalInterestAmountReclaimed += availableClaimableAmount;\n _totalInterestAmountUnused -= availableClaimableAmount;\n emit Reclaimed(\n _msgSender(),\n wallet,\n availableClaimableAmount,\n paymentPeriodIdx\n );\n paymentToken_.safeTransfer(reclaimerAddress, availableClaimableAmount);\n }\n\n function reclaimInterest(\n address wallet,\n uint256 amount\n )\n external\n nonReentrant\n onlyTransferAdmin\n onlyValidReclaimerAddress(reclaimerAddress)\n whenNotPaused\n {\n uint256 currentTimestamp = block.timestamp;\n uint256 currentPaymentTimestamp = nearestInterestPaymentTimestampAt(\n currentTimestamp\n );\n\n uint256 totalClaimableAmount = 0;\n\n // Check if there are any funded periods\n if (paymentPeriods.length == 0) {\n revert InterestPayment_NoPaymentPeriods();\n }\n\n // Process each funded period\n for (uint256 i = 0; i < paymentPeriods.length; i++) {\n PaymentPeriod storage period = paymentPeriods[i];\n\n // Check if period is accrued (fully or partially)\n if (currentPaymentTimestamp <= period.startTimestamp) {\n // Period has not started accruing yet\n // we can break here because all subsequent periods will not be accrued\n break;\n }\n\n // Cap available claimable amount to unused funds in the period or total unused funds\n uint256 claimableAmount = unclaimedAmountForPeriod(wallet, i);\n // Skip if no available funds to claim\n if (claimableAmount == 0 || period.paused) {\n continue;\n }\n\n // Add to total claimable amount\n totalClaimableAmount += claimableAmount;\n\n // Check if we've reached the amount cap (if provided)\n if (amount > 0 && totalClaimableAmount > amount) {\n // Adjust the claimable amount for this period to respect the cap\n uint256 adjustment = totalClaimableAmount - amount;\n claimableAmount -= adjustment;\n totalClaimableAmount = amount;\n }\n\n // Update claimed funds and unused funds\n period.totalReclaimedInterest += claimableAmount;\n accountInfoByPeriods[i][wallet]\n .reclaimedInterest += claimableAmount;\n\n emit Reclaimed(_msgSender(), wallet, claimableAmount, i);\n\n // If we've reached the amount cap, exit the loop\n if (amount > 0 && totalClaimableAmount >= amount) {\n break;\n }\n }\n\n if (amount > 0 && totalClaimableAmount < amount) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n // Check if we have any funds to claim after processing all periods\n if (totalClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (totalClaimableAmount > _totalInterestAmountUnused) {\n revert InterestPayment_NoFundsToClaim();\n }\n _totalInterestAmountUnused -= totalClaimableAmount;\n _totalInterestAmountReclaimed += totalClaimableAmount;\n\n // Transfer the total claimable amount\n paymentToken_.safeTransfer(reclaimerAddress, totalClaimableAmount);\n }\n\n function batchReclaimInterest(\n address wallet,\n uint256[] memory paymentPeriodIdxs,\n uint256 amount\n )\n external\n nonReentrant\n onlyTransferAdmin\n onlyValidReclaimerAddress(reclaimerAddress)\n whenNotPaused\n {\n uint256 currentTimestamp = block.timestamp;\n uint256 currentPaymentTimestamp = nearestInterestPaymentTimestampAt(\n currentTimestamp\n );\n\n uint256 totalClaimableAmount = 0;\n\n if (paymentPeriodIdxs.length == 0) {\n revert InterestPayment_InvalidPeriodIndex();\n }\n\n // Check if there are any funded periods\n if (paymentPeriods.length == 0) {\n revert InterestPayment_NoPaymentPeriods();\n }\n\n // Process each funded period\n for (uint256 i = 0; i < paymentPeriodIdxs.length; i++) {\n if (paymentPeriodIdxs[i] >= paymentPeriods.length) {\n revert InterestPayment_InvalidPeriodIndex();\n }\n PaymentPeriod storage period = paymentPeriods[paymentPeriodIdxs[i]];\n\n // Check if period is accrued (fully or partially)\n if (currentPaymentTimestamp <= period.startTimestamp) {\n // Period has not started accruing yet\n // we can break here because all subsequent periods will not be accrued\n break;\n }\n\n // Cap available claimable amount to unused funds in the period or total unused funds\n uint256 claimableAmount = unclaimedAmountForPeriod(\n wallet,\n paymentPeriodIdxs[i]\n );\n // Skip if no available funds to claim\n if (claimableAmount == 0) {\n continue;\n }\n\n // Add to total claimable amount\n totalClaimableAmount += claimableAmount;\n\n // Check if we've reached the amount cap (if provided)\n if (amount > 0 && totalClaimableAmount > amount) {\n // Adjust the claimable amount for this period to respect the cap\n uint256 adjustment = totalClaimableAmount - amount;\n claimableAmount -= adjustment;\n totalClaimableAmount = amount;\n }\n\n // Update claimed funds and unused funds\n period.totalReclaimedInterest += claimableAmount;\n accountInfoByPeriods[paymentPeriodIdxs[i]][wallet]\n .reclaimedInterest += claimableAmount;\n\n emit Reclaimed(\n _msgSender(),\n wallet,\n claimableAmount,\n paymentPeriodIdxs[i]\n );\n\n // If we've reached the amount cap, exit the loop\n if (amount > 0 && totalClaimableAmount >= amount) {\n break;\n }\n }\n\n if (amount > 0 && totalClaimableAmount < amount) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n // Check if we have any funds to claim after processing all periods\n if (totalClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (totalClaimableAmount > _totalInterestAmountUnused) {\n revert InterestPayment_NoFundsToClaim();\n }\n _totalInterestAmountUnused -= totalClaimableAmount;\n _totalInterestAmountReclaimed += totalClaimableAmount;\n\n // Transfer the total claimable amount\n paymentToken_.safeTransfer(reclaimerAddress, totalClaimableAmount);\n }\n\n /// @notice Batch force claim interest for specific periods for a wallet\n /// @dev Only callable by Transfer Admin. Similar to batchReclaimInterest but transfers amount to wallet instead of reclaimer\n /// @param wallet The wallet address to claim interest for\n /// @param paymentPeriodIdxs Array of payment period indices to claim interest from\n /// @param amount The maximum amount to claim, or 0 for all available funds\n function batchForceClaimInterest(\n address wallet,\n uint256[] memory paymentPeriodIdxs,\n uint256 amount\n )\n external\n nonReentrant\n onlyTransferAdmin\n whenNotPaused\n {\n uint256 currentTimestamp = block.timestamp;\n uint256 currentPaymentTimestamp = nearestInterestPaymentTimestampAt(\n currentTimestamp\n );\n\n uint256 totalClaimableAmount = 0;\n\n if (paymentPeriodIdxs.length == 0) {\n revert InterestPayment_InvalidPeriodIndex();\n }\n\n // Check if there are any funded periods\n if (paymentPeriods.length == 0) {\n revert InterestPayment_NoPaymentPeriods();\n }\n\n // Process each specified period\n for (uint256 i = 0; i < paymentPeriodIdxs.length; i++) {\n if (paymentPeriodIdxs[i] >= paymentPeriods.length) {\n revert InterestPayment_InvalidPeriodIndex();\n }\n PaymentPeriod storage period = paymentPeriods[paymentPeriodIdxs[i]];\n\n // Check if period is accrued (fully or partially)\n if (currentPaymentTimestamp <= period.startTimestamp) {\n // Period has not started accruing yet\n continue;\n }\n\n // Get available claimable amount for this period\n uint256 claimableAmount = unclaimedAmountForPeriod(\n wallet,\n paymentPeriodIdxs[i]\n );\n // Skip if no available funds to claim\n if (claimableAmount == 0) {\n continue;\n }\n\n // Add to total claimable amount\n totalClaimableAmount += claimableAmount;\n\n // Check if we've reached the amount cap (if provided)\n if (amount > 0 && totalClaimableAmount > amount) {\n // Adjust the claimable amount for this period to respect the cap\n uint256 adjustment = totalClaimableAmount - amount;\n claimableAmount -= adjustment;\n totalClaimableAmount = amount;\n }\n\n // Update claimed funds and unused funds\n period.totalClaimedInterest += claimableAmount;\n accountInfoByPeriods[paymentPeriodIdxs[i]][wallet]\n .claimedInterest += claimableAmount;\n\n emit ForceClaimed(\n _msgSender(),\n wallet,\n claimableAmount,\n paymentPeriodIdxs[i]\n );\n\n // If we've reached the amount cap, exit the loop\n if (amount > 0 && totalClaimableAmount >= amount) {\n break;\n }\n }\n\n if (amount > 0 && totalClaimableAmount < amount) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n // Check if we have any funds to claim after processing all periods\n if (totalClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (totalClaimableAmount > _totalInterestAmountUnused) {\n revert InterestPayment_NoFundsToClaim();\n }\n _totalInterestAmountUnused -= totalClaimableAmount;\n _totalInterestAmountClaimed += totalClaimableAmount;\n\n // Transfer the total claimable amount to the wallet (not to reclaimerAddress)\n paymentToken_.safeTransfer(wallet, totalClaimableAmount);\n }\n\n function reclaimInterestForAllRecipients(\n uint256 paymentPeriodIdx\n )\n external\n nonReentrant\n onlyTransferAdmin\n onlyValidPeriod(paymentPeriodIdx)\n onlyValidReclaimerAddress(reclaimerAddress)\n {\n if (block.timestamp < paymentPeriods[paymentPeriodIdx].endTimestamp) {\n revert InterestPayment_CannotReclaimAllForOngoingPeriod();\n }\n uint256 availableClaimableAmount = periodAvailableInterest(\n paymentPeriodIdx\n );\n if (availableClaimableAmount == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (_totalInterestAmountUnused < availableClaimableAmount) {\n revert InterestPayment_NoFundsToClaim();\n }\n\n paymentPeriods[paymentPeriodIdx]\n .totalReclaimedInterest += availableClaimableAmount;\n _totalInterestAmountReclaimed += availableClaimableAmount;\n _totalInterestAmountUnused -= availableClaimableAmount;\n emit ReclaimedAll(\n reclaimerAddress,\n availableClaimableAmount,\n paymentPeriodIdx\n );\n\n // Transfer the total reclaimed amount to reclaimerAddress\n paymentToken_.safeTransfer(reclaimerAddress, availableClaimableAmount);\n }\n\n function reclaimTotalInterest(\n uint256 amount\n )\n external\n nonReentrant\n onlyTransferAdmin\n whenNotPaused\n onlyValidAmount(amount)\n onlyValidReclaimerAddress(reclaimerAddress)\n {\n if (\n _totalInterestAmountFunded <=\n _totalInterestAmountClaimed + _totalInterestAmountReclaimed\n ) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (amount > _totalInterestAmountUnused) {\n revert InterestPayment_NoFundsToClaim();\n }\n uint256 totalReclaimedAmount = amount;\n _totalInterestAmountReclaimed += totalReclaimedAmount;\n _totalInterestAmountUnused -= totalReclaimedAmount;\n emit ReclaimedAll(\n reclaimerAddress,\n totalReclaimedAmount,\n type(uint256).max\n );\n paymentToken_.safeTransfer(reclaimerAddress, totalReclaimedAmount);\n }\n\n function fundPrincipal(\n uint256 amount\n )\n public\n onlyTransferAdmin\n whenNotPaused\n nonReentrant\n onlyValidAmount(amount)\n {\n uint256 totalSupply = restrictedLockupToken_.totalSupply();\n uint256 claimedBalanceLeft = totalSupply -\n restrictedLockupToken_.balanceOf(address(this));\n if (claimedBalanceLeft == 0) {\n revert InterestPayment_TokenSupplyIsZero();\n }\n // validate that distributed is divisible by supply left in circulation (out of InterestPayment contract)\n uint256 expectedPrincipalTotalAmount = _totalPrincipalAmountUnused +\n amount;\n if (expectedPrincipalTotalAmount % claimedBalanceLeft != 0) {\n revert InterestPayment_PrincipalAmountNotDivisibleByTokenSupply();\n }\n\n _fundedPrincipalAmount += amount;\n _totalPrincipalAmountUnused += amount;\n\n emit PrincipalFunded(_msgSender(), amount);\n\n uint256 _balanceBefore = paymentToken_.balanceOf(address(this));\n paymentToken_.safeTransferFrom(_msgSender(), address(this), amount);\n uint256 _balanceAfter = paymentToken_.balanceOf(address(this));\n if (_balanceBefore + amount != _balanceAfter) {\n revert InterestPayment_InvalidFeeApplied();\n }\n }\n\n function _claimPrincipal(\n address target,\n uint256 amount\n ) internal {\n if (_totalPrincipalAmountUnused == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n if (block.timestamp < interestAccrualEndTimestamp) {\n revert InterestPayment_MaturityNotReached();\n }\n\n uint256 accountBalance = restrictedLockupToken_.balanceOf(target);\n if (accountBalance == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n uint256 allowedPrincipalAmount = accountBalance *\n principalAmountPerToken_;\n if (_totalPrincipalAmountUnused < allowedPrincipalAmount) {\n revert InterestPayment_NotEnoughFundedPrincipal();\n }\n\n // Handle amount parameter: 0 means claim all, >0 means claim specific amount\n uint256 principalToClaim = allowedPrincipalAmount;\n uint256 tokensToBurn = accountBalance;\n if (amount > 0) {\n // Claim specific amount\n if (amount > allowedPrincipalAmount) {\n revert InterestPayment_NotEnoughFundedPrincipal();\n }\n principalToClaim = amount;\n tokensToBurn = amount / principalAmountPerToken_;\n // Ensure exact division\n if (tokensToBurn * principalAmountPerToken_ != amount) {\n revert InterestPayment_PrincipalAmountNotDivisibleByTokenSupply();\n }\n }\n\n _totalPrincipalAmountUnused -= principalToClaim;\n _totalPrincipalAmountClaimed += principalToClaim;\n\n restrictedLockupToken_.softBurn(target, tokensToBurn);\n emit PrincipalClaimed(target, principalToClaim);\n\n paymentToken_.safeTransfer(target, principalToClaim);\n }\n\n function claimPrincipal(\n uint256 amount\n ) external nonReentrant whenNotPaused {\n address sender = _msgSender();\n _claimPrincipal(sender, amount);\n }\n\n /**\n * @notice Force claim principal for a specific wallet address\n * @dev Only callable by Transfer Admin. Similar to claimPrincipal but claims on behalf of a wallet address\n * @param wallet The wallet address to claim principal for\n * @param amount The amount to claim, or 0 for all available principal\n */\n function forceClaimPrincipal(\n address wallet,\n uint256 amount\n ) external nonReentrant onlyTransferAdmin whenNotPaused {\n _claimPrincipal(wallet, amount);\n }\n\n function reclaimPrincipal(\n uint256 amount\n )\n external\n nonReentrant\n onlyTransferAdmin\n onlyValidReclaimerAddress(reclaimerAddress)\n {\n if (_totalPrincipalAmountUnused == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n\n // Handle amount parameter: 0 means reclaim all, >0 means reclaim specific amount\n uint256 principalToReclaim = _totalPrincipalAmountUnused;\n if (amount > 0) {\n // Reclaim specific amount\n if (amount > _totalPrincipalAmountUnused) {\n revert InterestPayment_NotEnoughFundedPrincipal();\n }\n principalToReclaim = amount;\n }\n\n _totalPrincipalAmountUnused -= principalToReclaim;\n _totalPrincipalAmountReclaimed += principalToReclaim;\n\n emit PrincipalReclaimed(_msgSender(), principalToReclaim);\n paymentToken_.safeTransfer(reclaimerAddress, principalToReclaim);\n }\n\n function fundedPrincipalAmount() public view returns (uint256) {\n return _fundedPrincipalAmount;\n }\n\n function reclaimedPrincipalAmount() public view returns (uint256) {\n return _totalPrincipalAmountReclaimed;\n }\n\n function claimedPrincipalAmount() public view returns (uint256) {\n return _totalPrincipalAmountClaimed;\n }\n\n function totalAvailablePrincipalAmount() public view returns (uint256) {\n return _totalPrincipalAmountUnused;\n }\n\n function availablePrincipalAmount(\n address account\n ) public view returns (uint256) {\n uint256 accountBalance = restrictedLockupToken_.balanceOf(account);\n if (accountBalance == 0) {\n return 0;\n }\n uint256 allowedPrincipalAmount = accountBalance *\n principalAmountPerToken_;\n if (_totalPrincipalAmountUnused < allowedPrincipalAmount) {\n return _totalPrincipalAmountUnused;\n }\n return allowedPrincipalAmount;\n }\n\n /// @inheritdoc IInterestPayment\n function earlyRepayment(uint256 timestamp) external onlyContractAdminOrTransferAdmin {\n if (timestamp < block.timestamp) {\n revert InterestPayment_InvalidTimestamp();\n }\n\n _pausePaymentAfterTimestamp(timestamp);\n _updateInterestAccrualEnd(timestamp);\n\n emit EarlyRepayment(_msgSender(), timestamp);\n }\n\n /// @notice Force claim interest for an account for a specific period\n /// @dev Only callable by Transfer Admin. Similar to claimInterestForPeriod but claims on behalf of a wallet address\n /// @param wallet The wallet address to claim interest for\n /// @param paymentPeriodIdx The payment period index to claim interest for\n /// @param amount The amount to claim, capped at available claimable amount\n function forceClaimForPeriod(\n address wallet,\n uint256 paymentPeriodIdx,\n uint256 amount\n )\n external\n nonReentrant\n onlyTransferAdmin\n onlyValidPeriod(paymentPeriodIdx)\n whenNotPaused\n {\n _claimInterestForPeriod(wallet, paymentPeriodIdx, amount, true);\n }\n\n /// @inheritdoc IInterestPayment\n function restrictedLockupToken() external view returns (address) {\n return address(restrictedLockupToken_);\n }\n\n /// @inheritdoc IInterestPayment\n function paymentToken() external view returns (address) {\n return address(paymentToken_);\n }\n\n /// @inheritdoc IInterestPayment\n function principalAmountPerToken() external view returns (uint256) {\n return principalAmountPerToken_;\n }\n\n // =============================================================================\n // DIVIDEND FUNCTIONALITY (Adapted from Dividends Contract)\n // =============================================================================\n\n /// @inheritdoc IDividends\n function totalAwardedBalanceAt(\n address token_,\n address receiver_,\n uint256 timestamp_\n ) public view override onlyValidDividendSnapshotId(timestamp_) returns (uint256) {\n return\n unclaimedBalanceAt(token_, receiver_, timestamp_) +\n claimedBalanceAt(token_, receiver_, timestamp_);\n }\n\n /**\n * @dev Check if amount is divisible into totalSupply at timestamp\n * @param amount_ Amount to check divisibility\n * @param timestamp_ Timestamp for historical data\n * @return isDivisible_ boolean. True if amount is valid and divisible by totalSupply.\n */\n function isAmountDivisible(\n uint256 amount_,\n uint256 timestamp_\n ) public view returns (bool isDivisible_) {\n uint256 totalSupplyAtTimestamp = snapshotPeriods.getPastTotalSupply(\n address(restrictedLockupToken_),\n timestamp_\n );\n isDivisible_ = amount_ % totalSupplyAtTimestamp == 0;\n }\n\n /// @inheritdoc IDividends\n function claimedBalanceAt(\n address token_,\n address receiver_,\n uint256 timestamp_\n ) public view override onlyValidDividendSnapshotId(timestamp_) returns (uint256) {\n return dividendClaimedFunds[timestamp_][token_][receiver_];\n }\n\n /// @inheritdoc IDividends\n function claimDividend(\n address token_,\n uint256 timestamp_,\n uint256 amount_\n )\n public\n override\n nonReentrant\n onlyValidDividendSnapshotId(timestamp_)\n whenNotPaused\n {\n uint256 _unclaimedBalance = unclaimedBalanceAt(\n token_,\n _msgSender(),\n timestamp_\n );\n\n if (amount_ == 0) {\n amount_ = _unclaimedBalance;\n }\n if (amount_ > _unclaimedBalance) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n\n if (_unclaimedBalance == 0) {\n revert InterestPayment_NoRemainingUnclaimedDividendBalance();\n }\n\n dividendClaimedFunds[timestamp_][token_][_msgSender()] += amount_;\n fundedDividends[timestamp_][token_].unusedFunds -= amount_;\n\n emit DividendClaimed(_msgSender(), token_, amount_, timestamp_);\n\n IERC20(token_).safeTransfer(_msgSender(), amount_);\n }\n\n /**\n * @dev Reclaim dividends for a specific target address and send to contract reclaimer\n * Can only be called by transfer admin\n * @param token_ ERC-20 token address\n * @param targetAddress_ Address to reclaim dividends for\n * @param timestamp_ timestamp for dividend distribution\n */\n function reclaimDividend(\n address token_,\n address targetAddress_,\n uint256 timestamp_,\n uint256 amount_\n )\n external\n override\n nonReentrant\n onlyTransferAdmin\n onlyValidDividendSnapshotId(timestamp_)\n whenNotPaused\n onlyValidReclaimerAddress(reclaimerAddress)\n {\n uint256 _unclaimedBalance = unclaimedBalanceAt(\n token_,\n targetAddress_,\n timestamp_\n );\n\n if (amount_ == 0) {\n amount_ = _unclaimedBalance;\n }\n if (amount_ > _unclaimedBalance) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n\n if (_unclaimedBalance == 0) {\n revert InterestPayment_NoRemainingUnclaimedDividendBalance();\n }\n\n // Mark the dividends as claimed for the target address\n dividendClaimedFunds[timestamp_][token_][targetAddress_] += amount_;\n fundedDividends[timestamp_][token_].unusedFunds -= amount_;\n\n emit DividendReclaimed(_msgSender(), targetAddress_, token_, amount_, timestamp_);\n\n // Transfer to contract reclaimer address\n IERC20(token_).safeTransfer(reclaimerAddress, amount_);\n }\n\n /// @inheritdoc IDividends\n function unclaimedBalanceAt(\n address token_,\n address receiver_,\n uint256 timestamp_\n ) public view override onlyValidDividendSnapshotId(timestamp_) returns (uint256 _unclaimedBalanceAt) {\n uint256 _claimableFunds = _claimableFundsAt(\n token_,\n receiver_,\n timestamp_\n );\n uint256 _claimedFunds = claimedBalanceAt(\n token_,\n receiver_,\n timestamp_\n );\n if (_claimedFunds > _claimableFunds) {\n revert InterestPayment_InvalidUnclaimedDividendBalance(\n _claimableFunds,\n _claimedFunds\n );\n }\n\n _unclaimedBalanceAt = _claimableFunds - _claimedFunds;\n }\n\n /// @inheritdoc IDividends\n function batchClaimDividend(\n address token_,\n uint256[] calldata timestamps_,\n uint256[] calldata amounts_\n ) external override whenNotPaused {\n uint256 _timestampsLength = timestamps_.length;\n if (_timestampsLength != amounts_.length) {\n revert InterestPayment_InvalidArrayLengths();\n }\n for (uint256 i; i < _timestampsLength; ++i) {\n claimDividend(token_, timestamps_[i], amounts_[i]);\n }\n }\n\n /// @inheritdoc IDividends\n function fundDividend(\n address token_,\n uint256 amount_,\n uint256 timestamp_\n )\n external\n override\n nonReentrant\n onlyTransferAdmin\n onlyValidDividendSnapshotId(timestamp_)\n whenNotPaused\n {\n if (amount_ == 0) {\n revert InterestPayment_InvalidAmount();\n }\n if (token_ == address(0)) {\n revert InterestPayment_InvalidDividendTokenAddress();\n }\n IERC20 _paymentToken = IERC20(token_);\n uint8 _restrictedLockupTokenDecimals = restrictedLockupToken_.decimals();\n if (_restrictedLockupTokenDecimals > ERC20(token_).decimals()) {\n revert InterestPayment_InvalidTokenDecimals(\n _restrictedLockupTokenDecimals\n );\n }\n\n uint256 _totalSupply = snapshotPeriods.getPastTotalSupply(\n address(restrictedLockupToken_),\n timestamp_\n );\n if (_totalSupply == 0) {\n revert InterestPayment_ZeroTotalSupply();\n }\n if (amount_ % _totalSupply != 0) {\n revert InterestPayment_IndivisibleAmount(amount_, _totalSupply);\n }\n\n uint256 _dividendPerSecurity = amount_ / _totalSupply;\n\n fundedDividends[timestamp_][token_].unusedFunds += amount_;\n fundedDividends[timestamp_][token_].totalFunds += amount_;\n fundedDividends[timestamp_][token_].dividendPerSecurity =\n fundedDividends[timestamp_][token_].dividendPerSecurity +\n _dividendPerSecurity;\n\n emit DividendFunded(_msgSender(), token_, amount_, timestamp_);\n uint256 _balanceBefore = _paymentToken.balanceOf(address(this));\n _paymentToken.safeTransferFrom(_msgSender(), address(this), amount_);\n uint256 _balanceAfter = _paymentToken.balanceOf(address(this));\n if (_balanceBefore + amount_ != _balanceAfter) {\n revert InterestPayment_InvalidFeeApplied();\n }\n }\n\n /**\n * @dev Reclaim ERC-20 tokens from a specific dividend snapshot\n * Can only be done if no dividends have been claimed for this snapshot (unusedFunds == totalFunds)\n * If amount is 0, reclaims all available funds\n * @param token_ ERC-20 token address\n * @param amount_ amount of tokens to reclaim (0 = reclaim all)\n * @param timestamp_ timestamp for dividend distribution\n */\n function reclaimTotalDividend(\n address token_,\n uint256 amount_,\n uint256 timestamp_\n )\n external\n override\n nonReentrant\n onlyTransferAdmin\n onlyValidDividendSnapshotId(timestamp_)\n whenNotPaused\n onlyValidReclaimerAddress(reclaimerAddress)\n {\n FundedDividends storage _fundedDividend = fundedDividends[timestamp_][token_];\n \n // Check if any dividends have been claimed (unusedFunds should equal totalFunds if no claims)\n if (_fundedDividend.unusedFunds != _fundedDividend.totalFunds) {\n revert InterestPayment_DividendsAlreadyClaimed();\n }\n\n // Check if there are any funds to reclaim\n if (_fundedDividend.unusedFunds == 0) {\n revert InterestPayment_NoFundsToClaim();\n }\n\n uint256 _amountToReclaim;\n if (amount_ == 0) {\n // Reclaim all funds\n _amountToReclaim = _fundedDividend.totalFunds;\n } else {\n // Validate amount is not greater than available funds\n if (amount_ > _fundedDividend.totalFunds) {\n revert InterestPayment_NotEnoughFundsToClaim();\n }\n\n // Validate divisibility (same as fundDividend)\n uint256 _totalSupply = snapshotPeriods.getPastTotalSupply(\n address(restrictedLockupToken_),\n timestamp_\n );\n // it is always greater than 0 because we can't fund dividend with 0 supply\n // so it fails on InterestPayment_NoFundsToClaim\n if (amount_ % _totalSupply != 0) {\n revert InterestPayment_IndivisibleAmount(amount_, _totalSupply);\n }\n\n _amountToReclaim = amount_;\n }\n\n // Calculate the dividend per security to subtract\n uint256 _dividendPerSecurityToSubtract = _amountToReclaim / snapshotPeriods.getPastTotalSupply(\n address(restrictedLockupToken_),\n timestamp_\n );\n\n // Update dividend tracking\n _fundedDividend.unusedFunds -= _amountToReclaim;\n _fundedDividend.totalFunds -= _amountToReclaim;\n _fundedDividend.dividendPerSecurity -= _dividendPerSecurityToSubtract;\n\n emit DividendReclaimed(_msgSender(), address(this), token_, _amountToReclaim, timestamp_);\n\n // Transfer tokens back to the caller\n IERC20(token_).safeTransfer(reclaimerAddress, _amountToReclaim);\n }\n\n /// @inheritdoc IDividends\n function tokensAt(\n address token_,\n uint256 timestamp_\n ) external view override onlyValidDividendSnapshotId(timestamp_) returns (uint256) {\n return fundedDividends[timestamp_][token_].unusedFunds;\n }\n\n /**\n * @dev Proxy function for restricted token to retrieve totalSupply at timestamp\n * @param timestamp_ Timestamp for historical data\n * @return totalSupply at timestamp\n */\n function totalSupplyAt(\n uint256 timestamp_\n ) external view onlyValidDividendSnapshotId(timestamp_) returns (uint256) {\n return snapshotPeriods.getPastTotalSupply(address(restrictedLockupToken_), timestamp_);\n }\n\n /**\n * @dev Proxy function for restricted token to retrieve balanceOf address at timestamp\n * @param sender_ address to check balance\n * @param timestamp_ timestamp for historical data\n * @return balance of address at timestamp\n */\n function balanceOfAt(\n address sender_,\n uint256 timestamp_\n ) external view onlyValidDividendSnapshotId(timestamp_) returns (uint256) {\n return snapshotPeriods.getPastBalanceOf(address(restrictedLockupToken_), sender_, timestamp_);\n }\n\n /// @inheritdoc IDividends\n function fundsAt(\n address token_,\n uint256 timestamp_\n ) external view override onlyValidDividendSnapshotId(timestamp_) returns (uint256) {\n return fundedDividends[timestamp_][token_].totalFunds;\n }\n\n /**\n * @dev _claimableFundsAt private function to calculate claimable funds for receiver at a given timestamp\n * @param token_ ERC20 token address\n * @param receiver_ address of receiver\n * @param timestamp_ Timestamp for historical data\n * @return claimable funds for receiver at timestamp\n */\n function _claimableFundsAt(\n address token_,\n address receiver_,\n uint256 timestamp_\n ) private view returns (uint256) {\n return (fundedDividends[timestamp_][token_].dividendPerSecurity *\n snapshotPeriods.getPastBalanceOf(address(restrictedLockupToken_), receiver_, timestamp_));\n }\n}\n"},"contracts/PurchaseContract.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {IInterestPayment} from \"./interfaces/IInterestPayment.sol\";\nimport {RestrictedLockupToken} from \"./RestrictedLockupToken.sol\";\n\n/**\n * @title PurchaseContract\n * @notice Contract for handling token purchases with USDC payments and automatic distribution\n * @dev Implements atomic purchase transactions with originator payments, admin fees, and token minting\n */\ncontract PurchaseContract is Ownable, ERC2771Context, ReentrancyGuard {\n using SafeERC20 for IERC20;\n\n // ============================================\n // EVENTS\n // ============================================\n\n event PurchaseExecuted(\n string indexed purchaseId,\n address indexed payerAddress,\n address indexed tokenRecipientAddress,\n address authorityAddress,\n uint256 originatorPurchaseAmount,\n uint256 prefundedInterestAmount,\n uint256 adminFeeExcludingPrefundedInterestAmount,\n uint256 mintAmount,\n address originatorAddress\n );\n\n event PurchaseCanceled(\n address indexed authorityAddress,\n address indexed paymentTokenAddress,\n address indexed canceledPurchaseWallet,\n uint256 amount\n );\n\n event AutomationAdminUpdated(address indexed oldAdmin, address indexed newAdmin);\n event AdminFeeWalletUpdated(address indexed oldWallet, address indexed newWallet);\n event CanceledPurchaseWalletUpdated(address indexed oldWallet, address indexed newWallet);\n event OriginatorPaymentWalletUpdated(address indexed oldWallet, address indexed newWallet);\n\n // ============================================\n // ERRORS\n // ============================================\n\n error PurchaseContract_InvalidZeroAddress();\n error PurchaseContract_InvalidPaymentToken();\n error PurchaseContract_InvalidTokenContract();\n error PurchaseContract_OnlyAutomationAdmin();\n error PurchaseContract_PurchaseIdAlreadyUsed();\n error PurchaseContract_PurchaseIdNotUsed();\n error PurchaseContract_InvalidAmount();\n error PurchaseContract_DecimalPrecisionMismatch();\n error PurchaseContract_TransferFailed();\n error PurchaseContract_AdminFeeWalletNotSet();\n error PurchaseContract_CanceledPurchaseWalletNotSet();\n error PurchaseContract_OriginatorPaymentWalletNotSet();\n error PurchaseContract_TokenRecipientAmlKycNotPassed();\n error PurchaseContract_PayerAmlKycNotPassed();\n error PurchaseContract_InvalidTotalAmount();\n error PurchaseContract_RemainderNotZero();\n error PurchaseContract_InterestPaymentNotConfigured();\n\n // ============================================\n // STATE VARIABLES\n // ============================================\n\n /// @notice Interest payment contract for funding interest\n IInterestPayment public immutable interestPayment;\n RestrictedLockupToken public immutable tokenContract;\n\n /// @notice USDC token contract\n IERC20 public immutable paymentToken;\n\n /// @notice Automation admin address that can execute purchases and cancellations\n address public automationAdmin;\n\n /// @notice Admin fee wallet address\n address public adminFeeWallet;\n\n /// @notice Canceled purchase wallet address\n address public canceledPurchaseWallet;\n\n /// @notice Originator payment wallet address\n address public originatorPaymentWallet;\n\n /// @notice Mapping to track used purchase IDs\n mapping(string => bool) public usedPurchaseIds;\n\n // ============================================\n // MODIFIERS\n // ============================================\n\n modifier onlyAutomationAdmin() {\n if (_msgSender() != automationAdmin) {\n revert PurchaseContract_OnlyAutomationAdmin();\n }\n _;\n }\n\n modifier validAddress(address addr) {\n if (addr == address(0)) {\n revert PurchaseContract_InvalidZeroAddress();\n }\n _;\n }\n\n modifier validAmount(uint256 amount) {\n if (amount == 0) {\n revert PurchaseContract_InvalidAmount();\n }\n _;\n }\n\n // ============================================\n // CONSTRUCTOR\n // ============================================\n\n constructor(\n address contractAdmin_,\n address trustedForwarder_,\n address tokenContract_,\n address interestPayment_,\n address paymentToken_\n ) \n Ownable(contractAdmin_)\n ERC2771Context(trustedForwarder_)\n validAddress(contractAdmin_)\n validAddress(trustedForwarder_)\n validAddress(tokenContract_)\n validAddress(paymentToken_)\n {\n if (interestPayment_ != address(0)) {\n interestPayment = IInterestPayment(interestPayment_);\n if (interestPayment.paymentToken() != paymentToken_) {\n revert PurchaseContract_InvalidPaymentToken();\n }\n if (interestPayment.restrictedLockupToken() != tokenContract_) {\n revert PurchaseContract_InvalidTokenContract();\n }\n }\n // Note: interestPayment can be address(0) if contract is deployed without interest functionality\n \n paymentToken = IERC20(paymentToken_);\n tokenContract = RestrictedLockupToken(tokenContract_);\n }\n\n // ============================================\n // ADMIN FUNCTIONS\n // ============================================\n\n /**\n * @notice Update automation admin address\n * @param newAutomationAdmin_ New automation admin address\n */\n function updateAutomationAdmin(address newAutomationAdmin_) \n external \n onlyOwner \n validAddress(newAutomationAdmin_) \n {\n address oldAdmin = automationAdmin;\n automationAdmin = newAutomationAdmin_;\n emit AutomationAdminUpdated(oldAdmin, newAutomationAdmin_);\n }\n\n /**\n * @notice Update admin fee wallet address\n * @param newAdminFeeWallet_ New admin fee wallet address\n */\n function updateAdminFeeWallet(address newAdminFeeWallet_) \n external \n onlyOwner \n validAddress(newAdminFeeWallet_) \n {\n address oldWallet = adminFeeWallet;\n adminFeeWallet = newAdminFeeWallet_;\n emit AdminFeeWalletUpdated(oldWallet, newAdminFeeWallet_);\n }\n\n /**\n * @notice Update canceled purchase wallet address\n * @param newCanceledPurchaseWallet_ New canceled purchase wallet address\n */\n function updateCanceledPurchaseWallet(address newCanceledPurchaseWallet_) \n external \n onlyOwner \n validAddress(newCanceledPurchaseWallet_) \n {\n address oldWallet = canceledPurchaseWallet;\n canceledPurchaseWallet = newCanceledPurchaseWallet_;\n emit CanceledPurchaseWalletUpdated(oldWallet, newCanceledPurchaseWallet_);\n }\n\n /**\n * @notice Update originator payment wallet address\n * @param newOriginatorPaymentWallet_ New originator payment wallet address\n */\n function updateOriginatorPaymentWallet(address newOriginatorPaymentWallet_) \n external \n onlyOwner \n validAddress(newOriginatorPaymentWallet_) \n {\n address oldWallet = originatorPaymentWallet;\n originatorPaymentWallet = newOriginatorPaymentWallet_;\n emit OriginatorPaymentWalletUpdated(oldWallet, newOriginatorPaymentWallet_);\n }\n\n // ============================================\n // PURCHASE FUNCTIONS\n // ============================================\n\n struct PurchaseParams {\n string purchaseId;\n address payerAddress;\n address tokenRecipientAddress;\n uint256 originatorPurchaseAmount;\n uint256 mintAmount;\n uint256 adminFeeAmount;\n uint256 totalAmount;\n }\n\n struct PurchaseParamsWithInterest {\n string purchaseId;\n address payerAddress;\n address tokenRecipientAddress;\n uint256 mintAmount;\n uint256 originatorPurchaseAmount;\n uint256 prefundedInterestAmount;\n uint256 adminFeeExcludingPrefundedInterestAmount;\n uint256 totalAmount;\n }\n\n /**\n * @notice Execute a token purchase with USDC payment and interest funding\n * @param params Purchase parameters\n */\n function executePurchaseWithInterest(\n PurchaseParamsWithInterest memory params\n ) \n external \n nonReentrant \n onlyAutomationAdmin\n validAddress(params.payerAddress)\n validAddress(params.tokenRecipientAddress)\n {\n // Validation: Ensure interest payment contract is configured when using this function\n if (address(interestPayment) == address(0)) {\n revert PurchaseContract_InterestPaymentNotConfigured();\n }\n // Validation: Check unique purchaseId\n if (usedPurchaseIds[params.purchaseId]) {\n revert PurchaseContract_PurchaseIdAlreadyUsed();\n }\n\n // Validation: Ensure required addresses are set\n if (adminFeeWallet == address(0)) {\n revert PurchaseContract_AdminFeeWalletNotSet();\n }\n if (originatorPaymentWallet == address(0)) {\n revert PurchaseContract_OriginatorPaymentWalletNotSet();\n }\n if (params.mintAmount == 0) {\n revert PurchaseContract_InvalidAmount();\n }\n if (params.totalAmount != params.originatorPurchaseAmount + params.prefundedInterestAmount + params.adminFeeExcludingPrefundedInterestAmount) {\n revert PurchaseContract_InvalidTotalAmount();\n }\n if (!tokenContract.isAmlKycPassed(params.tokenRecipientAddress)) {\n revert PurchaseContract_TokenRecipientAmlKycNotPassed();\n }\n if (!tokenContract.isAmlKycPassed(params.payerAddress)) {\n revert PurchaseContract_PayerAmlKycNotPassed();\n }\n\n // Step 2: Transfer originator amount to originator address\n if (params.originatorPurchaseAmount > 0) {\n paymentToken.safeTransfer(originatorPaymentWallet, params.originatorPurchaseAmount);\n }\n\n // Step 3: Fund interest in InterestPayment contract\n if (params.prefundedInterestAmount > 0) {\n paymentToken.safeIncreaseAllowance(address(interestPayment), params.prefundedInterestAmount);\n interestPayment.fundInterest(params.prefundedInterestAmount);\n }\n\n // Step 4: Transfer admin fee to admin fee wallet\n if (params.adminFeeExcludingPrefundedInterestAmount > 0) {\n paymentToken.safeTransfer(adminFeeWallet, params.adminFeeExcludingPrefundedInterestAmount);\n }\n\n // Step 5: Mint tokens to recipient\n tokenContract.mint(params.tokenRecipientAddress, params.mintAmount);\n\n // Step 6: Mark purchase as completed\n usedPurchaseIds[params.purchaseId] = true;\n\n // Step 7: Emit event\n emit PurchaseExecuted(\n params.purchaseId,\n params.payerAddress,\n params.tokenRecipientAddress,\n _msgSender(),\n params.originatorPurchaseAmount,\n params.prefundedInterestAmount,\n params.adminFeeExcludingPrefundedInterestAmount,\n params.mintAmount,\n originatorPaymentWallet\n );\n }\n\n /**\n * @notice Execute a token purchase with USDC payment (without interest funding)\n * @dev This function can be used when the contract is deployed without an interest payment contract\n * @param params Purchase parameters (prefundedInterestAmount will be ignored)\n */\n function executePurchase(\n PurchaseParams memory params\n ) \n external \n nonReentrant \n onlyAutomationAdmin\n validAddress(params.payerAddress)\n validAddress(params.tokenRecipientAddress)\n {\n // Validation: Check unique purchaseId\n if (usedPurchaseIds[params.purchaseId]) {\n revert PurchaseContract_PurchaseIdAlreadyUsed();\n }\n\n // Validation: Ensure required addresses are set\n if (adminFeeWallet == address(0)) {\n revert PurchaseContract_AdminFeeWalletNotSet();\n }\n if (originatorPaymentWallet == address(0)) {\n revert PurchaseContract_OriginatorPaymentWalletNotSet();\n }\n if (!tokenContract.isAmlKycPassed(params.tokenRecipientAddress)) {\n revert PurchaseContract_TokenRecipientAmlKycNotPassed();\n }\n if (!tokenContract.isAmlKycPassed(params.payerAddress)) {\n revert PurchaseContract_PayerAmlKycNotPassed();\n }\n \n // For executePurchase (without interest), we expect prefundedInterestAmount to be 0\n // and totalAmount to equal originatorPurchaseAmount + adminFeeExcludingPrefundedInterestAmount\n if (params.totalAmount != params.originatorPurchaseAmount + params.adminFeeAmount) {\n revert PurchaseContract_InvalidTotalAmount();\n }\n\n // Step 2: Transfer originator amount to originator address\n if (params.originatorPurchaseAmount > 0) {\n paymentToken.safeTransfer(originatorPaymentWallet, params.originatorPurchaseAmount);\n }\n\n // Step 3: Skip interest funding since no interest payment contract is configured\n // params.prefundedInterestAmount is ignored in this function\n\n // Step 4: Transfer admin fee to admin fee wallet\n if (params.adminFeeAmount > 0) {\n paymentToken.safeTransfer(adminFeeWallet, params.adminFeeAmount);\n }\n\n // Step 5: Mint tokens to recipient\n // When no interest payment contract is configured, we need to calculate mint amount differently\n // For now, we'll use a 1:1 ratio or could use a configurable ratio\n if (params.mintAmount == 0) {\n revert PurchaseContract_InvalidAmount();\n }\n tokenContract.mint(params.tokenRecipientAddress, params.mintAmount);\n\n // Step 6: Mark purchase as completed\n usedPurchaseIds[params.purchaseId] = true;\n\n // Step 7: Emit event (prefundedInterestAmount will be 0 for this function)\n emit PurchaseExecuted(\n params.purchaseId,\n params.payerAddress,\n params.tokenRecipientAddress,\n _msgSender(),\n params.originatorPurchaseAmount,\n 0, // No prefunded interest amount\n params.adminFeeAmount,\n params.mintAmount,\n originatorPaymentWallet\n );\n }\n\n /**\n * @notice Cancel a purchase and transfer amount to canceled purchase wallet\n * @param paymentTokenAddress_ The payment token address to cancel\n * @param amount_ Amount to transfer to canceled purchase wallet\n */\n function cancelPurchase(\n address paymentTokenAddress_,\n uint256 amount_\n ) \n external \n nonReentrant \n onlyAutomationAdmin\n validAmount(amount_)\n {\n // Validation: Ensure canceled purchase wallet is set\n if (canceledPurchaseWallet == address(0)) {\n revert PurchaseContract_InvalidZeroAddress();\n }\n\n // Transfer amount to canceled purchase wallet\n IERC20(paymentTokenAddress_).safeTransfer(canceledPurchaseWallet, amount_);\n\n emit PurchaseCanceled(_msgSender(), paymentTokenAddress_, canceledPurchaseWallet, amount_);\n }\n\n // ============================================\n // VIEW FUNCTIONS\n // ============================================\n\n /**\n * @notice Check if a purchase ID has been used\n * @param purchaseId_ The purchase ID to check\n * @return Whether the purchase ID has been used\n */\n function isPurchaseIdUsed(string calldata purchaseId_) external view returns (bool) {\n return usedPurchaseIds[purchaseId_];\n }\n\n // ============================================\n // ERC2771 OVERRIDES\n // ============================================\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n} "},"contracts/RestrictedLockupToken.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport {IERC165, ERC165} from \"@openzeppelin/contracts/utils/introspection/ERC165.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {BitScan} from \"@solidity-bits/contracts/BitScan.sol\";\nimport {ITransferRules} from \"./interfaces/ITransferRules.sol\";\nimport {ISnapshotPeriods} from \"./interfaces/ISnapshotPeriods.sol\";\nimport {IIdentityRegistry} from \"./interfaces/IIdentityRegistry.sol\";\nimport {IERC1404} from \"./interfaces/IERC1404.sol\";\nimport {SnapshotPeriods} from \"./SnapshotPeriods.sol\";\nimport {VestingMath} from \"./libraries/VestingMath.sol\";\nimport {BitManipulationLib} from \"./libraries/BitManipulationLib.sol\";\nimport {Storage} from \"./Storage.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport {IRestrictedLockupTokenExtension} from \"./interfaces/IRestrictedLockupTokenExtension.sol\";\nimport \"./AccessControlErrors.sol\"; // Import for access to errors\n\n/**\n @title A smart contract for unlocking tokens based on a release schedule with multi-type token support\n @author By CoMakery, Inc., Upside, Republic\n @dev When deployed the contract is as a proxy for a single token that it creates release schedules for\n it implements the ERC20 token interface to integrate with wallets but it is not an independent token.\n The token must implement a burn function.\n Enhanced with multi-type token functionality for different regulatory classifications.\n*/\ncontract RestrictedLockupToken is\n Storage,\n ERC20,\n ReentrancyGuard,\n ERC165,\n ERC2771Context\n{\n using BitScan for uint256;\n using BitManipulationLib for uint256;\n /// @dev Struct to hold constructor parameters to avoid stack too deep issues\n struct ConstructorParams {\n address transferRules;\n address accessControl; // external access control\n address snapshotPeriods;\n address trustedForwarder;\n address identityRegistry;\n address restrictedLockupTokenManagementExtension;\n address restrictedLockupTokenExtension;\n string symbol;\n string name;\n uint8 decimals;\n uint256 maxTotalSupply;\n uint256 minTimelockAmount;\n uint256 maxReleaseDelay;\n bool recordMintTimestamp;\n }\n\n struct TransferParams {\n address from;\n address to;\n IIdentityRegistry.IdentityInfo recipientIdentity;\n bool isAmlKycPassed;\n bool validateRestrictions;\n uint256 remainingAmount;\n uint256 bitmaskToSet;\n uint256 bitmaskToClear;\n }\n\n uint8 private immutable _decimals;\n\n /**\n * @dev Configure deployment for a specific token with release schedule security parameters\n * @dev The symbol should end with \" Unlock\" & be less than 11 characters for MetaMask \"custom token\" compatibility\n */\n constructor(\n ConstructorParams memory params\n )\n ERC20(params.name, params.symbol)\n ERC2771Context(params.trustedForwarder)\n {\n // Restricted Token\n if (bytes(params.name).length == 0) {\n revert RestrictedLockupToken_InvalidName();\n }\n if (bytes(params.symbol).length == 0) {\n revert RestrictedLockupToken_InvalidSymbol();\n }\n if (params.transferRules == address(0)) {\n revert RestrictedLockupToken_InvalidTransferRules();\n }\n if (params.accessControl == address(0)) {\n revert RestrictedLockupToken_InvalidAccessControl();\n }\n if (params.trustedForwarder == address(0)) {\n revert RestrictedLockupToken_InvalidTrustedForwarder();\n }\n if (params.identityRegistry == address(0)) {\n revert RestrictedLockupToken_InvalidIdentityRegistry();\n }\n if (params.restrictedLockupTokenManagementExtension == address(0)) {\n revert RestrictedLockupToken_InvalidRestrictedLockupTokenManagementExtension();\n }\n if (params.restrictedLockupTokenExtension == address(0)) {\n revert RestrictedLockupToken_InvalidRestrictedLockupTokenExtension();\n }\n if (params.snapshotPeriods == address(0)) {\n snapshotsEnabled = false;\n } else {\n snapshotsEnabled = true;\n }\n // Token Lockup\n if (params.minTimelockAmount == 0) {\n revert RestrictedLockupToken_InvalidMinTimelockAmount();\n }\n if (params.maxTotalSupply > _maxSafeSupply()) {\n revert RestrictedLockupToken_MaxTotalSupplyTooLarge();\n }\n // Transfer rules can be swapped out for a new contract inheriting from the ITransferRules interface\n transferRules = ITransferRules(params.transferRules);\n snapshotPeriods = ISnapshotPeriods(params.snapshotPeriods);\n identityRegistry = IIdentityRegistry(params.identityRegistry);\n accessControl = IAccessControl(params.accessControl);\n restrictedLockupTokenManagementExtension = params.restrictedLockupTokenManagementExtension;\n restrictedLockupTokenExtension = params.restrictedLockupTokenExtension;\n\n maxTotalSupply = params.maxTotalSupply;\n _decimals = params.decimals;\n\n minTimelockAmount = params.minTimelockAmount;\n maxReleaseDelay = params.maxReleaseDelay;\n recordMintTimestamp = params.recordMintTimestamp;\n deploymentDay = VestingMath.toMidnightTimestamp(block.timestamp);\n \n // Calculate and set immutable slotsPerWord based on maxTotalSupply (already in basis units)\n slotsPerWord = BitManipulationLib.calculateSlotsPerWord(maxTotalSupply);\n uint256 slotsPerWordExtension = IRestrictedLockupTokenExtension(restrictedLockupTokenExtension).slotsPerWord();\n if (slotsPerWordExtension != slotsPerWord) {\n revert RestrictedLockupToken_ExtensionSlotsPerWordMismatch(slotsPerWord, slotsPerWordExtension);\n }\n slotsPerWordExtension = IRestrictedLockupTokenExtension(restrictedLockupTokenManagementExtension).slotsPerWord();\n if (slotsPerWordExtension != slotsPerWord) {\n revert RestrictedLockupToken_ExtensionSlotsPerWordMismatch(slotsPerWord, slotsPerWordExtension);\n }\n maxBalancePerSubIndex = BitManipulationLib.calculateMaxBalancePerSubIndex(slotsPerWord);\n elementBitSize = BitManipulationLib.calculateElementBitSize(slotsPerWord);\n \n // Initialize delegated function selectors\n _initializeDelegatedSelectors();\n }\n\n // ============================================\n // FALLBACK PATTERN FOR HOLDER MANAGEMENT\n // ============================================\n\n /// @dev Function selectors that should be delegated to the management extension\n mapping(bytes4 => bool) private _delegatedToManagementExtension;\n /// @dev Function selectors that should be delegated to the extension\n mapping(bytes4 => bool) private _delegatedToExtension;\n \n /**\n * @dev Initialize delegated function selectors in constructor\n * This is done in a separate function to avoid constructor size issues\n */\n function _initializeDelegatedSelectors() private {\n // Management Extension - Holder management functions\n _delegatedToManagementExtension[bytes4(keccak256(\"createHolderFromAddress(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"appendHolderAddress(address,uint256)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"addHolderWithAddresses(address[])\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"removeHolder(uint256)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"removeWalletFromHolder(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"batchRemoveWalletFromHolder(address[])\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"getHolderAddresses(uint256)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"getHolderId(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"addressHasHolder(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"holderExists(uint256)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"setHolderMax(uint256)\"))] = true;\n\n // Management Extension - Admin functions\n _delegatedToManagementExtension[bytes4(keccak256(\"pause(bool)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"freeze(address,bool)\"))] = true;\n\n // Extension - Release schedule functions\n _delegatedToExtension[bytes4(keccak256(\"createReleaseSchedule(uint256,uint256,uint256,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"fundReleaseScheduleWithHolding((address,uint256,uint256,uint256,uint256),address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"fundReleaseSchedule(address,uint256,uint256,uint256,address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"batchFundReleaseScheduleWithHolding((address,uint256,uint256,uint256,uint256)[],address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"batchFundReleaseSchedule(address[],uint256[],uint256[],uint256[],address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"mintReleaseScheduleTokenType(address,uint256,uint256,uint256,uint256,address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"mintReleaseSchedule(address,uint256,uint256,uint256,address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"batchMintReleaseScheduleTokenType(address[],uint256[],uint256[],uint256[],uint256[],address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"batchMintReleaseSchedule(address[],uint256[],uint256[],uint256[],address[])\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"updateTimelockTokenType(address,uint256,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"findHoldingFor(address,address,uint256)\"))] = true;\n\n // Extension - Burn functions, safe appove, max total supply and holding management\n _delegatedToExtension[bytes4(keccak256(\"burn(address,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"softBurn(address,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"burnHolding(address,uint256,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"updateHoldingTokenType(address,uint256,uint256,uint256,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"safeApprove(address,uint256)\"))] = true;\n _delegatedToExtension[bytes4(keccak256(\"setMaxTotalSupply(uint256)\"))] = true;\n\n // Management Extension - Holding view functions\n _delegatedToManagementExtension[bytes4(keccak256(\"holdingCountOf(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"holdingOf(address,uint256)\"))] = true;\n // Management Extension - Upgrade functions\n _delegatedToManagementExtension[bytes4(keccak256(\"upgradeTransferRules(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"upgradeIdentityRegistry(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"getFrozenStatus(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"isValidTransferRules(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"isValidIdentityRegistry(address)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"existingTokenTypesCount()\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"existingTokenTypes(uint256)\"))] = true;\n _delegatedToManagementExtension[bytes4(keccak256(\"tokenTypeExists(uint256)\"))] = true;\n }\n \n /**\n * @dev Fallback function that delegates calls to appropriate extension\n */\n fallback() external {\n bytes4 selector = msg.sig;\n address extension;\n \n if (_delegatedToManagementExtension[selector]) {\n extension = restrictedLockupTokenManagementExtension;\n } else if (_delegatedToExtension[selector]) {\n extension = restrictedLockupTokenExtension;\n } else {\n revert(\"Function not found\");\n }\n \n assembly (\"memory-safe\") {\n // Copy calldata to memory\n calldatacopy(0, 0, calldatasize())\n \n // Delegatecall to extension\n let result := delegatecall(gas(), extension, 0, calldatasize(), 0, 0)\n \n // Copy return data\n returndatacopy(0, 0, returndatasize())\n \n // Return or revert based on result\n switch result\n case 0 { revert(0, returndatasize()) }\n default { return(0, returndatasize()) }\n }\n }\n\n /**\n * Support of ERC165\n * @dev See https://eips.ethereum.org/EIPS/eip-165\n * @param interfaceId_ The interface identifier, as specified in ERC-165\n */\n function supportsInterface(\n bytes4 interfaceId_\n ) public view override(ERC165) returns (bool) {\n return\n interfaceId_ == type(IERC1404).interfaceId ||\n super.supportsInterface(interfaceId_);\n }\n\n /**\n * @dev get circulating token supply, ie tokens that have already been minted and are in circulation. Akin to Outstanding, Issued shares.\n * @return _circulatingTokenSupply total circulating token supply.\n * @notice circulatingTokenSupply() == totalSupply() == \"outstanding shares\" == \"issued shares\"\n */\n function circulatingTokenSupply()\n public\n view\n returns (uint256 _circulatingTokenSupply)\n {\n _circulatingTokenSupply = totalSupply();\n }\n\n /**\n * @dev get unissued token supply, ie tokens that can still be minted. Akin to Unissued shares.\n * @return _unissuedTokenSupply total unissued token supply.\n */\n function unissuedTokenSupply()\n public\n view\n returns (uint256 _unissuedTokenSupply)\n {\n _unissuedTokenSupply = maxTotalSupply - circulatingTokenSupply();\n }\n\n /**\n * @dev Calls the TransferRules detectTransferRetriction function to determine if tokens can be transferred.\n * detectTransferRestriction returns a status code. ERC-1404 standard.\n * @param from_ The address the tokens are transferred from\n * @param to_ The address the tokens would be transferred to\n * @param value_ The quantity of tokens to be transferred\n */\n function detectTransferRestriction(\n address from_,\n address to_,\n uint256 value_\n ) public view returns (uint256) {\n return\n transferRules.detectTransferRestriction(\n address(this),\n from_,\n to_,\n value_\n );\n }\n\n /**\n * @dev Calls the TransferRules checkTransferAllowed function to determine if tokens of a specific type can be transferred to a recipient.\n * This function checks transfer restrictions based on token type, mint timestamp, and recipient address.\n * @param tokenType_ The type of token being transferred\n * @param mintTimestamp_ The timestamp when the tokens were originally minted\n * @param recipientIdentity The identity of the recipient\n * @param isAmlKycPassed_ Whether the recipient has passed AML/KYC\n * @return uint256 A status code indicating if the transfer is allowed (0 = allowed, non-zero = restricted)\n */\n function detectTransferRestrictionFor(\n uint256 tokenType_,\n uint256 mintTimestamp_,\n IIdentityRegistry.IdentityInfo memory recipientIdentity,\n bool isAmlKycPassed_\n ) public view returns (uint256) {\n return transferRules.detectTransferRestrictionForHolding(\n tokenType_,\n mintTimestamp_,\n recipientIdentity,\n isAmlKycPassed_\n );\n }\n\n /**\n * @dev Calls TransferRules to look up a human readable error message for a given error code.\n * This is the original implementation that accepts a uint256 parameter; the uint8 overload below provides ERC-1404 compliance.\n * @param restrictionCode_ is an error code to lookup an error code for\n * @return a human readable error message\n */\n function messageForTransferRestriction(\n uint256 restrictionCode_\n ) public view returns (string memory) {\n return transferRules.messageForTransferRestriction(restrictionCode_);\n }\n\n /**\n * @dev Calls TransferRules to lookup a human readable error message that goes with an error code. ERC-1404 standard.\n * @param restrictionCode_ is an error code to lookup an error code for, u8 for ERC-1404 standard compatibility\n * @return a human readable error message\n */\n function messageForTransferRestriction(\n uint8 restrictionCode_\n ) public view returns (string memory) {\n return transferRules.messageForTransferRestriction(uint256(restrictionCode_));\n }\n\n /**\n * @notice Balance of simple ERC20 tokens without any timelocks for a given address\n * @param who_ Address to calculate\n * @return amount The amount of simple ERC20 tokens available. See token.balanceOf\n **/\n function superBalanceOf(address who_) public view returns (uint256) {\n return\n balanceOf(who_) - unlockedBalanceOf(who_) - lockedBalanceOf(who_);\n }\n\n /**\n * @notice Total unlocked balance for a given address - 1) balance of simple ERC20 tokens without any timelocks (superBalanceOf) + 2) unlocked tokens that remain (unlockedBalanceOf)\n * @param who_ Address to calculate\n * @return amount The unlocked total balance of\n **/\n function unlockedTotalBalanceOf(\n address who_\n ) public view returns (uint256) {\n return balanceOf(who_) - lockedBalanceOf(who_);\n }\n\n /**\n * @notice Total locked balance for a given address across all timelocks\n * @param who_ Address to calculate\n * @return amount_\n */\n function lockedBalanceOf(\n address who_\n ) public view returns (uint256 amount_) {\n uint256 _timelockCountOf = timelockCountOf(who_);\n for (uint256 i; i < _timelockCountOf; ++i) {\n amount_ += lockedBalanceOfTimelock(who_, i);\n }\n }\n\n /**\n * @dev Override ERC20 transferFrom to enforce transfer restrictions\n * @param from_ is the address to transfer from\n * @param to_ is the address to transfer to\n * @return true if transfer is successful\n */\n function transferFrom(\n address from_,\n address to_,\n uint256 amount_\n ) public virtual override returns (bool) {\n _enforceTransferRestrictions(from_, to_, amount_);\n uint256 transferredAmount = _updateTimelock(from_, to_, amount_, true);\n _handleMultiTypeTransfer(from_, to_, amount_ - transferredAmount, true);\n super.transferFrom(from_, to_, amount_);\n\n return true;\n }\n\n /**\n * @notice Total unlocked balance remaining for a given address across all timelocks\n * @param who_ Address to calculate\n * @return amount_\n */\n function unlockedBalanceOf(\n address who_\n ) public view returns (uint256 amount_) {\n uint256 _timelockCountOf = timelockCountOf(who_);\n for (uint256 i; i < _timelockCountOf; ++i) {\n amount_ += unlockedBalanceOfTimelock(who_, i);\n }\n }\n\n /**\n * @dev get token decimals\n * @return decimals\n */\n function decimals() public view virtual override returns (uint8) {\n return _decimals;\n }\n\n /**\n * @notice calculates how many tokens would be released at a specified time for a scheduleId.\n This is independent of any specific address or address's timelock.\n * @param commencedTimestamp_ the commencement time to use in the calculation for the scheduled\n * @param currentTimestamp_ the timestamp to calculate unlocked tokens for\n * @param amount_ the amount of tokens\n * @param scheduleId_ the schedule id used to calculate the unlocked amount\n * @return _unlocked the total amount unlocked for the schedule given the other parameters\n */\n function calculateUnlocked(\n uint256 commencedTimestamp_,\n uint256 currentTimestamp_,\n uint256 amount_,\n uint256 scheduleId_\n ) public view returns (uint256) {\n return\n calculateUnlocked(\n commencedTimestamp_,\n currentTimestamp_,\n amount_,\n releaseSchedules[scheduleId_]\n );\n }\n\n /**\n * @notice returns the total count of timelocks for a specific address\n * @param who_ the address to get the timelock count for\n * @return number of timelocks\n */\n function timelockCountOf(address who_) public view returns (uint256) {\n return timelocks[who_].length;\n }\n\n /**\n * @notice Get The locked balance for a specific address and specific timelock\n * @param who_ The address to check\n * @param timelockIndex_ Specific timelock belonging to the who address\n * @return _locked Balance of the timelock\n */\n function lockedBalanceOfTimelock(\n address who_,\n uint256 timelockIndex_\n ) public view returns (uint256 _locked) {\n Timelock memory _timelock = timelockOf(who_, timelockIndex_);\n if (_timelock.totalAmount > _timelock.tokensTransferred) {\n _locked =\n _timelock.totalAmount -\n totalUnlockedToDateOfTimelock(who_, timelockIndex_);\n }\n }\n\n /**\n * @notice Get the unlocked balance for a specific address and specific timelock\n * @param who_ the address to check\n * @param timelockIndex_ for a specific timelock belonging to the who address\n * @return _unlocked balance of the timelock\n */\n function unlockedBalanceOfTimelock(\n address who_,\n uint256 timelockIndex_\n ) public view returns (uint256 _unlocked) {\n Timelock memory _timelock = timelockOf(who_, timelockIndex_);\n if (_timelock.totalAmount > _timelock.tokensTransferred) {\n _unlocked =\n totalUnlockedToDateOfTimelock(who_, timelockIndex_) -\n _timelock.tokensTransferred;\n }\n }\n\n /**\n * @notice Gets the total locked and unlocked balance of a specific address's timelocks to the current block timestamp\n * @param who_ The address to check\n * @param timelockIndex_ The index of the timelock for the who address\n * @return Locked and unlocked amount for the specified timelock\n */\n function totalUnlockedToDateOfTimelock(\n address who_,\n uint256 timelockIndex_\n ) public view returns (uint256) {\n Timelock memory _timelock = timelockOf(who_, timelockIndex_);\n\n return\n calculateUnlocked(\n _timelock.commencementTimestamp,\n block.timestamp,\n _timelock.totalAmount,\n _timelock.scheduleId\n );\n }\n\n /**\n * @notice Get the struct details for an address's specific timelock\n * @param who_ Address to check\n * @param index_ The index of the timelock for the who address\n * @return Struct with the attributes of the timelock\n */\n function timelockOf(\n address who_,\n uint256 index_\n ) public view returns (Timelock memory) {\n return timelocks[who_][index_];\n }\n\n /**\n * @notice calculates how many tokens would be released at a specified time for a ReleaseSchedule struct.\n This is independent of any specific address or address's timelock.\n\n * @param commencedTimestamp_ the commencement time to use in the calculation for the scheduled\n * @param currentTimestamp_ the timestamp to calculate unlocked tokens for\n * @param amount_ the amount of tokens\n * @param releaseSchedule_ a ReleaseSchedule struct used to calculate the unlocked amount\n * @return the total amount unlocked for the schedule given the other parameters\n */\n function calculateUnlocked(\n uint256 commencedTimestamp_,\n uint256 currentTimestamp_,\n uint256 amount_,\n ReleaseSchedule memory releaseSchedule_\n ) public pure returns (uint256) {\n return VestingMath.calculateUnlocked(\n commencedTimestamp_,\n currentTimestamp_,\n amount_,\n releaseSchedule_.releaseCount,\n releaseSchedule_.delayUntilFirstReleaseInSeconds,\n releaseSchedule_.initialReleasePortionInBips,\n releaseSchedule_.periodBetweenReleasesInSeconds\n );\n }\n\n /**\n * @notice ERC20 standard interface function\n * Provide controls of Restricted and Lockup tokens\n * Can transfer simple ERC20 tokens and unlocked tokens at the same time\n * First will transfer unlocked tokens and then simple ERC20\n * @param to_ recipient of transfer\n * @param amount_ amount of tokens to transfer\n * @return bool. True on success / Reverted on error\n */\n function transfer(\n address to_,\n uint256 amount_\n ) public virtual override returns (bool) {\n _enforceTransferRestrictions(_msgSender(), to_, amount_);\n return _transfer(to_, amount_);\n }\n\n /**\n * @dev Transfer a holding of a specific token type\n * @param to recipient of transfer\n * @param amount amount of tokens to transfer\n * @param globalHoldingIdx holding index\n * @return bool. True on success / Reverted on error\n */\n function transferHolding(\n address to,\n uint256 amount,\n uint256 globalHoldingIdx\n ) external returns (bool) {\n if (amount == 0) {\n revert RestrictedLockupToken_InvalidAmount();\n }\n if (globalHoldingIdx >= mintTimestampCount) {\n revert RestrictedLockupToken_HoldingIndexOutOfBound();\n }\n\n address _sender = _msgSender();\n // Get real balance (considering overflow)\n uint256 senderBalance = _getRealBalance(_sender, globalHoldingIdx);\n if (amount > senderBalance) {\n revert RestrictedLockupToken_NoItemWithEnoughBalance();\n }\n\n _enforceTransferRestrictions(_sender, to, amount);\n _processHoldingTransfer(\n _sender,\n to,\n amount,\n globalHoldingIdx,\n senderBalance\n );\n super._transfer(_sender, to, amount);\n\n return true;\n }\n\n /**\n * @dev Internal function to process holding transfer (reduces stack depth)\n */\n function _processHoldingTransfer(\n address sender,\n address to,\n uint256 amount,\n uint256 globalHoldingIdx,\n uint256 senderBalance\n ) internal {\n uint256 bucketIndex = globalHoldingIdx / slotsPerWord;\n uint256 subIndex = globalHoldingIdx % slotsPerWord;\n uint256 element = BitManipulationLib.getPackedElement(globalMintTimestamps[bucketIndex], subIndex, elementBitSize);\n (uint256 tokenType, uint256 daysAfterDeployment) = BitManipulationLib.unpackElement(element);\n\n _enforceTransferRestrictionsFor(tokenType, BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay), to);\n _executeTransferFromHolding(\n sender,\n to,\n tokenType,\n daysAfterDeployment,\n globalHoldingIdx,\n senderBalance,\n amount\n );\n }\n\n /**\n * @dev Allows the reserve admin to create new tokens in a specified address.\n * The total number of tokens cannot exceed the maxTotalSupply (the \"Hard Cap\").\n * Automatically determines the appropriate token type based on recipient's identity.\n * @param to_ The addres to mint tokens into.\n * @param value_ The number of tokens to mint.\n */\n function mint(\n address to_,\n uint256 value_\n ) public validAddress(to_) onlyReserveOrMintAdmin {\n // Determine token type based on recipient's identity\n uint256 _mintTokenType = transferRules.determineTokenType(to_, identityRegistry);\n _mintWithType(to_, value_, _mintTokenType);\n }\n\n /**\n * @dev batchTransfer tokens\n */\n function batchTransfer(\n address[] calldata recipients,\n uint256[] calldata amounts\n ) external returns (bool) {\n uint256 _recipientsLength = recipients.length;\n if (_recipientsLength != amounts.length) {\n revert RestrictedLockupToken_RecipientAndAmountLengthsShouldMatch();\n }\n for (uint256 i; i < _recipientsLength; ++i) {\n transfer(recipients[i], amounts[i]);\n }\n\n return true;\n }\n\n /**\n * @dev Transfer tokens from one address to another with ignoring of transfer rules\n * With reserve admin access only\n * @param from_ sender address\n * @param to_ recipient address\n * @param amount_ amount of tokens\n */\n function forceTransferBetween(\n address from_,\n address to_,\n uint256 amount_\n ) external onlyReserveAdmin onlyExistingAddress(to_) {\n if (from_ == to_) {\n revert RestrictedLockupToken_SenderCannotBeRecipient(from_);\n }\n if (from_ == address(0)) {\n revert RestrictedLockupToken_InvalidZeroAddress();\n }\n if (amount_ == 0) {\n revert RestrictedLockupToken_InvalidAmount();\n }\n\n uint256 transferredAmount = _updateTimelock(from_, to_, amount_, false);\n _handleMultiTypeTransfer(from_, to_, amount_ - transferredAmount, false);\n\n super._transfer(from_, to_, amount_);\n emit ForceTransferBetween(_msgSender(), from_, to_, amount_);\n }\n\n /**\n * @notice the total number of schedules that have been created\n * @return count of schedules\n */\n function scheduleCount() external view returns (uint256 count) {\n return releaseSchedules.length;\n }\n\n /**\n * @notice Check the total remaining balance of a timelock including the locked and unlocked portions\n * @param who_ the address to check\n * @param timelockIndex_ Specific timelock belonging to the who address\n * @return total remaining balance of a timelock\n */\n function balanceOfTimelock(\n address who_,\n uint256 timelockIndex_\n ) external view returns (uint256) {\n Timelock memory _timelock = timelockOf(who_, timelockIndex_);\n if (_timelock.totalAmount <= _timelock.tokensTransferred) {\n return 0;\n } else {\n return _timelock.totalAmount - _timelock.tokensTransferred;\n }\n }\n\n /**\n * @notice Cancel a cancelable timelock created by the fundReleaseSchedule, fundReleaseScheduleWithHolding, mintReleaseSchedule, or mintReleaseScheduleTokenType functions.\n If canceled the tokens that are locked at the time of the cancellation will be returned to the funder\n and unlocked tokens will be transferred to the recipient.\n * @param target_ The initial recipient address of the timelock which will be cancelled.\n * @param timelockIndex_ timelock index\n * @param scheduleId_ require it matches expected\n * @param commencementTimestamp_ require it matches expected\n * @param totalAmount_ require it matches expected\n * @param reclaimTokenTo_ reclaim token to\n * @return success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function cancelTimelock(\n address target_,\n uint256 timelockIndex_,\n uint256 scheduleId_,\n uint256 commencementTimestamp_,\n uint256 totalAmount_,\n address reclaimTokenTo_\n ) external nonReentrant returns (bool success) {\n if (timelockCountOf(target_) <= timelockIndex_) {\n revert RestrictedLockupToken_InvalidTimelock();\n }\n if (reclaimTokenTo_ == address(0)) {\n revert RestrictedLockupToken_InvalidReclaimTo();\n }\n\n Timelock storage _timelock = timelocks[target_][timelockIndex_];\n\n if (!_canBeCanceled(_timelock)) {\n revert RestrictedLockupToken_TimelockCannotBeCanceled();\n }\n if (_timelock.scheduleId != scheduleId_) {\n revert RestrictedLockupToken_ScheduleIdDoesNotMatch();\n }\n if (_timelock.commencementTimestamp != commencementTimestamp_) {\n revert RestrictedLockupToken_CommencementTimestampDoesNotMatch();\n }\n if (_timelock.totalAmount != totalAmount_) {\n revert RestrictedLockupToken_TotalAmountDoesNotMatch();\n }\n\n uint256 _canceledAmount = lockedBalanceOfTimelock(\n target_,\n timelockIndex_\n );\n\n if (_canceledAmount == 0) {\n revert RestrictedLockupToken_TimelockHasNoValueRemaining();\n }\n\n // Get token type and mint timestamp from global holding index\n (uint256 tokenType, uint256 mintTimestamp) = _getTokenTypeAndMintTimestampFromGlobalIndex(_timelock.globalHoldingIndex);\n \n // Check token type transfer restrictions if mintTimestamp + lockDuration > current time\n // to keep restrictions consistent, we check the restrictions on the reclaimTokenTo address\n // in order to not allow to bypass restrictions by using a different address\n // don't trigger SENDER TOKENS LOCKED - we expect this because the canceled amount is still locked\n _enforceTransferRestrictionsFor(\n tokenType,\n mintTimestamp,\n reclaimTokenTo_\n );\n uint256 _paidAmount = unlockedBalanceOfTimelock(\n target_,\n timelockIndex_\n );\n _timelock.tokensTransferred = _timelock.totalAmount;\n uint256 daysAfterDeployment = BitManipulationLib.calculateDaysAfterDeployment(mintTimestamp, deploymentDay);\n // Add to reclaimer's optimized holdings structure\n _addToHoldingsByIndex(reclaimTokenTo_, _timelock.globalHoldingIndex, _canceledAmount);\n \n // Add to target's optimized holdings structure if there's a paid amount\n if (_paidAmount > 0) {\n _addToHoldingsByIndex(target_, _timelock.globalHoldingIndex, _paidAmount);\n }\n // only canceledAmount must be transferred back from initial target recipient to reclaimTokenTo\n // paid amount (ie already-unlocked tokens) is already initially transferred to the target recipient but locked\n super._transfer(target_, reclaimTokenTo_, _canceledAmount);\n\n emit TimelockCanceled(\n _msgSender(),\n target_,\n timelockIndex_,\n reclaimTokenTo_,\n _canceledAmount,\n _paidAmount\n );\n return true;\n }\n\n /**\n * @notice transfers the unlocked token from an address's specific timelock\n It is typically more convenient to call transfer. But if the account has many timelocks the cost of gas\n for calling transfer may be too high. Calling transferTimelock from a specific timelock limits the transfer cost.\n * @param to_ the address that the tokens will be transferred to\n * @param amount_ the number of token base units to be transferred to the to address\n * @param timelockId_ the specific timelock of the function caller to transfer unlocked tokens from\n * @return bool always true when completed\n */\n function transferTimelock(\n address to_,\n uint256 amount_,\n uint256 timelockId_\n ) external nonReentrant returns (bool) {\n address _sender = _msgSender();\n if (unlockedBalanceOfTimelock(_sender, timelockId_) < amount_) {\n revert RestrictedLockupToken_AmountExceedsUnlockedBalance();\n }\n\n Timelock storage _timelock = timelocks[_sender][timelockId_];\n \n // Get token type and mint timestamp from global holding index\n (uint256 tokenType, uint256 mintTimestamp) = _getTokenTypeAndMintTimestampFromGlobalIndex(_timelock.globalHoldingIndex);\n \n // Check token type transfer restrictions if mintTimestamp + lockDuration > current time\n _enforceTransferRestrictionsFor(\n tokenType,\n mintTimestamp,\n to_\n );\n _enforceTransferRestrictions(_sender, to_, amount_);\n\n // Update timelock state\n _timelock.tokensTransferred += amount_;\n\n // Add to recipient's optimized holdings structure\n _addToHoldingsByIndex(\n to_,\n _timelock.globalHoldingIndex,\n amount_\n );\n\n // Perform standard ERC20 transfer\n super._transfer(_sender, to_, amount_);\n\n // Emit token type transfer event\n emit TokenTypeTransferred(_sender, to_, amount_, tokenType);\n\n return true;\n }\n\n /**\n * @dev update hook\n * @notice During a burn, this is invoked by the _burn function (ERC20.sol) which artificially sets `to` to the 0x0 address. Tokens are NOT actually transferred to 0x0 during a burn.\n * @param from_ from address\n * @param to_ to address\n * @param amount_ amount\n */\n function _update(\n address from_,\n address to_,\n uint256 amount_\n ) internal virtual override {\n if (snapshotsEnabled) {\n snapshotPeriods.onUpdate(address(this), from_, to_, amount_);\n }\n super._update(from_, to_, amount_); // Call parent hook\n\n /// @notice when to is 0x0, it's a burn (note - 0x0 is not actually transferred any tokens during burn)\n /// do NOT create a holder in that case\n if (!_addressHasHolder(to_) && to_ != address(0)) {\n _createHolderFromAddress(to_);\n }\n }\n\n /**\n * @notice Check if timelock can be cancelable by _msgSender()\n * @param timelock_ Timelock struct to check for cancelation\n */\n function _canBeCanceled(\n Timelock storage timelock_\n ) private view returns (bool) {\n uint256 _len = timelock_.cancelableBy.length;\n for (uint256 i; i < _len; ++i) {\n if (_msgSender() == timelock_.cancelableBy[i]) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @notice Update the timelock state to account for unlocked tokens that are transferred out\n * @param from Address from\n * @param to Address to (for token type restriction checking)\n * @param amount_ Amount of tokens to transfer, includes unlocked tokens still within timelock, and simple tokens\n * @return _unlockedAmount Amount of unlocked tokens that are transferred out (does NOT include simple tokens)\n */\n function _updateTimelock(\n address from,\n address to,\n uint256 amount_,\n bool validateRestrictions\n ) private returns (uint256 _unlockedAmount) {\n if (amount_ == 0) {\n return 0;\n }\n uint256 _simpleTokenBalance = amount_;\n uint256 lockedBalance = 0;\n IIdentityRegistry.IdentityInfo memory recipientIdentity = identityRegistry.identity(to);\n bool isAmlKycPassed_ = identityRegistry.isAmlKycPassed(to);\n\n /// @notice transfer from unlocked tokens\n for (uint256 i; i < timelockCountOf(from); ++i) {\n /// @notice if the timelock has no value left\n if (\n timelocks[from][i].tokensTransferred ==\n timelocks[from][i].totalAmount\n ) {\n continue;\n } else {\n uint256 _unlockedBalanceOfTimelock = unlockedBalanceOfTimelock(\n from,\n i\n );\n if (_unlockedBalanceOfTimelock == 0) {\n continue;\n }\n lockedBalance += lockedBalanceOfTimelock(from, i);\n \n if (validateRestrictions) {\n // Check token type transfer restrictions for timelock tokens\n Timelock memory _timelock = timelocks[from][i];\n (uint256 tokenType, uint256 mintTimestamp) = _getTokenTypeAndMintTimestampFromGlobalIndex(_timelock.globalHoldingIndex);\n uint256 restrictionCode = detectTransferRestrictionFor(\n tokenType,\n mintTimestamp,\n recipientIdentity,\n isAmlKycPassed_\n );\n \n if (restrictionCode != 0) {\n continue; // skip this timelock because it's not allowed to transfer\n }\n }\n \n if (_simpleTokenBalance > _unlockedBalanceOfTimelock) {\n _simpleTokenBalance -= _unlockedBalanceOfTimelock;\n timelocks[from][i]\n .tokensTransferred += _unlockedBalanceOfTimelock;\n \n // Add to recipient's optimized holdings structure\n Timelock memory _timelock = timelocks[from][i];\n (uint256 tokenType,) = _getTokenTypeAndMintTimestampFromGlobalIndex(_timelock.globalHoldingIndex);\n _addToHoldingsByIndex(\n to,\n _timelock.globalHoldingIndex,\n _unlockedBalanceOfTimelock\n );\n \n emit TokenTypeTransferred(from, to, _unlockedBalanceOfTimelock, tokenType);\n } else {\n timelocks[from][i].tokensTransferred += _simpleTokenBalance;\n \n // Add to recipient's optimized holdings structure\n Timelock memory _timelock = timelocks[from][i];\n (uint256 tokenType,) = _getTokenTypeAndMintTimestampFromGlobalIndex(_timelock.globalHoldingIndex);\n _addToHoldingsByIndex(\n to,\n _timelock.globalHoldingIndex,\n _simpleTokenBalance\n );\n \n emit TokenTypeTransferred(from, to, _simpleTokenBalance, tokenType);\n \n _simpleTokenBalance = 0;\n break;\n }\n }\n }\n // validate that we have enough total unlocked balance to transfer if we have any unlocked tokens left\n // balanceOf includes 1) locked, 2) unlocked, and 3) simple tokens\n // check if the amount to transfer is <= the total transferrable balance (unlocked + simple)\n if ((balanceOf(from) - lockedBalance) < amount_) {\n // revert RestrictedLockupToken_InvalidAmount();\n revert RestrictedLockupToken_InsufficientTotalBalanceOf();\n }\n // by the end, _simpleTokenBalance MUST be LTE the amount because unlocked amount is the difference\n _unlockedAmount = amount_ - _simpleTokenBalance;\n }\n\n /**\n * @param to address to send to\n * @param amount amount to send\n */\n function _transfer(address to, uint256 amount) private returns (bool) {\n address _sender = _msgSender();\n uint256 transferredAmount = _updateTimelock(_sender, to, amount, true);\n _handleMultiTypeTransfer(_sender, to, amount - transferredAmount, true);\n\n super._transfer(_sender, to, amount);\n return true;\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context, Storage)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n\n function snapshotPeriodsAddress() external view returns (address) {\n return address(snapshotPeriods);\n }\n\n function isAmlKycPassed(address wallet) external view returns (bool) {\n return identityRegistry.isAmlKycPassed(wallet);\n }\n\n function determineTokenType(address wallet) external view returns (uint256) {\n return transferRules.determineTokenType(wallet, identityRegistry);\n }\n\n /**\n * @dev Get total balance across all token types for an address\n * @param account The address to query\n * @return balance The total balance across all token types\n */\n function totalBalanceOf(address account) external view returns (uint256 balance) {\n return balanceOf(account); // Use standard ERC20 balanceOf which is kept in sync\n }\n\n /**\n * @dev Mint tokens of a specific type to an address\n * @param to The address to mint to\n * @param amount The amount to mint\n * @param tokenType The type of token to mint (0-255, uses 8 bits)\n */\n function mintTokenType(address to, uint256 amount, uint256 tokenType) external onlyReserveOrMintAdmin {\n // Verify tokenType is within 8-bit limit (0-255)\n if (tokenType > 255) {\n revert RestrictedLockupToken_InvalidTokenType();\n }\n \n // Verify if the specified token type is allowed for the recipient\n if (!transferRules.tokenTypeAllowed(tokenType, to, identityRegistry)) {\n revert RestrictedLockupToken_TokenTypeNotAllowedForRecipient();\n }\n _mintWithType(to, amount, tokenType);\n }\n\n /**\n * @dev Internal function to mint tokens with a specific type\n */\n function _mintWithType(address to, uint256 amount, uint256 tokenType) internal {\n if (amount == 0) {\n revert RestrictedLockupToken_InvalidZeroAmount();\n }\n if (totalSupply() + amount > maxTotalSupply) {\n revert RestrictedLockupToken_CannotExceedMaxTotalSupply();\n }\n\n uint256 dayMintTimestamp = recordMintTimestamp ? \n VestingMath.toMidnightTimestamp(block.timestamp) : \n BitManipulationLib.daysSinceDeployToTimestamp(recordPausedOnDaysAfterDeployment, deploymentDay);\n\n // Add to optimized holdings structure\n _addToHoldingsByType(\n to,\n tokenType,\n BitManipulationLib.calculateDaysAfterDeployment(dayMintTimestamp, deploymentDay),\n amount\n );\n\n // Perform standard ERC20 mint to maintain compatibility\n _mint(to, amount);\n\n emit TokenTypeMinted(to, amount, tokenType, dayMintTimestamp);\n }\n\n /**\n * @dev Process transfers for a single token type with cached identity info (optimized direct bitmap iteration)\n * @dev Gas optimization: Split into helper functions to avoid stack too deep, no pre-allocation\n */\n function _processTransfers(\n address from,\n address to,\n uint256 remainingAmount,\n bool validateRestrictions\n ) internal returns (uint256) {\n // Cache identity information outside the loop to save gas\n uint256 walletIndexesLength = walletIndexesByType[from].length;\n TransferParams memory transferParams = TransferParams({\n from: from,\n to: to,\n recipientIdentity: identityRegistry.identity(to),\n isAmlKycPassed: identityRegistry.isAmlKycPassed(to),\n validateRestrictions: validateRestrictions,\n remainingAmount: remainingAmount,\n bitmaskToSet: 0,\n bitmaskToClear: 0\n });\n // Iterate through bitmap words directly - no pre-allocation!\n for (uint256 i = 0; i < walletIndexesLength && transferParams.remainingAmount > 0; i++) {\n uint256 word = walletIndexesByType[from][i];\n if (word == 0) continue;\n \n // Process word with ultra-optimization (batched bitmap updates)\n transferParams = _processTransferBitmapWord(\n transferParams, word, i\n );\n }\n\n return transferParams.remainingAmount;\n }\n\n /**\n * @dev helper function to process transfers for a single bitmap word\n * @dev Processes transfers in bucket-aligned batches for maximum efficiency\n * @dev Uses new globalMintTimestamps structure with slotsPerWord elements per bucket\n */\n function _processTransferBitmapWord(\n TransferParams memory transferParams,\n uint256 word_,\n uint256 wordIndex_\n ) internal returns (TransferParams memory) {\n // Note: Individual bitmap operations are now used instead of batched operations\n\n while (word_ != 0 && transferParams.remainingAmount > 0) {\n uint256 globalIndex = wordIndex_ * 256 + word_.bitScanForward256();\n\n // Process entire bucket if not already processed and it exists\n // Inline bucket calculations to reduce variable count\n if (globalIndex < mintTimestampCount) {\n transferParams = _processTransferAlignedPackedHoldings(\n transferParams, globalIndex\n );\n\n // Clear all bits in the current word chunk regardless of what was processed\n // This ensures we don't process the same bucket multiple times\n // Clear all bits that belong to this bucket and are in the current word\n word_ = BitManipulationLib.clearBucketBits(word_, globalIndex, wordIndex_, slotsPerWord);\n } else {\n // Index out of range, just clear current bit\n word_ &= word_ - 1;\n }\n }\n\n // Apply batched bitmap changes for this word (only if we have changes)\n if (transferParams.bitmaskToClear > 0) {\n _applyBitmaskToClear(transferParams.from, wordIndex_, transferParams.bitmaskToClear);\n transferParams.bitmaskToClear = 0;\n }\n if (transferParams.bitmaskToSet > 0) {\n _applyBitmaskToWord(transferParams.to, wordIndex_, transferParams.bitmaskToSet);\n transferParams.bitmaskToSet = 0;\n }\n\n return transferParams;\n }\n \n\n /**\n * @dev Process transfer from packed holdings (up to slotsPerWord balances at once)\n * @dev Uses new optimized globalMintTimestamps structure with slotsPerWord elements per bucket\n * @dev Returns remaining amount, total transferred amount, and bitmap masks for efficient updates\n */\n function _processTransferAlignedPackedHoldings(\n TransferParams memory transferParams,\n uint256 globalIndex\n ) internal returns (TransferParams memory) {\n uint256 tempRemainingAmount = transferParams.remainingAmount;\n \n // Calculate bucket index and starting subIndex from global index\n // uint256 bucketIndex = globalIndex / 8; \n // Load the packed timestamps once for efficiency\n uint256 packedTimestamps = globalMintTimestamps[globalIndex / slotsPerWord];\n \n // Get packed balances for this bucket once\n uint256 fromPackedBalances = packedBalancesByTypeAndTime[transferParams.from][globalIndex / slotsPerWord];\n uint256 toPackedBalances = packedBalancesByTypeAndTime[transferParams.to][globalIndex / slotsPerWord];\n\n // Process slotsPerWord consecutive holdings starting from bucket start\n // Align globalIndex to bucket start to process all holdings in the bucket\n for (uint256 i = 0; i < slotsPerWord && tempRemainingAmount > 0; i++) {\n // Only process holdings that are in the same word as the original globalIndex\n // This prevents processing holdings from buckets that span across word boundaries\n if (((globalIndex / slotsPerWord) * slotsPerWord + i) / 256 != globalIndex / 256) {\n continue;\n }\n uint256 packedBalance = BitManipulationLib.getPackedBalance(fromPackedBalances, i, elementBitSize);\n if (packedBalance == 0) continue;\n\n // Unpack token type and days (needed for event and possible validation)\n // Check restrictions if validation is enabled\n if (transferParams.validateRestrictions) {\n (uint256 currentTokenType, uint256 daysAfterDeployment) = BitManipulationLib.unpackElement(\n (packedTimestamps >> (i * elementBitSize)) & ((1 << elementBitSize) - 1)\n );\n if (transferRules.detectTransferRestrictionForHolding(\n currentTokenType,\n BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay),\n transferParams.recipientIdentity,\n transferParams.isAmlKycPassed\n ) != 0) {\n continue; // Skip this holding due to restrictions\n }\n }\n \n // Get real balance (considering overflow)\n uint256 currentBalance = packedBalance;\n uint256 currentGlobalIndex = (globalIndex / slotsPerWord) * slotsPerWord + i;\n if (packedBalance == maxBalancePerSubIndex) {\n currentBalance = overflowBalances[transferParams.from][currentGlobalIndex];\n }\n \n // Calculate transfer amount for this holding\n uint256 transferAmount = tempRemainingAmount > currentBalance ? currentBalance : tempRemainingAmount;\n\n // Calculate new balances\n uint256 newFromBalance = currentBalance - transferAmount;\n \n // Handle sender balance update (considering overflow)\n if (newFromBalance == 0) {\n // Balance becomes zero, clear everything\n fromPackedBalances = BitManipulationLib.setPackedBalance(fromPackedBalances, i, 0, elementBitSize);\n if (packedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[transferParams.from][currentGlobalIndex];\n }\n // Clear bitmap bit\n _clearBitmapBit(transferParams.from, currentGlobalIndex);\n } else if (newFromBalance < maxBalancePerSubIndex) {\n // New balance fits in uint32, store in packed storage\n fromPackedBalances = BitManipulationLib.setPackedBalance(fromPackedBalances, i, newFromBalance, elementBitSize);\n if (packedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[transferParams.from][currentGlobalIndex];\n }\n } else {\n // New balance still exceeds uint32 max, keep max in packed storage and update overflow mapping\n overflowBalances[transferParams.from][currentGlobalIndex] = newFromBalance;\n }\n \n uint256 newToBalance = 0;\n (newToBalance, toPackedBalances) = _updateRecipientBalance(\n transferParams.to,\n toPackedBalances,\n currentGlobalIndex,\n i,\n transferAmount\n );\n \n tempRemainingAmount -= transferAmount;\n \n // Set bit for recipient if they now have balance\n if (newToBalance > 0) {\n transferParams.bitmaskToSet |= (1 << currentGlobalIndex % 256);\n }\n\n // Clear bit for sender if they now have zero balance\n // Only clear bits that are in the current word to avoid cross-word issues\n if (newFromBalance == 0) {\n transferParams.bitmaskToClear |= (1 << currentGlobalIndex % 256);\n }\n \n // Emit transfer event\n emit TokenTypeTransferred(\n transferParams.from,\n transferParams.to,\n transferAmount,\n BitManipulationLib.unpackTokenTypeElement((packedTimestamps >> (i * elementBitSize)) & ((1 << elementBitSize) - 1))\n );\n }\n \n // Update storage only once if any transfers occurred\n if (transferParams.remainingAmount > tempRemainingAmount) {\n packedBalancesByTypeAndTime[transferParams.from][globalIndex / slotsPerWord] = fromPackedBalances;\n packedBalancesByTypeAndTime[transferParams.to][globalIndex / slotsPerWord] = toPackedBalances;\n transferParams.remainingAmount = tempRemainingAmount;\n }\n \n return transferParams;\n }\n\n function _updateRecipientBalance(\n address to,\n uint256 toPackedBalances_,\n uint256 globalIndex,\n uint256 subIndex,\n uint256 transferAmount\n ) internal returns (uint256 newToBalance, uint256 toPackedBalances) {\n uint256 currentToBalance = BitManipulationLib.getPackedBalance(toPackedBalances_, subIndex, elementBitSize);\n if (currentToBalance == maxBalancePerSubIndex) {\n currentToBalance = overflowBalances[to][globalIndex];\n }\n \n newToBalance = currentToBalance + transferAmount;\n toPackedBalances = _setPackedBalanceWithOverflow(\n to,\n globalIndex,\n subIndex,\n newToBalance,\n toPackedBalances_\n );\n }\n\n function _setPackedBalanceWithOverflow(\n address account,\n uint256 globalIndex,\n uint256 subIndex,\n uint256 newBalance,\n uint256 packedBalances\n ) internal returns (uint256 packedBalancesResult) {\n if (newBalance < maxBalancePerSubIndex) {\n // New balance fits in uint32, store in packed storage\n packedBalancesResult = BitManipulationLib.setPackedBalance(packedBalances, subIndex, newBalance, elementBitSize);\n } else {\n // New balance exceeds uint32 max, store max in packed storage and real amount in overflow mapping\n packedBalancesResult = BitManipulationLib.setPackedBalance(packedBalances, subIndex, maxBalancePerSubIndex, elementBitSize);\n overflowBalances[account][globalIndex] = newBalance;\n }\n }\n \n /**\n * @dev Helper function to update packed balance considering overflow\n * @param account The account to update\n * @param globalIndex_ The global index\n * @param newBalance The new balance value\n * @param oldPackedBalance The old packed balance value (for overflow detection)\n */\n function _updatePackedBalance(\n address account,\n uint256 globalIndex_,\n uint256 newBalance,\n uint256 oldPackedBalance\n ) internal {\n uint256 packedKey = BitManipulationLib.getPackedBalanceKeyFromGlobalIndex(globalIndex_, slotsPerWord);\n uint256 subIndex = globalIndex_ % slotsPerWord;\n uint256 packedBalances = packedBalancesByTypeAndTime[account][packedKey];\n \n if (newBalance == 0) {\n // Balance becomes zero, clear everything\n packedBalances = BitManipulationLib.setPackedBalance(packedBalances, subIndex, 0, elementBitSize);\n packedBalancesByTypeAndTime[account][packedKey] = packedBalances;\n // Clear any overflow balance\n if (oldPackedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[account][globalIndex_];\n }\n // Clear bitmap bit\n _clearBitmapBit(account, globalIndex_);\n } else if (newBalance < maxBalancePerSubIndex) {\n // New balance fits in uint32, store in packed storage\n packedBalances = BitManipulationLib.setPackedBalance(packedBalances, subIndex, newBalance, elementBitSize);\n packedBalancesByTypeAndTime[account][packedKey] = packedBalances;\n // Clear any existing overflow balance\n if (oldPackedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[account][globalIndex_];\n }\n } else {\n // New balance exceeds uint32 max, store max in packed storage and real amount in overflow mapping\n packedBalances = BitManipulationLib.setPackedBalance(packedBalances, subIndex, maxBalancePerSubIndex, elementBitSize);\n packedBalancesByTypeAndTime[account][packedKey] = packedBalances;\n overflowBalances[account][globalIndex_] = newBalance;\n }\n }\n\n /**\n * @dev Execute the actual transfer from a holding\n * @dev Final step with minimal variables\n */\n function _executeTransferFromHolding(\n address from,\n address to,\n uint256 tokenType,\n uint256 daysAfterDeployment,\n uint256 globalIndex_,\n uint256 currentBalance,\n uint256 remainingAmount_\n ) internal returns (uint256) {\n uint256 transferAmount = remainingAmount_ > currentBalance ? currentBalance : remainingAmount_;\n \n // Get old packed balance for overflow detection\n uint256 packedKey = BitManipulationLib.getPackedBalanceKeyFromGlobalIndex(globalIndex_, slotsPerWord);\n uint256 subIndex = globalIndex_ % slotsPerWord;\n uint256 packedBalance = BitManipulationLib.getPackedBalance(packedBalancesByTypeAndTime[from][packedKey], subIndex, elementBitSize);\n \n // Calculate and update new balance using helper\n uint256 newFromBalance = currentBalance - transferAmount;\n _updatePackedBalance(from, globalIndex_, newFromBalance, packedBalance);\n \n // Add to recipient's optimized holdings structure\n _addToHoldingsByIndex(to, globalIndex_, transferAmount);\n \n // Emit transfer event\n emit TokenTypeTransferred(from, to, transferAmount, tokenType);\n \n return remainingAmount_ - transferAmount;\n }\n\n /**\n * @dev Handle transfers with token type consideration (optimized structure only)\n */\n function _handleMultiTypeTransfer(address from, address to, uint256 amount, bool validateRestrictions) internal {\n // self-transfer 0 amount is allowed to convert timelock to simple tokens (holdings)\n if (amount == 0) {\n return;\n }\n if (from == to) {\n revert RestrictedLockupToken_SenderCannotBeRecipient(from);\n }\n \n // Process transfers for this token type\n uint256 remainingAmount = _processTransfers(\n from,\n to,\n amount,\n validateRestrictions\n );\n \n if (remainingAmount > 0) {\n revert RestrictedLockupToken_InsufficientTotalBalanceOf();\n }\n }\n\n /**\n * @dev Apply batched bitmap changes to a specific word\n * @dev This replaces multiple _setBitmapBit calls with a single OR operation\n */\n function _applyBitmaskToWord(\n address account,\n uint256 wordIndex,\n uint256 bitmaskToSet\n ) internal {\n // Ensure bitmap array is large enough\n while (walletIndexesByType[account].length <= wordIndex) {\n walletIndexesByType[account].push(0);\n }\n \n // Apply all bitmap changes for this word at once (major gas optimization)\n walletIndexesByType[account][wordIndex] |= bitmaskToSet;\n }\n\n function getWalletIndexesLength(address who_) public view returns (uint256) {\n return walletIndexesByType[who_].length;\n }\n \n function getWalletIndexes(address who_, uint256 index) public view returns (uint256) {\n return walletIndexesByType[who_][index];\n }\n\n /**\n * @dev Get total count of global holdings (each bucket contains slotsPerWord elements)\n * @return The total number of possible global holding indexes\n */\n function globalHoldingCount() public view returns (uint256) {\n return mintTimestampCount;\n }\n\n function setRecordMintTimestamp(bool enabled) external onlyTransferAdmin {\n if (enabled == recordMintTimestamp) {\n revert RestrictedLockupToken_AlreadySet();\n }\n \n // When disabling recordMintTimestamp, store the current daysAfterDeployment\n if (!enabled) {\n recordPausedOnDaysAfterDeployment = BitManipulationLib.calculateDaysAfterDeployment(\n VestingMath.toMidnightTimestamp(block.timestamp), \n deploymentDay\n );\n }\n \n recordMintTimestamp = enabled;\n }\n\n /**\n * @dev Get token type and mint timestamp from global holding index\n * @param globalIndex The global holding index\n * @return tokenType The token type\n * @return mintTimestamp The mint timestamp\n */\n function getTokenTypeAndMintTimestampFromGlobalIndex(uint256 globalIndex) external view returns (uint256 tokenType, uint256 mintTimestamp) {\n return _getTokenTypeAndMintTimestampFromGlobalIndex(globalIndex);\n }\n}\n"},"contracts/RestrictedLockupTokenExtension.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport {BitScan} from \"@solidity-bits/contracts/BitScan.sol\";\nimport {Storage} from \"./Storage.sol\";\nimport {VestingMath} from \"./libraries/VestingMath.sol\";\nimport {BitManipulationLib} from \"./libraries/BitManipulationLib.sol\";\nimport {IIdentityRegistry} from \"./interfaces/IIdentityRegistry.sol\";\nimport \"./AccessControlErrors.sol\";\n\n/**\n * @title RestrictedLockupTokenExtension\n * @notice Extension contract for holding management and burn operations\n * @dev This contract is called via delegatecall from RestrictedLockupToken\n * Following the fallback extension pattern with identical storage layout\n * Handles fundReleaseSchedule, mintReleaseSchedule, setMaxTotalSupply,\n * safeApprove and all burn operations\n */\ncontract RestrictedLockupTokenExtension is\n Storage,\n ERC20,\n ERC2771Context,\n ReentrancyGuard\n{\n using BitScan for uint256;\n using BitManipulationLib for uint256;\n\n struct FundReleaseScheduleParams {\n address to;\n uint256 amount;\n uint256 commencementTimestamp;\n uint256 scheduleId;\n uint256 holdingIdx;\n }\n\n struct BurnParams {\n address from;\n uint256 globalIndex;\n uint256 remainingAmount;\n uint256 totalBurned;\n uint256 bitmaskToClear;\n uint256 bucketIndex;\n uint256 alignedBaseIndex;\n uint256 fromPackedBalances;\n uint256 packedTimestamps;\n }\n\n constructor(address trustedForwarder_, uint256 maxTotalSupply_)\n ERC20(\"Restricted Lockup Token Extension\", \"RLTE\")\n ERC2771Context(trustedForwarder_)\n {\n if (trustedForwarder_ == address(0)) {\n revert RestrictedLockupToken_InvalidTrustedForwarder();\n }\n maxTotalSupply = maxTotalSupply_;\n // Initialize immutable slotsPerWord with the same value as the main contract\n slotsPerWord = BitManipulationLib.calculateSlotsPerWord(maxTotalSupply_);\n maxBalancePerSubIndex = BitManipulationLib.calculateMaxBalancePerSubIndex(slotsPerWord);\n elementBitSize = BitManipulationLib.calculateElementBitSize(slotsPerWord);\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context, Storage)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n\n // ============================================\n // HOLDING MANAGEMENT\n // ============================================\n\n /**\n * @notice Update the token type for a holding\n * @dev Only callable by reserve admin. Converts tokens from one type to another while preserving mint timestamp\n * @param wallet The wallet address whose holding is being updated\n * @param oldTokenType The current token type of the holding\n * @param newTokenType The new token type to assign\n * @param globalIndex The global index of the holding to update\n * @param amount The amount of tokens to update (must not exceed holding balance). Use 0 to convert entire holding balance.\n */\n function updateHoldingTokenType(\n address wallet,\n uint256 oldTokenType,\n uint256 newTokenType,\n uint256 globalIndex,\n uint256 amount\n ) external onlyReserveAdmin {\n if (oldTokenType == newTokenType) {\n revert RestrictedLockupToken_InvalidTokenType();\n }\n if (globalIndex >= mintTimestampCount) {\n revert RestrictedLockupToken_InvalidHolding();\n }\n (uint256 daysAfterDeployment, , uint256 finalAmount) = \n _validateAndPrepareTokenTypeUpdate(wallet, oldTokenType, newTokenType, globalIndex, amount);\n \n // Remove from old token type\n _removeFromHoldings(wallet, finalAmount, globalIndex);\n // Add to new token type with same daysAfterDeployment\n _addToHoldingsByType(wallet, newTokenType, daysAfterDeployment, finalAmount);\n\n emit HoldingTokenTypeUpdated(\n _msgSender(),\n wallet,\n oldTokenType,\n newTokenType,\n BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay),\n finalAmount\n );\n }\n\n /**\n * @dev Validate and prepare token type update parameters\n */\n function _validateAndPrepareTokenTypeUpdate(\n address wallet,\n uint256 oldTokenType,\n uint256 newTokenType,\n uint256 globalIndex,\n uint256 amount\n ) internal view returns (uint256 daysAfterDeployment, uint256 currentBalance, uint256 finalAmount) {\n daysAfterDeployment = _validateTokenTypeAndGetDays(oldTokenType, globalIndex);\n currentBalance = _getRealBalance(wallet, globalIndex);\n \n // If amount is 0, convert the entire balance of this holding\n finalAmount = amount == 0 ? currentBalance : amount;\n \n if (finalAmount == 0) {\n revert RestrictedLockupToken_InvalidAmount();\n }\n \n if (currentBalance < finalAmount) {\n revert RestrictedLockupToken_InsufficientTotalBalanceOf();\n }\n\n // Ensure the new token type is allowed for this wallet\n if (!transferRules.tokenTypeAllowed(newTokenType, wallet, identityRegistry)) {\n revert RestrictedLockupToken_TokenTypeNotAllowedForRecipient();\n }\n }\n\n /**\n * @dev Validate token type and get days after deployment\n */\n function _validateTokenTypeAndGetDays(\n uint256 expectedTokenType,\n uint256 globalIndex\n ) internal view returns (uint256 daysAfterDeployment) {\n uint256 packed = globalMintTimestamps[globalIndex / slotsPerWord];\n uint256 element = BitManipulationLib.getPackedElement(packed, globalIndex % slotsPerWord, elementBitSize);\n uint256 tokenType;\n (tokenType, daysAfterDeployment) = BitManipulationLib.unpackElement(element);\n \n if (tokenType != expectedTokenType) {\n revert RestrictedLockupToken_InvalidTokenType();\n }\n }\n\n /**\n * @dev Remove tokens from optimized holdings structure (bitmap-based)\n * @param account The account to remove tokens from\n * @param amount The amount to remove\n * @param holdingGlobalIdx The global index of the holding\n */\n function _removeFromHoldings(\n address account,\n uint256 amount,\n uint256 holdingGlobalIdx\n ) internal {\n // Get current balance from packed storage\n uint256 bucketIndex = holdingGlobalIdx / slotsPerWord;\n uint256 subIndex = holdingGlobalIdx % slotsPerWord;\n uint256 packedBalances = packedBalancesByTypeAndTime[account][bucketIndex];\n uint256 packedBalance = BitManipulationLib.getPackedBalance(packedBalances, subIndex, elementBitSize);\n \n // Get real balance (considering overflow)\n uint256 realBalance = packedBalance;\n if (packedBalance == maxBalancePerSubIndex) {\n realBalance = overflowBalances[account][holdingGlobalIdx];\n }\n \n if (realBalance < amount) {\n revert RestrictedLockupToken_InvalidAmount();\n }\n\n // Calculate new balance\n uint256 newBalance = realBalance - amount;\n \n // Update balance in packed storage and overflow mapping as needed\n if (newBalance == 0) {\n // Balance becomes zero, clear everything\n uint256 newPackedBalances = BitManipulationLib.setPackedBalance(packedBalances, subIndex, 0, elementBitSize);\n packedBalancesByTypeAndTime[account][bucketIndex] = newPackedBalances;\n // Clear any overflow balance\n if (packedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[account][holdingGlobalIdx];\n }\n // Clear bitmap bit\n _clearBitmapBit(account, holdingGlobalIdx);\n } else if (newBalance < maxBalancePerSubIndex) {\n // New balance fits in uint32, store in packed storage\n uint256 newPackedBalances = BitManipulationLib.setPackedBalance(packedBalances, subIndex, newBalance, elementBitSize);\n packedBalancesByTypeAndTime[account][bucketIndex] = newPackedBalances;\n // Clear any existing overflow balance\n if (packedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[account][holdingGlobalIdx];\n }\n } else {\n // New balance still exceeds uint32 max, keep max in packed storage and update overflow mapping\n overflowBalances[account][holdingGlobalIdx] = newBalance;\n }\n }\n\n function _superBalanceOf(address account) internal view returns (uint256) {\n return _queryBalance(\"superBalanceOf(address)\", account);\n }\n\n // ============================================\n // BURN FUNCTIONS\n // ============================================\n \n /**\n * @dev Destroys tokens and removes them from the total supply. Can only be called by an address with a Reserve Admin role.\n * @notice The only burnable amount of tokens are the simple tokens owned by a wallet. Timelocked tokens are not burnable.\n * @notice Cancel a timelock prior to burning tokens if there is a desire to burn timelocked tokens.\n * @notice DOES NOT ACTUALLY TRANSFER TOKENS TO 0X0! Merely emits Transfer event and calls _update with recipient artificially set to 0x0.\n * @param from_ The address to destroy the tokens from.\n * @param value_ The number of tokens to destroy from the address.\n */\n function burn(\n address from_,\n uint256 value_\n ) external validAddress(from_) onlyReserveAdmin {\n if (_superBalanceOf(from_) < value_) {\n revert RestrictedLockupToken_InsufficientBurnableBalance();\n }\n _burnWithHoldingsDestroy(from_, value_);\n }\n\n function burnHolding(\n address from_,\n uint256 globalHoldingIdx,\n uint256 amount\n ) external validAddress(from_) onlyReserveAdmin {\n if (globalHoldingIdx >= mintTimestampCount) {\n revert RestrictedLockupToken_HoldingIndexOutOfBound();\n }\n\n _burnSingleHolding(from_, globalHoldingIdx, amount);\n _burn(from_, amount);\n }\n\n /**\n * @dev Burns tokens from an account using the soft burn mechanism.\n * Can only be called by an address with the Soft Burn Admin role.\n * Requires sufficient allowance and burnable balance.\n * Burns tokens in FIFO order across all token types and holdings.\n * @param from_ The address to burn tokens from.\n * @param value_ The number of tokens to burn.\n */\n function softBurn(\n address from_,\n uint256 value_\n ) external onlySoftBurnAdmin {\n address spender = _msgSender();\n _spendAllowance(from_, spender, value_);\n \n if (_superBalanceOf(from_) < value_) {\n revert RestrictedLockupToken_InsufficientBurnableBalance();\n }\n _burnWithHoldingsDestroy(from_, value_);\n }\n\n /**\n * @dev Internal function to burn tokens and destroy holdings in FIFO order (optimized direct bitmap iteration)\n * @dev Gas optimization: Reduced local variables to avoid stack too deep, iterate bitmap directly\n * @param from_ The address to destroy the tokens from\n * @param value_ The number of tokens to destroy\n */\n function _burnWithHoldingsDestroy(address from_, uint256 value_) internal {\n _burnHoldings(from_, value_);\n \n // Perform standard ERC20 burn\n _burn(from_, value_);\n }\n\n /**\n * @dev Helper function to burn holdings for a specific token type\n * @dev Separated to avoid stack too deep issues\n * @param from_ The address to burn from\n * @param amount Amount left to burn\n */\n function _burnHoldings(\n address from_, \n uint256 amount\n ) internal {\n uint256 remainingAmount = amount;\n \n // Iterate through bitmap words directly - no pre-allocation!\n for (uint256 wordIndex = 0; wordIndex < walletIndexesByType[from_].length && remainingAmount > 0; wordIndex++) {\n uint256 word = walletIndexesByType[from_][wordIndex];\n if (word == 0) continue;\n \n // Process each set bit in this word\n remainingAmount = _processBitmapWordForBurn(from_, word, wordIndex, remainingAmount);\n }\n // we have not validated that remaining amount is 0\n // because it is validated that superBalanceOf is greater than \n // or equal to amount in burn and soft burn functions\n }\n\n /**\n * @dev Process a single bitmap word for burning\n * @dev Further separated to minimize stack usage\n */\n function _processBitmapWordForBurn(\n address from_,\n uint256 word_,\n uint256 wordIndex_,\n uint256 remainingAmount_\n ) internal returns (uint256) {\n uint256 bitmaskToClear = 0; // Collect all bits to clear for sender\n\n while (word_ != 0 && remainingAmount_ > 0) {\n uint256 globalIndex = wordIndex_ * 256 + word_.bitScanForward256();\n\n // Process entire bucket if not already processed and it exists\n if (globalIndex < mintTimestampCount) {\n uint256 totalBurned;\n uint256 bucketBitmaskToClear;\n (remainingAmount_, totalBurned, bucketBitmaskToClear) = _processBurnAlignedPackedHoldings(\n from_, globalIndex, remainingAmount_\n );\n\n // Accumulate bitmasks from the processed bucket\n if (totalBurned > 0) {\n bitmaskToClear |= bucketBitmaskToClear;\n }\n\n // Clear all bits that belong to this bucket and are in the current word\n word_ = BitManipulationLib.clearBucketBits(word_, globalIndex, wordIndex_, slotsPerWord);\n } else {\n // Index out of range, just clear current bit\n word_ &= word_ - 1;\n }\n }\n\n // Apply batched bitmap changes for this word (only if we have changes)\n if (bitmaskToClear > 0) {\n _applyBitmaskToClear(from_, wordIndex_, bitmaskToClear);\n }\n\n return remainingAmount_;\n }\n\n /**\n * @dev OPTIMIZED process burn from packed holdings (up to slotsPerWord balances at once)\n * @dev Uses new optimized globalMintTimestamps structure with slotsPerWord elements per bucket\n * @dev Returns remaining amount, total burned amount, and bitmap mask for efficient updates\n */\n function _processBurnAlignedPackedHoldings(\n address from,\n uint256 globalIndex,\n uint256 remainingAmount_\n ) internal returns (uint256, uint256, uint256) {\n BurnParams memory burnParams = BurnParams({\n from: from,\n globalIndex: globalIndex,\n remainingAmount: remainingAmount_,\n totalBurned: 0,\n bitmaskToClear: 0,\n bucketIndex: globalIndex / slotsPerWord,\n alignedBaseIndex: (globalIndex / slotsPerWord) * slotsPerWord,\n fromPackedBalances: packedBalancesByTypeAndTime[from][globalIndex / slotsPerWord],\n packedTimestamps: globalMintTimestamps[globalIndex / slotsPerWord]\n });\n\n // Process slotsPerWord consecutive holdings starting from bucket start\n // Align globalIndex to bucket start to process all holdings in the bucket\n for (uint256 i = 0; i < slotsPerWord && burnParams.remainingAmount > 0; i++) {\n // Only process holdings that are in the same word as the original globalIndex\n // This prevents processing holdings from buckets that span across word boundaries\n if (((globalIndex / slotsPerWord) * slotsPerWord + i) / 256 != globalIndex / 256) {\n continue;\n }\n burnParams = _processSingleBurnHolding(burnParams, i);\n }\n \n // Update storage only once if any burns occurred\n if (burnParams.totalBurned > 0) {\n packedBalancesByTypeAndTime[burnParams.from][burnParams.bucketIndex] = burnParams.fromPackedBalances;\n }\n \n return (burnParams.remainingAmount, burnParams.totalBurned, burnParams.bitmaskToClear);\n }\n\n /**\n * @dev Process a single burn holding to reduce stack depth\n */\n function _processSingleBurnHolding(\n BurnParams memory burnParams,\n uint256 i\n ) internal returns (BurnParams memory) {\n uint256 packedBalance = BitManipulationLib.getPackedBalance(burnParams.fromPackedBalances, i, elementBitSize);\n if (packedBalance == 0) return burnParams;\n \n // Get real balance and calculate burn amount\n uint256 currentGlobalIndex = burnParams.alignedBaseIndex + i;\n uint256 currentBalance = packedBalance == maxBalancePerSubIndex ? \n overflowBalances[burnParams.from][currentGlobalIndex] : packedBalance;\n \n uint256 burnAmount = burnParams.remainingAmount > currentBalance ? \n currentBalance : burnParams.remainingAmount;\n uint256 newFromBalance = currentBalance - burnAmount;\n \n // Update balance and handle overflow\n burnParams.fromPackedBalances = _updateBalance(\n burnParams.from,\n burnParams.fromPackedBalances,\n i,\n currentGlobalIndex,\n packedBalance,\n newFromBalance\n );\n \n // Update burn tracking\n burnParams.totalBurned += burnAmount;\n burnParams.remainingAmount -= burnAmount;\n \n // Update bitmask if balance becomes zero (matches transfer logic)\n // Only update bits that are in the current word to avoid cross-word issues\n if (newFromBalance == 0) {\n burnParams.bitmaskToClear |= (1 << (currentGlobalIndex % 256));\n }\n \n // Emit burn event\n uint256 element = (burnParams.packedTimestamps >> (i * elementBitSize)) & ((1 << elementBitSize) - 1);\n emit TokenTypeBurned(burnParams.from, burnAmount, BitManipulationLib.unpackTokenTypeElement(element));\n \n return burnParams;\n }\n\n /**\n * @dev Update balance for target address\n */\n function _updateBalance(\n address from,\n uint256 fromPackedBalances,\n uint256 i,\n uint256 currentGlobalIndex,\n uint256 packedBalance,\n uint256 newFromBalance\n ) internal returns (uint256) {\n if (newFromBalance == 0) {\n // Balance becomes zero, clear everything\n if (packedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[from][currentGlobalIndex];\n }\n return BitManipulationLib.setPackedBalance(fromPackedBalances, i, 0, elementBitSize);\n } else if (newFromBalance < maxBalancePerSubIndex) {\n // New balance fits in uint32, store in packed storage\n if (packedBalance == maxBalancePerSubIndex) {\n delete overflowBalances[from][currentGlobalIndex];\n }\n return BitManipulationLib.setPackedBalance(fromPackedBalances, i, newFromBalance, elementBitSize);\n } else {\n // New balance still exceeds uint32 max, update overflow mapping\n overflowBalances[from][currentGlobalIndex] = newFromBalance;\n return BitManipulationLib.setPackedBalance(fromPackedBalances, i, maxBalancePerSubIndex, elementBitSize);\n }\n }\n\n /**\n * @dev Burn from a single holding\n * @dev Minimized to avoid stack issues\n */\n function _burnSingleHolding(\n address from_,\n uint256 globalIndex_,\n uint256 amount\n ) internal {\n // Get balance from packed storage\n uint256 packedKey = BitManipulationLib.getPackedBalanceKeyFromGlobalIndex(globalIndex_, slotsPerWord);\n uint256 subIndex = globalIndex_ % slotsPerWord;\n uint256 packedBalances = packedBalancesByTypeAndTime[from_][packedKey];\n uint256 currentBalance = BitManipulationLib.getPackedBalance(packedBalances, subIndex, elementBitSize);\n if (currentBalance == maxBalancePerSubIndex) {\n uint256 overflowBalance = overflowBalances[from_][globalIndex_];\n currentBalance = overflowBalance;\n }\n if (amount > currentBalance) {\n revert RestrictedLockupToken_NoItemWithEnoughBalance();\n }\n \n // Update packed balances\n uint256 newBalance = currentBalance - amount;\n\n packedBalances = _updateBalance(\n from_,\n packedBalances,\n subIndex,\n globalIndex_,\n currentBalance,\n newBalance\n );\n\n packedBalancesByTypeAndTime[from_][packedKey] = packedBalances;\n \n // Clear bitmap bit if balance becomes zero\n if (newBalance == 0) {\n _clearBitmapBit(from_, globalIndex_);\n }\n \n uint256 tokenType_ = _getTokenTypeFromGlobalIndex(globalIndex_);\n // Emit token type burn event\n emit TokenTypeBurned(from_, amount, tokenType_);\n }\n\n /**\n * @notice Create a release schedule template that can be used to generate many token timelocks\n * @param releaseCount Total number of releases including any initial \"cliff'\n * @param delayUntilFirstReleaseInSeconds \"cliff\" or 0 for immediate release\n * @param initialReleasePortionInBips Portion to release in 100ths of 1% (10000 BIPS per 100%)\n * @param periodBetweenReleasesInSeconds After the delay and initial release\n * the remaining tokens will be distributed evenly across the remaining number of releases (releaseCount - 1)\n * @return unlockScheduleId The id used to refer to the release schedule at the time of funding the schedule\n */\n function createReleaseSchedule(\n uint256 releaseCount,\n uint256 delayUntilFirstReleaseInSeconds,\n uint256 initialReleasePortionInBips,\n uint256 periodBetweenReleasesInSeconds\n ) external anyAdmin returns (uint256 unlockScheduleId) {\n if (delayUntilFirstReleaseInSeconds > maxReleaseDelay) {\n revert RestrictedLockupToken_InvalidFirstRelease();\n }\n if (releaseCount == 0) {\n revert RestrictedLockupToken_InvalidNumberOfRelases();\n }\n if (initialReleasePortionInBips > VestingMath.BIPS_PRECISION) {\n revert RestrictedLockupToken_InvalidRInitialReleaseBIPS();\n }\n\n if (releaseCount > 1) {\n if (periodBetweenReleasesInSeconds == 0) {\n revert RestrictedLockupToken_InvalidReleasePeriod();\n }\n if (initialReleasePortionInBips == VestingMath.BIPS_PRECISION) {\n revert RestrictedLockupToken_CantVestAllForMultipleReleases();\n }\n } else {\n if (initialReleasePortionInBips != VestingMath.BIPS_PRECISION) {\n revert RestrictedLockupToken_InvalidReleasePortionForSingleRelease();\n }\n periodBetweenReleasesInSeconds = 0;\n }\n\n releaseSchedules.push(\n ReleaseSchedule(\n releaseCount,\n delayUntilFirstReleaseInSeconds,\n initialReleasePortionInBips,\n periodBetweenReleasesInSeconds\n )\n );\n\n unlockScheduleId = releaseSchedules.length - 1;\n emit ScheduleCreated(_msgSender(), unlockScheduleId);\n\n return unlockScheduleId;\n }\n\n function timelockCountOf(address wallet) internal view returns (uint256) {\n return timelocks[wallet].length;\n }\n\n /**\n * @notice Update the token type for a specific timelock\n * @dev Only callable by reserve admin. Updates token type classification for timelocked tokens\n * @param wallet The wallet address whose timelock is being updated\n * @param timelockIndex The index of the timelock to update\n * @param newTokenType The new token type to assign\n */\n function updateTimelockTokenType(\n address wallet,\n uint256 timelockIndex,\n uint256 newTokenType\n ) external onlyReserveAdmin {\n if (timelockIndex >= timelockCountOf(wallet)) {\n revert RestrictedLockupToken_InvalidTimelock();\n }\n\n Timelock storage timelock = timelocks[wallet][timelockIndex];\n (uint256 oldTokenType, uint256 mintTimestamp) = _getTokenTypeAndMintTimestampFromGlobalIndex(timelock.globalHoldingIndex);\n\n if (oldTokenType == newTokenType) {\n revert RestrictedLockupToken_InvalidTokenType();\n }\n\n // Ensure the new token type is allowed for this wallet\n if (!transferRules.tokenTypeAllowed(newTokenType, wallet, identityRegistry)) {\n revert RestrictedLockupToken_TokenTypeNotAllowedForRecipient();\n }\n\n // For timelock token type updates, we need to create a new global index\n _removeFromHoldings(wallet, 0, timelock.globalHoldingIndex);\n uint256 globalHoldingIndex = _addToHoldingsByType(\n wallet,\n newTokenType,\n BitManipulationLib.calculateDaysAfterDeployment(mintTimestamp, deploymentDay),\n 0\n );\n timelock.globalHoldingIndex = globalHoldingIndex;\n\n emit TimelockTokenTypeUpdated(\n _msgSender(),\n wallet,\n timelockIndex,\n oldTokenType,\n newTokenType,\n timelock.totalAmount\n );\n }\n\n function _fundReleaseSchedule(\n FundReleaseScheduleParams memory params,\n address[] memory cancelableBy_\n ) internal returns (bool _success) {\n if (cancelableBy_.length > MAX_CANCELABLE_BY) {\n revert RestrictedLockupToken_MaxCancelersExceeded();\n }\n\n address _funder = _msgSender();\n uint256 _timelockId = _fund(\n params.to,\n params.amount,\n params.commencementTimestamp,\n params.scheduleId,\n 0, // token type is not used for fundReleaseSchedule, it is got from the holdingIdx\n _funder,\n false,\n params.holdingIdx\n );\n\n if (cancelableBy_.length > 0) {\n timelocks[params.to][_timelockId].cancelableBy = cancelableBy_;\n }\n\n emit ScheduleFunded(\n _funder,\n params.to,\n params.scheduleId,\n params.amount,\n params.commencementTimestamp,\n _timelockId,\n cancelableBy_\n );\n return true;\n }\n\n /**\n * @notice Fund the programmatic release of tokens to a recipient with explicit parameters including holding index\n * @param params FundReleaseScheduleParams struct containing all necessary parameters\n * @param cancelableBy_ array of canceler addresses\n * @return _success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function fundReleaseScheduleWithHolding(\n FundReleaseScheduleParams memory params,\n address[] memory cancelableBy_\n ) public nonReentrant anyAdmin returns (bool _success) {\n return _fundReleaseSchedule(params, cancelableBy_);\n }\n\n /**\n * @dev Find the first holding for a sender that has enough amount and is allowed to transfer to recipient\n * @dev Optimized to iterate only through set bits using bit manipulation\n * @param sender_ The address to transfer from\n * @param recipient_ The address to transfer to\n * @param amount_ The amount needed\n * @return globalIndex The global index of the holding, or type(uint256).max if not found\n * @return holdingAmount The amount available in this holding\n * @return tokenType The token type of the holding\n */\n function findHoldingFor(\n address sender_,\n address recipient_,\n uint256 amount_\n ) public view returns (\n uint256,\n uint256,\n uint256\n ) {\n // Get recipient identity for transfer validation\n IIdentityRegistry.IdentityInfo memory recipientIdentity = identityRegistry.identity(recipient_);\n bool isAmlKycPassed = identityRegistry.isAmlKycPassed(recipient_);\n\n // Iterate through all holdings for the sender\n for (uint256 wordIndex = 0; wordIndex < walletIndexesByType[sender_].length; wordIndex++) {\n uint256 word = walletIndexesByType[sender_][wordIndex];\n if (word == 0) continue;\n\n while (word != 0) {\n // Find the position of the lowest set bit\n uint256 currentGlobalIndex = wordIndex * 256 + word.bitScanForward256();\n \n // Get holding details\n uint256 element = BitManipulationLib.getPackedElement(\n globalMintTimestamps[currentGlobalIndex / slotsPerWord],\n currentGlobalIndex % slotsPerWord,\n elementBitSize\n );\n // Get the actual balance for this holding\n uint256 currentAmount = _getRealBalance(sender_, currentGlobalIndex);\n \n // Check if this holding has enough amount\n if (currentAmount >= amount_) {\n (uint256 currentTokenType, uint256 daysAfterDeployment) = BitManipulationLib.unpackElement(element);\n uint256 currentMintTimestamp = BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay);\n // Check if transfer is allowed for this holding\n uint256 restrictionCode = transferRules.detectTransferRestrictionForHolding(\n currentTokenType,\n currentMintTimestamp,\n recipientIdentity,\n isAmlKycPassed\n );\n \n // If transfer is allowed (restriction code is 0), return this holding\n if (restrictionCode == 0) {\n return (currentGlobalIndex, currentAmount, currentTokenType);\n }\n }\n \n // Clear the lowest set bit for next iteration (Brian Kernighan's algorithm)\n word = word & (word - 1);\n }\n }\n return (type(uint256).max, 0, 0);\n }\n\n /**\n * @notice Fund a release schedule by finding a suitable holding from the funder and automatically determining token type.\n * This function finds the first holding with sufficient amount and uses its token type for the release schedule.\n * @param to_ recipient address that will have tokens unlocked on a release schedule\n * @param amount_ of tokens to transfer in base units (the smallest unit without the decimal point)\n * @param commencementTimestamp_ the time (in unixtime) the release schedule will start\n * @param scheduleId_ the id of the release schedule that will be used to release the tokens\n * @param cancelableBy_ array of canceler addresses\n * @return _success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function fundReleaseSchedule(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n address[] memory cancelableBy_\n ) public nonReentrant anyAdmin returns (bool _success) {\n address _funder = _msgSender();\n \n // Find a suitable holding from the funder using the existing findHoldingFor function\n (uint256 holdingIdx,,) = findHoldingFor(_funder, to_, amount_);\n \n if (holdingIdx == type(uint256).max) {\n revert RestrictedLockupToken_NoHoldingWithEnoughBalance();\n }\n\n FundReleaseScheduleParams memory params = FundReleaseScheduleParams({\n to: to_,\n amount: amount_,\n commencementTimestamp: commencementTimestamp_,\n scheduleId: scheduleId_,\n holdingIdx: holdingIdx\n });\n \n // Create FundReleaseScheduleParams with found values and call fundReleaseSchedule\n return _fundReleaseSchedule(\n params,\n cancelableBy_\n );\n }\n\n function _mintReleaseSchedule(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n uint256 tokenType_,\n address[] memory cancelableBy_\n ) internal returns (bool _success) {\n if (cancelableBy_.length > MAX_CANCELABLE_BY) {\n revert RestrictedLockupToken_MaxCancelersExceeded();\n }\n\n address _funder = _msgSender();\n uint256 _timelockId = _fund(\n to_,\n amount_,\n commencementTimestamp_,\n scheduleId_,\n tokenType_,\n _funder,\n true,\n 0 // holdingIdx is not used for mintReleaseSchedule\n );\n _registerTokenType(tokenType_);\n\n if (cancelableBy_.length > 0) {\n timelocks[to_][_timelockId].cancelableBy = cancelableBy_;\n }\n\n emit ScheduleFunded(\n _funder,\n to_,\n scheduleId_,\n amount_,\n commencementTimestamp_,\n _timelockId,\n cancelableBy_\n );\n return true;\n }\n\n /**\n * @notice Fund the programmatic release of tokens to a recipient by minting directly to them with explicit token type.\n WARNING: this function IS CANCELABLE by cancelableBy.\n If canceled the tokens that are locked at the time of the cancellation will be returned to the funder\n and unlocked tokens will be transferred to the recipient.\n * @param to_ recipient address that will have tokens unlocked on a release schedule\n * @param amount_ of tokens to transfer in base units (the smallest unit without the decimal point)\n * @param commencementTimestamp_ the time (in unixtime) the release schedule will start\n * @param scheduleId_ the id of the release schedule that will be used to release the tokens\n * @param tokenType_ the token type to assign to the minted tokens\n * @param cancelableBy_ array of canceler addresses\n * @return _success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function mintReleaseScheduleTokenType(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n uint256 tokenType_,\n address[] memory cancelableBy_\n ) public nonReentrant onlyReserveOrMintAdmin returns (bool _success) {\n return _mintReleaseSchedule(to_, amount_, commencementTimestamp_, scheduleId_, tokenType_, cancelableBy_);\n }\n\n /**\n * @notice Mint tokens directly to a recipient on a release schedule with automatic token type determination.\n * This is akin to token minting, it does not have to adhere to transfer restrictions, and is purely under the discretion of the Reserve Admin.\n * @param to_ recipient address that will have tokens unlocked on a release schedule\n * @param amount_ of tokens to transfer in base units (the smallest unit without the decimal point)\n * @param commencementTimestamp_ the time (in unixtime) the release schedule will start\n * @param scheduleId_ the id of the release schedule that will be used to release the tokens\n * @param cancelableBy_ array of canceler addresses\n * @return _success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function mintReleaseSchedule(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n address[] memory cancelableBy_\n ) public nonReentrant onlyReserveOrMintAdmin returns (bool _success) {\n // Determine token type based on recipient's identity\n uint256 tokenType_ = transferRules.determineTokenType(to_, identityRegistry);\n\n return _mintReleaseSchedule(to_, amount_, commencementTimestamp_, scheduleId_, tokenType_, cancelableBy_);\n }\n\n\n /**\n * @param to_ Address to\n * @param amount_ Amount of tokens\n * @param commencementTimestamp_ commencement timestamp\n * @param scheduleId_ schedule Id\n * @param funder_ funder address\n * @param isMintReleaseSchedule_ if this funding is via a mint release schedule\n * @return timelock Id\n */\n function _fund(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_, // unix timestamp\n uint256 scheduleId_,\n uint256 tokenType_,\n address funder_,\n bool isMintReleaseSchedule_,\n uint256 globalHoldingIdx_\n ) internal returns (uint256) {\n if (timelocks[to_].length >= MAX_TIMELOCKS) {\n revert RestrictedLockupToken_MaxTimelocksExceeded();\n }\n if (amount_ < minTimelockAmount) {\n revert RestrictedLockupToken_InvalidFundAmount();\n }\n if (to_ == address(0)) {\n revert RestrictedLockupToken_InvalidFundAddress();\n }\n if (scheduleId_ >= releaseSchedules.length) {\n revert RestrictedLockupToken_InvalidScheduleId();\n }\n if (amount_ < releaseSchedules[scheduleId_].releaseCount) {\n revert RestrictedLockupToken_AmountLessThanReleaseCount();\n }\n if (\n commencementTimestamp_ +\n releaseSchedules[scheduleId_].delayUntilFirstReleaseInSeconds >\n block.timestamp + maxReleaseDelay\n ) {\n revert RestrictedLockupToken_InitialReleaseOutOfRange();\n }\n\n uint256 mintTimestamp = recordMintTimestamp ? \n VestingMath.toMidnightTimestamp(block.timestamp) : \n BitManipulationLib.daysSinceDeployToTimestamp(recordPausedOnDaysAfterDeployment, deploymentDay);\n \n uint256 globalIndex = 0; // Initialize global index\n \n if (isMintReleaseSchedule_) {\n uint256 daysAfterDeployment = BitManipulationLib.calculateDaysAfterDeployment(mintTimestamp, deploymentDay);\n // Get global index from _addToHoldings and store it in timelock\n globalIndex = _addToHoldingsByType(to_, tokenType_, daysAfterDeployment, 0);\n // Verify if the specified token type is allowed for the recipient\n if (!transferRules.tokenTypeAllowed(tokenType_, to_, identityRegistry)) {\n revert RestrictedLockupToken_TokenTypeNotAllowedForRecipient();\n }\n // we mint new tokens so no need to find anything\n // Perform standard ERC20 mint to maintain compatibility\n _mint(to_, amount_);\n } else {\n _enforceTransferRestrictions(funder_, to_, amount_);\n (mintTimestamp, tokenType_) = _updateHolding(funder_, to_, amount_, globalHoldingIdx_);\n // For fundReleaseSchedule, we need to get the global index from the existing holding\n // This will be handled by _updateHolding which should return the global index\n globalIndex = globalHoldingIdx_; // Use the holding index as global index for now\n\n emit TokenTypeTransferred(funder_, to_, amount_, tokenType_);\n super._transfer(funder_, to_, amount_);\n }\n\n Timelock memory _timelock;\n _timelock.scheduleId = scheduleId_;\n _timelock.commencementTimestamp = commencementTimestamp_;\n _timelock.totalAmount = amount_;\n _timelock.funder = funder_;\n _timelock.globalHoldingIndex = globalIndex;\n\n timelocks[to_].push(_timelock);\n return timelockCountOf(to_) - 1;\n }\n\n /**\n * @dev Updates the holding by decreasing the amount from the 'from' wallet's holding, but does NOT update the 'to' wallet.\n * This function only removes the specified amount from the sender's (from) holding for the given tokenType and holdingIdx.\n * The recipient's (to) holdings are not updated here.\n */\n function _updateHolding(\n address from,\n address to,\n uint256 amount,\n uint256 globalHoldingIdx\n ) private returns (uint256, uint256) {\n // Validate that holdingIdx is within bounds\n if (globalHoldingIdx >= mintTimestampCount) {\n revert RestrictedLockupToken_InvalidHoldingIndex();\n }\n\n uint256 packed = globalMintTimestamps[globalHoldingIdx / slotsPerWord];\n uint256 element = BitManipulationLib.getPackedElement(packed, globalHoldingIdx % slotsPerWord, elementBitSize);\n (uint256 _tokenType, uint256 daysAfterDeployment) = BitManipulationLib.unpackElement(element);\n uint256 _mintTimestamp = BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay);\n _enforceTransferRestrictionsFor(_tokenType, _mintTimestamp, to);\n _removeFromHoldings(\n from,\n amount,\n globalHoldingIdx\n );\n\n return (_mintTimestamp, _tokenType);\n }\n\n /**\n * @dev update hook\n * @notice During a burn, this is invoked by the _burn function (ERC20.sol) which artificially sets `to` to the 0x0 address. Tokens are NOT actually transferred to 0x0 during a burn.\n * @param from_ from address\n * @param to_ to address\n * @param amount_ amount\n */\n function _update(\n address from_,\n address to_,\n uint256 amount_\n ) internal virtual override {\n if (snapshotsEnabled) {\n snapshotPeriods.onUpdate(address(this), from_, to_, amount_);\n }\n super._update(from_, to_, amount_); // Call parent hook\n\n /// @notice when to is 0x0, it's a burn (note - 0x0 is not actually transferred any tokens during burn)\n /// do NOT create a holder in that case\n if (!_addressHasHolder(to_) && to_ != address(0)) {\n _createHolderFromAddress(to_);\n }\n }\n\n /**\n * @notice Batch version of fund cancelable release schedule with explicit parameters including holding index\n * @param params An array of parameters for the release schedule\n * @param cancelableBy_ An array of cancelables\n * @return success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function batchFundReleaseScheduleWithHolding(\n FundReleaseScheduleParams[] memory params,\n address[] memory cancelableBy_\n ) external anyAdmin returns (bool success) {\n for (uint256 i; i < params.length; ++i) {\n _fundReleaseSchedule(\n params[i],\n cancelableBy_\n );\n }\n\n return true;\n }\n\n /**\n * @notice Batch version of fund cancelable release schedule with automatic holding and token type detection\n * @param to_ An array of recipient addresses that will have tokens unlocked on a release schedule\n * @param amounts_ An array of amounts of tokens to transfer in base units (the smallest unit without the decimal point)\n * @param commencementTimestamps_ An array of the times the release schedules will start\n * @param scheduleIds_ An array of the ids of the release schedules that will be used to release the tokens\n * @param cancelableBy_ An array of cancelables\n * @return success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function batchFundReleaseSchedule(\n address[] calldata to_,\n uint256[] calldata amounts_,\n uint256[] calldata commencementTimestamps_,\n uint256[] calldata scheduleIds_,\n address[] calldata cancelableBy_\n ) external anyAdmin returns (bool success) {\n if (\n to_.length != amounts_.length ||\n to_.length != commencementTimestamps_.length ||\n to_.length != scheduleIds_.length\n ) {\n revert RestrictedLockupToken_MismatchedArrayLength();\n }\n\n for (uint256 i; i < to_.length; ++i) {\n fundReleaseSchedule(\n to_[i],\n amounts_[i],\n commencementTimestamps_[i],\n scheduleIds_[i],\n cancelableBy_\n );\n }\n\n return true;\n }\n\n /**\n * @notice Batch version of mint cancelable release schedule with explicit token types\n * @param to An array of recipient address that will have tokens unlocked on a release schedule\n * @param amounts An array of amount of tokens to transfer in base units (the smallest unit without the decimal point)\n * @param commencementTimestamps An array of the time the release schedule will start\n * @param scheduleIds An array of the id of the release schedule that will be used to release the tokens\n * @param tokenTypes An array of token types for each timelock\n * @param cancelableBy An array of cancelables\n * @return success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function batchMintReleaseScheduleTokenType(\n address[] calldata to,\n uint256[] calldata amounts,\n uint256[] calldata commencementTimestamps,\n uint256[] calldata scheduleIds,\n uint256[] calldata tokenTypes,\n address[] calldata cancelableBy\n ) external onlyReserveOrMintAdmin returns (bool success) {\n if (\n to.length != amounts.length ||\n to.length != commencementTimestamps.length ||\n to.length != scheduleIds.length ||\n to.length != tokenTypes.length\n ) {\n revert RestrictedLockupToken_MismatchedArrayLength();\n }\n\n for (uint256 i; i < to.length; ++i) {\n _mintReleaseSchedule(\n to[i],\n amounts[i],\n commencementTimestamps[i],\n scheduleIds[i],\n tokenTypes[i],\n cancelableBy\n );\n }\n\n return true;\n }\n\n /**\n * @notice Batch version of mint cancelable release schedule with automatic token type determination\n * @param to An array of recipient address that will have tokens unlocked on a release schedule\n * @param amounts An array of amount of tokens to transfer in base units (the smallest unit without the decimal point)\n * @param commencementTimestamps An array of the time the release schedule will start\n * @param scheduleIds An array of the id of the release schedule that will be used to release the tokens\n * @param cancelableBy An array of cancelables\n * @return success Always returns true on completion so that a function calling it can revert if the required call did not succeed\n */\n function batchMintReleaseSchedule(\n address[] calldata to,\n uint256[] calldata amounts,\n uint256[] calldata commencementTimestamps,\n uint256[] calldata scheduleIds,\n address[] calldata cancelableBy\n ) external onlyReserveOrMintAdmin returns (bool success) {\n if (\n to.length != amounts.length ||\n to.length != commencementTimestamps.length ||\n to.length != scheduleIds.length\n ) {\n revert RestrictedLockupToken_MismatchedArrayLength();\n }\n\n for (uint256 i; i < to.length; ++i) {\n mintReleaseSchedule(\n to[i],\n amounts[i],\n commencementTimestamps[i],\n scheduleIds[i],\n cancelableBy\n );\n }\n\n return true;\n }\n\n /**\n * @dev safeApprove should only be called when setting an initial allowance,\n * or when resetting it to zero.\n * @param spender_ spending address to approve\n * @param value_ amount to approve\n */\n function safeApprove(address spender_, uint256 value_) external {\n if (value_ != 0 && (allowance(address(_msgSender()), spender_) != 0)) {\n revert RestrictedLockupToken_SafeApprove();\n }\n approve(spender_, value_);\n }\n\n /**\n * @notice Sets a new maximum total supply for the token.\n * @dev Only callable by the reserve admin. The new max total supply must be greater than the current total supply.\n * @param maxTotalSupply_ The new maximum total supply to set.\n */\n function setMaxTotalSupply(\n uint256 maxTotalSupply_\n ) external onlyReserveAdmin {\n if (maxTotalSupply_ > _maxSafeSupply()) {\n revert RestrictedLockupToken_MaxTotalSupplyTooLarge();\n }\n if (maxTotalSupply_ <= totalSupply()) {\n revert RestrictedLockupToken_NewMaxTotalSupplyMustExceedCurrentTotalSupply();\n }\n\n maxTotalSupply = maxTotalSupply_;\n emit SetMaxTotalSupply(_msgSender(), maxTotalSupply_);\n }\n}\n"},"contracts/RestrictedLockupTokenManagementExtension.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {IERC165} from \"@openzeppelin/contracts/utils/introspection/ERC165.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport {Storage} from \"./Storage.sol\";\nimport {BitManipulationLib} from \"./libraries/BitManipulationLib.sol\";\nimport {Popcount} from \"@solidity-bits/contracts/Popcount.sol\";\nimport {ITransferRules} from \"./interfaces/ITransferRules.sol\";\nimport {IIdentityRegistry} from \"./interfaces/IIdentityRegistry.sol\";\nimport \"./AccessControlErrors.sol\";\n\n/**\n * @title RestrictedLockupTokenManagementExtension\n * @notice Extension contract for managing token holders, access control\n * @dev This contract is called via delegatecall from RestrictedLockupToken\n * Following the fallback extension pattern with identical storage layout\n */\ncontract RestrictedLockupTokenManagementExtension is\n Storage,\n Context,\n ERC2771Context,\n ReentrancyGuard\n{\n using BitManipulationLib for uint256;\n using Popcount for uint256;\n\n // ============================================\n // HOLDER MANAGEMENT MODIFIERS\n // ============================================\n \n modifier onlyExistingHolder(uint256 holderId_) {\n if (!holderExists(holderId_)) {\n revert RestrictedLockupToken_HolderDoesNotExist();\n }\n _;\n }\n\n constructor(address trustedForwarder_, uint256 maxTotalSupply_)\n ERC2771Context(trustedForwarder_)\n {\n if (trustedForwarder_ == address(0)) {\n revert RestrictedLockupToken_InvalidTrustedForwarder();\n }\n maxTotalSupply = maxTotalSupply_;\n // Initialize immutable slotsPerWord with the same value as the main contract\n slotsPerWord = BitManipulationLib.calculateSlotsPerWord(maxTotalSupply_);\n maxBalancePerSubIndex = BitManipulationLib.calculateMaxBalancePerSubIndex(slotsPerWord);\n elementBitSize = BitManipulationLib.calculateElementBitSize(slotsPerWord);\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context, Storage)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n\n // ============================================\n // HOLDER MANAGEMENT VIEW FUNCTIONS\n // ============================================\n \n /**\n * @dev getHolderId\n * @param addr_ address of holder\n * @return holder Id from address\n */\n function getHolderId(address addr_) public view returns (uint256) {\n return holderIdByAddress[addr_];\n }\n\n /**\n * @dev check if address has holder\n * @param addr_ address to check\n * @return true if address has holder\n */\n function addressHasHolder(address addr_) public view returns (bool) {\n return _addressHasHolder(addr_);\n }\n\n /**\n * @dev check if holder exists\n * @param holderId_ id of holder to check\n * @return true if holderId exists\n * */\n function holderExists(uint256 holderId_) public view returns (bool) {\n return walletAddressesByHolderId[holderId_].length > 0;\n }\n\n /**\n * @dev get holder addresses by Id\n * @param holderId_ holder id\n * @return array of addresses\n */\n function getHolderAddresses(\n uint256 holderId_\n ) external view onlyExistingHolder(holderId_) returns (address[] memory) {\n return walletAddressesByHolderId[holderId_];\n }\n\n // ============================================\n // HOLDER MANAGEMENT FUNCTIONS\n // ============================================\n \n /**\n * @dev create Holder from a given Address\n * @param addr_ address of holder\n * @return _holderId id of holder\n */\n function createHolderFromAddress(\n address addr_\n ) public onlyWalletsAdminOrTransferAdmin validAddress(addr_) returns (uint256 _holderId) {\n if (addressHasHolder(addr_)) {\n revert RestrictedLockupToken_HolderAlreadyExists();\n }\n _holderId = _createHolderFromAddress(addr_);\n }\n\n /**\n * @dev Append a new wallet to existing Holder\n * @param addr_ address of holder\n * @param holderId_ id of holder\n */\n function appendHolderAddress(address addr_, uint256 holderId_) public {\n _appendHolderAddress(addr_, holderId_);\n }\n\n /**\n * @dev add a Holder with multiple wallet addresses.\n * @param addresses_ array of addresses\n * @return _holderId id of holder\n */\n function addHolderWithAddresses(\n address[] calldata addresses_\n ) external onlyWalletsAdminOrTransferAdmin returns (uint256 _holderId) {\n uint256 _addrLen = addresses_.length;\n if (_addrLen == 0) {\n revert RestrictedLockupToken_InvalidAddressArray();\n }\n if (addresses_.length > MAX_WALLETS_PER_HOLDER) {\n revert RestrictedLockupToken_MaxHolderAddressesReached();\n }\n\n _holderId = createHolderFromAddress(addresses_[0]);\n // use first entry in array to create Holder id\n for (uint256 i = 1; i < _addrLen; ++i) {\n _appendHolderAddress(addresses_[i], _holderId);\n }\n }\n\n /**\n * @dev set max number of holder addresses\n * @param holderMax_ max number of holder addresses\n */\n function setHolderMax(uint256 holderMax_) external onlyTransferAdmin {\n if (holderMax_ < holderCount) {\n revert RestrictedLockupToken_NewHolderMaxMustExceedCurrentHolderCount();\n }\n holderMax = holderMax_;\n\n emit SetHolderMax(_msgSender(), holderMax_);\n }\n\n /**\n * @dev removeHolder from the system. Delete their wallet address from holder state. Timelock info remains for the wallet address.\n * @notice Holder can only be removed if it has no active timelocks\n * @param holderId_ holder id\n */\n function removeHolder(\n uint256 holderId_\n ) external onlyWalletsAdminOrTransferAdmin onlyExistingHolder(holderId_) {\n address[] storage addresses = walletAddressesByHolderId[holderId_];\n uint256 len = addresses.length;\n for (uint256 i; i < len; ++i) {\n address addr = addresses[i];\n // Check if address has active timelocks by calling main contract function\n if (_queryBalance(\"lockedBalanceOf(address)\", addr) > 0) {\n revert RestrictedLockupToken_CannotRemoveHolderWithActiveTimelocks();\n }\n delete holderIdByAddress[addr];\n }\n delete walletAddressesByHolderId[holderId_];\n holderCount--;\n\n emit RemoveHolder(_msgSender(), holderId_);\n }\n\n /**\n * @dev removeWalletFromHolder Remove wallet from a holderId association.\n * @dev use case - when a wallet is no longer owned by that holder.\n * @param addr_ address to remove\n */\n function removeWalletFromHolder(\n address addr_\n ) public onlyWalletsAdminOrTransferAdmin onlyExistingAddress(addr_) {\n uint256 _holderId = holderIdByAddress[addr_];\n address[] memory _holderWalletAddresses = walletAddressesByHolderId[\n _holderId\n ];\n uint256 _len = _holderWalletAddresses.length;\n if (_len == 1) {\n revert RestrictedLockupToken_CannotRemoveLastWallet();\n }\n\n address[] memory _newHolderWalletAddresses = new address[](_len - 1);\n uint256 _ind;\n for (uint8 i; i < _len; ++i) {\n if (_holderWalletAddresses[i] != addr_) {\n _newHolderWalletAddresses[_ind] = _holderWalletAddresses[i];\n _ind++;\n }\n }\n\n walletAddressesByHolderId[_holderId] = _newHolderWalletAddresses;\n delete holderIdByAddress[addr_];\n\n emit RemoveWalletFromHolder(_msgSender(), _holderId, addr_);\n }\n\n /**\n * @notice batch version of removeWalletFromHolder\n * @param addresses_ array of addresses to remove\n */\n function batchRemoveWalletFromHolder(\n address[] calldata addresses_\n ) external onlyWalletsAdminOrTransferAdmin {\n uint256 _len = addresses_.length;\n for (uint256 i; i < _len; ++i) {\n removeWalletFromHolder(addresses_[i]);\n }\n }\n\n /**\n * @dev Append a new wallet to existing Holder\n * @param addr_ address of holder\n * @param holderId_ id of holder\n */\n function _appendHolderAddress(\n address addr_,\n uint256 holderId_\n ) internal onlyExistingHolder(holderId_) onlyWalletsAdminOrTransferAdmin {\n if (addressHasHolder(addr_)) {\n revert RestrictedLockupToken_AddressAlreadyHasHolder();\n }\n if (walletAddressesByHolderId[holderId_].length >= MAX_WALLETS_PER_HOLDER) {\n revert RestrictedLockupToken_MaxHolderAddressesReached();\n }\n\n holderIdByAddress[addr_] = holderId_;\n walletAddressesByHolderId[holderId_].push(addr_);\n\n emit AppendHolderAddress(addr_, holderId_);\n }\n\n /**\n * @dev Allows the contract admin to pause transfers.\n * @param isPaused_ true to pause\n */\n function pause(bool isPaused_) external onlyContractAdminOrTransferAdmin {\n isPaused = isPaused_;\n emit Pause(_msgSender(), isPaused_);\n }\n\n /**\n * @dev Freezes or unfreezes an address.\n * Tokens in a frozen address cannot be transferred from until the address is unfrozen.\n * @param addr_ The address to be frozen.\n * @param status_ The frozenAddress status of the address. True means frozen false means not frozen.\n */\n function freeze(\n address addr_,\n bool status_\n ) public validAddress(addr_) onlyWalletsAdminOrTransferAdmin {\n frozenAddresses[addr_] = status_;\n emit AddressFrozen(_msgSender(), addr_, status_);\n }\n\n\n /**\n * @dev Count total holdings for an address by counting set bits in bitmap\n * @param who_ the address to get the holding count for\n * @return number of total unique holdings this wallet has across all token types\n */\n function holdingCountOf(address who_) external view returns (uint256) {\n uint256 count = 0;\n \n for (uint256 i = 0; i < walletIndexesByType[who_].length; i++) {\n count += walletIndexesByType[who_][i].popcount256B();\n }\n \n return count;\n }\n\n /**\n * @notice Get the struct details for an address's specific holding (optimized direct bitmap iteration)\n * @param who_ Address to check\n * @param index_ The index of the holding (position among wallet's holdings, not global index)\n * @return Struct with the attributes of the holding (balance, mintTimestamp, tokenType)\n */\n function holdingOf(\n address who_,\n uint256 index_\n ) public view returns (uint256, uint256, uint256) {\n // Find the global index for this wallet's targetIndex_-th holding\n uint256 globalIndex = _findGlobalIndexForWalletHolding(who_, index_);\n \n // If invalid index was provided, return zeros\n if (globalIndex == type(uint256).max) {\n return (0, 0, 0);\n }\n \n // Get element from global registry\n uint256 element = BitManipulationLib.getPackedElement(\n globalMintTimestamps[globalIndex / slotsPerWord],\n globalIndex % slotsPerWord,\n elementBitSize\n );\n (uint256 tokenType, uint256 daysAfterDeployment) = BitManipulationLib.unpackElement(element);\n \n // Get real balance (considering overflow)\n uint256 balance = _getRealBalance(who_, globalIndex);\n \n return (\n balance,\n BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay),\n tokenType\n );\n }\n\n /**\n * @dev Find the global index for a wallet's Nth holding\n * @param who_ The wallet address\n * @param targetIndex_ The position among wallet's holdings (0-indexed)\n * @return globalIndex The global index, or type(uint256).max if invalid\n */\n function _findGlobalIndexForWalletHolding(\n address who_,\n uint256 targetIndex_\n ) internal view returns (uint256) {\n uint256 foundCount = 0;\n \n // Iterate through bitmap words to find the targetIndex_-th set bit\n for (uint256 wordIndex = 0; wordIndex < walletIndexesByType[who_].length; wordIndex++) {\n uint256 word = walletIndexesByType[who_][wordIndex];\n if (word == 0) continue;\n \n // Count set bits in this word\n uint256 bitsInWord = word.popcount256B();\n \n if (foundCount + bitsInWord > targetIndex_) {\n // The target bit is in this word\n uint256 targetBitInWord = targetIndex_ - foundCount;\n \n // Find the targetBitInWord-th set bit in this word\n for (uint256 bitIndex = 0; bitIndex < 256; bitIndex++) {\n if ((word >> bitIndex) & 1 == 1) {\n if (targetBitInWord == 0) {\n // Found the target bit\n return wordIndex * 256 + bitIndex;\n }\n targetBitInWord--;\n }\n }\n }\n \n foundCount += bitsInWord;\n }\n \n // Index out of bounds\n return type(uint256).max;\n }\n\n /**\n * @dev Allows the contract admin to upgrade the transfer rules. onlyContractAdmin access.\n * The upgraded transfer rules must implement the ITransferRules interface which conforms to the ERC-1404 token standard.\n * @param newTransferRules The address of the deployed TransferRules contract.\n */\n function upgradeTransferRules(\n ITransferRules newTransferRules\n ) external onlyContractAdmin {\n address _newTransferRulesAddress = address(newTransferRules);\n if (_newTransferRulesAddress == address(0)) {\n revert RestrictedLockupToken_InvalidTransferRules();\n }\n if (\n !IERC165(_newTransferRulesAddress).supportsInterface(\n _ITRANSFER_RULES_INTERFACE_ID\n )\n ) {\n revert RestrictedLockupToken_NewTransferRulesContractDoesNotImplementITransferRules();\n }\n\n address oldRules = address(transferRules);\n\n transferRules = newTransferRules;\n emit Upgrade(_msgSender(), oldRules, _newTransferRulesAddress);\n }\n\n /**\n * @dev Allows the contract admin to upgrade the identity registry. onlyContractAdmin access.\n * The upgraded identity registry must implement the IIdentityRegistry interface.\n * @param newIdentityRegistry The address of the deployed IdentityRegistry contract.\n */\n function upgradeIdentityRegistry(\n IIdentityRegistry newIdentityRegistry\n ) external onlyContractAdmin {\n address _newIdentityRegistryAddress = address(newIdentityRegistry);\n if (_newIdentityRegistryAddress == address(0)) {\n revert RestrictedLockupToken_InvalidIdentityRegistry();\n }\n if (\n !IERC165(_newIdentityRegistryAddress).supportsInterface(\n _IIDENTITY_REGISTRY_INTERFACE_ID\n )\n ) {\n revert RestrictedLockupToken_NewIdentityRegistryContractDoesNotImplementIIdentityRegistry();\n }\n\n address oldIdentityRegistry = address(identityRegistry);\n\n identityRegistry = newIdentityRegistry;\n emit Upgrade(_msgSender(), oldIdentityRegistry, _newIdentityRegistryAddress);\n }\n\n /**\n * @dev Checks the status of an address to see if its frozen\n * @param addr_ The address to check\n * @return status Returns true if the address is frozen and false if its not frozen.\n */\n function getFrozenStatus(\n address addr_\n ) external view returns (bool status) {\n return frozenAddresses[addr_];\n }\n\n /**\n * @dev Get status of address\n * @param transferRulesAddr_ The address of the new transfer rules contract\n * @return _isValidTransferRules True if the transfer rules contract is valid\n */\n function isValidTransferRules(\n address transferRulesAddr_\n ) external view virtual returns (bool) {\n return\n IERC165(transferRulesAddr_).supportsInterface(\n _ITRANSFER_RULES_INTERFACE_ID\n );\n }\n\n /**\n * @dev Get status of identity registry address\n * @param identityRegistryAddr_ The address of the new identity registry contract\n * @return _isValidIdentityRegistry True if the identity registry contract is valid\n */\n function isValidIdentityRegistry(\n address identityRegistryAddr_\n ) external view virtual returns (bool) {\n return\n IERC165(identityRegistryAddr_).supportsInterface(\n _IIDENTITY_REGISTRY_INTERFACE_ID\n );\n }\n\n /**\n * @dev Get the count of existing token types\n * @return Number of token types that have been used\n */\n function existingTokenTypesCount() external view returns (uint256) {\n return _existingTokenTypes.length;\n }\n\n function existingTokenTypes(uint256 index) external view returns (uint256) {\n return _existingTokenTypes[index];\n }\n\n /**\n * @dev Check if a token type exists in the system\n * @param tokenType The token type to check\n * @return True if the token type has been used\n */\n function tokenTypeExists(uint256 tokenType) external view returns (bool) {\n return _tokenTypeExists[tokenType];\n }\n}\n"},"contracts/RestrictedSwap.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {ReentrancyGuard} from \"@openzeppelin/contracts/utils/ReentrancyGuard.sol\";\nimport {IERC165, ERC165} from \"@openzeppelin/contracts/utils/introspection/ERC165.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {IRestrictedSwap} from \"./interfaces/IRestrictedSwap.sol\";\nimport {RestrictedLockupToken} from \"./RestrictedLockupToken.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport {IERC1404} from \"./interfaces/IERC1404.sol\";\nimport {Pausable} from \"@openzeppelin/contracts/utils/Pausable.sol\";\nimport \"./AccessControlErrors.sol\";\n\ncontract RestrictedSwap is\n IRestrictedSwap,\n ReentrancyGuard,\n ERC165,\n Pausable,\n ERC2771Context\n{\n RestrictedLockupToken public immutable restrictedLockupToken;\n IAccessControl public accessControl;\n\n using SafeERC20 for IERC20;\n\n /// @dev swap number\n uint256 public _swapNumber = 0;\n bytes4 public immutable INTERFACE_ID;\n\n /// @dev swap number => swap\n mapping(uint256 => Swap) private _swap;\n\n /// @dev userAddress => tokenAddress => the required allowance of tokens to swapContract by active swaps\n mapping(address => mapping(address => uint256)) private _requiredAllowance; // specific to configurer. Is a requirement!\n\n /// @dev userAddress => pending restricted tokens to buy - configured by another users\n mapping(address => uint256) private _pendingBuys;\n\n // @dev userAddress => pending restricted tokens to sell - configured by current user\n mapping(address => uint256) private _pendingSells;\n\n error RestrictedSwap_AlreadyCanceled();\n error RestrictedSwap_AlreadyCompleted();\n error RestrictedSwap_InvalidRestrictedTokenAmount();\n error RestrictedSwap_InvalidQuoteTokenAmount();\n error RestrictedSwap_QuoteTokenMustNotSupportIERC1404();\n error RestrictedSwap_InvalidQuoteTokenSender();\n error RestrictedSwap_InvalidQuoteToken();\n error RestrictedSwap_InvalidRestrictedTokenSender();\n error RestrictedSwap_InsufficientQuoteTokenAmount();\n error RestrictedSwap_InsufficientRestrictedTokenAmount();\n error RestrictedSwap_InvalidTokenSender();\n error RestrictedSwap_InvalidSwapStatus();\n error RestrictedSwap_InsufficientRestrictedTokenAllowance();\n error RestrictedSwap_InsufficientQuoteTokenAllowance();\n error RestrictedSwap_InconsistentQuoteTokenAmount();\n error RestrictedSwap_InconsistentRestrictedTokenAmount();\n error RestrictedSwap_SwapNotConfigured();\n error RestrictedSwap_InvalidCanceler();\n error RestrictedSwap_InvalidSwapRecord();\n error RestrictedSwap_InvalidAccessControl();\n error RestrictedSwap_InvalidRestrictedLockupToken();\n error RestrictedSwap_InvalidTrustedForwarder();\n error RestrictedSwap_SwapExpired();\n\n modifier onlyValidSwap(uint256 swapNumber_) {\n Swap memory swap = _swap[swapNumber_];\n _onlyActiveSwap(swap.status);\n // Check if swap has expired (deadline > 0 and current time > deadline)\n _onlyValidDeadline(swap.deadline);\n _;\n }\n\n modifier onlyActiveSwap(uint256 swapNumber_) {\n Swap memory swap = _swap[swapNumber_];\n _onlyActiveSwap(swap.status);\n _;\n }\n function _onlyActiveSwap(SwapStatus status_) internal view {\n if (status_ == SwapStatus.Canceled) {\n revert RestrictedSwap_AlreadyCanceled();\n }\n if (status_ == SwapStatus.Complete) {\n revert RestrictedSwap_AlreadyCompleted();\n }\n }\n\n modifier onlyValidDeadline(uint256 deadline_) {\n _onlyValidDeadline(deadline_);\n _;\n }\n function _onlyValidDeadline(uint256 deadline_) internal view {\n if (deadline_ > 0 && block.timestamp > deadline_) {\n revert RestrictedSwap_SwapExpired();\n }\n }\n\n constructor(\n address restrictedLockupTokenAddress_,\n address trustedForwarder_,\n address accessControl_\n ) \n ReentrancyGuard() \n ERC2771Context(trustedForwarder_)\n {\n if (accessControl_ == address(0)) {\n revert RestrictedSwap_InvalidAccessControl();\n }\n if (restrictedLockupTokenAddress_ == address(0)) {\n revert RestrictedSwap_InvalidRestrictedLockupToken();\n }\n if (trustedForwarder_ == address(0)) {\n revert RestrictedSwap_InvalidTrustedForwarder();\n }\n restrictedLockupToken = RestrictedLockupToken(\n restrictedLockupTokenAddress_\n );\n INTERFACE_ID = type(IRestrictedSwap).interfaceId;\n \n accessControl = IAccessControl(accessControl_);\n }\n\n function _msgSender()\n internal\n view\n override(Context, ERC2771Context)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(Context, ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(Context, ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n\n /**\n * Support of ERC165\n * @dev See https://eips.ethereum.org/EIPS/eip-165\n * @param interfaceId The interface identifier, as specified in ERC-165\n */\n function supportsInterface(\n bytes4 interfaceId\n ) public view override(ERC165, IERC165) returns (bool) {\n return\n interfaceId == INTERFACE_ID || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev pause contract. Only contract admin.\n */\n function pause(bool isPaused_) external onlyContractAdminOrTransferAdmin {\n if (isPaused_) {\n _pause();\n } else {\n _unpause();\n }\n }\n\n modifier onlyContractAdminOrTransferAdmin() {\n _onlyContractAdminOrTransferAdmin();\n _;\n }\n\n function _onlyContractAdminOrTransferAdmin() internal view {\n if (\n !accessControl.hasRole(_msgSender(), accessControl.CONTRACT_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.TRANSFER_ADMIN_ROLE())\n ) {\n revert EasyAccessControl_DoesNotHaveContractOrTransferAdminRole(_msgSender());\n }\n }\n \n /**\n * @dev Get the swap number counter value\n */\n function swapNumber() external view returns (uint256) {\n return _swapNumber;\n }\n\n /**\n * @dev Get the allowance required for active swaps for the wallet address and token address.\n Can be used by UI client to calculate the required allowance before creating the swap\n * @param addr Wallet address\n * @param token ERC-20 token address\n */\n function requiredAllowance(\n address addr,\n address token\n ) external view returns (uint256) {\n return _requiredAllowance[addr][token];\n }\n\n\n /**\n * @dev Pending buy transactions for the wallet address\n * @param addr Buyer address\n * @return pending restricted tokens to buy\n */\n function pendingBuys(address addr) external view returns (uint256) {\n return _pendingBuys[addr];\n }\n\n /**\n * @dev Pending sell transactions for the wallet address\n * @param addr Seller address\n * @return pending restricted tokens to sell\n */\n function pendingSells(address addr) external view returns (uint256) {\n return _pendingSells[addr];\n }\n\n /**\n * @dev Calculate required allowance for counterparty across active swaps (paginated)\n * @param addr Counterparty address\n * @param token Token address\n * @param pageNumber Page number (0-based) to calculate allowance for\n * @param pageSize Number of swaps per page (recommended: 100-1000 for gas efficiency)\n * @return total Required allowance amount for the specified page of swaps\n */\n function calculateCounterpartyAllowance(\n address addr,\n address token,\n uint256 pageNumber,\n uint256 pageSize\n ) external view returns (uint256 total) {\n // Calculate pagination bounds\n if (pageSize == 0) {\n return 0; // Invalid page size\n }\n \n uint256 startSwap = pageNumber * pageSize + 1;\n uint256 endSwap = startSwap + pageSize - 1;\n \n // Ensure we don't exceed the total number of swaps\n if (endSwap > _swapNumber) {\n endSwap = _swapNumber;\n }\n \n // If startSwap is beyond total swaps, return 0\n if (startSwap > _swapNumber) {\n return 0;\n }\n \n // Iterate through the specified page range\n for (uint256 i = startSwap; i <= endSwap; i++) {\n Swap storage swap = _swap[i];\n if (swap.status == SwapStatus.SellConfigured) {\n // Check if addr is counterparty for quote token in sell swaps\n if (swap.quoteTokenSender == addr && swap.quoteToken == token) {\n total += swap.quoteTokenAmount;\n }\n } else if (swap.status == SwapStatus.BuyConfigured) {\n // Check if addr is counterparty for restricted token in buy swaps\n if (swap.restrictedTokenSender == addr && token == address(restrictedLockupToken)) {\n total += swap.restrictedTokenAmount;\n }\n }\n }\n }\n\n /**\n * @dev Configure sell and emit an event with new swap number\n * Restricted Tokens should be approved to this contract before calling this function\n * @param restrictedTokenAmount the required amount for the erc1404Sender to send\n * @param quoteToken the address of an erc1404 or erc20 that will be swapped\n * @param quoteTokenSender the address that is approved to fund quoteToken\n * @param quoteTokenAmount the required amount of quoteToken to swap\n * @param deadline the deadline for swap expiration (0 means no deadline)\n */\n function configureSell(\n uint256 restrictedTokenAmount,\n address quoteToken,\n address quoteTokenSender,\n uint256 quoteTokenAmount,\n uint256 deadline\n ) external override whenNotPaused onlyValidDeadline(deadline) {\n if (quoteTokenSender == address(0)) {\n revert RestrictedSwap_InvalidQuoteTokenSender();\n }\n if (quoteToken == address(0)) {\n revert RestrictedSwap_InvalidQuoteToken();\n }\n address msgSender = _msgSender();\n if (\n restrictedLockupToken.allowance(msgSender, address(this)) <\n _requiredAllowance[msgSender][address(restrictedLockupToken)] +\n restrictedTokenAmount\n ) {\n revert RestrictedSwap_InsufficientRestrictedTokenAllowance();\n }\n\n _pendingSells[msgSender] += restrictedTokenAmount;\n\n _configureSwap(\n msgSender,\n quoteTokenSender,\n quoteToken,\n restrictedTokenAmount,\n quoteTokenAmount,\n SwapStatus.SellConfigured,\n deadline\n );\n }\n\n /**\n * @dev Configure buy and emit an event with new swap number\n * @param restrictedTokenAmount the required amount for the erc1404Sender to send\n * @param restrictedTokenSender restricted token sender\n * @param quoteToken the address of an erc1404 or erc20 that will be swapped\n * @param quoteTokenAmount the required amount of quoteToken to swap\n * @param deadline the deadline for swap expiration (0 means no deadline)\n */\n function configureBuy(\n uint256 restrictedTokenAmount,\n address restrictedTokenSender,\n address quoteToken,\n uint256 quoteTokenAmount,\n uint256 deadline\n ) external override whenNotPaused onlyValidDeadline(deadline) {\n if (restrictedTokenSender == address(0)) {\n revert RestrictedSwap_InvalidRestrictedTokenSender();\n }\n if (quoteToken == address(0)) {\n revert RestrictedSwap_InvalidQuoteToken();\n }\n address msgSender = _msgSender();\n if (IERC20(quoteToken).balanceOf(msgSender) < quoteTokenAmount) {\n revert RestrictedSwap_InsufficientQuoteTokenAmount();\n }\n if (\n IERC20(quoteToken).allowance(msgSender, address(this)) <\n _requiredAllowance[msgSender][quoteToken] + quoteTokenAmount\n ) {\n revert RestrictedSwap_InsufficientQuoteTokenAllowance();\n }\n\n _pendingBuys[restrictedTokenSender] += restrictedTokenAmount;\n\n _configureSwap(\n restrictedTokenSender,\n msgSender,\n quoteToken,\n restrictedTokenAmount,\n quoteTokenAmount,\n SwapStatus.BuyConfigured,\n deadline\n );\n }\n\n /**\n * @dev Complete swap with quote token\n * @param swapNumber_ swap number\n */\n function completeSwapWithQuoteToken(\n uint256 swapNumber_\n ) external override nonReentrant onlyValidSwap(swapNumber_) whenNotPaused {\n Swap memory swap = _swap[swapNumber_];\n\n if (swap.quoteTokenSender != _msgSender()) {\n revert RestrictedSwap_InvalidTokenSender();\n }\n if (swap.status != SwapStatus.SellConfigured) {\n revert RestrictedSwap_InvalidSwapStatus();\n }\n if (\n IERC20(swap.quoteToken).balanceOf(swap.quoteTokenSender) <\n swap.quoteTokenAmount\n ) {\n revert RestrictedSwap_InsufficientQuoteTokenAmount();\n }\n if (\n restrictedLockupToken.balanceOf(swap.restrictedTokenSender) <\n swap.restrictedTokenAmount\n ) {\n revert RestrictedSwap_InsufficientRestrictedTokenAmount();\n }\n\n uint256 code = restrictedLockupToken.detectTransferRestriction(\n swap.restrictedTokenSender,\n swap.quoteTokenSender,\n swap.restrictedTokenAmount\n );\n\n require(\n restrictedLockupToken.transferRules().checkSuccess(code),\n restrictedLockupToken.transferRules().messageForTransferRestriction(\n code\n )\n );\n\n _pendingSells[swap.restrictedTokenSender] -= swap.restrictedTokenAmount;\n\n _completeSwap(swapNumber_);\n }\n\n /**\n * @dev Complete swap with restricted token\n * @param swapNumber_ swap number\n */\n function completeSwapWithRestrictedToken(\n uint256 swapNumber_\n ) external override nonReentrant onlyValidSwap(swapNumber_) whenNotPaused {\n Swap memory swap = _swap[swapNumber_];\n\n if (swap.restrictedTokenSender != _msgSender()) {\n revert RestrictedSwap_InvalidTokenSender();\n }\n if (swap.status != SwapStatus.BuyConfigured) {\n revert RestrictedSwap_InvalidSwapStatus();\n }\n if (\n IERC20(swap.quoteToken).balanceOf(swap.quoteTokenSender) <\n swap.quoteTokenAmount\n ) {\n revert RestrictedSwap_InsufficientQuoteTokenAmount();\n }\n if (\n restrictedLockupToken.balanceOf(swap.restrictedTokenSender) <\n swap.restrictedTokenAmount\n ) {\n revert RestrictedSwap_InsufficientRestrictedTokenAmount();\n }\n\n uint256 code = restrictedLockupToken.detectTransferRestriction(\n swap.restrictedTokenSender,\n swap.quoteTokenSender,\n swap.restrictedTokenAmount\n );\n\n require(\n restrictedLockupToken.transferRules().checkSuccess(code),\n restrictedLockupToken.transferRules().messageForTransferRestriction(\n code\n )\n );\n\n _pendingBuys[swap.restrictedTokenSender] -= swap.restrictedTokenAmount;\n\n _completeSwap(swapNumber_);\n }\n\n /**\n * @dev cancel swap\n * @param swapNumber_ swap number\n */\n function cancelSwap(\n uint256 swapNumber_\n ) external override nonReentrant onlyActiveSwap(swapNumber_) {\n Swap memory swap = _swap[swapNumber_];\n\n if (\n swap.restrictedTokenSender == address(0) ||\n swap.quoteTokenSender == address(0)\n ) {\n revert RestrictedSwap_SwapNotConfigured();\n }\n\n // Check if swap has expired\n bool isExpired = swap.deadline > 0 && block.timestamp > swap.deadline;\n\n address msgSender = _msgSender();\n // Update pending amounts and required allowances\n if (swap.status == SwapStatus.SellConfigured) {\n if (!isExpired && msgSender != swap.restrictedTokenSender) {\n revert RestrictedSwap_InvalidCanceler();\n }\n _pendingSells[swap.restrictedTokenSender] -= swap\n .restrictedTokenAmount;\n _requiredAllowance[swap.restrictedTokenSender][\n address(restrictedLockupToken)\n ] -= swap.restrictedTokenAmount;\n } else if (swap.status == SwapStatus.BuyConfigured) {\n if (!isExpired && msgSender != swap.quoteTokenSender) {\n revert RestrictedSwap_InvalidCanceler();\n }\n _pendingBuys[swap.restrictedTokenSender] -= swap\n .restrictedTokenAmount;\n _requiredAllowance[swap.quoteTokenSender][swap.quoteToken] -= swap\n .quoteTokenAmount;\n }\n\n _swap[swapNumber_].status = SwapStatus.Canceled;\n\n emit SwapCanceled(msgSender, swapNumber_);\n }\n\n /**\n * @dev Returns the swap status if exists\n * @param swapNumber_ swap number\n * @return SwapStatus status of the swap record\n */\n function swapStatus(\n uint256 swapNumber_\n ) external view override returns (SwapStatus) {\n if (_swap[swapNumber_].restrictedTokenSender == address(0)) {\n revert RestrictedSwap_InvalidSwapRecord();\n }\n return _swap[swapNumber_].status;\n }\n\n /**\n * @dev Check if a swap has expired based on its deadline\n * @param swapNumber_ swap number\n * @return bool true if the swap has expired, false otherwise\n */\n function isSwapExpired(uint256 swapNumber_) external view override returns (bool) {\n Swap memory swap = _swap[swapNumber_];\n if (swap.restrictedTokenSender == address(0)) {\n revert RestrictedSwap_InvalidSwapRecord();\n }\n // Check if swap has expired (deadline > 0 and current time > deadline)\n return swap.deadline > 0 && block.timestamp > swap.deadline;\n }\n\n\n /**\n * @dev Configures swap and emits an event. This function does not fund tokens. Only for swap configuration.\n * @param restrictedTokenSender restricted token sender\n * @param quoteTokenSender quote token sender\n * @param quoteToken quote token\n * @param restrictedTokenAmount restricted token amount\n * @param quoteTokenAmount quote token amount\n * @param configuror initial status or configuration type for the swap\n * @param deadline the deadline for swap expiration (0 means no deadline)\n */\n function _configureSwap(\n address restrictedTokenSender,\n address quoteTokenSender,\n address quoteToken,\n uint256 restrictedTokenAmount,\n uint256 quoteTokenAmount,\n SwapStatus configuror,\n uint256 deadline\n ) private {\n if (restrictedTokenAmount == 0) {\n revert RestrictedSwap_InvalidRestrictedTokenAmount();\n }\n if (quoteTokenAmount == 0) {\n revert RestrictedSwap_InvalidQuoteTokenAmount();\n }\n\n uint256 code = restrictedLockupToken.detectTransferRestriction(\n restrictedTokenSender,\n quoteTokenSender,\n restrictedTokenAmount\n );\n\n string memory message = restrictedLockupToken\n .messageForTransferRestriction(code);\n\n require(\n restrictedLockupToken.transferRules().checkSuccess(code),\n message\n );\n\n try\n IERC165(quoteToken).supportsInterface(type(IERC1404).interfaceId)\n returns (bool supported1404) {\n if (supported1404) {\n revert RestrictedSwap_QuoteTokenMustNotSupportIERC1404();\n }\n // happy path, ie ERC1404 is not supported, even if ERC165 is supported\n } catch {\n // happy path, ie ERC165 is not supported. Most ERC-20 tokens do not support ERC-165\n }\n\n _swapNumber += 1;\n\n Swap storage swap = _swap[_swapNumber];\n swap.restrictedTokenSender = restrictedTokenSender;\n swap.restrictedTokenAmount = restrictedTokenAmount;\n swap.quoteTokenSender = quoteTokenSender;\n swap.quoteTokenAmount = quoteTokenAmount;\n swap.quoteToken = quoteToken;\n swap.status = configuror;\n swap.deadline = deadline;\n\n if (configuror == SwapStatus.SellConfigured) {\n _requiredAllowance[swap.restrictedTokenSender][\n address(restrictedLockupToken)\n ] += restrictedTokenAmount;\n } else {\n _requiredAllowance[swap.quoteTokenSender][\n quoteToken\n ] += quoteTokenAmount;\n }\n\n emit SwapConfigured(\n _swapNumber,\n restrictedTokenSender,\n restrictedTokenAmount,\n quoteToken,\n quoteTokenSender,\n quoteTokenAmount,\n deadline\n );\n }\n\n /**\n * @dev Complete swap and emit an event\n * @param swapNumber_ swap number\n */\n function _completeSwap(uint256 swapNumber_) private {\n Swap memory swap = _swap[swapNumber_];\n\n uint256 balanceBeforeTransfer = IERC20(swap.quoteToken).balanceOf(\n swap.restrictedTokenSender\n );\n\n IERC20(swap.quoteToken).safeTransferFrom(\n swap.quoteTokenSender,\n swap.restrictedTokenSender,\n swap.quoteTokenAmount\n );\n\n uint256 balanceAfterTransfer = IERC20(swap.quoteToken).balanceOf(\n swap.restrictedTokenSender\n );\n\n if (\n balanceAfterTransfer - balanceBeforeTransfer !=\n swap.quoteTokenAmount\n ) {\n revert RestrictedSwap_InconsistentQuoteTokenAmount();\n }\n\n uint256 _restrictedTokenBalanceBefore = restrictedLockupToken.balanceOf(\n swap.quoteTokenSender\n );\n restrictedLockupToken.transferFrom(\n swap.restrictedTokenSender,\n swap.quoteTokenSender,\n swap.restrictedTokenAmount\n );\n\n uint256 _restrictedTokenBalanceAfter = restrictedLockupToken.balanceOf(\n swap.quoteTokenSender\n );\n if (\n _restrictedTokenBalanceAfter - _restrictedTokenBalanceBefore !=\n swap.restrictedTokenAmount\n ) {\n revert RestrictedSwap_InconsistentRestrictedTokenAmount();\n }\n\n if (swap.status == SwapStatus.SellConfigured) {\n _requiredAllowance[swap.restrictedTokenSender][\n address(restrictedLockupToken)\n ] -= swap.restrictedTokenAmount;\n } else if (swap.status == SwapStatus.BuyConfigured) {\n _requiredAllowance[swap.quoteTokenSender][swap.quoteToken] -= swap\n .quoteTokenAmount;\n }\n\n _swap[swapNumber_].status = SwapStatus.Complete;\n\n emit SwapComplete(\n swapNumber_,\n swap.restrictedTokenSender,\n swap.restrictedTokenAmount,\n swap.quoteTokenSender,\n swap.quoteToken,\n swap.quoteTokenAmount,\n swap.deadline\n );\n }\n}\n"},"contracts/SnapshotPeriods.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport {RestrictedLockupToken} from \"./RestrictedLockupToken.sol\";\nimport {ISnapshotPeriods} from \"./interfaces/ISnapshotPeriods.sol\";\nimport {ERC165} from \"@openzeppelin/contracts/utils/introspection/ERC165.sol\";\n\ncontract SnapshotPeriods is ISnapshotPeriods, ERC165 {\n bytes4 public immutable INTERFACE_ID;\n\n mapping(address token => mapping(address account => WalletInfo))\n public walletInfo;\n mapping(address token => mapping(address account => Period[]))\n public walletPeriods;\n\n // total supply periods\n mapping(address token => Period[]) public totalSupplyPeriods;\n // total supply info\n mapping(address token => WalletInfo) public totalSupplyInfo;\n\n error SnapshotPeriods_InvalidToken();\n error SnapshotPeriods_UnauthorizedCaller();\n error SnapshotPeriods_TimestampInFuture();\n constructor() {\n INTERFACE_ID = type(ISnapshotPeriods).interfaceId;\n }\n\n /**\n * Support of ERC165\n * @dev See https://eips.ethereum.org/EIPS/eip-165\n * @param interfaceId The interface identifier, as specified in ERC-165\n */\n function supportsInterface(\n bytes4 interfaceId\n ) public view override returns (bool) {\n return\n interfaceId == INTERFACE_ID || super.supportsInterface(interfaceId);\n }\n\n /// @inheritdoc ISnapshotPeriods\n function getPastBalanceOf(\n address token,\n address account,\n uint256 timestamp\n ) public view returns (uint256) {\n if (timestamp >= block.timestamp) {\n revert SnapshotPeriods_TimestampInFuture();\n }\n return ownershipForPeriod(token, account, timestamp, timestamp + 1);\n }\n\n /// @inheritdoc ISnapshotPeriods\n function getPastTotalSupply(\n address token,\n uint256 timestamp\n ) public view returns (uint256) {\n if (timestamp >= block.timestamp) {\n revert SnapshotPeriods_TimestampInFuture();\n }\n return totalOwnershipForPeriod(token, timestamp, timestamp + 1);\n }\n\n /// @inheritdoc ISnapshotPeriods\n function onUpdate(\n address tokenAddress,\n address from,\n address to,\n uint256 amount\n ) external {\n if (msg.sender != tokenAddress) {\n revert SnapshotPeriods_UnauthorizedCaller();\n }\n if (amount == 0) {\n return;\n }\n\n if (from != address(0)) {\n _updatePeriods(tokenAddress, from, amount);\n }\n if (to != address(0)) {\n _updatePeriods(\n tokenAddress,\n to,\n 0 // for recipient, we don't need to validate if new balance is 0\n );\n }\n // update total supply periods only for mint or burn when total supply is changed\n if (from == address(0) || to == address(0)) {\n _updateTotalSupplyPeriods(\n tokenAddress,\n to == address(0) ? amount : 0 // for burn, we need to clear latestAccrualTimestamp if new total supply is 0\n );\n }\n }\n\n function calculateOwnershipForPeriod(\n uint256 amount,\n uint256 periodStart,\n uint256 periodEnd\n ) public pure returns (uint256) {\n return amount * (periodEnd - periodStart);\n }\n\n function _updateTotalSupplyPeriods(\n address tokenAddress,\n uint256 amount\n ) internal {\n uint256 totalAccruedOwnership = totalSupplyInfo[tokenAddress]\n .totalAccruedOwnership;\n RestrictedLockupToken token = RestrictedLockupToken(tokenAddress);\n uint256 currentTimestamp = block.timestamp;\n if (\n totalSupplyInfo[tokenAddress].latestAccrualTimestamp >=\n currentTimestamp\n ) {\n return; // not need to update periods\n }\n\n uint256 curentTotalSupply = token.totalSupply();\n if (totalSupplyInfo[tokenAddress].latestAccrualTimestamp > 0) {\n uint256 periodOwnership = calculateOwnershipForPeriod(\n curentTotalSupply,\n totalSupplyInfo[tokenAddress].latestAccrualTimestamp,\n currentTimestamp\n );\n totalAccruedOwnership += periodOwnership;\n totalSupplyPeriods[tokenAddress].push(\n Period({\n start: totalSupplyInfo[tokenAddress].latestAccrualTimestamp,\n end: currentTimestamp,\n totalAccruedOwnership: totalAccruedOwnership,\n accruedOwnership: periodOwnership\n })\n );\n totalSupplyInfo[tokenAddress].periodCount++;\n totalSupplyInfo[tokenAddress]\n .totalAccruedOwnership = totalAccruedOwnership;\n }\n\n if (amount != 0 && curentTotalSupply == amount) {\n totalSupplyInfo[tokenAddress].latestAccrualTimestamp = 0;\n } else {\n totalSupplyInfo[tokenAddress]\n .latestAccrualTimestamp = currentTimestamp;\n }\n }\n\n function _updatePeriods(\n address tokenAddress,\n address account,\n uint256 amount\n ) internal {\n WalletInfo memory accountInfo = walletInfo[tokenAddress][account];\n\n uint256 totalAccruedOwnership = accountInfo.totalAccruedOwnership;\n RestrictedLockupToken token = RestrictedLockupToken(tokenAddress);\n uint256 currentTimestamp = block.timestamp;\n if (accountInfo.latestAccrualTimestamp >= currentTimestamp) {\n return; // not need to update periods\n }\n\n uint256 currentBalance = token.balanceOf(account);\n if (accountInfo.latestAccrualTimestamp > 0) {\n uint256 periodOwnership = calculateOwnershipForPeriod(\n currentBalance,\n accountInfo.latestAccrualTimestamp,\n currentTimestamp\n );\n totalAccruedOwnership += periodOwnership;\n walletPeriods[tokenAddress][account].push(\n Period({\n start: accountInfo.latestAccrualTimestamp,\n end: currentTimestamp,\n totalAccruedOwnership: totalAccruedOwnership,\n accruedOwnership: periodOwnership\n })\n );\n accountInfo.periodCount++;\n accountInfo.totalAccruedOwnership = totalAccruedOwnership;\n }\n\n if (amount != 0 && currentBalance == amount) {\n accountInfo.latestAccrualTimestamp = 0;\n } else {\n accountInfo.latestAccrualTimestamp = currentTimestamp;\n }\n\n walletInfo[tokenAddress][account] = accountInfo;\n }\n\n // @dev calculate total ownership at timestamp\n // @param token The token address\n // @param timestamp The timestamp to calculate the ownership at\n // @return ownership The ownership at the timestamp\n function totalOwnershipAt(\n address token,\n uint256 timestamp\n ) public view returns (uint256) {\n WalletInfo memory totalSupplyInfoNow = supplyInfo(token);\n if (\n totalSupplyInfoNow.periodCount == 0 &&\n totalSupplyInfoNow.latestAccrualTimestamp == 0\n ) {\n return 0;\n }\n // end timestamp can be before last account accrual timestamp\n // in this case, we should find period which includes timestamp and use previous period\n uint256 periodIdx = type(uint256).max;\n uint256 totalSupplyAccruedOwnership = totalSupplyInfoNow\n .totalAccruedOwnership;\n timestamp = timestamp > block.timestamp ? block.timestamp : timestamp;\n if (\n (timestamp <= totalSupplyInfoNow.latestAccrualTimestamp ||\n totalSupplyInfoNow.latestAccrualTimestamp == 0) &&\n totalSupplyInfoNow.periodCount > 0\n ) {\n periodIdx = findTotalSupplyPeriodIndexForTimestamp(\n token,\n timestamp\n );\n if (periodIdx == type(uint256).max) {\n // no previous period found -> no accrued ownership\n totalSupplyAccruedOwnership = 0;\n }\n if (periodIdx != type(uint256).max) {\n // we found previous period\n Period memory period = totalSupplyPeriodByIndex(\n token,\n periodIdx\n );\n totalSupplyAccruedOwnership = period.totalAccruedOwnership;\n }\n }\n\n bool isIncludedPeriodExists = false;\n // if included period already cached\n if (\n (periodIdx != type(uint256).max &&\n totalSupplyInfoNow.periodCount > periodIdx + 1) ||\n (periodIdx == type(uint256).max &&\n totalSupplyInfoNow.periodCount > 0)\n ) {\n Period memory period = totalSupplyPeriodByIndex(\n token,\n periodIdx == type(uint256).max ? 0 : periodIdx + 1\n );\n isIncludedPeriodExists =\n period.start < timestamp &&\n timestamp <= period.end;\n if (isIncludedPeriodExists) {\n uint256 portion = timestamp - period.start;\n uint256 portionOfOwnership = (period.accruedOwnership *\n portion) / (period.end - period.start);\n totalSupplyAccruedOwnership += portionOfOwnership;\n }\n }\n\n // ( ]( ]( timestamp ..... now\n // | latestAccrualTimestamp\n // if included period not cached - still ongoing\n if (\n totalSupplyInfoNow.latestAccrualTimestamp != 0 &&\n totalSupplyInfoNow.latestAccrualTimestamp < timestamp &&\n periodIdx == type(uint256).max\n ) {\n RestrictedLockupToken restrictedLockupToken = RestrictedLockupToken(\n token\n );\n uint256 ownershipAccrued = calculateOwnershipForPeriod(\n restrictedLockupToken.totalSupply(),\n totalSupplyInfoNow.latestAccrualTimestamp,\n timestamp\n );\n totalSupplyAccruedOwnership += ownershipAccrued;\n }\n\n return totalSupplyAccruedOwnership;\n }\n\n // @dev calculate ownership at timestamp\n // @param token The token address\n // @param account The account address\n // @param timestamp The timestamp to calculate the ownership at\n // @return ownership The ownership at the timestamp\n function ownershipAt(\n address token,\n address account,\n uint256 timestamp\n ) public view returns (uint256) {\n RestrictedLockupToken restrictedLockupToken = RestrictedLockupToken(\n token\n );\n WalletInfo memory accountInfo = addressInfo(token, account);\n if (\n accountInfo.periodCount == 0 &&\n accountInfo.latestAccrualTimestamp == 0\n ) {\n return 0;\n }\n // end timestamp can be before last account accrual timestamp\n // in this case, we should find period which includes timestamp and use previous period\n uint256 periodIdx = type(uint256).max;\n uint256 accountAccruedOwnership = accountInfo.totalAccruedOwnership;\n timestamp = timestamp > block.timestamp ? block.timestamp : timestamp;\n if (\n (timestamp <= accountInfo.latestAccrualTimestamp ||\n accountInfo.latestAccrualTimestamp == 0) &&\n accountInfo.periodCount > 0\n ) {\n periodIdx = findPeriodIndexForTimestamp(token, account, timestamp);\n if (periodIdx == type(uint256).max) {\n // no previous period found -> no accrued ownership\n accountAccruedOwnership = 0;\n }\n if (periodIdx != type(uint256).max) {\n // we found previous period\n Period memory period = walletPeriodByIndex(\n token,\n account,\n periodIdx\n );\n accountAccruedOwnership = period.totalAccruedOwnership;\n }\n }\n\n bool isIncludedPeriodExists = false;\n // if included period already cached\n if (\n (periodIdx != type(uint256).max &&\n accountInfo.periodCount > periodIdx + 1) ||\n (periodIdx == type(uint256).max && accountInfo.periodCount > 0)\n ) {\n Period memory period = walletPeriodByIndex(\n token,\n account,\n periodIdx == type(uint256).max ? 0 : periodIdx + 1\n );\n isIncludedPeriodExists =\n period.start < timestamp &&\n timestamp <= period.end;\n if (isIncludedPeriodExists) {\n uint256 portion = timestamp - period.start;\n uint256 portionOfOwnership = (period.accruedOwnership *\n portion) / (period.end - period.start);\n accountAccruedOwnership += portionOfOwnership;\n }\n }\n\n // ( ]( ]( timestamp ..... now\n // | latestAccrualTimestamp\n // if included period not cached - still ongoing\n if (\n accountInfo.latestAccrualTimestamp != 0 &&\n accountInfo.latestAccrualTimestamp < timestamp &&\n (periodIdx == type(uint256).max)\n ) {\n uint256 ownershipAccrued = calculateOwnershipForPeriod(\n restrictedLockupToken.balanceOf(account),\n accountInfo.latestAccrualTimestamp,\n timestamp\n );\n accountAccruedOwnership += ownershipAccrued;\n }\n\n return accountAccruedOwnership;\n }\n\n function totalOwnershipForPeriod(\n address token,\n uint256 startTimestamp,\n uint256 endTimestamp\n ) public view returns (uint256) {\n if (startTimestamp >= endTimestamp) {\n return 0;\n }\n\n return\n totalOwnershipAt(token, endTimestamp) -\n totalOwnershipAt(token, startTimestamp);\n }\n\n function ownershipForPeriod(\n address token,\n address account,\n uint256 startTimestamp,\n uint256 endTimestamp\n ) public view returns (uint256) {\n if (startTimestamp >= endTimestamp) {\n return 0;\n }\n\n return\n ownershipAt(token, account, endTimestamp) -\n ownershipAt(token, account, startTimestamp);\n }\n\n function addressInfo(\n address token,\n address account\n ) public view returns (WalletInfo memory) {\n return walletInfo[token][account];\n }\n\n function supplyInfo(address token) public view returns (WalletInfo memory) {\n return totalSupplyInfo[token];\n }\n\n function walletPeriodByIndex(\n address token,\n address account,\n uint256 index\n ) public view returns (Period memory) {\n return walletPeriods[token][account][index];\n }\n\n function totalSupplyPeriodByIndex(\n address token,\n uint256 index\n ) public view returns (Period memory) {\n return totalSupplyPeriods[token][index];\n }\n\n /**\n * @dev Find the period index for a wallet address that the nearest period that ends before the timestamp\n * if no period includes it\n * @param token The token address\n * @param account The wallet address to check\n * @param timestamp The timestamp to find the period for\n * @return periodIndex The index of the period that nearest to the timestamp,\n * or type(uint256).max if no suitable period is found\n */\n function findPeriodIndexForTimestamp(\n address token,\n address account,\n uint256 timestamp\n ) public view returns (uint256 periodIndex) {\n WalletInfo memory accountWalletInfo = addressInfo(token, account);\n\n // If no periods exist for this wallet, return max value to indicate not found\n if (accountWalletInfo.periodCount == 0) {\n return type(uint256).max;\n }\n\n // Binary search to efficiently find the period\n uint256 low = 0;\n uint256 high = accountWalletInfo.periodCount - 1;\n uint256 nearestLeftPeriod = type(uint256).max;\n\n while (low <= high) {\n uint256 mid = (low + high) / 2;\n Period memory currentPeriod = walletPeriods[token][account][mid];\n\n // Check if timestamp is in this period (timestamp > start && timestamp <= end)\n if (\n timestamp > currentPeriod.start &&\n timestamp <= currentPeriod.end\n ) {\n return mid > 0 ? mid - 1 : type(uint256).max; // return previous period\n }\n\n // If timestamp is before this period's start, search in the lower half\n if (timestamp <= currentPeriod.start) {\n // If we're at index 0, we can't go lower\n if (mid == 0) {\n break;\n }\n high = mid - 1;\n }\n // If timestamp is after this period's end, search in the upper half\n // and update the nearest left period if this period ends before the timestamp\n else {\n // This period ends before the timestamp, so it's a candidate for nearest left period\n if (\n currentPeriod.end < timestamp &&\n (nearestLeftPeriod == type(uint256).max ||\n currentPeriod.end >\n walletPeriods[token][account][nearestLeftPeriod].end)\n ) {\n nearestLeftPeriod = mid;\n }\n low = mid + 1;\n }\n }\n\n // If we found a nearest left period, return it\n if (nearestLeftPeriod != type(uint256).max) {\n return nearestLeftPeriod;\n }\n\n // If we get here, no period includes the timestamp and no nearest left period was found\n return type(uint256).max;\n }\n\n /**\n * @dev Find the period index for the total supply that ends before the timestamp\n * @param token The token address\n * @param timestamp The timestamp to find the period for\n * @return periodIndex The index of the previous period if timestamp is within a period,\n * or the nearest period that ends before the timestamp if no period includes it,\n * or type(uint256).max if no suitable period is found\n */\n function findTotalSupplyPeriodIndexForTimestamp(\n address token,\n uint256 timestamp\n ) public view returns (uint256 periodIndex) {\n // If no periods exist for total supply, return max value to indicate not found\n if (totalSupplyInfo[token].periodCount == 0) {\n return type(uint256).max;\n }\n\n // Binary search to efficiently find the period\n uint256 low = 0;\n uint256 high = totalSupplyInfo[token].periodCount - 1;\n uint256 nearestLeftPeriod = type(uint256).max;\n\n while (low <= high) {\n uint256 mid = (low + high) / 2;\n Period memory currentPeriod = totalSupplyPeriods[token][mid];\n\n // Check if timestamp is in this period (timestamp > start && timestamp <= end)\n if (\n timestamp > currentPeriod.start &&\n timestamp <= currentPeriod.end\n ) {\n return mid > 0 ? mid - 1 : type(uint256).max; // return previous period\n }\n\n // If timestamp is before this period's start, search in the lower half\n if (timestamp <= currentPeriod.start) {\n // If we're at index 0, we can't go lower\n if (mid == 0) {\n break;\n }\n high = mid - 1;\n }\n // If timestamp is after this period's end, search in the upper half\n // and update the nearest left period if this period ends before the timestamp\n else {\n // This period ends before the timestamp, so it's a candidate for nearest left period\n if (\n currentPeriod.end < timestamp &&\n (nearestLeftPeriod == type(uint256).max ||\n currentPeriod.end >\n totalSupplyPeriods[token][nearestLeftPeriod].end)\n ) {\n nearestLeftPeriod = mid;\n }\n low = mid + 1;\n }\n }\n\n // If we found a nearest left period, return it\n if (nearestLeftPeriod != type(uint256).max) {\n return nearestLeftPeriod;\n }\n\n // If we get here, no period includes the timestamp and no nearest left period was found\n return type(uint256).max;\n }\n\n function walletPeriodCount(\n address token,\n address account\n ) public view returns (uint256) {\n return walletInfo[token][account].periodCount;\n }\n\n function totalSupplyPeriodCount(\n address token\n ) public view returns (uint256) {\n return totalSupplyInfo[token].periodCount;\n }\n\n function totalSupplyPeriodAccruedOwnership(\n address token,\n uint256 index\n ) public view returns (uint256) {\n return totalSupplyPeriods[token][index].totalAccruedOwnership;\n }\n\n function walletPeriodAccruedOwnership(\n address token,\n address account,\n uint256 index\n ) public view returns (uint256) {\n return walletPeriods[token][account][index].totalAccruedOwnership;\n }\n}\n"},"contracts/Storage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {ITransferRules} from \"./interfaces/ITransferRules.sol\";\nimport {ISnapshotPeriods} from \"./interfaces/ISnapshotPeriods.sol\";\nimport {IIdentityRegistry} from \"./interfaces/IIdentityRegistry.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport {BitManipulationLib} from \"./libraries/BitManipulationLib.sol\";\nimport \"./AccessControlErrors.sol\";\n\n/**\n * @title Storage\n * @notice Shared storage contract for RestrictedLockupToken and its extensions\n * @dev Both main contract and extensions inherit from this to ensure identical storage layout\n */\ncontract Storage {\n using BitManipulationLib for uint256;\n\n struct ReleaseSchedule {\n uint256 releaseCount;\n uint256 delayUntilFirstReleaseInSeconds;\n uint256 initialReleasePortionInBips;\n uint256 periodBetweenReleasesInSeconds;\n }\n\n struct Timelock {\n address funder;\n uint256 scheduleId;\n uint256 commencementTimestamp;\n uint256 tokensTransferred;\n uint256 totalAmount;\n uint256 globalHoldingIndex; // Global index for token type + daysAfterDeployment combination\n address[] cancelableBy; // not cancelable unless set at the time of funding\n }\n\n // ============================================\n // CORE CONTRACT REFERENCES\n // ============================================\n ITransferRules public transferRules;\n ISnapshotPeriods public snapshotPeriods;\n IIdentityRegistry public identityRegistry;\n address public restrictedLockupTokenManagementExtension;\n address public restrictedLockupTokenExtension;\n IAccessControl public accessControl; // external access control contract\n\n // ============================================\n // RELEASE SCHEDULES AND TIMELOCKS\n // ============================================\n ReleaseSchedule[] public releaseSchedules;\n // wallet address => timelock details\n mapping(address => Timelock[]) internal timelocks;\n uint256 public minTimelockAmount;\n bool public snapshotsEnabled;\n bool public recordMintTimestamp;\n uint256 public recordPausedOnDaysAfterDeployment;\n\n // ============================================\n // HOLDER MANAGEMENT STATE\n // ============================================\n uint256 internal holderIds; // starts at 1 for 1st holder\n uint256 public holderMax = 2 ** 255 - 1; // Maximum uint256 value - walletsMax\n uint256 public holderCount;\n // wallet address => holderId\n mapping(address => uint256) internal holderIdByAddress;\n // holderId => wallet addresses\n mapping(uint256 => address[]) internal walletAddressesByHolderId;\n\n // ============================================\n // TOKEN STATE\n // ============================================\n bool public isPaused;\n // address => isFrozen\n mapping(address => bool) internal frozenAddresses;\n uint256 public maxTotalSupply;\n \n // ============================================\n // SLOT CONFIGURATION\n // ============================================\n /// @dev Number of slots per 256-bit word, calculated based on maxTotalSupply and decimals\n /// @dev Determines how many balance values can fit in a single storage slot\n uint256 public immutable slotsPerWord;\n \n /// @dev Number of bits per element (tokenType + daysAfterDeployment) or balance\n /// @dev Calculated as 256 / slotsPerWord\n uint256 public immutable elementBitSize;\n\n uint256 public immutable maxBalancePerSubIndex;\n\n // ============================================\n // MULTI-TYPE TOKEN FUNCTIONALITY (OPTIMIZED)\n // ============================================\n \n // Deployment day rounded up to midnight for calculating days since deploy\n uint256 public deploymentDay;\n \n // Global registry of packed 256-bit structures, each containing slotsPerWord elements\n // Each uint256 stores slotsPerWord elements of: [8 bits: tokenType][elementBitSize-8 bits: daysAfterDeployment]\n // This creates direct 1:1 mapping to packedBalancesByTypeAndTime storage slots\n uint256[] public globalMintTimestamps;\n \n // Count of elements in globalMintTimestamps (for efficient validation)\n uint256 public mintTimestampCount;\n \n // Wallet ownership bitmaps: address => uint256[] (each uint256 = 256 bits)\n // Each bit represents ownership of a global TokenTypeMintTimestamp combination\n mapping(address => uint256[]) internal walletIndexesByType;\n \n // ============================================\n // PACKED BALANCE STORAGE OPTIMIZATION\n // ============================================\n\n // Packed balance storage: address => packed TokenTypeMintTimestamp (without subIndex) => packed balances\n // Each uint256 stores slotsPerWord balances for example for 8 elements\n // (uint32 each): [bal7][bal6][bal5][bal4][bal3][bal2][bal1][bal0]\n mapping(address => mapping(uint256 => uint256)) internal packedBalancesByTypeAndTime;\n \n // Overflow balance storage for amounts > uint32..64 max\n // Used when packed balance == maxBalancePerSubIndex (uint32..64 max)\n // Mapping: address => globalIndex => actual overflow amount\n mapping(address => mapping(uint256 => uint256)) internal overflowBalances;\n\n // Global registry of all token types that have been used\n uint256[] internal _existingTokenTypes;\n mapping(uint256 => bool) internal _tokenTypeExists;\n mapping(uint256 => uint256) internal _tokenTypeIndex; // tokenType => index in _existingTokenTypes array\n\n // ============================================\n // FALLBACK PATTERN STATE\n // ============================================\n\n uint256 public maxReleaseDelay;\n uint8 public constant MAX_CANCELABLE_BY = 10;\n uint256 internal constant MAX_TIMELOCKS = 10_000;\n uint256 public constant MAX_WALLETS_PER_HOLDER = 10;\n string public constant contractVersion = \"5.0.0\";\n bytes4 public constant _ITRANSFER_RULES_INTERFACE_ID =\n type(ITransferRules).interfaceId;\n bytes4 public constant _IIDENTITY_REGISTRY_INTERFACE_ID =\n type(IIdentityRegistry).interfaceId;\n\n // ============================================\n // EVENTS\n // ============================================\n\n // Multi-type token events\n event TokenTypeMinted(\n address indexed to,\n uint256 amount,\n uint256 indexed tokenType,\n uint256 timestamp\n );\n\n event TokenTypeTransferred(\n address indexed from,\n address indexed to,\n uint256 amount,\n uint256 indexed tokenType\n );\n\n event TokenTypeBurned(\n address indexed from,\n uint256 amount,\n uint256 indexed tokenType\n );\n\n event TokenHoldingsMerged(\n address indexed wallet,\n uint256 indexed tokenType,\n uint256 holdingsMerged,\n uint256 totalAmount,\n uint256 newMintTimestamp\n );\n\n /// @notice This event is emitted when a schedule is funded\n /// @param from The address of the funder\n /// @param to The address of the recipient of the tokens when schedule is released\n /// @param scheduleId The id of the release schedule\n /// @param amount The amount of tokens to be released\n /// @param commencementTimestamp The timestamp when the release schedule starts\n /// @param timelockId The id of the timelock\n /// @param cancelableBy The addresses that can cancel the timelock\n event ScheduleFunded(\n address indexed from,\n address indexed to,\n uint256 indexed scheduleId,\n uint256 amount,\n uint256 commencementTimestamp,\n uint256 timelockId,\n address[] cancelableBy\n );\n\n /// @notice This event is emitted when a timelock is canceled\n /// @param canceledBy The address of the canceller\n /// @param target The address of the recipient of the unlocked tokens\n /// @param timelockIndex The index of the timelock in the timelocks array\n /// @param reclaimTokenTo The address of the recipient of the locked tokens\n /// @param canceledAmount The total amount of canceled tokens\n /// @param paidAmount The amount of paid tokens to target\n event TimelockCanceled(\n address indexed canceledBy,\n address indexed target,\n uint256 indexed timelockIndex,\n address reclaimTokenTo,\n uint256 canceledAmount,\n uint256 paidAmount\n );\n\n /// @notice This event is emitted when a transfer rules is updated\n /// @param admin The address of the admin\n /// @param oldRules The old transfer rules contract address\n /// @param newRules The new transfer rules contract address\n event Upgrade(\n address indexed admin,\n address indexed oldRules,\n address indexed newRules\n );\n\n /// @notice This event is emitted when a force transfer between wallets is executed by admin\n /// @param admin The address of the admin\n /// @param from The address of the sender\n /// @param to The address of the recipient\n /// @param amount The amount of tokens to transfer\n event ForceTransferBetween(\n address indexed admin,\n address indexed from,\n address indexed to,\n uint256 amount\n );\n\n /// @notice This event is emitted when an updated max total supply\n /// @param admin The address of the admin\n /// @param maxTotalSupply The new max total supply\n event SetMaxTotalSupply(address indexed admin, uint256 maxTotalSupply);\n\n /// @notice This event is emitted when a holder is created\n /// @param holderId The id of the holder\n /// @param addr The primary address of the holder\n event HolderCreated(uint256 indexed holderId, address indexed addr);\n\n /// @notice This event is emitted when an append new wallet address to existing holder\n /// @param addr The address of the wallet\n /// @param holderId The id of the holder\n event AppendHolderAddress(address indexed addr, uint256 indexed holderId);\n\n /// @notice This event is emitted when an updated holder max limit\n /// @param admin The address of the admin\n /// @param holderMax The new holder max limit\n event SetHolderMax(address indexed admin, uint256 holderMax);\n\n /// @notice This event is emitted when a holder is removed\n /// @param admin The address of the admin\n /// @param holderId The id of the holder\n event RemoveHolder(address indexed admin, uint256 indexed holderId);\n\n /// @notice This event is emitted when a wallet address is removed from a given holderId\n /// @param admin The address of the tx sender\n /// @param holderId The id of the holder\n /// @param addr The primary address of the holder to be removed\n event RemoveWalletFromHolder(\n address indexed admin,\n uint256 indexed holderId,\n address indexed addr\n );\n\n /// @notice This event is emitted when a new release schedule is created\n /// @param from The address of the schedule creator\n /// @param scheduleId The id of the new release schedule\n event ScheduleCreated(address indexed from, uint256 indexed scheduleId);\n\n /// @notice This event is emitted when a holding's token type is updated\n /// @param admin The address of the admin performing the update\n /// @param wallet The wallet address whose holding is being updated\n /// @param oldTokenType The previous token type\n /// @param newTokenType The new token type\n /// @param mintTimestamp The mint timestamp of the holding\n /// @param amount The amount of tokens in the holding\n event HoldingTokenTypeUpdated(\n address indexed admin,\n address indexed wallet,\n uint256 indexed oldTokenType,\n uint256 newTokenType,\n uint256 mintTimestamp,\n uint256 amount\n );\n\n /// @notice This event is emitted when a timelock's token type is updated\n /// @param admin The address of the admin performing the update\n /// @param wallet The wallet address whose timelock is being updated\n /// @param timelockIndex The index of the timelock\n /// @param oldTokenType The previous token type\n /// @param newTokenType The new token type\n /// @param amount The total amount of tokens in the timelock\n event TimelockTokenTypeUpdated(\n address indexed admin,\n address indexed wallet,\n uint256 indexed timelockIndex,\n uint256 oldTokenType,\n uint256 newTokenType,\n uint256 amount\n );\n\n /// @notice This event is emitted when a address is frozen or unfrozen\n /// @param admin The address of the admin\n /// @param addr The (un)frozen address\n /// @param status true - frozen, false - unfrozen\n event AddressFrozen(\n address indexed admin,\n address indexed addr,\n bool indexed status\n );\n\n /// @notice This event is emitted when a contract is paused or unpaused\n /// @param admin The address of the admin\n /// @param status true - paused, false - unpaused\n event Pause(address indexed admin, bool indexed status);\n\n // ============================================\n // TOKENTYPE-TIMESTAMP PACKING UTILITIES\n // ============================================\n \n // Constants moved to BitManipulationLib - keeping references for compatibility\n uint256 internal constant TOKEN_TYPE_BITS = BitManipulationLib.TOKEN_TYPE_BITS;\n uint256 internal constant TOKEN_TYPE_MASK = BitManipulationLib.TOKEN_TYPE_MASK;\n \n // ============================================\n // WRAPPER FUNCTIONS REMOVED - USE BitManipulationLib DIRECTLY\n // ============================================\n \n /**\n * @dev Get token type from global index\n * @param globalIndex The global index\n * @return tokenType The token type\n */\n function _getTokenTypeFromGlobalIndex(uint256 globalIndex) internal view returns (uint256 tokenType) {\n uint256 packed = globalMintTimestamps[globalIndex / slotsPerWord];\n uint256 element = BitManipulationLib.getPackedElement(packed, globalIndex % slotsPerWord, elementBitSize);\n (tokenType, ) = BitManipulationLib.unpackElement(element);\n }\n \n /**\n * @dev Get token type and mint timestamp from global index\n * @param globalIndex The global index\n * @return tokenType The token type\n * @return mintTimestamp The mint timestamp\n */\n function _getTokenTypeAndMintTimestampFromGlobalIndex(uint256 globalIndex) internal view returns (uint256 tokenType, uint256 mintTimestamp) {\n uint256 packed = globalMintTimestamps[globalIndex / slotsPerWord];\n uint256 element = BitManipulationLib.getPackedElement(packed, globalIndex % slotsPerWord, elementBitSize);\n uint256 daysAfterDeployment;\n (tokenType, daysAfterDeployment) = BitManipulationLib.unpackElement(element);\n mintTimestamp = BitManipulationLib.daysSinceDeployToTimestamp(daysAfterDeployment, deploymentDay);\n }\n \n // ============================================\n // RESTRICTED LOCKUP TOKEN ERRORS\n // ============================================\n \n error RestrictedLockupToken_HolderAlreadyExists();\n error RestrictedLockupToken_AddressAlreadyHasHolder();\n error RestrictedLockupToken_MaxHolderAddressesReached();\n error RestrictedLockupToken_NewHolderMaxMustExceedCurrentHolderCount();\n error RestrictedLockupToken_MaxHolderCountReached();\n error RestrictedLockupToken_HolderAddressDoesNotExist();\n error RestrictedLockupToken_HolderDoesNotExist();\n error RestrictedLockupToken_InvalidName();\n error RestrictedLockupToken_InvalidSymbol();\n error RestrictedLockupToken_InvalidTransferRules();\n error RestrictedLockupToken_InvalidAccessControl();\n error RestrictedLockupToken_InvalidMinTimelockAmount();\n error RestrictedLockupToken_InvalidNumberOfRelases();\n error RestrictedLockupToken_InvalidRInitialReleaseBIPS();\n error RestrictedLockupToken_InvalidReleasePeriod();\n error RestrictedLockupToken_InvalidReleasePortionForSingleRelease();\n error RestrictedLockupToken_RecipientAndAmountLengthsShouldMatch();\n error RestrictedLockupToken_NewTransferRulesContractDoesNotImplementITransferRules();\n error RestrictedLockupToken_NewIdentityRegistryContractDoesNotImplementIIdentityRegistry();\n error RestrictedLockupToken_MismatchedArrayLength();\n error RestrictedLockupToken_SenderCannotBeRecipient(address sender);\n error RestrictedLockupToken_InvalidAmount();\n error RestrictedLockupToken_InvalidZeroAddress();\n error RestrictedLockupToken_CannotExceedMaxTotalSupply();\n error RestrictedLockupToken_MaxCancelersExceeded();\n error RestrictedLockupToken_InvalidAddressArray();\n error RestrictedLockupToken_NewMaxTotalSupplyMustExceedCurrentTotalSupply();\n error RestrictedLockupToken_SafeApprove();\n error RestrictedLockupToken_InvalidFirstRelease();\n error RestrictedLockupToken_InvalidTimelock();\n error RestrictedLockupToken_InvalidReclaimTo();\n error RestrictedLockupToken_TimelockCannotBeCanceled();\n error RestrictedLockupToken_ScheduleIdDoesNotMatch();\n error RestrictedLockupToken_CommencementTimestampDoesNotMatch();\n error RestrictedLockupToken_TotalAmountDoesNotMatch();\n error RestrictedLockupToken_TimelockHasNoValueRemaining();\n error RestrictedLockupToken_AmountExceedsUnlockedBalance();\n error RestrictedLockupToken_InsufficientTotalBalanceOf();\n error RestrictedLockupToken_NoHoldingWithEnoughBalance();\n error RestrictedLockupToken_MaxTimelocksExceeded();\n error RestrictedLockupToken_InvalidFundAmount();\n error RestrictedLockupToken_InvalidFundAddress();\n error RestrictedLockupToken_InvalidScheduleId();\n error RestrictedLockupToken_AmountLessThanReleaseCount();\n error RestrictedLockupToken_InitialReleaseOutOfRange();\n error RestrictedLockupToken_CannotRemoveLastWallet();\n error RestrictedLockupToken_InvalidTrustedForwarder();\n error RestrictedLockupToken_InvalidIdentityRegistry();\n error RestrictedLockupToken_ExtensionSlotsPerWordMismatch(uint256 expected, uint256 actual);\n error RestrictedLockupToken_InvalidRestrictedLockupTokenExtension();\n error RestrictedLockupToken_InvalidRestrictedLockupTokenManagementExtension();\n error RestrictedLockupToken_CannotRemoveHolderWithActiveTimelocks();\n error RestrictedLockupToken_InsufficientBurnableBalance();\n error RestrictedLockupToken_CantVestAllForMultipleReleases();\n error RestrictedLockupToken_InvalidTokenType();\n error RestrictedLockupToken_NoItemWithEnoughBalance();\n error RestrictedLockupToken_HoldingIndexOutOfBound();\n error RestrictedLockupToken_InvalidHolding();\n error RestrictedLockupToken_InvalidHoldingIndex();\n error RestrictedLockupToken_InvalidZeroAmount();\n error RestrictedLockupToken_MaxTotalSupplyTooLarge();\n error RestrictedLockupToken_TokenTypeNotAllowedForRecipient();\n error RestrictedLockupToken_AlreadySet();\n error RestrictedLockupToken_BalanceCheckFailed();\n\n // ============================================\n // INTERNAL FUNCTIONS\n // ============================================\n \n function _maxSafeSupply() internal pure returns (uint256) {\n return type(uint240).max;\n }\n\n /**\n * @dev Find or create optimized global index for TokenType + DaysAfterDeployment\n * @param tokenType The token type (0-255)\n * @param daysAfterDeployment The days after deployment\n * @return globalIndex The global index for this combination\n */\n function _findOrCreateOptimizedGlobalIndex(uint256 tokenType, uint256 daysAfterDeployment) internal returns (uint256) {\n // Create element for this token type and days\n uint256 element = BitManipulationLib.packElement(tokenType, daysAfterDeployment, elementBitSize);\n \n // Search from end to beginning to find same token type and daysAfterDeployment\n // Break early if we find a daysAfterDeployment > current (chronologically older)\n // Use direct bit manipulation instead of unpacking to save gas\n for (uint256 bucketIndex = globalMintTimestamps.length; bucketIndex > 0; bucketIndex--) {\n uint256 actualBucketIndex = bucketIndex - 1; // Convert to 0-based index\n uint256 packed = globalMintTimestamps[actualBucketIndex];\n \n // Check each element in this bucket (reverse order for better efficiency)\n // Work directly with packed bits instead of unpacking\n for (uint256 subIndex = slotsPerWord; subIndex > 0; subIndex--) {\n uint256 actualSubIndex = subIndex - 1; // Convert to 0-based index\n \n // Extract element directly from packed data using bit shifting\n uint256 currentElement = (packed >> (actualSubIndex * elementBitSize)) & ((1 << elementBitSize) - 1);\n \n if (currentElement == 0) {\n continue; // Skip empty slots\n }\n \n if (currentElement == element) {\n // Found existing combination - return its global index\n return actualBucketIndex * slotsPerWord + actualSubIndex;\n }\n \n // Extract daysAfterDeployment directly from current element using bit manipulation\n uint256 currentDays = currentElement >> TOKEN_TYPE_BITS;\n \n // If current days > target days,\n // we can break early since items are generally added in chronological order\n if (currentDays < daysAfterDeployment) {\n bucketIndex = 1; // Set to 1 so that outer for loop will terminate after decrement\n break; // No point searching further, older entries won't match\n }\n }\n }\n \n // Element not found, add new item to the last available subindex or create new bucket\n if (globalMintTimestamps.length > 0) {\n // Try to add to the last bucket first\n uint256 lastBucketIndex = globalMintTimestamps.length - 1;\n uint256 packed = globalMintTimestamps[lastBucketIndex];\n \n // Find the first available slot in the last bucket using direct bit manipulation\n for (uint256 subIndex = 0; subIndex < slotsPerWord; subIndex++) {\n // Extract element directly from packed data\n uint256 currentElement = (packed >> (subIndex * elementBitSize)) & ((1 << elementBitSize) - 1);\n \n if (currentElement == 0) {\n // Found empty slot in last bucket, use it\n // Set the element directly in the packed data using bit manipulation\n uint256 updatedPacked = packed | (element << (subIndex * elementBitSize));\n globalMintTimestamps[lastBucketIndex] = updatedPacked;\n mintTimestampCount++;\n return lastBucketIndex * slotsPerWord + subIndex;\n }\n }\n }\n \n // Last bucket is full or no buckets exist, create new bucket with subindex 0\n // Use direct bit manipulation instead of array creation\n uint256 newBucketIndex = globalMintTimestamps.length;\n uint256 newPacked = element; // element goes to subIndex 0, all other slots are 0\n globalMintTimestamps.push(newPacked);\n mintTimestampCount++;\n \n return newBucketIndex * slotsPerWord; // subIndex = 0 for first element\n }\n \n /**\n * @dev Set a bit in the wallet's bitmap\n * @param account The wallet address\n * @param globalIndex The global index to set\n */\n function _setBitmapBit(address account, uint256 globalIndex) internal {\n uint256 wordIndex = globalIndex / 256;\n uint256 bitIndex = globalIndex % 256;\n \n // Ensure bitmap array is large enough\n while (walletIndexesByType[account].length <= wordIndex) {\n walletIndexesByType[account].push(0);\n }\n \n // Set the bit\n walletIndexesByType[account][wordIndex] |= (1 << bitIndex);\n }\n\n /**\n * @dev Register a token type in the global registry if it doesn't exist\n * @param tokenType The token type to register\n */\n function _registerTokenType(uint256 tokenType) internal {\n if (!_tokenTypeExists[tokenType]) {\n _tokenTypeIndex[tokenType] = _existingTokenTypes.length;\n _existingTokenTypes.push(tokenType);\n _tokenTypeExists[tokenType] = true;\n }\n }\n /**\n * @dev Adds a specified amount to the holdings of a given account at a specific global holding index.\n * Handles packed storage and overflow for balances exceeding the maximum per sub-index.\n * @param account The address of the account to update holdings for\n * @param globalHoldingIndex The global index representing the token type and mint timestamp\n * @param amount The amount to add to the holdings\n */\n function _addToHoldingsByIndex(\n address account,\n uint256 globalHoldingIndex,\n uint256 amount\n ) internal {\n // Calculate subIndex from globalHoldingIndex\n uint256 subIndex = globalHoldingIndex % slotsPerWord;\n \n // Calculate bucket index (which globalMintTimestamps entry to use)\n uint256 bucketIndex = globalHoldingIndex / slotsPerWord;\n \n // Get current packed balances for this bucket\n uint256 currentPackedBalances = packedBalancesByTypeAndTime[account][bucketIndex];\n \n // Get current balance for this subIndex\n uint256 currentBalance = BitManipulationLib.getPackedBalance(currentPackedBalances, subIndex, elementBitSize);\n \n // Handle overflow balances: if current balance is max uint32, get real balance from overflow mapping\n uint256 realCurrentBalance = currentBalance;\n if (currentBalance == maxBalancePerSubIndex) {\n realCurrentBalance = overflowBalances[account][globalHoldingIndex];\n }\n \n // If this combination doesn't exist for this wallet yet, set bitmap bit\n if (currentBalance == 0) {\n // Set bitmap bit for this wallet\n _setBitmapBit(account, globalHoldingIndex);\n }\n \n // Calculate new total balance\n uint256 newTotalBalance = realCurrentBalance + amount;\n \n // Update balance in packed storage and overflow mapping as needed\n if (newTotalBalance < maxBalancePerSubIndex) {\n // Balance fits in uint32, store in packed storage\n packedBalancesByTypeAndTime[account][bucketIndex] = BitManipulationLib.setPackedBalance(\n currentPackedBalances,\n subIndex,\n newTotalBalance,\n elementBitSize\n );\n // Clear any existing overflow balance\n if (currentBalance == maxBalancePerSubIndex) {\n delete overflowBalances[account][globalHoldingIndex];\n }\n } else {\n // Balance exceeds uint32 max, store max in packed storage and real amount in overflow mapping\n packedBalancesByTypeAndTime[account][bucketIndex] = BitManipulationLib.setPackedBalance(\n currentPackedBalances,\n subIndex,\n maxBalancePerSubIndex,\n elementBitSize\n );\n overflowBalances[account][globalHoldingIndex] = newTotalBalance;\n }\n }\n\n /**\n * @dev Add tokens to holdings structure using optimized packed balance system\n * @param account The account to add tokens to\n * @param tokenType The type of tokens (0-255, uses 8 bits)\n * @param daysAfterDeployment The days after deployment of the tokens\n * @param amount The amount to add\n * @return globalIndex The global index for this token type + daysAfterDeployment combination\n */\n function _addToHoldingsByType(\n address account,\n uint256 tokenType,\n uint256 daysAfterDeployment,\n uint256 amount\n ) internal returns (uint256 globalIndex) {\n // Register token type globally if it doesn't exist\n _registerTokenType(tokenType);\n \n // Find or create the global index for this combination\n globalIndex = _findOrCreateOptimizedGlobalIndex(\n tokenType,\n daysAfterDeployment\n );\n \n _addToHoldingsByIndex(account, globalIndex, amount);\n return globalIndex;\n }\n\n /**\n * @dev Get the real balance for a specific holding, considering overflow storage\n * @param account The account to check\n * @param globalIndex The global index of the holding\n * @return balance The real balance (from packed storage or overflow mapping)\n */\n function _getRealBalance(address account, uint256 globalIndex) internal view returns (uint256) {\n uint256 subIndex = globalIndex % slotsPerWord;\n uint256 bucketIndex = globalIndex / slotsPerWord;\n \n uint256 currentPackedBalances = packedBalancesByTypeAndTime[account][bucketIndex];\n uint256 packedBalance = BitManipulationLib.getPackedBalance(currentPackedBalances, subIndex, elementBitSize);\n \n // If packed balance is max uint32, get real balance from overflow mapping\n if (packedBalance == maxBalancePerSubIndex) {\n return overflowBalances[account][globalIndex];\n }\n \n return packedBalance;\n }\n\n /**\n * @dev Clear a bit in the wallet's bitmap for a token type\n * @param account The wallet address\n * @param globalIndex The global index to clear\n */\n function _clearBitmapBit(address account, uint256 globalIndex) internal {\n uint256 wordIndex = globalIndex / 256;\n uint256 bitIndex = globalIndex % 256;\n \n if (wordIndex < walletIndexesByType[account].length) {\n walletIndexesByType[account][wordIndex] &= ~(1 << bitIndex);\n }\n }\n\n\n /**\n * @dev Enforces transfer restrictions managed using the ERC-1404 standard functions.\n * The TransferRules contract defines what the rules are. The data inputs to those rules remains in the RestrictedToken contract.\n * TransferRules is a separate contract so its logic can be upgraded.\n * @param from_ The address the tokens are transferred from\n * @param to_ The address the tokens would be transferred to\n * @param value_ the quantity of tokens to be transferred\n */\n function _enforceTransferRestrictions(\n address from_,\n address to_,\n uint256 value_\n ) internal view {\n uint256 _restrictionCode = transferRules.detectTransferRestrictionBasic(\n address(this),\n from_,\n to_,\n value_\n );\n // if (!transferRules.checkSuccess(_restrictionCode)) revert TransferRestricted(_restrictionCode);\n require(\n transferRules.checkSuccess(_restrictionCode),\n transferRules.messageForTransferRestriction(_restrictionCode)\n );\n }\n\n function _enforceTransferRestrictionsFor(\n uint256 tokenType_,\n uint256 mintTimestamp_,\n address recipient_\n ) internal view {\n IIdentityRegistry.IdentityInfo memory recipientIdentity = identityRegistry.identity(recipient_);\n bool isAmlKycPassed_ = identityRegistry.isAmlKycPassed(recipient_);\n uint256 _restrictionCode = transferRules.detectTransferRestrictionForHolding(\n tokenType_,\n mintTimestamp_,\n recipientIdentity,\n isAmlKycPassed_\n );\n require(\n transferRules.checkSuccess(_restrictionCode),\n transferRules.messageForTransferRestriction(_restrictionCode)\n );\n }\n\n /**\n * @dev Apply batched bitmap clearing to a specific word\n * @dev This replaces multiple _clearBitmapBit calls with a single AND operation\n */\n function _applyBitmaskToClear(\n address account,\n uint256 wordIndex,\n uint256 bitmaskToClear\n ) internal {\n if (wordIndex < walletIndexesByType[account].length) {\n // Clear all bitmap bits for this word at once (major gas optimization)\n walletIndexesByType[account][wordIndex] &= ~bitmaskToClear;\n }\n }\n\n /**\n * @dev check if address has holder\n * @param addr_ address to check\n * @return true if address has holder\n */\n function _addressHasHolder(address addr_) internal view returns (bool) {\n return holderIdByAddress[addr_] > 0;\n }\n\n /**\n * @param addr_ address to associate with holderId. First holder has holderId = 1.\n * @return _holderId holder id\n */\n function _createHolderFromAddress(\n address addr_\n ) internal returns (uint256 _holderId) {\n if (holderCount >= holderMax) {\n revert RestrictedLockupToken_MaxHolderCountReached();\n }\n holderCount++;\n holderIds += 1;\n _holderId = holderIds;\n\n holderIdByAddress[addr_] = _holderId;\n walletAddressesByHolderId[_holderId].push(addr_);\n\n emit HolderCreated(_holderId, addr_);\n }\n\n /**\n * @dev Generic helper for balance query staticcalls with consistent error handling\n * @param functionSignature The function signature to call (e.g., \"superBalanceOf(address)\")\n * @param account The account to query\n * @return balance The balance result\n */\n function _queryBalance(string memory functionSignature, address account) internal view returns (uint256 balance) {\n (bool success, bytes memory data) = address(this).staticcall(\n abi.encodeWithSignature(functionSignature, account)\n );\n if (!success) {\n revert RestrictedLockupToken_BalanceCheckFailed();\n }\n if (data.length < 32) {\n revert RestrictedLockupToken_BalanceCheckFailed();\n }\n return abi.decode(data, (uint256));\n }\n\n modifier validAddress(address addr_) {\n if (addr_ == address(0)) {\n revert EasyAccessControl_InvalidZeroAddress();\n }\n _;\n }\n\n modifier onlyExistingAddress(address addr_) {\n if (!_addressHasHolder(addr_)) {\n revert RestrictedLockupToken_HolderAddressDoesNotExist();\n }\n _;\n }\n\n modifier onlyReserveAdmin() {\n _onlyReserveAdmin();\n _;\n }\n\n modifier onlyTransferAdmin() {\n _onlyTransferAdmin();\n _;\n }\n\n modifier onlyReserveOrMintAdmin() {\n _onlyReserveOrMintAdmin();\n _;\n }\n\n modifier onlySoftBurnAdmin() {\n _onlySoftBurnAdmin();\n _;\n }\n\n modifier anyAdmin() {\n _anyAdmin();\n _;\n }\n\n modifier onlyWalletsAdminOrTransferAdmin() {\n _onlyWalletsAdminOrTransferAdmin();\n _;\n }\n\n modifier onlyContractAdminOrTransferAdmin() {\n _onlyContractAdminOrTransferAdmin();\n _;\n }\n\n modifier onlyContractAdmin() {\n _onlyContractAdmin();\n _;\n }\n\n function _anyAdmin() internal view {\n if (\n !accessControl.hasRole(_msgSender(), accessControl.RESERVE_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.WALLETS_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.TRANSFER_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.CONTRACT_ADMIN_ROLE())\n ) {\n revert EasyAccessControl_DoesNotHaveAdminRole(_msgSender());\n }\n }\n\n function _onlyReserveAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), accessControl.RESERVE_ADMIN_ROLE())) {\n revert EasyAccessControl_DoesNotHaveReserveAdminRole(_msgSender());\n }\n }\n\n function _onlySoftBurnAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), accessControl.SOFT_BURN_ADMIN_ROLE())) {\n revert EasyAccessControl_DoesNotHaveSoftBurnAdminRole(_msgSender());\n }\n }\n\n function _onlyReserveOrMintAdmin() internal view {\n if (\n !accessControl.hasRole(_msgSender(), accessControl.RESERVE_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.MINT_ADMIN_ROLE())\n ) {\n revert EasyAccessControl_DoesNotHaveAdminRole(_msgSender());\n }\n }\n\n function _onlyTransferAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), accessControl.TRANSFER_ADMIN_ROLE())) {\n revert EasyAccessControl_DoesNotHaveTransferAdminRole(_msgSender());\n }\n }\n\n function _onlyWalletsAdminOrTransferAdmin() internal view {\n if (\n !accessControl.hasRole(_msgSender(), accessControl.WALLETS_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.TRANSFER_ADMIN_ROLE())\n ) {\n revert EasyAccessControl_DoesNotHaveAdminRole(_msgSender());\n }\n }\n\n function _onlyContractAdminOrTransferAdmin() internal view {\n if (\n !accessControl.hasRole(_msgSender(), accessControl.CONTRACT_ADMIN_ROLE()) &&\n !accessControl.hasRole(_msgSender(), accessControl.TRANSFER_ADMIN_ROLE())\n ) {\n revert EasyAccessControl_DoesNotHaveContractOrTransferAdminRole(_msgSender());\n }\n }\n\n function _onlyContractAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), accessControl.CONTRACT_ADMIN_ROLE())) {\n revert EasyAccessControl_DoesNotHaveContractAdminRole(_msgSender());\n }\n }\n\n /// @dev This function is intended to be overridden in child contracts (e.g., for ERC2771Context support)\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n}\n"},"contracts/TransferRules.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport {ERC165} from \"@openzeppelin/contracts/utils/introspection/ERC165.sol\";\nimport {ITransferRules} from \"./interfaces/ITransferRules.sol\";\nimport {IIdentityRegistry} from \"./interfaces/IIdentityRegistry.sol\";\nimport {IRestrictedLockupToken} from \"./interfaces/IRestrictedLockupToken.sol\";\nimport {IAccessControl} from \"./interfaces/IAccessControl.sol\";\nimport {Context} from \"@openzeppelin/contracts/utils/Context.sol\";\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {IERC1404} from \"./interfaces/IERC1404.sol\";\nimport {AccessControl} from \"./AccessControl.sol\";\nimport \"./AccessControlErrors.sol\";\n\ncontract TransferRules is ITransferRules, ERC165, ERC2771Context {\n // ============================================\n // ORIGINAL TRANSFER RULES CONSTANTS & STORAGE\n // ============================================\n \n mapping(uint256 => string) internal errorMessage;\n\n uint256 public constant SUCCESS = 0;\n uint256 public constant GREATER_THAN_RECIPIENT_MAX_BALANCE = 1;\n uint256 public constant SENDER_TOKENS_TIME_LOCKED = 2;\n uint256 public constant DO_NOT_SEND_TO_TOKEN_CONTRACT = 3;\n uint256 public constant DO_NOT_SEND_TO_EMPTY_ADDRESS = 4;\n uint256 public constant SENDER_ADDRESS_FROZEN = 5;\n uint256 public constant ALL_TRANSFERS_PAUSED = 6;\n uint256 public constant RECIPIENT_ADDRESS_FROZEN = 7;\n uint256 public constant LOWER_THAN_RECIPIENT_MIN_BALANCE = 8;\n uint256 public constant INSUFFICIENT_BALANCE_OF_SENDER = 9;\n uint256 public constant SENDER_NOT_AMLKYCPASSED = 10;\n uint256 public constant RECIPIENT_NOT_AMLKYCPASSED = 11;\n uint256 public constant HOLDING_PERIOD_NOT_MET = 12;\n uint256 public constant NO_RULE_FOR_RECIPIENT = 13;\n uint256 public constant RECIPIENT_NOT_QUALIFIED = 14;\n\n uint256 public constant MAX_RESTRICTION_CODE = 14;\n\n bytes4 public immutable INTERFACE_ID;\n\n // ============================================\n // TOKEN TYPE RULES ENGINE STORAGE\n // ============================================\n \n // Constants for predefined token types\n uint256 public constant TOKEN_TYPE_GENERIC = 0; // Generic/Default\n\n // Efficient lockup table: region -> accreditation -> TokenTypeRule\n mapping(uint256 => mapping(uint256 => TokenTypeRule)) private _tokenTypeRules;\n TokenTypeRule private _defaultTokenTypeRule;\n\n // ============================================\n // TOKEN TYPE TRANSFER RULES STORAGE\n // ============================================\n \n // Bit manipulation constants for packed TransferRule\n uint256 private constant IS_ACTIVE_MASK = 0x1; // 0000...0001\n uint256 private constant REQUIRES_AML_KYC_MASK = 0x2; // 0000...0010\n uint256 private constant FLAGS_MASK = 0x3; // 0000...0011\n uint256 private constant LOCK_DURATION_SHIFT = 2;\n \n // Packed storage: tokenType -> region -> accreditation -> uint256 (packed TransferRule)\n // Bit layout: bits 0-1 = flags (isActive, requiresAmlKyc), bits 2-255 = lockDurationSeconds\n mapping(uint256 => mapping(uint256 => mapping(uint256 => uint256))) private _packedRules;\n\n\n modifier onlyTransferAdmin() {\n _onlyTransferAdmin();\n _;\n }\n function _onlyTransferAdmin() internal view {\n if (!accessControl.hasRole(_msgSender(), accessControl.TRANSFER_ADMIN_ROLE())) {\n revert EasyAccessControl_DoesNotHaveTransferAdminRole(_msgSender());\n }\n }\n\n // ============================================\n // PACKED TRANSFERRULE HELPER FUNCTIONS\n // ============================================\n \n /**\n * @dev Pack a TransferRule into a single uint256\n * @param lockDurationSeconds The lock duration in seconds\n * @param requiresAmlKyc Whether AML/KYC is required\n * @param isActive Whether the rule is active\n * @return packed The packed uint256 value\n */\n function _packRule(\n uint256 lockDurationSeconds,\n bool requiresAmlKyc,\n bool isActive\n ) private pure returns (uint256 packed) {\n packed = lockDurationSeconds << LOCK_DURATION_SHIFT;\n if (isActive) packed |= IS_ACTIVE_MASK;\n if (requiresAmlKyc) packed |= REQUIRES_AML_KYC_MASK;\n }\n \n /**\n * @dev Unpack a uint256 into TransferRule components\n * @param packed The packed uint256 value\n * @return lockDurationSeconds The lock duration in seconds\n * @return requiresAmlKyc Whether AML/KYC is required\n * @return isActive Whether the rule is active\n */\n function _unpackRule(uint256 packed) private pure returns (\n uint256 lockDurationSeconds,\n bool requiresAmlKyc,\n bool isActive\n ) {\n lockDurationSeconds = packed >> LOCK_DURATION_SHIFT;\n requiresAmlKyc = (packed & REQUIRES_AML_KYC_MASK) != 0;\n isActive = (packed & IS_ACTIVE_MASK) != 0;\n }\n \n // ============================================\n // ERRORS\n // ============================================\n \n error TransferRules_BadRestrictionCode();\n error TransferRules_InvalidAccessControl();\n error TransferRules_InvalidTrustedForwarder();\n error TransferRules_TokenTypeRuleNotFound();\n error TransferRules_RecipientNotAmlKycPassed();\n error TransferRules_InvalidTokenType();\n error TransferRules_InvalidArrayLengths();\n\n IAccessControl public accessControl;\n\n constructor(\n address trustedForwarder_,\n address accessControl_\n ) ERC2771Context(trustedForwarder_) {\n if (accessControl_ == address(0)) {\n revert TransferRules_InvalidAccessControl();\n } \n if (trustedForwarder_ == address(0)) {\n revert TransferRules_InvalidTrustedForwarder();\n }\n // Initialize error messages\n errorMessage[SUCCESS] = \"SUCCESS\";\n errorMessage[\n LOWER_THAN_RECIPIENT_MIN_BALANCE\n ] = \"LOWER THAN RECIPIENT MIN BALANCE\";\n errorMessage[\n GREATER_THAN_RECIPIENT_MAX_BALANCE\n ] = \"GREATER THAN RECIPIENT MAX BALANCE\";\n errorMessage[SENDER_TOKENS_TIME_LOCKED] = \"SENDER TOKENS LOCKED\";\n errorMessage[\n DO_NOT_SEND_TO_TOKEN_CONTRACT\n ] = \"DO NOT SEND TO TOKEN CONTRACT\";\n errorMessage[\n DO_NOT_SEND_TO_EMPTY_ADDRESS\n ] = \"DO NOT SEND TO EMPTY ADDRESS\";\n errorMessage[SENDER_ADDRESS_FROZEN] = \"SENDER ADDRESS IS FROZEN\";\n errorMessage[ALL_TRANSFERS_PAUSED] = \"ALL TRANSFERS PAUSED\";\n errorMessage[RECIPIENT_ADDRESS_FROZEN] = \"RECIPIENT ADDRESS IS FROZEN\";\n errorMessage[\n INSUFFICIENT_BALANCE_OF_SENDER\n ] = \"INSUFFICIENT BALANCE OF SENDER\";\n errorMessage[SENDER_NOT_AMLKYCPASSED] = \"SENDER NOT AMLKYCPASSED\";\n errorMessage[RECIPIENT_NOT_AMLKYCPASSED] = \"RECIPIENT NOT AMLKYCPASSED\";\n errorMessage[HOLDING_PERIOD_NOT_MET] = \"HOLDING PERIOD NOT MET\";\n errorMessage[NO_RULE_FOR_RECIPIENT] = \"NO RULE FOR RECIPIENT\";\n errorMessage[RECIPIENT_NOT_QUALIFIED] = \"RECIPIENT NOT QUALIFIED\";\n\n INTERFACE_ID = type(ITransferRules).interfaceId;\n \n accessControl = IAccessControl(accessControl_);\n }\n\n /**\n * Support of ERC165\n * @dev See https://eips.ethereum.org/EIPS/eip-165\n * @param interfaceId The interface identifier, as specified in ERC-165\n */\n function supportsInterface(\n bytes4 interfaceId\n ) public view override returns (bool) {\n return\n interfaceId == INTERFACE_ID || super.supportsInterface(interfaceId);\n }\n\n /// @inheritdoc ITransferRules\n function detectTransferRestrictionBasic(\n address tokenAddr,\n address from,\n address to,\n uint256 value\n ) public view override returns (uint256) {\n IRestrictedLockupToken token = IRestrictedLockupToken(tokenAddr);\n\n // ============================================\n // TRADITIONAL TRANSFER RESTRICTION CHECKS\n // ============================================\n if (token.isPaused()) return ALL_TRANSFERS_PAUSED;\n if (to == address(0)) return DO_NOT_SEND_TO_EMPTY_ADDRESS;\n if (to == address(token)) return DO_NOT_SEND_TO_TOKEN_CONTRACT;\n if (token.getFrozenStatus(from)) return SENDER_ADDRESS_FROZEN;\n if (token.getFrozenStatus(to)) return RECIPIENT_ADDRESS_FROZEN;\n\n if (!token.isAmlKycPassed(from)) return SENDER_NOT_AMLKYCPASSED;\n // recipient is aml kyc passed is checked on holdingtransfer rule level\n\n if (token.balanceOf(from) < value)\n return INSUFFICIENT_BALANCE_OF_SENDER;\n if (token.unlockedTotalBalanceOf(from) < value)\n return SENDER_TOKENS_TIME_LOCKED;\n\n return SUCCESS;\n }\n\n /// @inheritdoc ITransferRules\n function detectTransferRestriction(\n address tokenAddr,\n address from,\n address to,\n uint256 value\n ) external view override returns (uint256) {\n uint256 restrictionCode = detectTransferRestrictionBasic(tokenAddr, from, to, value);\n if (restrictionCode != SUCCESS) return restrictionCode;\n\n IRestrictedLockupToken token = IRestrictedLockupToken(tokenAddr);\n IIdentityRegistry identityRegistry = token.identityRegistry();\n uint256 transferableAmount = _calculateTransferableFromTimelocks(\n token,\n from,\n to,\n identityRegistry,\n value\n );\n if (transferableAmount >= value) return SUCCESS;\n\n uint256 amountToTransferFromHoldings = value - transferableAmount;\n uint256 transferableAmountFromHoldings = _calculateTransferableFromHoldings(\n token,\n from,\n to,\n identityRegistry,\n amountToTransferFromHoldings\n );\n\n if (transferableAmountFromHoldings < amountToTransferFromHoldings) return HOLDING_PERIOD_NOT_MET;\n\n return SUCCESS;\n }\n \n\n /**\n * @dev Calculates the transferable amount from token holdings considering holding periods\n * @param token The token contract\n * @param from The sender address\n * @param to The recipient address\n * @param identityRegistry The identity registry for checking holding periods\n * @param expectedTransferableAmount The expected amount needed (0 = calculate all available)\n * @return transferableAmount The amount that can be transferred from holdings\n */\n // TODO: Optimize this function with bitmask indexing\n function _calculateTransferableFromHoldings(\n IRestrictedLockupToken token,\n address from,\n address to,\n IIdentityRegistry identityRegistry,\n uint256 expectedTransferableAmount\n ) internal view returns (uint256 transferableAmount) {\n // Check holdings for transferable tokens without loading entire array into memory\n IIdentityRegistry.IdentityInfo memory recipientIdentity = identityRegistry.identity(to);\n bool isAmlKycPassed = token.isAmlKycPassed(to);\n\n for (uint256 j = 0; j < token.holdingCountOf(from); j++) {\n (uint256 amount, uint256 mintTimestamp, uint256 tokenType) = token.holdingOf(from, j);\n if (amount == 0) continue;\n \n // Check if this holding can be transferred based on holding period rules\n uint256 tokenTypeRestriction = detectTransferRestrictionForHolding(\n tokenType,\n mintTimestamp,\n recipientIdentity,\n isAmlKycPassed\n );\n if (tokenTypeRestriction != 0) {\n continue; // skip this holding\n }\n\n transferableAmount += amount;\n\n // Early termination if we have enough and expectedTransferableAmount > 0\n if (expectedTransferableAmount > 0 && transferableAmount >= expectedTransferableAmount) {\n break;\n }\n }\n }\n\n /**\n * @dev Calculates the transferable amount from timelocks\n * @param token The token contract\n * @param from The sender address\n * @param expectedTransferableAmount The expected amount needed (0 = calculate all available)\n * @return transferableAmount The amount that can be transferred from timelocks\n */\n function _calculateTransferableFromTimelocks(\n IRestrictedLockupToken token,\n address from,\n address to,\n IIdentityRegistry identityRegistry,\n uint256 expectedTransferableAmount\n ) internal view returns (uint256 transferableAmount) {\n // Check timelocks for unlocked tokens\n IIdentityRegistry.IdentityInfo memory recipientIdentity = identityRegistry.identity(to);\n bool isAmlKycPassed = token.isAmlKycPassed(to);\n for (uint256 i = 0; i < token.timelockCountOf(from); i++) {\n IRestrictedLockupToken.Timelock memory timelock = token.timelockOf(from, i);\n if (timelock.totalAmount == timelock.tokensTransferred) continue;\n\n // Get token type and mint timestamp from global holding index\n (uint256 tokenType, uint256 mintTimestamp) = token.getTokenTypeAndMintTimestampFromGlobalIndex(timelock.globalHoldingIndex);\n \n uint256 tokenTypeRestriction = detectTransferRestrictionForHolding(\n tokenType,\n mintTimestamp,\n recipientIdentity,\n isAmlKycPassed\n );\n if (tokenTypeRestriction != 0) {\n continue; // skip this timelock\n }\n transferableAmount += token.unlockedBalanceOfTimelock(from, i);\n \n // Early termination if we have enough and expectedTransferableAmount > 0\n if (expectedTransferableAmount > 0 && transferableAmount >= expectedTransferableAmount) {\n break;\n }\n }\n }\n\n /// @inheritdoc ITransferRules\n function messageForTransferRestriction(\n uint256 restrictionCode\n ) external view override returns (string memory) {\n if (restrictionCode > MAX_RESTRICTION_CODE) {\n revert TransferRules_BadRestrictionCode();\n }\n return errorMessage[restrictionCode];\n }\n\n /// @notice a method for checking a response code to determine if a transfer was succesful.\n /// Defining this separately from the token contract allows it to be upgraded.\n /// For instance this method would need to be upgraded if the SUCCESS code was changed to 1\n /// as specified in ERC-1066 instead of 0 as specified in ERC-1404.\n /// @param restrictionCode The code to check.\n /// @return isSuccess A boolean indicating if the code is the SUCCESS code.\n function checkSuccess(\n uint256 restrictionCode\n ) external pure override returns (bool isSuccess) {\n return restrictionCode == SUCCESS;\n }\n\n // ============================================\n // TOKEN TYPE RULES ENGINE FUNCTIONALITY\n // ============================================\n\n /// @inheritdoc ITransferRules\n function determineTokenType(\n address wallet,\n IIdentityRegistry identityRegistry\n ) external view override returns (uint256 tokenType) {\n // Get wallet identity\n IIdentityRegistry.IdentityInfo memory identity = identityRegistry.identity(wallet);\n\n // Check each region in the array to find the first matching rule\n for (uint256 i = 0; i < identity.regions.length; i++) {\n TokenTypeRule memory rule = _tokenTypeRules[identity.regions[i]][identity.accreditationType];\n \n if (rule.isActive) {\n if (rule.requiresAmlKyc && !identityRegistry.isAmlKycPassed(wallet)) {\n continue;\n }\n\n return rule.tokenType;\n }\n }\n \n if (_defaultTokenTypeRule.isActive) {\n if (_defaultTokenTypeRule.requiresAmlKyc && !identityRegistry.isAmlKycPassed(wallet)) {\n revert TransferRules_RecipientNotAmlKycPassed();\n }\n\n return _defaultTokenTypeRule.tokenType;\n }\n\n revert TransferRules_TokenTypeRuleNotFound();\n }\n\n /// @inheritdoc ITransferRules\n function tokenTypeAllowed(\n uint256 tokenType,\n address wallet,\n IIdentityRegistry identityRegistry\n ) external view override returns (bool) {\n IIdentityRegistry.IdentityInfo memory identity = identityRegistry.identity(wallet);\n for (uint256 i = 0; i < identity.regions.length; i++) {\n TokenTypeRule memory rule = _tokenTypeRules[identity.regions[i]][identity.accreditationType];\n if (rule.isActive && rule.tokenType == tokenType) {\n if (!rule.requiresAmlKyc) {\n return true;\n }\n if (identityRegistry.isAmlKycPassed(wallet)) {\n return true;\n }\n }\n }\n\n if (_defaultTokenTypeRule.isActive && _defaultTokenTypeRule.tokenType == tokenType) {\n if (!_defaultTokenTypeRule.requiresAmlKyc) {\n return true;\n }\n\n return identityRegistry.isAmlKycPassed(wallet);\n }\n\n return false;\n }\n\n /// @inheritdoc ITransferRules\n function setTokenTypeRule(\n uint256 region,\n uint256 accreditation,\n uint256 tokenType,\n bool requiresAmlKyc,\n bool isActive\n ) public override onlyTransferAdmin {\n _tokenTypeRules[region][accreditation] = TokenTypeRule({\n tokenType: tokenType,\n requiresAmlKyc: requiresAmlKyc,\n isActive: isActive\n });\n\n emit TokenTypeRuleSet(\n region,\n accreditation,\n tokenType,\n requiresAmlKyc,\n isActive\n );\n }\n\n function setDefaultTokenTypeRule(\n uint256 tokenType,\n bool requiresAmlKyc,\n bool isActive\n ) public override onlyTransferAdmin {\n _defaultTokenTypeRule = TokenTypeRule({\n tokenType: tokenType,\n requiresAmlKyc: requiresAmlKyc,\n isActive: isActive\n });\n\n emit DefaultTokenTypeRuleSet(tokenType, requiresAmlKyc, isActive);\n }\n\n function resetDefaultTokenTypeRule() public override onlyTransferAdmin {\n _defaultTokenTypeRule = TokenTypeRule({\n tokenType: TOKEN_TYPE_GENERIC,\n requiresAmlKyc: false,\n isActive: false\n });\n\n emit DefaultTokenTypeRuleSet(TOKEN_TYPE_GENERIC, false, false);\n }\n\n function defaultTokenTypeRule() public view override returns (TokenTypeRule memory) {\n return _defaultTokenTypeRule;\n }\n\n /// @inheritdoc ITransferRules\n function removeTokenTypeRule(\n uint256 region,\n uint256 accreditation\n ) public override onlyTransferAdmin {\n delete _tokenTypeRules[region][accreditation];\n\n emit TokenTypeRuleRemoved(region, accreditation);\n }\n\n /// @inheritdoc ITransferRules\n function getTokenTypeRule(\n uint256 region,\n uint256 accreditation\n ) external view override returns (TokenTypeRule memory) {\n return _tokenTypeRules[region][accreditation];\n }\n\n /// @inheritdoc ITransferRules\n function batchSetTokenTypeRules(\n uint256[] calldata regions,\n uint256[] calldata accreditations,\n uint256[] calldata tokenTypes,\n bool[] calldata requiresAmlKycFlags,\n bool[] calldata isActiveFlags\n ) external override onlyTransferAdmin {\n if (\n regions.length != accreditations.length ||\n regions.length != tokenTypes.length ||\n regions.length != requiresAmlKycFlags.length ||\n regions.length != isActiveFlags.length\n ) {\n revert TransferRules_InvalidArrayLengths();\n }\n\n for (uint256 i = 0; i < regions.length; i++) {\n setTokenTypeRule(regions[i], accreditations[i], tokenTypes[i], requiresAmlKycFlags[i], isActiveFlags[i]);\n }\n }\n\n // ============================================\n // TOKEN TYPE TRANSFER RULES FUNCTIONALITY\n // ============================================\n\n /// @inheritdoc ITransferRules\n function detectTransferRestrictionForHolding(\n uint256 tokenType,\n uint256 mintTimestamp,\n IIdentityRegistry.IdentityInfo memory recipientIdentity,\n bool isAmlKycPassed\n ) public view override returns (uint256 restrictionCode) {\n restrictionCode = NO_RULE_FOR_RECIPIENT;\n \n // Check each region in the recipient's regions array\n for (uint256 i = 0; i < recipientIdentity.regions.length; i++) {\n // Single SLOAD - get packed rule (major gas optimization)\n uint256 packedRule = _packedRules[tokenType][recipientIdentity.regions[i]][recipientIdentity.accreditationType];\n \n // Quick check: is rule active? (single bitwise operation)\n if ((packedRule & IS_ACTIVE_MASK) == 0) continue;\n \n // Check AML/KYC requirement (single bitwise operation)\n if ((packedRule & REQUIRES_AML_KYC_MASK) != 0 && !isAmlKycPassed) {\n restrictionCode = RECIPIENT_NOT_AMLKYCPASSED;\n continue; // This region requires AML/KYC but recipient doesn't have it, try next region\n }\n \n // Check holding period (single shift operation)\n uint256 lockDuration = (packedRule >> LOCK_DURATION_SHIFT);\n if (lockDuration != 0 && mintTimestamp + lockDuration > block.timestamp) {\n restrictionCode = HOLDING_PERIOD_NOT_MET;\n continue; // This region's holding period not met, try next region\n }\n // If we reach here, this region allows the transfer\n return SUCCESS;\n }\n\n return restrictionCode;\n }\n\n /// @inheritdoc ITransferRules\n function setTransferRule(\n uint256 tokenType,\n uint256 recipientRegion,\n uint256 recipientAccreditation,\n TransferRule memory _rule\n ) public override onlyTransferAdmin {\n // Pack the rule into single uint256 for gas-efficient storage\n uint256 packed = _packRule(\n _rule.lockDurationSeconds,\n _rule.requiresAmlKyc,\n _rule.isActive\n );\n \n // Single SSTORE operation (major gas optimization)\n _packedRules[tokenType][recipientRegion][recipientAccreditation] = packed;\n\n emit TransferRuleSet(\n tokenType,\n recipientRegion,\n recipientAccreditation,\n _rule.lockDurationSeconds,\n _rule.requiresAmlKyc,\n _rule.isActive\n );\n }\n\n /// @inheritdoc ITransferRules\n function removeTransferRule(\n uint256 tokenType, \n uint256 recipientRegion, \n uint256 recipientAccreditation\n ) external override onlyTransferAdmin {\n delete _packedRules[tokenType][recipientRegion][recipientAccreditation];\n\n emit TransferRuleRemoved(tokenType, recipientRegion, recipientAccreditation);\n }\n\n /// @inheritdoc ITransferRules\n function getUnlockTimestamp(\n uint256 tokenType,\n uint256 mintTimestamp,\n address recipient,\n IIdentityRegistry identityRegistry\n ) external view override returns (uint256 unlockTimestamp) {\n IIdentityRegistry.IdentityInfo memory recipientIdentity = identityRegistry.identity(recipient);\n\n // Check each region in the recipient's regions array and return the earliest unlock timestamp\n uint256 earliestUnlockTimestamp = type(uint256).max;\n bool foundRule = false;\n\n for (uint256 i = 0; i < recipientIdentity.regions.length; i++) {\n // Direct packed storage access for better gas efficiency\n uint256 packedRule = _packedRules[tokenType][recipientIdentity.regions[i]][recipientIdentity.accreditationType];\n\n // Quick check: is rule active? (single bitwise operation)\n if ((packedRule & IS_ACTIVE_MASK) != 0) {\n foundRule = true;\n uint256 regionUnlockTimestamp = mintTimestamp + (packedRule >> LOCK_DURATION_SHIFT);\n \n if (regionUnlockTimestamp < earliestUnlockTimestamp) {\n earliestUnlockTimestamp = regionUnlockTimestamp;\n }\n }\n }\n\n if (!foundRule) {\n return 0; // No rule means no restriction\n }\n\n return earliestUnlockTimestamp;\n }\n\n /// @inheritdoc ITransferRules\n function batchSetTransferRules(\n uint256[] calldata tokenType,\n uint256[] calldata recipientRegion,\n uint256[] calldata recipientAccreditation,\n TransferRule[] calldata rules\n ) external override onlyTransferAdmin {\n if (\n tokenType.length != recipientRegion.length ||\n tokenType.length != recipientAccreditation.length ||\n tokenType.length != rules.length\n ) {\n revert TransferRules_InvalidArrayLengths();\n }\n\n for (uint256 i = 0; i < rules.length; i++) {\n setTransferRule(tokenType[i], recipientRegion[i], recipientAccreditation[i], rules[i]);\n }\n }\n\n /// @inheritdoc ITransferRules\n function transferRuleFor(\n uint256 tokenType,\n uint256 recipientRegion,\n uint256 recipientAccreditation\n ) public view override returns (TransferRule memory rule) {\n uint256 packed = _packedRules[tokenType][recipientRegion][recipientAccreditation];\n (rule.lockDurationSeconds, rule.requiresAmlKyc, rule.isActive) = _unpackRule(packed);\n }\n\n function _msgSender()\n internal\n view\n override(ERC2771Context)\n returns (address sender)\n {\n return ERC2771Context._msgSender();\n }\n\n function _msgData()\n internal\n view\n override(ERC2771Context)\n returns (bytes calldata)\n {\n return ERC2771Context._msgData();\n }\n\n function _contextSuffixLength()\n internal\n view\n override(ERC2771Context)\n returns (uint256)\n {\n return ERC2771Context._contextSuffixLength();\n }\n}\n"},"contracts/interfaces/IAccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ninterface IAccessControl {\n // ============================================\n // EVENTS\n // ============================================\n \n /// @notice This event is emitted when a new role is granted or revoked to a user\n /// @param grantor The address of admin who granted the role\n /// @param grantee The address of user who received the role\n /// @param role The role that was granted as a bitmask\n /// @param status The status of the role (true = granted, false = revoked)\n event RoleChange(\n address indexed grantor,\n address indexed grantee,\n uint8 role,\n bool indexed status\n );\n\n // Note: Errors are defined in AccessControlErrors.sol to avoid duplicate declarations\n\n // ============================================\n // VIEW FUNCTIONS\n // ============================================\n \n // View role checks\n function hasRole(address addr, uint8 role) external view returns (bool);\n function roles(address addr) external view returns (uint8);\n function contractAdminCount() external view returns (uint8);\n\n // Role constants\n function CONTRACT_ADMIN_ROLE() external view returns (uint8);\n function TRANSFER_ADMIN_ROLE() external view returns (uint8);\n function RESERVE_ADMIN_ROLE() external view returns (uint8);\n function WALLETS_ADMIN_ROLE() external view returns (uint8);\n function SOFT_BURN_ADMIN_ROLE() external view returns (uint8);\n function MINT_ADMIN_ROLE() external view returns (uint8);\n\n // ============================================\n // STATE-CHANGING FUNCTIONS\n // ============================================\n \n // Role management\n function grantRole(address addr, uint8 role) external;\n function batchGrantRoles(address[] calldata addresses, uint8[] calldata roles_) external;\n function revokeRole(address addr, uint8 role) external;\n function batchRevokeRoles(address[] calldata addresses, uint8[] calldata roles_) external;\n}"},"contracts/interfaces/IDividends.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\ninterface IDividends {\n /**\n * @title Functions\n */\n\n /// @dev Get unused ERC-20 tokens on timestamp\n /// @param token ERC-20 token address\n /// @param timestamp Timestamp for historical data\n /// @return amount of ERC-20 tokens\n function tokensAt(\n address token,\n uint256 timestamp\n ) external view returns (uint256);\n\n /**\n * @dev Fund any ERC-20 tokens into current contract\n * Tokens can be claimed by holders of RestrictedLockup Token uses claimDividends method\n * @param token ERC-20 token address\n * @param amount amount of tokens to fund\n * @param timestamp timestamp for dividend distribution\n */\n function fundDividend(\n address token,\n uint256 amount,\n uint256 timestamp\n ) external;\n\n /**\n * @dev Reclaim ERC-20 tokens from a specific dividend snapshot\n * Can only be done if no dividends have been claimed for this snapshot (unusedFunds == totalFunds)\n * If amount is 0, reclaims all available funds\n * @param token ERC-20 token address\n * @param amount amount of tokens to reclaim (0 = reclaim all)\n * @param timestamp timestamp for dividend distribution\n */\n function reclaimTotalDividend(\n address token,\n uint256 amount,\n uint256 timestamp\n ) external;\n\n /**\n * @dev Reclaim dividends for a specific target address and send to contract reclaimer\n * Can only be called by transfer admin\n * @param token ERC-20 token address\n * @param targetAddress Address to reclaim dividends for\n * @param timestamp timestamp for dividend distribution\n * @param amount amount of tokens to reclaim (0 = reclaim all)\n */\n function reclaimDividend(\n address token,\n address targetAddress,\n uint256 timestamp,\n uint256 amount\n ) external;\n\n /**\n * @dev Get balance of ERC-20 tokens funded at timestamp\n * @param token ERC-20 token address\n * @param timestamp timestamp for historical data\n * @return amount of ERC-20 tokens\n */\n function fundsAt(\n address token,\n uint256 timestamp\n ) external view returns (uint256);\n\n /**\n * @dev Amount of ERC-20 tokens distributed to the holder of RestrictedLockup Token at timestamp\n * @param token ERC-20 token address\n * @param receiver RestrictedLockup Token's holder address\n * @param timestamp timestamp for historical data\n * @return amount of total ERC-20 tokens distributed to the receiver\n */\n function totalAwardedBalanceAt(\n address token,\n address receiver,\n uint256 timestamp\n ) external view returns (uint256);\n\n /**\n * @dev Amount of ERC-20 tokens claimed by the holder of RestrictedLockup Token at timestamp\n * @param token ERC-20 token address\n * @param receiver RestrictedLockup Token's holder address\n * @param timestamp timestamp for historical data\n * @return amount of claimed ERC-20 tokens\n */\n function claimedBalanceAt(\n address token,\n address receiver,\n uint256 timestamp\n ) external view returns (uint256);\n\n /**\n * @dev Amount of ERC-20 tokens that can be claimed by the holder of RestrictedLockup Token at timestamp\n * @param token ERC-20 token address\n * @param receiver RestrictedLockup Token's holder address\n * @param timestamp timestamp for historical data\n * @return amount of can be claimed ERC-20 tokens\n */\n function unclaimedBalanceAt(\n address token,\n address receiver,\n uint256 timestamp\n ) external view returns (uint256);\n\n /**\n * @dev Claim ERC-20 tokens (dividends) by RestrictedLockup Tokens holder\n * Tokens can be claimed when its allowed by unclaimedBalanceAt\n * @param token ERC-20 token address\n * @param timestamp timestamp for dividend claim\n * @param amount amount of tokens to reclaim (0 = reclaim all)\n */\n function claimDividend(address token, uint256 timestamp, uint256 amount) external;\n\n /**\n * @dev Claim ERC-20 tokens (dividends) by RestrictedLockup Tokens holder, across multiple timestamps\n * Tokens can be claimed when its allowed by unclaimedBalanceAt\n * @param token ERC-20 token address\n * @param timestamps timestamps for dividend claims\n * @param amounts amounts of tokens to reclaim (0 = reclaim all)\n */\n function batchClaimDividend(\n address token,\n uint256[] calldata timestamps,\n uint256[] calldata amounts\n ) external;\n\n /**\n * @title Events\n */\n\n /// @notice This event is emitted when a timestamp is funded with ERC-20 tokens\n /// @param payer address of the payer\n /// @param token ERC-20 token address\n /// @param amount amount of ERC-20 tokens\n /// @param timestamp timestamp for dividend distribution\n event DividendFunded(\n address indexed payer,\n address indexed token,\n uint256 amount,\n uint256 indexed timestamp\n );\n\n /// @notice This event is emitted when a holder of RestrictedLockup Token claims ERC-20 tokens\n /// @param payee address of the receiver\n /// @param token ERC-20 token address\n /// @param amount amount of ERC-20 tokens\n /// @param timestamp timestamp for dividend claim\n event DividendClaimed(\n address indexed payee,\n address indexed token,\n uint256 amount,\n uint256 indexed timestamp\n );\n\n /// @notice This event is emitted when all ERC-20 tokens are reclaimed from a dividend snapshot\n /// @param payee address of the payee\n /// @param target address of the target\n /// @param token ERC-20 token address\n /// @param amount amount of ERC-20 tokens reclaimed\n /// @param timestamp timestamp for dividend distribution\n event DividendReclaimed(\n address indexed payee,\n address indexed target,\n address indexed token,\n uint256 amount,\n uint256 timestamp\n );\n}"},"contracts/interfaces/IERC1404.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface IERC1404 is IERC20 {\n function detectTransferRestriction(\n address from,\n address to,\n uint256 value\n ) external view returns (uint8);\n\n function messageForTransferRestriction(\n uint8 restrictionCode\n ) external view returns (string memory);\n}\n"},"contracts/interfaces/IERC20Decimals.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/// @notice enhanced ERC20 interface with decimals\ninterface IERC20Decimals is IERC20 {\n function decimals() external view returns (uint8);\n}\n"},"contracts/interfaces/IIdentityRegistry.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport \"@openzeppelin/contracts/utils/introspection/IERC165.sol\";\n\ninterface IIdentityRegistry {\n struct IdentityInfo {\n uint256[] regions;\n uint256 accreditationType;\n uint256 lastAmlKycChangeTimestamp;\n uint256 lastAccreditationChangeTimestamp;\n bool amlKycPassed;\n }\n\n /**\n * @dev Sets the validity duration for AML/KYC status in seconds.\n * @param amlKycValidityDuration The duration in seconds for which AML/KYC status remains valid\n */\n function setAmlKycValidityDuration(uint256 amlKycValidityDuration) external;\n\n /**\n * @dev Sets or updates the complete identity information for a wallet address.\n * @param owner The wallet address to set identity for\n * @param info The complete identity information structure\n */\n function setIdentity(address owner, IdentityInfo memory info) external;\n\n /**\n * @dev Sets or updates the complete identity information for a wallet address.\n * @param owners The wallet addresses to set identity for\n * @param info The complete identity information structure\n */\n function batchSetIdentity(address[] memory owners, IdentityInfo memory info) external;\n\n /**\n * @dev Removes all identity information for a wallet address.\n * @param owner The wallet address to remove identity from\n */\n function removeIdentity(address owner) external;\n\n /**\n * @dev Retrieves the complete identity information for a wallet address.\n * @param owner The wallet address to query\n * @return The complete identity information structure\n */\n function identity(\n address owner\n ) external view returns (IdentityInfo memory);\n\n /**\n * @dev Sets the regions for a wallet address.\n * @param owner The wallet address to set regions for\n * @param regions The array of region identifiers\n */\n function setRegions(address owner, uint256[] memory regions) external;\n\n /**\n * @dev Adds a region to a wallet address.\n * @param owner The wallet address to add region to\n * @param region The region identifier to add\n */\n function addRegion(address owner, uint256 region) external;\n\n /**\n * @dev Removes a region from a wallet address.\n * @param owner The wallet address to remove region from\n * @param region The region identifier to remove\n */\n function removeRegion(address owner, uint256 region) external;\n\n /**\n * @dev Grants AML/KYC approval for a wallet address.\n * @param owner The wallet address to grant AML/KYC for\n * @param amlKycTimestamp The timestamp when AML/KYC was granted (0 for current timestamp)\n */\n function grantAmlKyc(address owner, uint256 amlKycTimestamp) external;\n\n /**\n * @dev Revokes AML/KYC approval for a wallet address.\n * @param owner The wallet address to revoke AML/KYC from\n * @param amlKycTimestamp The timestamp when AML/KYC was revoked (0 for current timestamp)\n */\n function revokeAmlKyc(address owner, uint256 amlKycTimestamp) external;\n\n /**\n * @dev Grants accreditation level for a wallet address.\n * @param owner The wallet address to grant accreditation for\n * @param accreditationType The type/level of accreditation to grant\n * @param accreditationTimestamp The timestamp when accreditation was granted (0 for current timestamp)\n */\n function grantAccreditation(address owner, uint256 accreditationType, uint256 accreditationTimestamp) external;\n\n /**\n * @dev Revokes accreditation for a wallet address.\n * @param owner The wallet address to revoke accreditation from\n * @param accreditationTimestamp The timestamp when accreditation was revoked (0 for current timestamp)\n */\n function revokeAccreditation(address owner, uint256 accreditationTimestamp) external;\n\n /**\n * @dev Retrieves the regions for a wallet address.\n * @param owner The wallet address to query\n * @return The array of region identifiers\n */\n function regions(address owner) external view returns (uint256[] memory);\n\n /**\n * @dev Checks if a wallet address has a specific region.\n * @param owner The wallet address to check\n * @param region The region identifier to check\n * @return True if the wallet has the region, false otherwise\n */\n function hasRegion(address owner, uint256 region) external view returns (bool);\n\n /**\n * @dev Retrieves the accreditation type for a wallet address.\n * @param owner The wallet address to query\n * @return The accreditation type/level\n */\n function accreditationType(address owner) external view returns (uint256);\n\n /**\n * @dev Checks if a wallet address has valid AML/KYC approval.\n * @param owner The wallet address to check\n * @return True if AML/KYC is passed and still valid, false otherwise\n */\n function isAmlKycPassed(address owner) external view returns (bool);\n\n /**\n * @dev Retrieves the current AML/KYC validity duration in seconds.\n * @return The validity duration in seconds\n */\n function amlKycValidityDuration() external view returns (uint256);\n\n // Events\n event IdentityCreated(address indexed authority, address indexed owner);\n event IdentityRemoved(address indexed authority, address indexed owner);\n event RegionsSet(address indexed authority, address indexed owner, uint256[] regions);\n event RegionAdded(address indexed authority, address indexed owner, uint256 region);\n event RegionRemoved(address indexed authority, address indexed owner, uint256 region);\n event AmlKycPassed(address indexed authority, address indexed owner, uint256 amlKycTimestamp);\n event AmlKycFailed(address indexed authority, address indexed owner, uint256 amlKycTimestamp);\n event AccreditationGranted(address indexed authority, address indexed owner, uint256 newAccreditationLevel, uint256 accreditationTimestamp);\n event AccreditationRevoked(address indexed authority, address indexed owner, uint256 revokedAccreditationLevel, uint256 accreditationTimestamp);\n event AmlKycValidityDurationSet(address indexed authority, uint256 oldAmlKycValidityDuration, uint256 newAmlKycValidityDuration);\n}\n"},"contracts/interfaces/IInterestPayment.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\n/**\n * @title IInterestPayment\n * @notice Interface for managing interest payments for loans backed by RestrictedLockupToken\n * @dev Defines functions for tracking interest accrual based on token holdings and claiming interest payments\n */\ninterface IInterestPayment {\n /// @notice This event is emitted when a payment interest period is funded with ERC-20 tokens\n /// @param payer address of the payer who funded the interest\n /// @param amount amount of ERC-20 tokens funded\n event Funded(address indexed payer, uint256 amount);\n\n /// @notice This event is emitted when a payment period is created\n /// @param creator address of the creator who created the payment period\n /// @param paymentPeriodIdx payment period index\n /// @param startTimestamp start timestamp\n /// @param endTimestamp end timestamp\n /// @param interestRate interest rate\n event PaymentPeriodCreated(\n address indexed creator,\n uint256 indexed paymentPeriodIdx,\n uint256 startTimestamp,\n uint256 endTimestamp,\n uint256 interestRate\n );\n\n /// @notice This event is emitted when a interest rate is updated\n /// @param creator address of the creator who updated the interest rate\n /// @param paymentPeriodIdx payment period index\n /// @param interestRate interest rate\n event InterestRateUpdated(\n address indexed creator,\n uint256 indexed paymentPeriodIdx,\n uint256 interestRate\n );\n\n /// @notice This event is emitted when a holder of RestrictedLockup Token claims interests\n /// @param payee address of the receiver who claimed the interest\n /// @param amount amount of ERC-20 tokens claimed\n /// @param periodIdx period index for which interest was claimed\n event Claimed(\n address indexed payee,\n uint256 amount,\n uint256 indexed periodIdx\n );\n\n /// @notice This event is emitted when interest is reclaimed from a specific wallet\n /// @param payee address that reclaimed the interest\n /// @param target address from which interest was reclaimed\n /// @param amount amount of ERC-20 tokens reclaimed\n /// @param periodIdx period index for which interest was reclaimed\n event Reclaimed(\n address indexed payee,\n address indexed target,\n uint256 amount,\n uint256 periodIdx\n );\n\n /// @notice This event is emitted when all unclaimed interest for a period is reclaimed\n /// @param payee address that reclaimed the interest\n /// @param amount total amount of ERC-20 tokens reclaimed\n /// @param periodIdx period index for which all interest was reclaimed\n event ReclaimedAll(\n address indexed payee,\n uint256 amount,\n uint256 indexed periodIdx\n );\n\n /// @notice This event is emitted when principal amount is reclaimed\n /// @param payee address that reclaimed the principal\n /// @param amount amount of ERC-20 tokens reclaimed as principal\n event PrincipalReclaimed(address indexed payee, uint256 amount);\n\n /// @notice This event is emitted when principal amount is funded\n /// @param payer address of the payer who funded the principal\n /// @param amount amount of ERC-20 tokens funded as principal\n event PrincipalFunded(address indexed payer, uint256 amount);\n\n /// @notice This event is emitted when principal amount is claimed\n /// @param payee address of the receiver who claimed the principal\n /// @param amount amount of ERC-20 tokens claimed as principal\n event PrincipalClaimed(address indexed payee, uint256 amount);\n\n /// @notice This event is emitted when the reclaimer address is changed\n /// @param admin address of the admin who changed the reclaimer\n /// @param newReclaimer address of the new reclaimer\n event ReclaimerAddressChanged(\n address indexed admin,\n address indexed newReclaimer\n );\n\n /// @notice This event is emitted when the interest accrual end timestamp is changed\n /// @param admin address of the admin who changed the interest accrual end timestamp\n /// @param newInterestAccrualEndTimestamp new timestamp for the interest accrual end timestamp\n event InterestAccrualEndShifted(\n address indexed admin,\n uint256 newInterestAccrualEndTimestamp\n );\n\n /// @notice This event is emitted when a payment period is paused\n /// @param admin address of the admin who paused the period\n /// @param periodIdx period index that was paused\n event PeriodPaused(address indexed admin, uint256 indexed periodIdx);\n\n /// @notice This event is emitted when a payment period is unpaused\n /// @param admin address of the admin who unpaused the period\n /// @param periodIdx period index that was unpaused\n event PeriodUnpaused(address indexed admin, uint256 indexed periodIdx);\n\n /// @notice This event is emitted when a payment period is paused after a specific timestamp\n /// @param timestamp The timestamp after which payments are paused\n event PaymentPausedAfter(uint256 timestamp);\n\n /// @notice This event is emitted when a payments is unpaused\n event PaymentUnpausedAfter();\n\n /// @notice This event is emitted when a payments is paused after a specific timestamp\n /// @param admin address of the admin who paused the payments\n /// @param timestamp The timestamp after which payments are paused\n event EarlyRepayment(address indexed admin, uint256 timestamp);\n\n /// @notice This event is emitted when an admin force claims interest for a wallet\n /// @param admin address of the admin who force claimed\n /// @param wallet address of the wallet for which interest was claimed\n /// @param amount amount of ERC-20 tokens claimed\n /// @param periodIdx period index for which interest was claimed\n event ForceClaimed(\n address indexed admin,\n address indexed wallet,\n uint256 amount,\n uint256 indexed periodIdx\n );\n\n /// @notice This event is emitted when the max interest rate is set\n /// @param admin address of the admin who set the max interest rate\n /// @param maxInterestRate the new max interest rate\n event SetMaxInterestRate(address indexed admin, uint256 maxInterestRate);\n\n /// @notice Checks if a specific payment period is paused\n /// @param periodIdx The period index to check\n /// @return A boolean indicating if the period is paused (true) or active (false)\n function paymentPeriodPaused(\n uint256 periodIdx\n ) external view returns (bool);\n\n /// @notice Pauses payments for a specific period\n /// @param periodIdx The period index to pause\n function pausePaymentPeriod(uint256 periodIdx) external;\n\n /// @notice Unpauses payments for a specific period\n /// @param periodIdx The period index to unpause\n function unpausePaymentPeriod(uint256 periodIdx) external;\n\n /// @notice Pauses or unpauses the entire contract\n /// @param isPaused_ Whether to pause (true) or unpause (false) the contract\n function pause(bool isPaused_) external;\n\n /// @notice Pauses payment after a specific timestamp\n /// @param timestamp The timestamp after which payments will be paused\n function pausePaymentAfter(uint256 timestamp) external;\n\n /// @notice Unpauses payment after being paused with pausePaymentAfter\n function unpausePaymentAfter() external;\n\n /// @notice Sets the address that can reclaim unclaimed interest\n /// @param reclaimerAddress_ The new reclaimer address\n function setReclaimerAddress(address reclaimerAddress_) external;\n\n /// @notice Shifts the interest accrual end timestamp\n /// @param newInterestAccrualEndTimestamp The new interest accrual end timestamp\n function shiftInterestAccrualEnd(\n uint256 newInterestAccrualEndTimestamp\n ) external;\n\n /// @notice Calculates accrued interest for an account at the current time\n /// @param account The account address to check interest for\n /// @return The amount of accrued interest in the specified token\n function accruedInterest(address account) external view returns (uint256);\n\n /// @notice Calculates accrued interest for an account at a specific timestamp\n /// @param account The account address to check interest for\n /// @param timestamp The timestamp at which to calculate the accrued interest\n /// @return The amount of accrued interest at the given timestamp\n function accruedInterestAt(\n address account,\n uint256 timestamp\n ) external view returns (uint256);\n\n /// @notice Shows total funded interest\n /// @return The total funded interest\n function totalInterestAmountFunded() external view returns (uint256);\n\n /// @notice Shows total claimed interest\n /// @return The total claimed interest\n function totalInterestAmountClaimed() external view returns (uint256);\n\n /// @notice Shows total reclaimed interest\n /// @return The total reclaimed interest\n function totalInterestAmountReclaimed() external view returns (uint256);\n\n /// @notice Shows total unused interest\n /// @return The total unused interest\n function totalInterestAmountUnused() external view returns (uint256);\n\n /// @notice Gets the nearest interest payment timestamp at a given timestamp\n /// @param timestamp The reference timestamp\n /// @return The nearest interest payment timestamp (rounded to period boundaries)\n function nearestInterestPaymentTimestampAt(\n uint256 timestamp\n ) external view returns (uint256);\n\n /// @notice Finds the index of a funded payment period that covers the specified time range\n /// @param startTimestamp The start timestamp of the period to find\n /// @param endTimestamp The end timestamp of the period to find\n /// @return The period index or max uint256 if no matching period is found\n function findPaymentPeriodIndex(\n uint256 startTimestamp,\n uint256 endTimestamp\n ) external view returns (uint256);\n\n /// @notice Creates a payment period\n /// @param startTimestamp The start timestamp of the period\n /// @param endTimestamp The end timestamp of the period\n /// @param interestRate The interest rate for the period\n /// @param interestRatePeriodDuration The duration of the interest rate period\n function createPaymentPeriod(\n uint256 startTimestamp,\n uint256 endTimestamp,\n uint256 interestRate,\n uint256 interestRatePeriodDuration\n ) external;\n\n /// @notice Funds interest\n /// @param amount The amount of tokens to fund\n function fundInterest(uint256 amount) external;\n\n /// @notice Claims interest for a specific period\n /// @param paymentPeriodIdx The payment period index to claim interest for\n /// @param amount The amount to claim, capped at available claimable amount\n function claimInterestForPeriod(\n uint256 paymentPeriodIdx,\n uint256 amount\n ) external;\n\n /// @notice Claims interest for multiple periods\n /// @param paymentPeriodIdxs The payment period indexes to claim interest for\n /// @param amount The amount to claim, capped at available claimable amount\n function batchClaimInterestForPeriods(\n uint256[] memory paymentPeriodIdxs,\n uint256 amount\n ) external;\n\n /// @notice Claims interest for all available periods\n /// @param amount The amount to claim, capped at available claimable amount\n function claimInterest(uint256 amount) external;\n\n /// @notice Force claim interest for an account for a specific period\n /// @param wallet The wallet address to claim interest for\n /// @param paymentPeriodIdx The payment period index to claim interest for\n /// @param amount The amount to claim, capped at available claimable amount\n function forceClaimForPeriod(\n address wallet,\n uint256 paymentPeriodIdx,\n uint256 amount\n ) external;\n\n /// @notice Batch force claim interest for specific periods for a wallet\n /// @param wallet The wallet address to claim interest for\n /// @param paymentPeriodIdxs Array of payment period indices to claim interest from\n /// @param amount The maximum amount to claim, or 0 for all available funds\n function batchForceClaimInterest(\n address wallet,\n uint256[] memory paymentPeriodIdxs,\n uint256 amount\n ) external;\n\n /// @notice Gets the total number of payment periods\n /// @return The number of payment periods\n function paymentPeriodsCount() external view returns (uint256);\n\n /// @notice Gets the available interest for a specific period\n /// @param periodIdx The period index to check\n /// @return The available interest for the specified period\n function periodAvailableInterest(\n uint256 periodIdx\n ) external view returns (uint256);\n\n /// @notice Gets the duration of a specific period in seconds\n /// @param periodIdx The period index to check\n /// @return The period duration in seconds\n function periodDuration(uint256 periodIdx) external view returns (uint256);\n\n /// @notice Calculates unclaimed amount for a receiver at a specific period\n /// @param receiver_ The receiver address to check\n /// @param paymentPeriodIdx_ The payment period index to check\n /// @return The unclaimed amount for the specified parameters\n function unclaimedAmountForPeriod(\n address receiver_,\n uint256 paymentPeriodIdx_\n ) external view returns (uint256);\n\n /// @notice Gets claimed amount for a receiver at a specific period\n /// @param receiver_ The receiver address to check\n /// @param paymentPeriodIdx_ The payment period index to check\n /// @return The claimed amount for the specified parameters\n function claimedAmountForPeriod(\n address receiver_,\n uint256 paymentPeriodIdx_\n ) external view returns (uint256);\n\n /// @notice Reclaims interest for a specific period from a specific wallet\n /// @param wallet The wallet address from which to reclaim interest\n /// @param paymentPeriodIdx_ The payment period index to reclaim interest from\n /// @param amount The amount to reclaim, capped at available reclaimable amount\n function reclaimInterestForPeriod(\n address wallet,\n uint256 paymentPeriodIdx_,\n uint256 amount\n ) external;\n\n /// @notice Reclaims interest for all recipients for a specific period\n /// @param paymentPeriodIdx_ The payment period index to reclaim all interest from\n function reclaimInterestForAllRecipients(\n uint256 paymentPeriodIdx_\n ) external;\n\n /// @notice Reclaims interest for a specific period from a specific wallet\n /// @param wallet The wallet address from which to reclaim interest\n /// @param amount The amount to reclaim, capped at available reclaimable amount\n function reclaimInterest(address wallet, uint256 amount) external;\n\n /// @notice Reclaims interest for multiple periods from a specific wallet\n /// @param wallet The wallet address from which to reclaim interest\n /// @param paymentPeriodIdxs The payment period indexes to reclaim interest from\n /// @param amount The amount to reclaim, capped at available reclaimable amount\n function batchReclaimInterest(\n address wallet,\n uint256[] memory paymentPeriodIdxs,\n uint256 amount\n ) external;\n\n /// @notice Reclaims total unused interest without regard to periods or wallets\n /// @param amount The amount to reclaim from total unused interest\n function reclaimTotalInterest(uint256 amount) external;\n\n /// @notice Funds principal amount in payment token\n /// @param amount The amount of tokens to fund as principal\n function fundPrincipal(uint256 amount) external;\n\n /// @notice Claims principal amount in payment token\n /// @param amount The amount to claim, or 0 for all available principal\n function claimPrincipal(uint256 amount) external;\n\n /// @notice Force claim principal for a specific wallet address\n /// @param wallet The wallet address to claim principal for\n /// @param amount The amount to claim, or 0 for all available principal\n function forceClaimPrincipal(address wallet, uint256 amount) external;\n\n /// @notice Reclaims principal amount in payment token\n /// @param amount The amount to reclaim, or 0 for all available principal\n function reclaimPrincipal(uint256 amount) external;\n\n /// @notice Gets the available principal amount for a specific account\n /// @param account The account address to check\n /// @return The available principal amount for the specified account\n function availablePrincipalAmount(\n address account\n ) external view returns (uint256);\n\n /// @notice Gets the total principal funded\n /// @return The total principal funded\n function fundedPrincipalAmount() external view returns (uint256);\n\n /// @notice Gets the total principal reclaimed\n /// @return The total principal reclaimed\n function reclaimedPrincipalAmount() external view returns (uint256);\n\n /// @notice Gets the total principal claimed\n /// @return The total principal claimed\n function claimedPrincipalAmount() external view returns (uint256);\n\n /// @notice Gets the total available principal\n /// @return The total available principal\n function totalAvailablePrincipalAmount() external view returns (uint256);\n\n /// @notice Gets the total accrued interest at the block timestamp\n /// @return The total accrued interest\n function totalAccruedInterest() external view returns (uint256);\n\n /// @notice Gets the total accrued interest at a specific timestamp\n /// @param timestamp The timestamp to get the accrued interest for\n /// @return The total accrued interest\n function totalAccruedInterestAt(\n uint256 timestamp\n ) external view returns (uint256);\n\n /// @notice Triggers early repayment by pausing payments and setting accrual end to a specific timestamp\n /// @param timestamp The timestamp to set the accrual end to\n function earlyRepayment(uint256 timestamp) external;\n\n /// @notice Sets the maximum interest rate\n /// @param maxInterestRate_ The new maximum interest rate in basis points\n function setMaxInterestRate(uint256 maxInterestRate_) external;\n\n /// @notice Updates the interest rate for a specific period\n /// @param periodIdx The period index to update\n /// @param interestRate_ The new interest rate in basis points\n /// @param interestRatePeriodDuration The duration of the interest rate period\n function updateInterestRateForPeriod(\n uint256 periodIdx,\n uint256 interestRate_,\n uint256 interestRatePeriodDuration\n ) external;\n\n /// @notice Gets the restricted lockup token contract\n /// @return The restricted lockup token contract\n function restrictedLockupToken() external view returns (address);\n\n /// @notice Gets the payment token contract\n /// @return The payment token contract\n function paymentToken() external view returns (address);\n\n /// @notice Gets the principal amount per token\n /// @return The principal amount per token\n function principalAmountPerToken() external view returns (uint256);\n}\n"},"contracts/interfaces/IRestrictedLockupToken.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport \"./IIdentityRegistry.sol\";\nimport \"./IAccessControl.sol\";\n\n// Interface for integration with Restricted Lockup Token\ninterface IRestrictedLockupToken {\n struct Timelock {\n address funder;\n uint256 scheduleId;\n uint256 commencementTimestamp;\n uint256 tokensTransferred;\n uint256 totalAmount;\n uint256 globalHoldingIndex; // Global index for token type + daysAfterDeployment combination\n address[] cancelableBy; // not cancelable unless set at the time of funding\n }\n struct FundReleaseScheduleParams {\n address to;\n uint256 amount;\n uint256 commencementTimestamp;\n uint256 scheduleId;\n uint256 holdingIdx;\n }\n // Holdings (wallet-scoped)\n function holdingOf(address who_, uint256 index_) external view returns (uint256, uint256, uint256);\n function holdingCountOf(address who_) external view returns (uint256);\n\n // Global holdings introspection (token-type scoped)\n function globalHoldingCount() external view returns (uint256);\n function getWalletIndexesLength(address who_) external view returns (uint256);\n function getWalletIndexes(address who_, uint256 index) external view returns (uint256);\n\n // Identity/registry\n function isAmlKycPassed(address who_) external view returns (bool);\n function identityRegistry() external view returns (IIdentityRegistry);\n function isValidIdentityRegistry(address identityRegistry) external view returns (bool);\n\n // Balances\n function balanceOf(address who_) external view returns (uint256);\n function unlockedTotalBalanceOf(address who_) external view returns (uint256);\n function unlockedBalanceOfTimelock(address who_, uint256 index_) external view returns (uint256);\n function isPaused() external view returns (bool);\n function getFrozenStatus(address who_) external view returns (bool);\n\n // Timelocks\n function timelockCountOf(address who_) external view returns (uint256);\n function timelockOf(address who_, uint256 index_) external view returns (Timelock memory);\n\n // Administrative token type updates\n function updateHoldingTokenType(address wallet, uint256 oldTokenType, uint256 newTokenType, uint256 mintTimestamp, uint256 amount) external;\n function updateTimelockTokenType(address wallet, uint256 timelockIndex, uint256 newTokenType) external;\n \n // Burn functions\n function burn(address from_, uint256 value_) external;\n function softBurn(address from_, uint256 value_) external;\n function burnHolding(address from_, uint256 globalHoldingIdx, uint256 amount) external;\n\n function snapshotPeriodsAddress() external view returns (address);\n\n function totalSupply() external view returns (uint256);\n function decimals() external view returns (uint8);\n\n // Mint functions\n function mint(address to, uint256 amount) external;\n function mintTokenType(address to, uint256 amount, uint256 tokenType) external;\n\n // Transfer functions\n function transfer(address to, uint256 amount) external;\n function batchTransfer(address[] calldata to, uint256[] calldata amounts) external;\n function transferFrom(address from, address to, uint256 amount) external;\n function forceTransferBetween(address from, address to, uint256 amount) external;\n function transferHolding(address from, address to, uint256 amount) external;\n\n // Max total supply\n function setMaxTotalSupply(uint256 maxTotalSupply) external;\n\n // Transfer rules\n function transferRules() external view returns (address);\n function isValidTransferRules(address transferRules) external view returns (bool);\n function accessControl() external view returns (IAccessControl);\n function restrictedLockupTokenExtension() external view returns (address);\n function restrictedLockupTokenManagementExtension() external view returns (address);\n\n // token type view functions\n function existingTokenTypesCount() external view returns (uint256);\n function existingTokenTypes(uint256 index) external view returns (uint256);\n function tokenTypeExists(uint256 tokenType) external view returns (bool);\n\n // Holder Management Functions\n function createHolderFromAddress(address addr_) external returns (uint256);\n function appendHolderAddress(address addr_, uint256 holderId_) external;\n function addHolderWithAddresses(address[] calldata addresses_) external returns (uint256);\n function removeHolder(uint256 holderId_) external;\n function removeWalletFromHolder(address addr_) external;\n function batchRemoveWalletFromHolder(address[] calldata addresses_) external;\n function getHolderAddresses(uint256 holderId_) external view returns (address[] memory);\n function getHolderId(address addr_) external view returns (uint256);\n function addressHasHolder(address addr_) external view returns (bool);\n function holderExists(uint256 holderId_) external view returns (bool);\n function setHolderMax(uint256 holderMax_) external;\n\n // Admin Functions\n function pause(bool isPaused_) external;\n function freeze(address addr_, bool status_) external;\n\n // Upgrade Functions\n function upgradeTransferRules(address newTransferRules) external;\n function upgradeIdentityRegistry(address newIdentityRegistry) external;\n\n // Release Schedule Functions\n function createReleaseSchedule(\n uint256 releaseCount,\n uint256 delayUntilFirstReleaseInSeconds,\n uint256 initialReleasePortionInBips,\n uint256 periodBetweenReleasesInSeconds\n ) external returns (uint256);\n \n function fundReleaseScheduleWithHolding(\n FundReleaseScheduleParams memory params,\n address[] memory cancelableBy_\n ) external returns (bool);\n \n function fundReleaseSchedule(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n address[] memory cancelableBy_\n ) external returns (bool);\n \n function batchFundReleaseScheduleWithHolding(\n FundReleaseScheduleParams[] memory params,\n address[] memory cancelableBy_\n ) external returns (bool);\n \n function batchFundReleaseSchedule(\n address[] memory to_,\n uint256[] memory amounts_,\n uint256[] memory commencementTimestamps_,\n uint256[] memory scheduleIds_,\n address[] memory cancelableBy_\n ) external returns (bool);\n \n function mintReleaseScheduleTokenType(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n uint256 tokenType_,\n address[] memory cancelableBy_\n ) external returns (bool);\n \n function mintReleaseSchedule(\n address to_,\n uint256 amount_,\n uint256 commencementTimestamp_,\n uint256 scheduleId_,\n address[] memory cancelableBy_\n ) external returns (bool);\n \n function batchMintReleaseScheduleTokenType(\n address[] calldata to,\n uint256[] calldata amounts,\n uint256[] calldata commencementTimestamps,\n uint256[] calldata scheduleIds,\n uint256[] calldata tokenTypes,\n address[] calldata cancelableBy\n ) external returns (bool);\n \n function batchMintReleaseSchedule(\n address[] calldata to,\n uint256[] calldata amounts,\n uint256[] calldata commencementTimestamps,\n uint256[] calldata scheduleIds,\n address[] calldata cancelableBy\n ) external returns (bool);\n\n // Additional Functions\n function safeApprove(address spender_, uint256 value_) external;\n\n // Supply Functions\n function maxTotalSupply() external view returns (uint256);\n function circulatingTokenSupply() external view returns (uint256);\n function unissuedTokenSupply() external view returns (uint256);\n\n // Token Type Functions\n function determineTokenType(address wallet) external view returns (uint256);\n function totalBalanceOf(address account) external view returns (uint256);\n\n // Transfer Restriction Functions\n function detectTransferRestriction(address from, address to, uint256 value) external view returns (uint8);\n function detectTransferRestrictionFor(uint256 tokenType, uint256 mintTimestamp, address to) external view returns (uint8);\n function messageForTransferRestriction(uint8 restrictionCode) external pure returns (string memory);\n\n // Timelock Functions\n function balanceOfTimelock(address who_, uint256 index_) external view returns (uint256);\n function cancelTimelock(address who_, uint256 timelockIndex_, uint256 scheduleId_, uint256 commencementTimestamp_, uint256 totalAmount_, address reclaimTokenTo_) external;\n function transferTimelock(address to, uint256 value, uint256 timelockId) external;\n\n // Schedule Functions\n function scheduleCount() external view returns (uint256);\n\n // Configuration Functions\n function setRecordMintTimestamp(bool enabled) external;\n \n /**\n * @dev Get token type and mint timestamp from global holding index\n * @param globalIndex The global holding index\n * @return tokenType The token type\n * @return mintTimestamp The mint timestamp\n */\n function getTokenTypeAndMintTimestampFromGlobalIndex(uint256 globalIndex) external view returns (uint256 tokenType, uint256 mintTimestamp); \n}\n"},"contracts/interfaces/IRestrictedLockupTokenExtension.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\n\n// Interface for integration with Restricted Lockup Token Extension\ninterface IRestrictedLockupTokenExtension {\n function maxTotalSupply() external view returns (uint256);\n function slotsPerWord() external view returns (uint256);\n}\n"},"contracts/interfaces/IRestrictedSwap.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport \"@openzeppelin/contracts/utils/introspection/IERC165.sol\";\n\ninterface IRestrictedSwap is IERC165 {\n /**\n * @title Data Structures\n */\n enum SwapStatus {\n SellConfigured,\n BuyConfigured,\n Complete,\n Canceled\n }\n\n struct Swap {\n address restrictedTokenSender;\n address quoteTokenSender;\n address quoteToken;\n uint256 restrictedTokenAmount;\n uint256 quoteTokenAmount;\n SwapStatus status;\n uint256 deadline; // Optional deadline for swap expiration (0 means no deadline)\n }\n\n /**\n * @title Functions\n */\n\n /**\n * @dev Configure swap and emit an event with new swap number\n * @param restrictedTokenAmount the required amount for the erc1404Sender to send\n * @param quoteToken the address of an erc1404 or erc20 that will be swapped\n * @param token2Address the address that is approved to fund quoteToken\n * @param quoteTokenAmount the required amount of quoteToken to swap\n * @param deadline the deadline for swap expiration (0 means no deadline)\n */\n function configureSell(\n uint256 restrictedTokenAmount,\n address quoteToken,\n address token2Address,\n uint256 quoteTokenAmount,\n uint256 deadline\n ) external;\n\n /**\n * @dev Configure swap and emit an event with new swap number\n * @param restrictedTokenAmount the required amount for the erc1404Sender to send\n * @param restrictedTokenSender restricted token sender\n * @param quoteToken the address of an erc1404 or erc20 that will be swapped\n * @param quoteTokenAmount the required amount of quoteToken to swap\n * @param deadline the deadline for swap expiration (0 means no deadline)\n */\n function configureBuy(\n uint256 restrictedTokenAmount,\n address restrictedTokenSender,\n address quoteToken,\n uint256 quoteTokenAmount,\n uint256 deadline\n ) external;\n\n /**\n * @dev Complete swap with quote token\n * @param swapNumber swap number\n */\n function completeSwapWithQuoteToken(uint256 swapNumber) external;\n\n /**\n * @dev Complete swap with restricted token\n * @param swapNumber swap number\n */\n function completeSwapWithRestrictedToken(uint256 swapNumber) external;\n\n /**\n * @dev cancel swap\n * @param swapNumber swap number\n */\n function cancelSwap(uint256 swapNumber) external;\n\n /**\n * @dev Returns the swap status if exists\n * @param swapNumber swap number\n * @return SwapStatus status of the swap record\n */\n function swapStatus(uint256 swapNumber) external view returns (SwapStatus);\n\n /**\n * @dev Check if a swap has expired based on its deadline\n * @param swapNumber swap number\n * @return bool true if the swap has expired, false otherwise\n */\n function isSwapExpired(uint256 swapNumber) external view returns (bool);\n\n /****************************\n * Events\n ****************************/\n\n /// @notice This event is emitted when swap is canceled\n /// @param sender address of canceler\n /// @param swapNumber swap number\n event SwapCanceled(address indexed sender, uint256 indexed swapNumber);\n\n /// @notice This event is emitted when swap is configured\n /// @param swapNumber swap number\n /// @param restrictedTokenSender address of restricted token sender\n /// @param restrictedTokenAmount amount of restricted token\n /// @param quoteToken address of quote token\n /// @param quoteTokenSender address of quote token sender\n /// @param quoteTokenAmount amount of quote token\n event SwapConfigured(\n uint256 indexed swapNumber,\n address indexed restrictedTokenSender,\n uint256 restrictedTokenAmount,\n address quoteToken,\n address indexed quoteTokenSender,\n uint256 quoteTokenAmount,\n uint256 deadline\n );\n\n /// @notice This event is emitted when swap is completed\n /// @param swapNumber swap number\n /// @param restrictedTokenSender address of restricted token sender\n /// @param restrictedTokenAmount amount of restricted token\n /// @param quoteTokenSender address of quote token\n /// @param quoteToken address of quote token\n /// @param quoteTokenAmount amount of quote token\n event SwapComplete(\n uint256 indexed swapNumber,\n address indexed restrictedTokenSender,\n uint256 restrictedTokenAmount,\n address indexed quoteTokenSender,\n address quoteToken,\n uint256 quoteTokenAmount,\n uint256 deadline\n );\n}\n"},"contracts/interfaces/ISnapshotPeriods.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport \"@openzeppelin/contracts/utils/introspection/IERC165.sol\";\ninterface ISnapshotPeriods {\n struct Period {\n uint256 start;\n uint256 end;\n uint256 totalAccruedOwnership;\n uint256 accruedOwnership;\n }\n\n struct WalletInfo {\n uint256 latestAccrualTimestamp;\n uint256 totalAccruedOwnership;\n uint256 periodCount;\n }\n\n function onUpdate(\n address token,\n address from,\n address to,\n uint256 amount\n ) external;\n\n function addressInfo(\n address token,\n address account\n ) external view returns (WalletInfo memory);\n\n function supplyInfo(\n address token\n ) external view returns (WalletInfo memory);\n\n function walletPeriodByIndex(\n address token,\n address account,\n uint256 index\n ) external view returns (Period memory);\n\n function totalSupplyPeriodByIndex(\n address token,\n uint256 index\n ) external view returns (Period memory);\n\n function findPeriodIndexForTimestamp(\n address token,\n address account,\n uint256 timestamp\n ) external view returns (uint256 periodIndex);\n\n function findTotalSupplyPeriodIndexForTimestamp(\n address token,\n uint256 timestamp\n ) external view returns (uint256 periodIndex);\n\n function calculateOwnershipForPeriod(\n uint256 amount,\n uint256 periodStart,\n uint256 periodEnd\n ) external view returns (uint256 ownership);\n\n function totalOwnershipForPeriod(\n address token,\n uint256 startTimestamp,\n uint256 endTimestamp\n ) external view returns (uint256);\n\n function ownershipForPeriod(\n address token,\n address account,\n uint256 startTimestamp,\n uint256 endTimestamp\n ) external view returns (uint256);\n\n function totalOwnershipAt(\n address token,\n uint256 timestamp\n ) external view returns (uint256);\n\n function ownershipAt(\n address token,\n address account,\n uint256 timestamp\n ) external view returns (uint256);\n\n function walletPeriodCount(\n address token,\n address account\n ) external view returns (uint256);\n\n function totalSupplyPeriodCount(\n address token\n ) external view returns (uint256);\n\n /**\n * @dev Returns the balanceOf that `account` had at a specific moment in the past\n * @param token The address of the token\n * @param account The address of the account\n * @param timestamp The timestamp of the past balance\n * @return The balance of `account` at `timestamp`\n */\n function getPastBalanceOf(\n address token,\n address account,\n uint256 timestamp\n ) external view returns (uint256);\n\n /**\n * @dev Returns the total supply available at a specific moment in the past.\n * @param token The address of the token\n * @param timestamp The timestamp of the past total supply\n * @return The total supply of `token` at `timestamp`\n */\n function getPastTotalSupply(\n address token,\n uint256 timestamp\n ) external view returns (uint256);\n}\n"},"contracts/interfaces/ITransferRules.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\nimport \"@openzeppelin/contracts/utils/introspection/IERC165.sol\";\nimport \"./IIdentityRegistry.sol\";\n\ninterface ITransferRules {\n // ============================================\n // ORIGINAL TRANSFER RULES FUNCTIONALITY\n // ============================================\n \n /// @notice Detects if a transfer will be reverted and if so returns an appropriate reference code\n /// validates sufficient balance of each holdings and timelocks of sender to recipient \n /// @param token address of the token\n /// @param from Sending address\n /// @param to Receiving address\n /// @param value Amount of tokens being transferred\n /// @return Code by which to reference message for rejection reasoning\n function detectTransferRestriction(\n address token,\n address from,\n address to,\n uint256 value\n ) external view returns (uint256);\n\n /// @notice Detects if a transfer will be reverted and if so returns an appropriate reference code\n /// @param token address of the token\n /// @param from Sending address\n /// @param to Receiving address\n /// @param value Amount of tokens being transferred\n /// @return Code by which to reference message for rejection reasoning\n function detectTransferRestrictionBasic(\n address token,\n address from,\n address to,\n uint256 value\n ) external view returns (uint256);\n\n /// @notice Returns a human-readable message for a given restriction code\n /// @param restrictionCode Identifier for looking up a message\n /// @return Text showing the restriction's reasoning\n function messageForTransferRestriction(\n uint256 restrictionCode\n ) external view returns (string memory);\n\n function checkSuccess(uint256 restrictionCode) external view returns (bool);\n\n // ============================================\n // TOKEN TYPE RULES ENGINE FUNCTIONALITY\n // ============================================\n \n /**\n * @dev Structure for efficient lockup table entries\n */\n struct TokenTypeRule {\n uint256 tokenType; // Token type to assign\n bool requiresAmlKyc; // Whether AML/KYC is required\n bool isActive; // Whether this rule is active\n }\n\n /**\n * @dev Determine the token type for a wallet based on their identity\n * @param wallet The wallet address to check\n * @param identityRegistry The identity registry to query\n * @return tokenType The determined token type (TOKEN_TYPE_GENERIC if no match)\n */\n function determineTokenType(\n address wallet,\n IIdentityRegistry identityRegistry\n ) external view returns (uint256 tokenType);\n\n\n function tokenTypeAllowed(\n uint256 tokenType,\n address wallet,\n IIdentityRegistry identityRegistry\n ) external view returns (bool);\n\n /**\n * @dev Set a lockup rule directly in the efficient lockup table\n * @param region The region code (0 for any region)\n * @param accreditation The accreditation level (0 for any accreditation)\n * @param tokenType The token type to assign\n * @param requiresAmlKyc Whether AML/KYC is required\n * @param isActive Whether the rule is active\n */\n function setTokenTypeRule(\n uint256 region,\n uint256 accreditation,\n uint256 tokenType,\n bool requiresAmlKyc,\n bool isActive\n ) external;\n\n /**\n * @dev Remove a lockup rule from the table\n * @param region The region code\n * @param accreditation The accreditation level\n */\n function removeTokenTypeRule(\n uint256 region,\n uint256 accreditation\n ) external;\n\n /**\n * @dev Set the default token type rule\n * @param tokenType The token type to assign\n * @param requiresAmlKyc Whether AML/KYC is required\n * @param isActive Whether the rule is active\n */\n function setDefaultTokenTypeRule(\n uint256 tokenType,\n bool requiresAmlKyc,\n bool isActive\n ) external;\n\n /**\n * @dev Reset the default token type rule\n */\n function resetDefaultTokenTypeRule() external;\n\n /**\n * @dev Get the current default token type rule\n */\n function defaultTokenTypeRule() external view returns (TokenTypeRule memory);\n\n /**\n * @dev Get a lockup rule from the table\n * @param region The region code\n * @param accreditation The accreditation level\n * @return rule The lockup rule\n */\n function getTokenTypeRule(\n uint256 region,\n uint256 accreditation\n ) external view returns (TokenTypeRule memory rule);\n\n /**\n * @dev Batch set multiple lockup rules\n * @param regions Array of region codes\n * @param accreditations Array of accreditation levels\n * @param tokenTypes Array of token types\n * @param requiresAmlKycFlags Array of AML/KYC requirement flags\n * @param isActiveFlags Array of active flags\n */\n function batchSetTokenTypeRules(\n uint256[] calldata regions,\n uint256[] calldata accreditations,\n uint256[] calldata tokenTypes,\n bool[] calldata requiresAmlKycFlags,\n bool[] calldata isActiveFlags\n ) external;\n\n // ============================================\n // TOKEN TYPE TRANSFER RULES FUNCTIONALITY \n // ============================================\n \n /**\n * @dev Structure to define transfer restrictions by token type and recipient identity\n */\n struct TransferRule {\n uint256 lockDurationSeconds; // Holding period in seconds from mint timestamp\n bool requiresAmlKyc; // Whether recipient must have AML/KYC\n bool isActive; // Whether this rule is currently active\n }\n\n /**\n * @dev Detects if a token holding can be transferred based on all applicable rules\n * @param tokenType The type of token being transferred\n * @param mintTimestamp When the tokens were originally minted\n * @param recipientIdentity The identity of the recipient\n * @param isAmlKycPassed Whether the recipient has passed AML/KYC\n * @return restrictionCode Error code if transfer is blocked (0 if allowed)\n */\n function detectTransferRestrictionForHolding(\n uint256 tokenType,\n uint256 mintTimestamp,\n IIdentityRegistry.IdentityInfo memory recipientIdentity,\n bool isAmlKycPassed\n ) external view returns (uint256 restrictionCode);\n\n /**\n * @dev Add or update a transfer rule\n * @param tokenType The token type this rule applies to (1=RegS, 2=RegD, etc.)\n * @param recipientRegion The required recipient region (0 = any region)\n * @param recipientAccreditation The required recipient accreditation (0 = any accreditation)\n * @param rule The transfer rule to add/update\n */\n function setTransferRule(\n uint256 tokenType,\n uint256 recipientRegion,\n uint256 recipientAccreditation,\n TransferRule memory rule\n ) external;\n\n /**\n * @dev Remove a transfer rule\n * @param tokenType The token type to remove the rule for\n * @param recipientRegion The region to remove the rule for\n * @param recipientAccreditation The accreditation to remove the rule for\n */\n function removeTransferRule(uint256 tokenType, uint256 recipientRegion, uint256 recipientAccreditation) external;\n\n /**\n * @dev Get the transfer rule for a specific token type, recipient region, and recipient accreditation\n * @param tokenType The token type to query rules for\n * @param recipientRegion The recipient region to query rules for\n * @param recipientAccreditation The recipient accreditation to query rules for\n * @return rule The transfer rule\n */\n function transferRuleFor(\n uint256 tokenType,\n uint256 recipientRegion,\n uint256 recipientAccreditation\n ) external view returns (TransferRule memory rule);\n\n /**\n * @dev Get the unlock timestamp for a specific token holding\n * @param tokenType The type of token\n * @param mintTimestamp When the tokens were minted\n * @param recipient The intended recipient\n * @param identityRegistry The identity registry to query\n * @return unlockTimestamp When the tokens can be transferred (0 if never)\n */\n function getUnlockTimestamp(\n uint256 tokenType,\n uint256 mintTimestamp,\n address recipient,\n IIdentityRegistry identityRegistry\n ) external view returns (uint256 unlockTimestamp);\n\n /**\n * @dev Batch create multiple rules\n * @param rules Array of transfer rules to create\n */\n function batchSetTransferRules(\n uint256[] calldata tokenType,\n uint256[] calldata recipientRegion,\n uint256[] calldata recipientAccreditation,\n TransferRule[] calldata rules\n ) external;\n\n // ============================================\n // EVENTS\n // ============================================\n \n // Token Type Rules Engine Events\n event TokenTypeRuleSet(\n uint256 indexed region,\n uint256 indexed accreditation,\n uint256 indexed tokenType,\n bool requiresAmlKyc,\n bool isActive\n );\n\n event DefaultTokenTypeRuleSet(\n uint256 indexed tokenType,\n bool requiresAmlKyc,\n bool isActive\n );\n \n event TokenTypeRuleRemoved(\n uint256 indexed region,\n uint256 indexed accreditation\n );\n\n // Transfer Rules Events\n event TransferRuleSet(\n uint256 indexed tokenType,\n uint256 recipientRegion,\n uint256 recipientAccreditation,\n uint256 lockDurationSeconds,\n bool requiresAmlKyc,\n bool isActive\n );\n \n event TransferRuleRemoved(uint256 indexed tokenType, uint256 indexed recipientRegion, uint256 indexed recipientAccreditation);\n}\n"},"contracts/libraries/BitManipulationLib.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\n/**\n * @title BitManipulationLib\n * @notice Library for complex bit manipulation operations used in token storage optimization\n * @dev Extracted from Storage.sol to reduce contract size and improve modularity\n */\nlibrary BitManipulationLib {\n // ============================================\n // CONSTANTS\n // ============================================\n \n // Element packing constants (8-element packing)\n uint256 internal constant TOKEN_TYPE_BITS = 8; // Token type uses 8 bits (supports 0-255)\n uint256 internal constant TOKEN_TYPE_MASK = (1 << TOKEN_TYPE_BITS) - 1; // 0xFF\n \n // ============================================\n // ELEMENT PACKING FUNCTIONS\n // ============================================\n \n /**\n * @dev Pack a single element (tokenType + daysAfterDeployment) into dynamic bits\n * @param tokenType The token type (0-255, uses 8 bits)\n * @param daysAfterDeployment The days after deployment (uses remaining bits)\n * @param elementBitSize The total bits per element\n * @return packed The packed element\n */\n function packElement(uint256 tokenType, uint256 daysAfterDeployment, uint256 elementBitSize) \n internal \n pure \n returns (uint256 packed) \n {\n require(tokenType <= TOKEN_TYPE_MASK, \"TokenType exceeds 8-bit max\");\n uint256 daysBitSize = elementBitSize - TOKEN_TYPE_BITS;\n uint256 daysMask = (1 << daysBitSize) - 1;\n require(daysAfterDeployment <= daysMask, \"Days exceeds available bits\");\n \n // Pack: [daysBitSize bits: daysAfterDeployment][8 bits: tokenType]\n packed = (daysAfterDeployment << TOKEN_TYPE_BITS) | tokenType;\n }\n \n /**\n * @dev Unpack a dynamic-bit element into tokenType and daysAfterDeployment\n * @param packed The packed element\n * @return tokenType The token type\n * @return daysAfterDeployment The days after deployment\n */\n function unpackElement(uint256 packed) \n internal \n pure \n returns (uint256 tokenType, uint256 daysAfterDeployment) \n {\n tokenType = packed & TOKEN_TYPE_MASK; // Extract lower 8 bits\n daysAfterDeployment = packed >> TOKEN_TYPE_BITS; // Extract upper bits\n }\n\n /**\n * @dev Unpack tokenType from a packed 32-bit element\n * @param packedElement The packed 32-bit element\n * @return tokenType The token type (lower 8 bits)\n */\n function unpackTokenTypeElement(uint256 packedElement) \n internal \n pure \n returns (uint256 tokenType) \n {\n tokenType = packedElement & TOKEN_TYPE_MASK;\n }\n \n /**\n * @dev Get a single element from packed structure\n * @param packed The packed 256-bit value\n * @param subIndex The subIndex (0 to slotsPerWord-1)\n * @param elementBitSize The number of bits per element\n * @return element The element at the specified subIndex\n */\n function getPackedElement(uint256 packed, uint256 subIndex, uint256 elementBitSize) \n internal \n pure \n returns (uint256 element) \n {\n element = (packed >> (subIndex * elementBitSize)) & ((1 << elementBitSize) - 1);\n }\n\n // ============================================\n // BALANCE PACKING FUNCTIONS\n // ============================================\n \n /**\n * @dev Get a single balance from packed balances\n * @param packed The packed balances\n * @param subIndex The subIndex (0-7)\n * @return balance The balance at the specified subIndex\n */\n function getPackedBalance(uint256 packed, uint256 subIndex, uint256 elementBitSize) \n internal \n pure \n returns (uint256 balance) \n {\n balance = (packed >> (subIndex * elementBitSize)) & ((1 << elementBitSize) - 1);\n }\n \n /**\n * @dev Set a single balance in packed balances\n * @param packed The current packed balances\n * @param subIndex The subIndex (0-7)\n * @param newBalance The new balance to set\n * @return newPacked The updated packed balances\n */\n function setPackedBalance(uint256 packed, uint256 subIndex, uint256 newBalance, uint256 elementBitSize) \n internal \n pure \n returns (uint256 newPacked) \n {\n // Clear the old balance bits and set the new balance\n uint256 mask = ((1 << elementBitSize) - 1) << (subIndex * elementBitSize);\n newPacked = (packed & ~mask) | (newBalance << (subIndex * elementBitSize));\n }\n\n // ============================================\n // INDEX CALCULATION FUNCTIONS\n // ============================================\n \n /**\n * @dev Get the packed balance key (bucket index) from global index\n * @param globalIndex The global index\n * @return packedKey The bucket index for balance storage (maps 1:1 to globalMintTimestamps)\n */\n function getPackedBalanceKeyFromGlobalIndex(uint256 globalIndex, uint256 slotsPerWord) \n internal \n pure \n returns (uint256 packedKey) \n {\n // Calculate bucket index - each bucket in globalMintTimestamps contains 8 elements\n packedKey = globalIndex / slotsPerWord;\n }\n\n // ============================================\n // BITMAP MANIPULATION FUNCTIONS\n // ============================================\n\n /**\n * @dev Clear all bits that belong to a bucket and are in the current word\n * @param word_ The bitmap word to modify\n * @param globalIndex Any global index within the bucket (doesn't need to be aligned)\n * @param wordIndex_ The word index in the bitmap (0, 1, 2, ...)\n * @param slotsPerWord The number of slots per word (4-8)\n * @return The updated word with cleared bits\n * @notice Clears slotsPerWord consecutive bits starting from bucket start\n * @notice Only clears bits that are within the current word boundaries\n */\n function clearBucketBits(\n uint256 word_,\n uint256 globalIndex,\n uint256 wordIndex_,\n uint256 slotsPerWord\n ) internal pure returns (uint256) {\n uint256 bucketStartIndex = (globalIndex / slotsPerWord) * slotsPerWord;\n uint256 wordStartBit = wordIndex_ * 256;\n uint256 wordEndBit = wordStartBit + 256;\n \n // Calculate which bits of the bucket are in this word\n uint256 firstBitToClear = bucketStartIndex < wordStartBit ? 0 : bucketStartIndex - wordStartBit;\n uint256 lastBitToClear = bucketStartIndex + slotsPerWord > wordEndBit ? 255 : bucketStartIndex + slotsPerWord - wordStartBit - 1;\n \n // Create mask and clear bits in one operation\n if (firstBitToClear <= lastBitToClear && lastBitToClear < 256) {\n uint256 mask = ((1 << (lastBitToClear - firstBitToClear + 1)) - 1) << firstBitToClear;\n return word_ & ~mask;\n }\n \n return word_;\n }\n\n // ============================================\n // UTILITY FUNCTIONS\n // ============================================\n \n /**\n * @dev Calculate days after deployment from timestamps\n * @param mintTimestamp The mint timestamp to calculate from\n * @param deploymentDay_ The deployment timestamp to calculate relative to\n * @return daysAfterDeployment The number of days after deployment (midnight-aligned)\n */\n function calculateDaysAfterDeployment(\n uint256 mintTimestamp,\n uint256 deploymentDay_\n ) internal pure returns (uint256) {\n if (mintTimestamp < deploymentDay_) {\n return 0;\n }\n return (mintTimestamp - deploymentDay_) / 1 days;\n }\n\n /**\n * @dev Convert days since deploy back to absolute timestamp\n * @param daysSinceDeploy The days since deployment\n * @param deploymentDay_ The deployment day timestamp\n * @return timestamp The absolute timestamp\n */\n function daysSinceDeployToTimestamp(uint256 daysSinceDeploy, uint256 deploymentDay_) \n internal \n pure \n returns (uint256 timestamp) \n {\n timestamp = deploymentDay_ + (daysSinceDeploy * 1 days);\n }\n\n // ============================================\n // SLOT CALCULATION FUNCTIONS\n // ============================================\n \n /**\n * @dev Calculate the optimal number of slots per 256-bit word based on max total supply\n * @param maxTotalSupply The maximum total supply of the token (in basis units)\n * @return slotsPerWord The number of slots that fit in a 256-bit word (4-8)\n * @notice Decision is based on max total supply divided by 1000 to account for \n * worst case scenario of 1000 holdings with overflow value\n * @notice Uses powers of 2 for precise mathematical calculation\n */\n function calculateSlotsPerWord(uint256 maxTotalSupply) \n internal \n pure \n returns (uint256 slotsPerWord) \n {\n // Calculate the effective max value\n // Divide by 1000 to account for worst case scenario of 1000 holdings with overflow\n uint256 effectiveMaxValue = maxTotalSupply / 1000;\n \n // Determine optimal slot size based on effective max value using powers of 2\n if (effectiveMaxValue <= (2 ** 32)) {\n // 32 bits: fits 2^32 = 4,294,967,296\n // 8 slots in 256 bits (256/32 = 8)\n slotsPerWord = 8;\n } else if (effectiveMaxValue <= (2 ** 36)) {\n // 36 bits: fits 2^36 = 68,719,476,736\n // 7 slots in 256 bits (256/36 = 7.11, so 7 slots)\n slotsPerWord = 7;\n } else if (effectiveMaxValue <= (2 ** 42)) {\n // 42 bits: fits 2^42 = 4,398,046,511,104\n // 6 slots in 256 bits (256/42 = 6.09, so 6 slots)\n slotsPerWord = 6;\n } else if (effectiveMaxValue <= (2 ** 51)) {\n // 51 bits: fits 2^51 = 2,251,799,813,685,248\n // 5 slots in 256 bits (256/51 = 5.02, so 5 slots)\n slotsPerWord = 5;\n } else {\n // 64 bits: fits 2^64 = 18,446,744,073,709,551,616\n // 4 slots in 256 bits (256/64 = 4)\n slotsPerWord = 4;\n }\n \n return slotsPerWord;\n }\n\n /**\n * @dev Calculate the maximum balance value that can fit in each slot\n * @param slotsPerWord The number of slots per 256-bit word (4-8)\n * @return maxBalancePerSubIndex The maximum balance value per slot\n * @notice This calculates 2^(256/slotsPerWord) - 1 to get the maximum value per slot\n */\n function calculateMaxBalancePerSubIndex(uint256 slotsPerWord) \n internal \n pure \n returns (uint256 maxBalancePerSubIndex) \n {\n // Calculate the number of bits per slot\n uint256 bitsPerSlot = 256 / slotsPerWord;\n \n // Calculate 2^bitsPerSlot - 1 to get the maximum value per slot\n maxBalancePerSubIndex = (2 ** bitsPerSlot) - 1;\n }\n\n /**\n * @dev Calculate the number of bits needed to store an element (tokenType + daysAfterDeployment)\n * @param slotsPerWord The number of slots per 256-bit word (4-8)\n * @return elementBitSize The number of bits needed to store an element\n */\n function calculateElementBitSize(uint256 slotsPerWord) \n internal \n pure \n returns (uint256 elementBitSize) \n {\n elementBitSize = 256 / slotsPerWord;\n }\n\n /**\n * @dev Calculate the number of bits for days after deployment based on element size\n * @param elementBitSize The total bits per element\n * @return daysBitSize The number of bits for days (elementBitSize - 8 for tokenType)\n */\n function calculateDaysBitSize(uint256 elementBitSize) \n internal \n pure \n returns (uint256 daysBitSize) \n {\n require(elementBitSize > TOKEN_TYPE_BITS, \"Element bit size too small for token type\");\n daysBitSize = elementBitSize - TOKEN_TYPE_BITS;\n }\n}\n"},"contracts/libraries/VestingMath.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\n/// @title VestingMath Library\n/// @notice Contains pure helper functions that were previously embedded in RestrictedLockupToken.\n/// Linking this library removes a few kilobytes from the main contract byte-code.\nlibrary VestingMath {\n uint256 internal constant BIPS_PRECISION = 10_000;\n\n /**\n * @notice Calculates how many tokens are unlocked for a vesting schedule at a given timestamp.\n * @param commencedTimestamp_ Timestamp when the schedule started.\n * @param currentTimestamp_ Timestamp to calculate the unlocked amount for.\n * @param amount_ Total tokens under the schedule.\n * @param releaseCount_ Total number of releases (including initial one).\n * @param delayUntilFirstReleaseInSeconds_ Cliff period in seconds before first release.\n * @param initialReleasePortionInBips_ Portion unlocked at the first release in BIPS (1/100th of a percent).\n * @param periodBetweenReleasesInSeconds_ Time between subsequent releases after the first.\n * @return unlocked Amount of tokens that should be unlocked.\n */\n function calculateUnlocked(\n uint256 commencedTimestamp_,\n uint256 currentTimestamp_,\n uint256 amount_,\n uint256 releaseCount_,\n uint256 delayUntilFirstReleaseInSeconds_,\n uint256 initialReleasePortionInBips_,\n uint256 periodBetweenReleasesInSeconds_\n ) internal pure returns (uint256 unlocked) {\n // Nothing unlocked before commencement\n if (commencedTimestamp_ > currentTimestamp_) {\n return 0;\n }\n\n uint256 secondsElapsed = currentTimestamp_ - commencedTimestamp_;\n\n // All tokens unlocked if vesting fully complete\n if (\n secondsElapsed >=\n delayUntilFirstReleaseInSeconds_ +\n (periodBetweenReleasesInSeconds_ * (releaseCount_ - 1))\n ) {\n return amount_;\n }\n\n // Initial cliff release\n if (secondsElapsed >= delayUntilFirstReleaseInSeconds_) {\n unlocked = (amount_ * initialReleasePortionInBips_) / BIPS_PRECISION;\n\n // Subsequent periodic releases\n if (\n secondsElapsed - delayUntilFirstReleaseInSeconds_ >=\n periodBetweenReleasesInSeconds_\n ) {\n uint256 additionalPeriods = (secondsElapsed -\n delayUntilFirstReleaseInSeconds_) /\n periodBetweenReleasesInSeconds_;\n\n unlocked +=\n ((amount_ - unlocked) * additionalPeriods) /\n (releaseCount_ - 1);\n }\n }\n }\n\n /**\n * @notice Rounds a timestamp up to midnight UTC of the next day.\n */\n function toMidnightTimestamp(uint256 timestamp)\n internal\n pure\n returns (uint256)\n {\n return ((timestamp + 86399) / 86400) * 86400;\n }\n} "},"contracts/metatx/ERC2771CustomForwarder.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity 0.8.28;\n\nimport {ERC2771Context} from \"@openzeppelin/contracts/metatx/ERC2771Context.sol\";\nimport {ECDSA} from \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport {EIP712} from \"@openzeppelin/contracts/utils/cryptography/EIP712.sol\";\nimport {Address} from \"@openzeppelin/contracts/utils/Address.sol\";\nimport {Errors} from \"@openzeppelin/contracts/utils/Errors.sol\";\nimport {Nonces} from \"../utils/Nonces.sol\";\n\n/**\n * @dev Adapted from OpenZeppelin Contracts (last updated v5.0.0) (metatx/ERC2771Forwarder.sol)\n * @dev A forwarder compatible with ERC2771 contracts. See {ERC2771Context}.\n *\n * This forwarder operates on forward requests that include:\n *\n * * `from`: An address to operate on behalf of. It is required to be equal to the request signer.\n * * `to`: The address that should be called.\n * * `value`: The amount of native token to attach with the requested call.\n * * `gas`: The amount of gas limit that will be forwarded with the requested call.\n * * `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation.\n * * `deadline`: A timestamp after which the request is not executable anymore.\n * * `data`: Encoded `msg.data` to send with the requested call.\n *\n * Relayers are able to submit batches if they are processing a high volume of requests. With high\n * throughput, relayers may run into limitations of the chain such as limits on the number of\n * transactions in the mempool. In these cases the recommendation is to distribute the load among\n * multiple accounts.\n *\n * NOTE: Batching requests includes an optional refund for unused `msg.value` that is achieved by\n * performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance,\n * if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly\n * handle `msg.data.length == 0`. `ERC2771Context` in OpenZeppelin Contracts versions prior to 4.9.3\n * do not handle this properly.\n *\n * ==== Security Considerations\n *\n * If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount\n * specified in the request. This contract does not implement any kind of retribution for this gas,\n * and it is assumed that there is an out of band incentive for relayers to pay for execution on\n * behalf of signers. Often, the relayer is operated by a project that will consider it a user\n * acquisition cost.\n *\n * By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward\n * some other purpose that is not aligned with the expected out of band incentives. If you operate a\n * relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or\n * ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be\n * used to execute arbitrary code.\n */\ncontract ERC2771CustomForwarder is EIP712, Nonces {\n using ECDSA for bytes32;\n\n struct ForwardRequestData {\n address from;\n address to;\n uint256 value;\n uint256 gas;\n uint48 deadline;\n uint256 nonce; // new field vs OpenZeppelin ERC2771Forwarder.sol\n bytes data;\n bytes signature;\n }\n\n bytes32 internal constant _FORWARD_REQUEST_TYPEHASH =\n keccak256(\n \"ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,uint48 deadline,bytes data)\"\n );\n\n /**\n * @dev Emitted when a `ForwardRequest` is executed.\n *\n * NOTE: An unsuccessful forward request could be due to an invalid signature, an expired deadline,\n * or simply a revert in the requested call. The contract guarantees that the relayer is not able to force\n * the requested call to run out of gas.\n */\n event ExecutedForwardRequest(\n address indexed signer,\n uint256 nonce,\n bool success\n );\n\n /**\n * @dev The request `from` doesn't match with the recovered `signer`.\n */\n error ERC2771CustomForwarder_InvalidSigner(address signer, address from);\n\n /**\n * @dev The `requestedValue` doesn't match with the available `msgValue`.\n */\n error ERC2771CustomForwarder_MismatchedValue(\n uint256 requestedValue,\n uint256 msgValue\n );\n\n /**\n * @dev The request `deadline` has expired.\n */\n error ERC2771CustomForwarder_ExpiredRequest(uint48 deadline);\n\n /**\n * @dev The request target doesn't trust the `forwarder`.\n */\n error ERC2771CustomForwarder_UntrustfulTarget(\n address target,\n address forwarder\n );\n\n /**\n * @dev See {EIP712-constructor}.\n */\n constructor(string memory name) EIP712(name, \"1\") {}\n\n /**\n * @dev Returns `true` if a request is valid for a provided `signature` at the current block timestamp.\n *\n * A transaction is considered valid when the target trusts this forwarder, the request hasn't expired\n * (deadline is not met), and the signer matches the `from` parameter of the signed request.\n *\n * NOTE: A request may return false here but it won't cause {executeBatch} to revert if a refund\n * receiver is provided.\n */\n function verify(\n ForwardRequestData calldata request\n ) public view virtual returns (bool) {\n (bool isTrustedForwarder, bool active, bool signerMatch, ) = _validate(\n request\n );\n return isTrustedForwarder && active && signerMatch;\n }\n\n /**\n * @dev Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas\n * provided to the requested call may not be exactly the amount requested, but the call will not run\n * out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed.\n *\n * Requirements:\n *\n * - The request value should be equal to the provided `msg.value`.\n * - The request should be valid according to {verify}.\n */\n function execute(\n ForwardRequestData calldata request\n ) public payable virtual {\n // We make sure that msg.value and request.value match exactly.\n // If the request is invalid or the call reverts, this whole function\n // will revert, ensuring value isn't stuck.\n if (msg.value != request.value) {\n revert ERC2771CustomForwarder_MismatchedValue(\n request.value,\n msg.value\n );\n }\n\n if (!_execute(request, true)) {\n revert Errors.FailedCall();\n }\n }\n\n /**\n * @dev Batch version of {execute} with optional refunding and atomic execution.\n *\n * In case a batch contains at least one invalid request (see {verify}), the\n * request will be skipped and the `refundReceiver` parameter will receive back the\n * unused requested value at the end of the execution. This is done to prevent reverting\n * the entire batch when a request is invalid or has already been submitted.\n *\n * If the `refundReceiver` is the `address(0)`, this function will revert when at least\n * one of the requests was not valid instead of skipping it. This could be useful if\n * a batch is required to get executed atomically (at least at the top-level). For example,\n * refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids\n * including reverted transactions.\n *\n * Requirements:\n *\n * - The sum of the requests' values should be equal to the provided `msg.value`.\n * - All of the requests should be valid (see {verify}) when `refundReceiver` is the zero address.\n *\n * NOTE: Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for\n * the first-level forwarded calls. In case a forwarded request calls to a contract with another\n * subcall, the second-level call may revert without the top-level call reverting.\n */\n function executeBatch(\n ForwardRequestData[] calldata requests,\n address payable refundReceiver\n ) public payable virtual {\n bool atomic = refundReceiver == address(0);\n\n uint256 requestsValue;\n uint256 refundValue;\n\n for (uint256 i; i < requests.length; ++i) {\n requestsValue += requests[i].value;\n bool success = _execute(requests[i], atomic);\n if (!success) {\n refundValue += requests[i].value;\n }\n }\n\n // The batch should revert if there's a mismatched msg.value provided\n // to avoid request value tampering\n if (requestsValue != msg.value) {\n revert ERC2771CustomForwarder_MismatchedValue(\n requestsValue,\n msg.value\n );\n }\n\n // Some requests with value were invalid (possibly due to frontrunning).\n // To avoid leaving ETH in the contract this value is refunded.\n if (refundValue != 0) {\n // We know refundReceiver != address(0) && requestsValue == msg.value\n // meaning we can ensure refundValue is not taken from the original contract's balance\n // and refundReceiver is a known account.\n Address.sendValue(refundReceiver, refundValue);\n }\n }\n\n /**\n * @dev Validates if the provided request can be executed at current block timestamp with\n * the given `request.signature` on behalf of `request.signer`.\n */\n function _validate(\n ForwardRequestData calldata request\n )\n internal\n view\n virtual\n returns (\n bool isTrustedForwarder,\n bool active,\n bool signerMatch,\n address signer\n )\n {\n (bool isValid, address recovered) = _recoverForwardRequestSigner(\n request\n );\n\n return (\n _isTrustedByTarget(request.to),\n request.deadline >= block.timestamp,\n isValid && recovered == request.from,\n recovered\n );\n }\n\n /**\n * @dev Returns a tuple with the recovered the signer of an EIP712 forward request message hash\n * and a boolean indicating if the signature is valid.\n *\n * NOTE: The signature is considered valid if {ECDSA-tryRecover} indicates no recover error for it.\n */\n function _recoverForwardRequestSigner(\n ForwardRequestData calldata request\n ) internal view virtual returns (bool, address) {\n (address recovered, ECDSA.RecoverError err, ) = _hashTypedDataV4(\n keccak256(\n abi.encode(\n _FORWARD_REQUEST_TYPEHASH,\n request.from,\n request.to,\n request.value,\n request.gas,\n request.nonce,\n request.deadline,\n keccak256(request.data)\n )\n )\n ).tryRecover(request.signature);\n\n return (err == ECDSA.RecoverError.NoError, recovered);\n }\n\n /**\n * @dev Validates and executes a signed request returning the request call `success` value.\n *\n * Internal function without msg.value validation.\n *\n * Requirements:\n *\n * - The caller must have provided enough gas to forward with the call.\n * - The request must be valid (see {verify}) if the `requireValidRequest` is true.\n *\n * Emits an {ExecutedForwardRequest} event.\n *\n * IMPORTANT: Using this function doesn't check that all the `msg.value` was sent, potentially\n * leaving value stuck in the contract.\n */\n function _execute(\n ForwardRequestData calldata request,\n bool requireValidRequest\n ) internal virtual returns (bool success) {\n (\n bool isTrustedForwarder,\n bool active,\n bool signerMatch,\n address signer\n ) = _validate(request);\n\n // Need to explicitly specify if a revert is required since non-reverting is default for\n // batches and reversion is opt-in since it could be useful in some scenarios\n if (requireValidRequest) {\n if (!isTrustedForwarder) {\n revert ERC2771CustomForwarder_UntrustfulTarget(\n request.to,\n address(this)\n );\n }\n\n if (!active) {\n revert ERC2771CustomForwarder_ExpiredRequest(request.deadline);\n }\n\n if (!signerMatch) {\n revert ERC2771CustomForwarder_InvalidSigner(\n signer,\n request.from\n );\n }\n }\n\n // Ignore an invalid request because requireValidRequest = false\n if (isTrustedForwarder && signerMatch && active) {\n // Nonce should be used before the call to prevent reusing by reentrancy\n _useNonce(request.from, request.nonce);\n\n uint256 reqGas = request.gas;\n address to = request.to;\n uint256 value = request.value;\n bytes memory data = abi.encodePacked(request.data, request.from);\n\n uint256 gasLeft;\n\n assembly {\n success := call(\n reqGas,\n to,\n value,\n add(data, 0x20),\n mload(data),\n 0,\n 0\n )\n\n let returnDataSize := returndatasize()\n returndatacopy(0, 0, returnDataSize)\n // Only revert on error; do nothing on success\n if iszero(success) {\n // Revert with return data only if there is any\n if returnDataSize { revert(0, returnDataSize) }\n }\n gasLeft := gas()\n }\n\n _checkForwardedGas(gasLeft, request);\n\n emit ExecutedForwardRequest(signer, request.nonce, success);\n }\n }\n\n /**\n * @dev Returns whether the target trusts this forwarder.\n *\n * This function performs a static call to the target contract calling the\n * {ERC2771Context-isTrustedForwarder} function.\n */\n function _isTrustedByTarget(address target) private view returns (bool) {\n bytes memory encodedParams = abi.encodeCall(\n ERC2771Context.isTrustedForwarder,\n (address(this))\n );\n\n bool success;\n uint256 returnSize;\n uint256 returnValue;\n /// @solidity memory-safe-assembly\n assembly {\n // Perform the staticcal and save the result in the scratch space.\n // | Location | Content | Content (Hex) |\n // |-----------|----------|--------------------------------------------------------------------|\n // | | | result ↓ |\n // | 0x00:0x1F | selector | 0x0000000000000000000000000000000000000000000000000000000000000001 |\n success := staticcall(\n gas(),\n target,\n add(encodedParams, 0x20),\n mload(encodedParams),\n 0,\n 0x20\n )\n returnSize := returndatasize()\n returnValue := mload(0)\n }\n\n return success && returnSize >= 0x20 && returnValue > 0;\n }\n\n /**\n * @dev Checks if the requested gas was correctly forwarded to the callee.\n *\n * As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]:\n * - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee.\n * - At least `floor(gasleft() / 64)` is kept in the caller.\n *\n * It reverts consuming all the available gas if the forwarded gas is not the requested gas.\n *\n * IMPORTANT: The `gasLeft` parameter should be measured exactly at the end of the forwarded call.\n * Any gas consumed in between will make room for bypassing this check.\n */\n function _checkForwardedGas(\n uint256 gasLeft,\n ForwardRequestData calldata request\n ) private pure {\n // To avoid insufficient gas griefing attacks, as referenced in https://ronan.eth.limo/blog/ethereum-gas-dangers/\n //\n // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call reverts out-of-gas\n // but the forwarding itself still succeeds. In order to make sure that the subcall received sufficient gas,\n // we will inspect gasleft() after the forwarding.\n //\n // Let X be the gas available before the subcall, such that the subcall gets at most X * 63 / 64.\n // We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= req.gas.\n // Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall will be gasleft() = X - Y.\n // If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64.\n // Under this assumption req.gas / 63 > gasleft() is true is true if and only if\n // req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64.\n // This means that if the subcall runs out of gas we are able to detect that insufficient gas was passed.\n //\n // We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64.\n // The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas.\n // - req.gas / 63 > gasleft()\n // - req.gas / 63 >= X - req.gas\n // - req.gas >= X * 63 / 64\n // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the relayer behaves honestly\n // the forwarding does not revert.\n if (gasLeft < request.gas / 63) {\n // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since\n // neither revert or assert consume all gas since Solidity 0.8.20\n // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require\n /// @solidity memory-safe-assembly\n assembly {\n invalid()\n }\n }\n }\n}\n"},"contracts/utils/Nonces.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\n/**\n * @dev Provides tracking nonces for addresses. Nonces just need to be unique, but do not need to be sequential.\n */\nabstract contract Nonces {\n mapping(address account => mapping(uint256 => bool)) private _nonces; // address => nonce => isUsed\n\n event NonceUsed(address indexed account, uint256 nonce);\n\n /**\n * @dev The nonce used for an `account` is not the expected current nonce.\n */\n error Nonces_InvalidAccountNonce(address account, uint256 nonce);\n\n /**\n * @dev Returns whether the nonce is valid for the given owner.\n * @param owner_ The owner of the nonce.\n * @param nonce_ The nonce to check.\n * @return Whether the nonce is valid.\n */\n function isNonceValid(\n address owner_,\n uint256 nonce_\n ) public view virtual returns (bool) {\n // bool defaults to false, so it stores isUsed. Therefore false means it is valid.\n return !_nonces[owner_][nonce_];\n }\n\n /**\n * @dev Consumes a provided nonce.\n * @param owner_ The owner of the nonce.\n */\n function _useNonce(address owner_, uint256 nonce_) internal virtual {\n if (!isNonceValid(owner_, nonce_)) {\n revert Nonces_InvalidAccountNonce(owner_, nonce_);\n }\n _nonces[owner_][nonce_] = true;\n emit NonceUsed(owner_, nonce_);\n }\n}\n"}}},"solcLongVersion":"0.8.28+commit.7893614a","solcVersion":"0.8.28"}
|