ReceiptTokenManager

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/policies/deposits/ReceiptTokenManager.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.20;

// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IReceiptTokenManager} from "src/policies/interfaces/deposits/IReceiptTokenManager.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";

// Libraries
import {ERC6909Wrappable} from "src/libraries/ERC6909Wrappable.sol";
import {CloneableReceiptToken} from "src/libraries/CloneableReceiptToken.sol";
import {uint2str} from "src/libraries/Uint2Str.sol";
import {String} from "src/libraries/String.sol";
import {IDepositReceiptToken} from "src/interfaces/IDepositReceiptToken.sol";

/// @title  ReceiptTokenManager
/// @notice Manager contract for creating and managing ERC6909 receipt tokens for deposits
/// @dev    Extracted from DepositManager to reduce contract size.
///
///         Key Features:
///         - Creator-only minting/burning: Only the contract that creates a token can mint/burn it
///         - ERC6909 compatibility with optional ERC20 wrapping via CloneableReceiptToken clones
///         - Deterministic token ID generation based on owner, asset, deposit period, and operator
///         - Automatic wrapped token creation for seamless DeFi integration
///
///         Security Model:
///         - Token ownership is immutable and set to msg.sender during creation
///         - All mint/burn operations are gated by onlyTokenOwner modifier
///         - Token IDs include owner address to prevent collision attacks
contract ReceiptTokenManager is ERC6909Wrappable, IReceiptTokenManager {
    using String for string;

    // ========== STATE VARIABLES ========== //

    /// @notice Maps token ID to the authorized owner (for mint/burn operations)
    mapping(uint256 tokenId => address authorizedOwner) internal _tokenOwners;

    // ========== CONSTRUCTOR ========== //

    constructor() ERC6909Wrappable(address(new CloneableReceiptToken())) {}

    // ========== TOKEN CREATION ========== //

    /// @inheritdoc IReceiptTokenManager
    /// @dev        This function reverts if:
    ///             - The asset is the zero address
    ///             - The deposit period is 0
    ///             - The operator is the zero address
    ///             - A token with the same parameters already exists
    function createToken(
        IERC20 asset_,
        uint8 depositPeriod_,
        address operator_,
        string memory operatorName_
    ) external returns (uint256 tokenId) {
        // Validate parameters
        if (address(asset_) == address(0)) {
            revert ReceiptTokenManager_InvalidParams("asset");
        }
        if (depositPeriod_ == 0) {
            revert ReceiptTokenManager_InvalidParams("depositPeriod");
        }
        if (operator_ == address(0)) {
            revert ReceiptTokenManager_InvalidParams("operator");
        }

        // Use msg.sender as the owner for security
        address owner = msg.sender;

        // Generate token ID including owner in the hash
        tokenId = getReceiptTokenId(owner, asset_, depositPeriod_, operator_);

        // Validate token doesn't already exist
        if (isValidTokenId(tokenId)) {
            revert ReceiptTokenManager_TokenExists(tokenId);
        }

        // Store the authorized owner for this token
        _tokenOwners[tokenId] = owner;

        // Create the wrappable token with proper metadata layout for CloneableReceiptToken
        string memory tokenName = string
            .concat(
                operatorName_,
                asset_.name(),
                " - ",
                uint2str(depositPeriod_),
                depositPeriod_ == 1 ? " month" : " months"
            )
            .truncate32();
        string memory tokenSymbol = string
            .concat(operatorName_, asset_.symbol(), "-", uint2str(depositPeriod_), "m")
            .truncate32();

        _createWrappableToken(
            tokenId,
            tokenName,
            tokenSymbol,
            asset_.decimals(),
            abi.encodePacked(
                address(this), // Owner at 0x41
                address(asset_), // Asset at 0x55
                depositPeriod_, // Deposit Period at 0x69
                operator_ // Operator at 0x6A
            ),
            true // Automatically create the wrapped token
        );

        emit TokenCreated(tokenId, owner, address(asset_), depositPeriod_, operator_);
        return tokenId;
    }

    // ========== MINTING/BURNING ========== //

    function _onlyTokenOwner(uint256 tokenId_) internal view {
        address owner = getTokenOwner(tokenId_);
        if (msg.sender != owner) {
            revert ReceiptTokenManager_NotOwner(msg.sender, owner);
        }
    }

    modifier onlyTokenOwner(uint256 tokenId_) {
        _onlyTokenOwner(tokenId_);
        _;
    }

    /// @inheritdoc IReceiptTokenManager
    /// @dev        This function reverts if:
    ///             - The token ID is invalid (not created)
    ///             - The caller is not the token owner
    ///             - The recipient is the zero address
    ///             - The amount is 0
    function mint(
        address to_,
        uint256 tokenId_,
        uint256 amount_,
        bool shouldWrap_
    ) external onlyValidTokenId(tokenId_) onlyTokenOwner(tokenId_) {
        _mint(to_, tokenId_, amount_, shouldWrap_);
    }

    /// @inheritdoc IReceiptTokenManager
    /// @dev        This function reverts if:
    ///             - The token ID is invalid (not created)
    ///             - The caller is not the token owner
    ///             - The account is the zero address
    ///             - The amount is 0
    ///             - For wrapped tokens: account has not approved ReceiptTokenManager to spend the wrapped ERC20 token
    ///             - For unwrapped tokens: account has not approved the caller to spend ERC6909 tokens
    ///             - The account has insufficient token balance
    function burn(
        address from_,
        uint256 tokenId_,
        uint256 amount_,
        bool isWrapped_
    ) external onlyValidTokenId(tokenId_) onlyTokenOwner(tokenId_) {
        _burn(from_, tokenId_, amount_, isWrapped_);
    }

    // ========== VIEW FUNCTIONS ========== //

    /// @inheritdoc IReceiptTokenManager
    function getReceiptTokenId(
        address owner_,
        IERC20 asset_,
        uint8 depositPeriod_,
        address operator_
    ) public pure override returns (uint256) {
        return uint256(keccak256(abi.encode(owner_, asset_, depositPeriod_, operator_)));
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenName(uint256 tokenId_) public view override returns (string memory) {
        return name(tokenId_);
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenSymbol(uint256 tokenId_) public view override returns (string memory) {
        return symbol(tokenId_);
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenDecimals(uint256 tokenId_) public view override returns (uint8) {
        return decimals(tokenId_);
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenOwner(uint256 tokenId_) public view override returns (address) {
        return _tokenOwners[tokenId_];
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenAsset(uint256 tokenId_) external view override returns (IERC20) {
        address wrappedToken = getWrappedToken(tokenId_);
        if (wrappedToken == address(0)) return IERC20(address(0));
        return IDepositReceiptToken(wrappedToken).asset();
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenDepositPeriod(uint256 tokenId_) external view override returns (uint8) {
        address wrappedToken = getWrappedToken(tokenId_);
        if (wrappedToken == address(0)) return 0;
        return IDepositReceiptToken(wrappedToken).depositPeriod();
    }

    /// @inheritdoc IReceiptTokenManager
    function getTokenOperator(uint256 tokenId_) external view override returns (address) {
        address wrappedToken = getWrappedToken(tokenId_);
        if (wrappedToken == address(0)) return address(0);
        return IDepositReceiptToken(wrappedToken).operator();
    }

    // ========== ERC165 ========== //

    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override(ERC6909Wrappable, IERC165) returns (bool) {
        return
            interfaceId == type(IReceiptTokenManager).interfaceId ||
            ERC6909Wrappable.supportsInterface(interfaceId);
    }
}
"
    },
    "src/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

// Imported from forge-std

/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
    /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
    /// is the new allowance.
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /// @notice Returns the amount of tokens in existence.
    function totalSupply() external view returns (uint256);

    /// @notice Returns the amount of tokens owned by `account`.
    function balanceOf(address account) external view returns (uint256);

    /// @notice Moves `amount` tokens from the caller's account to `to`.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Returns the remaining number of tokens that `spender` is allowed
    /// to spend on behalf of `owner`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
    /// `amount` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice Returns the name of the token.
    function name() external view returns (string memory);

    /// @notice Returns the symbol of the token.
    function symbol() external view returns (string memory);

    /// @notice Returns the decimals places of the token.
    function decimals() external view returns (uint8);
}
"
    },
    "src/policies/interfaces/deposits/IReceiptTokenManager.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC6909} from "@openzeppelin-5.3.0/interfaces/draft-IERC6909.sol";
import {IERC6909Wrappable} from "src/interfaces/IERC6909Wrappable.sol";

/// @title IReceiptTokenManager
/// @notice Interface for the contract that creates and manages receipt tokens
interface IReceiptTokenManager is IERC6909, IERC6909Wrappable {
    // ========== EVENTS ========== //

    event TokenCreated(
        uint256 indexed tokenId,
        address indexed owner,
        address indexed asset,
        uint8 depositPeriod,
        address operator
    );

    // ========== ERRORS ========== //

    error ReceiptTokenManager_TokenExists(uint256 tokenId);
    error ReceiptTokenManager_NotOwner(address caller, address owner);
    error ReceiptTokenManager_InvalidParams(string reason);

    // ========== FUNCTIONS ========== //

    /// @notice Creates a new receipt token
    /// @dev    The caller (msg.sender) becomes the owner of the token for security
    ///
    /// @param  asset_          The underlying asset
    /// @param  depositPeriod_  The deposit period
    /// @param  operator_       The operator address
    /// @param  operatorName_   The operator name for token metadata
    /// @return tokenId         The created token ID
    function createToken(
        IERC20 asset_,
        uint8 depositPeriod_,
        address operator_,
        string memory operatorName_
    ) external returns (uint256 tokenId);

    /// @notice Mints tokens to a recipient
    /// @dev    Gated to the owner (creator) of the token
    ///
    /// @param  to_         The recipient
    /// @param  tokenId_    The token ID
    /// @param  amount_     The amount to mint
    /// @param  shouldWrap_ Whether to wrap as ERC20
    function mint(address to_, uint256 tokenId_, uint256 amount_, bool shouldWrap_) external;

    /// @notice Burns tokens from a holder
    /// @dev    Gated to the owner (creator) of the token
    ///
    /// @param  from_       The holder
    /// @param  tokenId_    The token ID
    /// @param  amount_     The amount to burn
    /// @param  isWrapped_  Whether the tokens are wrapped
    function burn(address from_, uint256 tokenId_, uint256 amount_, bool isWrapped_) external;

    /// @notice Generates a receipt token ID
    ///
    /// @param  owner_          The owner address
    /// @param  asset_          The asset
    /// @param  depositPeriod_  The deposit period
    /// @param  operator_       The operator
    /// @return tokenId         The generated token ID
    function getReceiptTokenId(
        address owner_,
        IERC20 asset_,
        uint8 depositPeriod_,
        address operator_
    ) external pure returns (uint256 tokenId);

    /// @notice Returns the name of a receipt token
    ///
    /// @param  tokenId_    The ID of the receipt token
    /// @return name        The name of the receipt token
    function getTokenName(uint256 tokenId_) external view returns (string memory name);

    /// @notice Returns the symbol of a receipt token
    ///
    /// @param  tokenId_    The ID of the receipt token
    /// @return symbol      The symbol of the receipt token
    function getTokenSymbol(uint256 tokenId_) external view returns (string memory symbol);

    /// @notice Returns the decimals of a receipt token
    ///
    /// @param  tokenId_    The ID of the receipt token
    /// @return decimals    The decimals of the receipt token
    function getTokenDecimals(uint256 tokenId_) external view returns (uint8 decimals);

    /// @notice Gets the owner of a token
    ///
    /// @param  tokenId_    The token ID
    /// @return owner       The token owner
    function getTokenOwner(uint256 tokenId_) external view returns (address owner);

    /// @notice Gets the asset of a token
    ///
    /// @param  tokenId_    The token ID
    /// @return asset       The underlying asset
    function getTokenAsset(uint256 tokenId_) external view returns (IERC20 asset);

    /// @notice Gets the deposit period of a token
    ///
    /// @param  tokenId_        The token ID
    /// @return depositPeriod   The deposit period
    function getTokenDepositPeriod(uint256 tokenId_) external view returns (uint8 depositPeriod);

    /// @notice Gets the operator of a token
    ///
    /// @param  tokenId_    The token ID
    /// @return operator    The operator address
    function getTokenOperator(uint256 tokenId_) external view returns (address operator);
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/interfaces/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";
"
    },
    "src/libraries/ERC6909Wrappable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;

// Interfaces
import {IERC20BurnableMintable} from "src/interfaces/IERC20BurnableMintable.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";
import {IERC6909Metadata, IERC6909TokenSupply} from "@openzeppelin-5.3.0/interfaces/draft-IERC6909.sol";
import {IERC6909Wrappable} from "src/interfaces/IERC6909Wrappable.sol";

// Libraries
import {ERC6909} from "@openzeppelin-5.3.0/token/ERC6909/draft-ERC6909.sol";
import {ERC6909Metadata} from "@openzeppelin-5.3.0/token/ERC6909/extensions/draft-ERC6909Metadata.sol";
import {ClonesWithImmutableArgs} from "@clones-with-immutable-args-1.1.2/ClonesWithImmutableArgs.sol";
import {EnumerableSet} from "@openzeppelin-5.3.0/utils/structs/EnumerableSet.sol";

/// @title ERC6909Wrappable
/// @notice This abstract contract extends ERC6909 to allow for wrapping and unwrapping of the token to an ERC20 token.
///         It extends the ERC6909Metadata contract, and additionally implements the IERC6909TokenSupply interface.
abstract contract ERC6909Wrappable is ERC6909Metadata, IERC6909Wrappable, IERC6909TokenSupply {
    using ClonesWithImmutableArgs for address;
    using EnumerableSet for EnumerableSet.UintSet;

    /// @notice The address of the implementation of the ERC20 contract
    address private immutable _ERC20_IMPLEMENTATION;

    /// @notice The set of all token IDs
    EnumerableSet.UintSet internal _wrappableTokenIds;

    /// @notice The total supply of each token
    mapping(uint256 tokenId => uint256) private _totalSupplies;

    /// @notice Additional metadata for each token
    mapping(uint256 tokenId => bytes) private _tokenMetadataAdditional;

    /// @notice The address of the wrapped ERC20 token for each token
    mapping(uint256 tokenId => address) internal _wrappedTokens;

    constructor(address erc20Implementation_) {
        // Validate that the ERC20 implementation implements the required interface
        if (
            !IERC165(erc20Implementation_).supportsInterface(
                type(IERC20BurnableMintable).interfaceId
            )
        ) revert ERC6909Wrappable_InvalidERC20Implementation(erc20Implementation_);

        _ERC20_IMPLEMENTATION = erc20Implementation_;
    }

    /// @notice Returns the clone initialisation data for a given token ID
    ///
    /// @param  tokenId_    The token ID
    /// @return tokenData   Packed bytes including the name, symbol, decimals and additional metadata
    function _getTokenData(uint256 tokenId_) internal view returns (bytes memory tokenData) {
        bytes memory additionalMetadata = _tokenMetadataAdditional[tokenId_];

        return
            abi.encodePacked(
                bytes32(bytes(name(tokenId_))),
                bytes32(bytes(symbol(tokenId_))),
                decimals(tokenId_),
                additionalMetadata
            );
    }

    /// @notice Returns the additional metadata for a token ID
    ///
    /// @param  tokenId_        The token ID
    /// @return additionalData  The additional metadata bytes
    function _getTokenAdditionalData(
        uint256 tokenId_
    ) internal view returns (bytes memory additionalData) {
        return _tokenMetadataAdditional[tokenId_];
    }

    // ========== MINT/BURN FUNCTIONS ========== //

    /// @notice Mints the ERC6909 or ERC20 wrapped token to the recipient
    ///
    /// @param onBehalfOf_   The address to mint the token to
    /// @param tokenId_      The ID of the ERC6909 token
    /// @param amount_       The amount of tokens to mint
    /// @param shouldWrap_   Whether to wrap the token to an ERC20 token
    function _mint(
        address onBehalfOf_,
        uint256 tokenId_,
        uint256 amount_,
        bool shouldWrap_
    ) internal onlyValidTokenId(tokenId_) {
        if (amount_ == 0) revert ERC6909Wrappable_ZeroAmount();
        if (onBehalfOf_ == address(0)) revert ERC6909InvalidReceiver(onBehalfOf_);

        if (shouldWrap_) {
            _getWrappedToken(tokenId_).mintFor(onBehalfOf_, amount_);
        } else {
            _mint(onBehalfOf_, tokenId_, amount_);
        }
    }

    /// @notice Burns the ERC6909 or ERC20 wrapped token from the recipient
    /// @dev    This function reverts if:
    ///         - amount_ is 0
    ///         - onBehalfOf_ is 0
    ///         - onBehalfOf_ is not the caller and has not approved the caller to spend the ERC6909 tokens (note: ERC6909 allowances govern both wrapped and unwrapped token burns)
    ///         - ERC6909 token handling reverts
    ///
    /// @param onBehalfOf_   The address to burn the token from
    /// @param tokenId_      The ID of the ERC6909 token
    /// @param amount_       The amount of tokens to burn
    /// @param wrapped_      Whether the token is wrapped
    function _burn(
        address onBehalfOf_,
        uint256 tokenId_,
        uint256 amount_,
        bool wrapped_
    ) internal onlyValidTokenId(tokenId_) {
        if (amount_ == 0) revert ERC6909Wrappable_ZeroAmount();
        if (onBehalfOf_ == address(0)) revert ERC6909InvalidSender(onBehalfOf_);

        // If the caller is not the owner, check allowance
        if (onBehalfOf_ != msg.sender) {
            // Spend allowance (since it is not implemented in `ERC6909._burn()` or `CloneableReceiptToken.burnFrom()`)
            // The caller is the spender, not this contract
            _spendAllowance(onBehalfOf_, msg.sender, tokenId_, amount_);
        }

        if (wrapped_) {
            // Burn the ERC20 token
            _getWrappedToken(tokenId_).burnFrom(onBehalfOf_, amount_);
        } else {
            // Burn the ERC6909 token
            _burn(onBehalfOf_, tokenId_, amount_);
        }
    }

    // ========== TOTAL SUPPLY EXTENSION ========== //

    /// @inheritdoc IERC6909TokenSupply
    function totalSupply(uint256 tokenId_) public view virtual override returns (uint256) {
        return _totalSupplies[tokenId_];
    }

    /// @dev Copied from draft-ERC6909TokenSupply.sol
    function _update(
        address from,
        address to,
        uint256 id,
        uint256 amount
    ) internal virtual override {
        // Calls ERC6909Metadata._update()
        super._update(from, to, id, amount);

        if (from == address(0)) {
            _totalSupplies[id] += amount;
        }
        if (to == address(0)) {
            unchecked {
                // amount <= _balances[from][id] <= _totalSupplies[id]
                _totalSupplies[id] -= amount;
            }
        }
    }

    // ========== WRAP/UNWRAP FUNCTIONS ========== //

    /// @dev Returns the address of the wrapped ERC20 token for a given token ID, or creates a new one if it does not exist
    function _getWrappedToken(
        uint256 tokenId_
    ) internal returns (IERC20BurnableMintable wrappedToken) {
        // If the wrapped token exists, return it
        if (_wrappedTokens[tokenId_] != address(0))
            return IERC20BurnableMintable(_wrappedTokens[tokenId_]);

        // Validate that the token id exists
        bytes memory tokenData = _getTokenData(tokenId_);
        if (tokenData.length == 0) revert ERC6909Wrappable_InvalidTokenId(tokenId_);

        // Otherwise, create a new wrapped token
        wrappedToken = IERC20BurnableMintable(_ERC20_IMPLEMENTATION.clone(tokenData));
        _wrappedTokens[tokenId_] = address(wrappedToken);
        return wrappedToken;
    }

    /// @inheritdoc IERC6909Wrappable
    function getWrappedToken(uint256 tokenId_) public view returns (address wrappedToken) {
        return _wrappedTokens[tokenId_];
    }

    /// @inheritdoc IERC6909Wrappable
    /// @dev        This function will burn the ERC6909 token from the caller and mint the wrapped ERC20 token to the same address.
    ///
    ///             This function reverts if:
    ///             - The token ID does not exist
    ///             - The amount is zero
    ///             - The caller has an insufficient balance of the token
    function wrap(uint256 tokenId_, uint256 amount_) public returns (address wrappedToken) {
        // Burn the ERC6909 token
        _burn(msg.sender, tokenId_, amount_, false);

        // Mint the wrapped ERC20 token to the recipient
        IERC20BurnableMintable wrappedToken_ = _getWrappedToken(tokenId_);
        wrappedToken_.mintFor(msg.sender, amount_);

        // Emit the Wrapped event
        emit Wrapped(tokenId_, address(wrappedToken_), msg.sender, amount_);

        return address(wrappedToken_);
    }

    /// @inheritdoc IERC6909Wrappable
    /// @dev        This function will burn the wrapped ERC20 token from the caller and mint the ERC6909 token to the same address.
    ///
    ///             This function reverts if:
    ///             - The token ID does not exist
    ///             - The amount is zero
    ///             - The caller has an insufficient balance of the wrapped token
    function unwrap(uint256 tokenId_, uint256 amount_) public {
        // Burn the wrapped ERC20 token
        _burn(msg.sender, tokenId_, amount_, true);

        // Mint the ERC6909 token
        _mint(msg.sender, tokenId_, amount_);

        // Emit the Unwrapped event
        emit Unwrapped(tokenId_, _wrappedTokens[tokenId_], msg.sender, amount_);
    }

    // ========== TOKEN FUNCTIONS ========== //

    /// @inheritdoc IERC6909Wrappable
    function isValidTokenId(uint256 tokenId_) public view returns (bool) {
        return _wrappableTokenIds.contains(tokenId_);
    }

    function _onlyValidTokenId(uint256 tokenId_) internal view {
        if (!isValidTokenId(tokenId_)) revert ERC6909Wrappable_InvalidTokenId(tokenId_);
    }

    modifier onlyValidTokenId(uint256 tokenId_) {
        _onlyValidTokenId(tokenId_);
        _;
    }

    /// @notice Creates a new wrappable token
    /// @dev    Reverts if the token ID already exists
    function _createWrappableToken(
        uint256 tokenId_,
        string memory name_,
        string memory symbol_,
        uint8 decimals_,
        bytes memory additionalMetadata_,
        bool createWrappedToken_
    ) internal {
        // If the token ID already exists, revert
        if (_wrappableTokenIds.contains(tokenId_))
            revert ERC6909Wrappable_TokenIdAlreadyExists(tokenId_);

        _setName(tokenId_, name_);
        _setSymbol(tokenId_, symbol_);
        _setDecimals(tokenId_, decimals_);
        _tokenMetadataAdditional[tokenId_] = additionalMetadata_;

        if (createWrappedToken_) {
            _getWrappedToken(tokenId_);
        }

        // Record the token ID
        _wrappableTokenIds.add(tokenId_);
    }

    /// @inheritdoc IERC6909Wrappable
    function getWrappableTokens()
        public
        view
        override
        returns (uint256[] memory tokenIds, address[] memory wrappedTokens)
    {
        tokenIds = _wrappableTokenIds.values();
        wrappedTokens = new address[](tokenIds.length);
        for (uint256 i; i < tokenIds.length; ++i) {
            wrappedTokens[i] = _wrappedTokens[tokenIds[i]];
        }

        return (tokenIds, wrappedTokens);
    }

    // ========== ERC165 ========== //

    function supportsInterface(
        bytes4 interfaceId_
    ) public view virtual override(ERC6909, IERC165) returns (bool) {
        return
            interfaceId_ == type(IERC165).interfaceId ||
            interfaceId_ == type(IERC6909Wrappable).interfaceId ||
            interfaceId_ == type(IERC6909Metadata).interfaceId ||
            interfaceId_ == type(IERC6909TokenSupply).interfaceId ||
            super.supportsInterface(interfaceId_);
    }
}
"
    },
    "src/libraries/CloneableReceiptToken.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.15;

// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IERC20BurnableMintable} from "src/interfaces/IERC20BurnableMintable.sol";
import {IDepositReceiptToken} from "src/interfaces/IDepositReceiptToken.sol";
import {IERC165} from "@openzeppelin-5.3.0/interfaces/IERC165.sol";

// Libraries
import {CloneERC20} from "src/external/clones/CloneERC20.sol";

/// @title  CloneableReceiptToken
/// @notice ERC20 implementation that is deployed as a clone
///         with immutable arguments for each supported input token.
contract CloneableReceiptToken is CloneERC20, IERC20BurnableMintable, IDepositReceiptToken {
    // ========== IMMUTABLE ARGS ========== //

    // Storage layout:
    // 0x00 - name, 32 bytes
    // 0x20 - symbol, 32 bytes
    // 0x40 - decimals, 1 byte
    // 0x41 - owner, 20 bytes
    // 0x55 - asset, 20 bytes
    // 0x69 - depositPeriod, 1 byte
    // 0x6A - operator, 20 bytes

    /// @notice The owner of the clone
    /// @return _owner The owner address stored in immutable args
    function owner() public pure returns (address _owner) {
        _owner = _getArgAddress(0x41);
    }

    /// @notice The underlying asset
    /// @return _asset The asset address stored in immutable args
    function asset() public pure returns (IERC20 _asset) {
        _asset = IERC20(_getArgAddress(0x55));
    }

    /// @notice The deposit period (in months)
    /// @return _depositPeriod The deposit period stored in immutable args
    function depositPeriod() public pure returns (uint8 _depositPeriod) {
        _depositPeriod = _getArgUint8(0x69);
    }

    /// @notice The operator that issued the receipt token
    /// @return _operator The operator address stored in immutable args
    function operator() public pure returns (address _operator) {
        _operator = _getArgAddress(0x6A);
    }

    // ========== OWNER-ONLY FUNCTIONS ========== //

    function _onlyOwner() internal view {
        if (msg.sender != owner()) revert OnlyOwner();
    }

    /// @notice Only the owner can call this function
    modifier onlyOwner() {
        _onlyOwner();
        _;
    }

    /// @notice Mint tokens to the specified address
    /// @dev    This is owner-only, as the underlying token is custodied by the owner.
    ///         Minting should be performed through the owner contract.
    ///
    /// @param to_ The address to mint tokens to
    /// @param amount_ The amount of tokens to mint
    function mintFor(address to_, uint256 amount_) external onlyOwner {
        _mint(to_, amount_);
    }

    /// @notice Burn tokens from the specified address
    /// @dev    This is gated to the owner, as burning is controlled.
    ///         Burning should be performed through the owner contract.
    ///         The owner is expected to handle spending approval before calling this function.
    ///         This function does NOT check or update allowances.
    ///
    /// @param from_ The address to burn tokens from
    /// @param amount_ The amount of tokens to burn
    function burnFrom(address from_, uint256 amount_) external onlyOwner {
        _burn(from_, amount_);
    }

    // ========== ERC165 ========== //

    function supportsInterface(bytes4 interfaceId_) public pure returns (bool) {
        // super does not implement ERC165, so no need to call it
        return
            interfaceId_ == type(IERC165).interfaceId ||
            interfaceId_ == type(IERC20).interfaceId ||
            interfaceId_ == type(IERC20BurnableMintable).interfaceId ||
            interfaceId_ == type(IDepositReceiptToken).interfaceId;
    }
}
"
    },
    "src/libraries/Uint2Str.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8;

// Some fancy math to convert a uint into a string, courtesy of Provable Things.
// Updated to work with solc 0.8.0.
// https://github.com/provable-things/ethereum-api/blob/master/provableAPI_0.6.sol
function uint2str(uint256 _i) pure returns (string memory) {
    if (_i == 0) {
        return "0";
    }
    uint256 j = _i;
    uint256 len;
    while (j != 0) {
        len++;
        j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint256 k = len;
    while (_i != 0) {
        k = k - 1;
        bstr[k] = bytes1(uint8(48 + (_i % 10)));
        _i /= 10;
    }
    return string(bstr);
}
"
    },
    "src/libraries/String.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;

library String {
    error EndBeforeStartIndex(uint256 startIndex, uint256 endIndex);
    error EndIndexOutOfBounds(uint256 endIndex, uint256 length);

    /// @notice Truncates a string to 32 bytes
    function truncate32(string memory str_) internal pure returns (string memory) {
        return string(abi.encodePacked(bytes32(abi.encodePacked(str_))));
    }

    /// @notice Returns a substring of a string
    ///
    /// @param  str_            The string to get the substring of
    /// @param  startIndex_     The index to start the substring at
    /// @param  endIndex_       The index to end the substring at
    /// @return resultString    The substring
    function substring(
        string memory str_,
        uint256 startIndex_,
        uint256 endIndex_
    ) internal pure returns (string memory) {
        bytes memory strBytes = bytes(str_);

        if (endIndex_ < startIndex_) revert EndBeforeStartIndex(startIndex_, endIndex_);
        if (endIndex_ > strBytes.length) revert EndIndexOutOfBounds(endIndex_, strBytes.length);

        bytes memory result = new bytes(endIndex_ - startIndex_);
        for (uint256 i = startIndex_; i < endIndex_; i++) {
            result[i - startIndex_] = strBytes[i];
        }
        return string(result);
    }

    /// @notice Returns a substring of a string from a given index
    ///
    /// @param  str_ The string to get the substring of
    /// @param  startIndex_ The index to start the substring at
    /// @return resultString The substring
    function substringFrom(
        string memory str_,
        uint256 startIndex_
    ) internal pure returns (string memory) {
        return substring(str_, startIndex_, bytes(str_).length);
    }
}
"
    },
    "src/interfaces/IDepositReceiptToken.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;

import {IERC20} from "src/interfaces/IERC20.sol";

/// @title  IDepositReceiptToken
/// @notice Interface for a deposit receipt token
/// @dev    This interface adds additional metadata to the IERC20 interface that is necessary for deposit receipt tokens.
interface IDepositReceiptToken is IERC20 {
    // ========== ERRORS ========== //

    error OnlyOwner();

    // ========== VIEW FUNCTIONS ========== //

    function owner() external view returns (address _owner);

    function asset() external view returns (IERC20 _asset);

    function depositPeriod() external view returns (uint8 _depositPeriod);

    function operator() external view returns (address _operator);
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/interfaces/draft-IERC6909.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (interfaces/draft-IERC6909.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-6909 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-6909[ERC].
 */
interface IERC6909 is IERC165 {
    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set for a token of type `id`.
     * The new allowance is `amount`.
     */
    event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);

    /**
     * @dev Emitted when `owner` grants or revokes operator status for a `spender`.
     */
    event OperatorSet(address indexed owner, address indexed spender, bool approved);

    /**
     * @dev Emitted when `amount` tokens of type `id` are moved from `sender` to `receiver` initiated by `caller`.
     */
    event Transfer(
        address caller,
        address indexed sender,
        address indexed receiver,
        uint256 indexed id,
        uint256 amount
    );

    /**
     * @dev Returns the amount of tokens of type `id` owned by `owner`.
     */
    function balanceOf(address owner, uint256 id) external view returns (uint256);

    /**
     * @dev Returns the amount of tokens of type `id` that `spender` is allowed to spend on behalf of `owner`.
     *
     * NOTE: Does not include operator allowances.
     */
    function allowance(address owner, address spender, uint256 id) external view returns (uint256);

    /**
     * @dev Returns true if `spender` is set as an operator for `owner`.
     */
    function isOperator(address owner, address spender) external view returns (bool);

    /**
     * @dev Sets an approval to `spender` for `amount` of tokens of type `id` from the caller's tokens. An `amount` of
     * `type(uint256).max` signifies an unlimited approval.
     *
     * Must return true.
     */
    function approve(address spender, uint256 id, uint256 amount) external returns (bool);

    /**
     * @dev Grants or revokes unlimited transfer permission of any token id to `spender` for the caller's tokens.
     *
     * Must return true.
     */
    function setOperator(address spender, bool approved) external returns (bool);

    /**
     * @dev Transfers `amount` of token type `id` from the caller's account to `receiver`.
     *
     * Must return true.
     */
    function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);

    /**
     * @dev Transfers `amount` of token type `id` from `sender` to `receiver`.
     *
     * Must return true.
     */
    function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
}

/**
 * @dev Optional extension of {IERC6909} that adds metadata functions.
 */
interface IERC6909Metadata is IERC6909 {
    /**
     * @dev Returns the name of the token of type `id`.
     */
    function name(uint256 id) external view returns (string memory);

    /**
     * @dev Returns the ticker symbol of the token of type `id`.
     */
    function symbol(uint256 id) external view returns (string memory);

    /**
     * @dev Returns the number of decimals for the token of type `id`.
     */
    function decimals(uint256 id) external view returns (uint8);
}

/**
 * @dev Optional extension of {IERC6909} that adds content URI functions.
 */
interface IERC6909ContentURI is IERC6909 {
    /**
     * @dev Returns URI for the contract.
     */
    function contractURI() external view returns (string memory);

    /**
     * @dev Returns the URI for the token of type `id`.
     */
    function tokenURI(uint256 id) external view returns (string memory);
}

/**
 * @dev Optional extension of {IERC6909} that adds a token supply function.
 */
interface IERC6909TokenSupply is IERC6909 {
    /**
     * @dev Returns the total supply of the token of type `id`.
     */
    function totalSupply(uint256 id) external view returns (uint256);
}
"
    },
    "src/interfaces/IERC6909Wrappable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title IERC6909Wrappable
/// @notice Declares interface for an ERC6909 implementation that allows for wrapping and unwrapping ERC6909 tokens to and from ERC20 tokens
interface IERC6909Wrappable {
    // ========== EVENTS ========== //

    event Wrapped(
        uint256 indexed tokenId,
        address indexed wrappedToken,
        address indexed account,
        uint256 amount
    );

    event Unwrapped(
        uint256 indexed tokenId,
        address indexed wrappedToken,
        address indexed account,
        uint256 amount
    );

    // ========== ERRORS ========== //

    error ERC6909Wrappable_TokenIdAlreadyExists(uint256 tokenId);
    error ERC6909Wrappable_InvalidTokenId(uint256 tokenId);
    error ERC6909Wrappable_InvalidERC20Implementation(address erc20Implementation);
    error ERC6909Wrappable_ZeroAmount();

    // ========== WRAP/UNWRAP FUNCTIONS ========== //

    /// @notice Wraps an ERC6909 token to an ERC20 token
    ///
    /// @param tokenId_      The ID of the ERC6909 token
    /// @param amount_       The amount of tokens to wrap
    /// @return wrappedToken The address of the wrapped ERC20 token
    function wrap(uint256 tokenId_, uint256 amount_) external returns (address wrappedToken);

    /// @notice Unwraps an ERC20 token to an ERC6909 token
    ///
    /// @param tokenId_      The ID of the ERC6909 token
    /// @param amount_       The amount of tokens to unwrap
    function unwrap(uint256 tokenId_, uint256 amount_) external;

    /// @notice Returns the address of the wrapped ERC20 token for a given token ID
    ///
    /// @param  tokenId_        The ID of the ERC6909 token
    /// @return wrappedToken    The address of the wrapped ERC20 token (or zero address)
    function getWrappedToken(uint256 tokenId_) external view returns (address wrappedToken);

    // ========== TOKEN FUNCTIONS ========== //

    /// @notice Returns whether a token ID is valid
    ///
    /// @param  tokenId_        The ID of the ERC6909 token
    /// @return isValid         Whether the token ID is valid
    function isValidTokenId(uint256 tokenId_) external view returns (bool isValid);

    /// @notice Returns the token IDs and wrapped token addresses of all tokens
    ///
    /// @return tokenIds        The IDs of all tokens
    /// @return wrappedTokens   The wrapped token addresses of all tokens
    function getWrappableTokens()
        external
        view
        returns (uint256[] memory tokenIds, address[] memory wrappedTokens);
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    },
    "src/interfaces/IERC20BurnableMintable.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;

import {IERC20} from "src/interfaces/IERC20.sol";

interface IERC20BurnableMintable is IERC20 {
    /// @notice Mints tokens to the specified address
    ///
    /// @param to_      The address to mint tokens to
    /// @param amount_  The amount of tokens to mint
    function mintFor(address to_, uint256 amount_) external;

    /// @notice Burns tokens from the specified address
    ///
    /// @param from_    The address to burn tokens from
    /// @param amount_  The amount of tokens to burn
    function burnFrom(address from_, uint256 amount_) external;
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/token/ERC6909/draft-ERC6909.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC6909/draft-ERC6909.sol)

pragma solidity ^0.8.20;

import {IERC6909} from "../../interfaces/draft-IERC6909.sol";
import {Context} from "../../utils/Context.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of ERC-6909.
 * See https://eips.ethereum.org/EIPS/eip-6909
 */
contract ERC6909 is Context, ERC165, IERC6909 {
    mapping(address owner => mapping(uint256 id => uint256)) private _balances;

    mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;

    mapping(address owner => mapping(address spender => mapping(uint256 id => uint256))) private _allowances;

    error ERC6909InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id);
    error ERC6909InsufficientAllowance(address spender, uint256 allowance, uint256 needed, uint256 id);
    error ERC6909InvalidApprover(address approver);
    error ERC6909InvalidReceiver(address receiver);
    error ERC6909InvalidSender(address sender);
    error ERC6909InvalidSpender(address spender);

    /// @inheritdoc IERC165
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC6909).interfaceId || super.supportsInterface(interfaceId);
    }

    /// @inheritdoc IERC6909
    function balanceOf(address owner, uint256 id) public view virtual override returns (uint256) {
        return _balances[owner][id];
    }

    /// @inheritdoc IERC6909
    function allowance(address owner, address spender, uint256 id) public view virtual override returns (uint256) {
        return _allowances[owner][spender][id];
    }

    /// @inheritdoc IERC6909
    function isOperator(address owner, address spender) public view virtual override returns (bool) {
        return _operatorApprovals[owner][spender];
    }

    /// @inheritdoc IERC6909
    function approve(address spender, uint256 id, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, id, amount);
        return true;
    }

    /// @inheritdoc IERC6909
    function setOperator(address spender, bool approved) public virtual override returns (bool) {
        _setOperator(_msgSender(), spender, approved);
        return true;
    }

    /// @inheritdoc IERC6909
    function transfer(address receiver, uint256 id, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), receiver, id, amount);
        return true;
    }

    /// @inheritdoc IERC6909
    function transferFrom(
        address sender,
        address receiver,
        uint256 id,
        uint256 amount
    ) public virtual override returns (bool) {
        address caller = _msgSender();
        if (sender != caller && !isOperator(sender, caller)) {
            _spendAllowance(sender, caller, id, amount);
        }
        _transfer(sender, receiver, id, amount);
        return true;
    }

    /**
     * @dev Creates `amount` of token `id` and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address to, uint256 id, uint256 amount) internal {
        if (to == address(0)) {
            revert ERC6909InvalidReceiver(address(0));
        }
        _update(address(0), to, id, amount);
    }

    /**
     * @dev Moves `amount` of token `id` from `from` to `to` without checking for approvals. This function verifies
     * that neither the sender nor the receiver are address(0), which means it cannot mint or burn tokens.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 id, uint256 amount) internal {
        if (from == address(0)) {
            revert ERC6909InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC6909InvalidReceiver(address(0));
        }
        _update(from, to, id, amount);
    }

    /**
     * @dev Destroys a `amount` of token `id` from `account`.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address from, uint256 id, uint256 amount) internal {
        if (from == address(0)) {
            revert ERC6909InvalidSender(address(0));
        }
        _update(from, address(0), id, amount);
    }

    /**
     * @dev Transfers `amount` of token `id` from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 id, uint256 amount) internal virtual {
        address caller = _msgSender();

        if (from != address(0)) {
            uint256 fromBalance = _balances[from][id];
            if (fromBalance < amount) {
                revert ERC6909InsufficientBalance(from, fromBalance, amount, id);
            }
            unchecked {
                // Overflow not possible: amount <= fromBalance.
                _balances[from][id] = fromBalance - amount;
            }
        }
        if (to != address(0)) {
            _balances[to][id] += amount;
        }

        emit Transfer(caller, from, to, id, amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`'s `id` tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain
     * subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 id, uint256 amount) internal virtual {
        if (owner == address(0)) {
            revert ERC6909InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC6909InvalidSpender(address(0));
        }
        _allowances[owner][spender][id] = amount;
        emit Approval(owner, spender, id, amount);
    }

    /**
     * @dev Approve `spender` to operate on all of `owner`'s tokens
     *
     * This internal function is equivalent to `setOperator`, and can be used to e.g. set automatic allowances for
     * certain subsystems, etc.
     *
     * Emits an {OperatorSet} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _setOperator(address owner, address spender, bool approved) internal virtual {
        if (owner == address(0)) {
            revert ERC6909InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC6909InvalidSpender(address(0));
        }
        _operatorApprovals[owner][spender] = approved;
        emit OperatorSet(owner, spender, approved);
    }

    /**
     * @dev Updates `owner`'s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 id, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender, id);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < amount) {
                revert ERC6909InsufficientAllowance(spender, currentAllowance, amount, id);
            }
            unchecked {
                _allowances[owner][spender][id] = currentAllowance - amount;
            }
        }
    }
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/token/ERC6909/extensions/draft-ERC6909Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC6909/extensions/draft-ERC6909Metadata.sol)

pragma solidity ^0.8.20;

import {ERC6909} from "../draft-ERC6909.sol";
import {IERC6909Metadata} from "../../../interfaces/draft-IERC6909.sol";

/**
 * @dev Implementation of the Metadata extension defined in ERC6909. Exposes the name, symbol, and decimals of each token id.
 */
contract ERC6909Metadata is ERC6909, IERC6909Metadata {
    struct TokenMetadata {
        string name;
        string symbol;
        uint8 decimals;
    }

    mapping(uint256 id => TokenMetadata) private _tokenMetadata;

    /// @dev The name of the token of type `id` was updated to `newName`.
    event ERC6909NameUpdated(uint256 indexed id, string newName);

    /// @dev The symbol for the token of type `id` was updated to `newSymbol`.
    event ERC6909SymbolUpdated(uint256 indexed id, string newSymbol);

    /// @dev The decimals value for token of type `id` was updated to `newDecimals`.
    event ERC6909DecimalsUpdated(uint256 indexed id, uint8 newDecimals);

    /// @inheritdoc IERC6909Metadata
    function name(uint256 id) public view virtual override returns (string memory) {
        return _tokenMetadata[id].name;
    }

    /// @inheritdoc IERC6909Metadata
    function symbol(uint256 id) public view virtual override returns (string memory) {
        return _tokenMetadata[id].symbol;
    }

    /// @inheritdoc IERC6909Metadata
    function decimals(uint256 id) public view virtual override returns (uint8) {
        return _tokenMetadata[id].decimals;
    }

    /**
     * @dev Sets the `name` for a given token of type `id`.
     *
     * Emits an {ERC6909NameUpdated} event.
     */
    function _setName(uint256 id, string memory newName) internal virtual {
        _tokenMetadata[id].name = newName;

        emit ERC6909NameUpdated(id, newName);
    }

    /**
     * @dev Sets the `symbol` for a given token of type `id`.
     *
     * Emits an {ERC6909SymbolUpdated} event.
     */
    function _setSymbol(uint256 id, string memory newSymbol) internal virtual {
        _tokenMetadata[id].symbol = newSymbol;

        emit ERC6909SymbolUpdated(id, newSymbol);
    }

    /**
     * @dev Sets the `decimals` for a given token of type `id`.
     *
     * Emits an {ERC6909DecimalsUpdated} event.
     */
    function _setDecimals(uint256 id, uint8 newDecimals) internal virtual {
        _tokenMetadata[id].decimals = newDecimals;

        emit ERC6909DecimalsUpdated(id, newDecimals);
    }
}
"
    },
    "dependencies/clones-with-immutable-args-1.1.2/src/ClonesWithImmutableArgs.sol": {
      "content": "// SPDX-License-Identifier: BSD

pragma solidity ^0.8.4;

/// @title ClonesWithImmutableArgs
/// @author wighawag, zefram.eth, nick.eth
/// @notice Enables creating clone contracts with immutable args
library ClonesWithImmutableArgs {
    /// @dev The CREATE3 proxy bytecode.
    uint256 private constant _CREATE3_PROXY_BYTECODE =
        0x67363d3d37363d34f03d5260086018f3;

    /// @dev Hash of the `_CREATE3_PROXY_BYTECODE`.
    /// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
    bytes32 private constant _CREATE3_PROXY_BYTECODE_HASH =
        0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

    error CreateFail();
    error InitializeFail();

    enum CloneType {
        CREATE,
        CREATE2,
        PREDICT_CREATE2
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @return instance The address of the created clone
    function clone(
        address implementation,
        bytes memory data
    ) internal returns (address payable instance) {
        return clone(implementation, data, 0);
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @param value The amount of wei to transfer to the created clone
    /// @return instance The address of the created clone
    function clone(
        address implementation,
        bytes memory data,
        uint256 value
    ) internal returns (address payable instance) {
        bytes memory creationcode = getCreationBytecode(implementation, data);
        // solhint-disable-next-line no-inline-assembly
        assembly {
            instance := create(
                value,
                add(creationcode, 0x20),
                mload(creationcode)
            )
        }
        if (instance == address(0)) {
            revert CreateFail();
        }
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args,
    ///         using CREATE2
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @return instance The address of the created clone
    function clone2(
        address implementation,
        bytes memory data
    ) internal returns (address payable instance) {
        return clone2(implementation, data, 0);
    }

    /// @notice Creates a clone proxy of the implementation contract, with immutable args,
    ///         using CREATE2
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @param value The amount of wei to transfer to the created clone
    /// @return instance The address of the created clone
    function clone2(
        address implementation,
        bytes memory data,
        uint256 value
    ) internal returns (address payable instance) {
        bytes memory creationcode = getCreationBytecode(implementation, data);
        // solhint-disable-next-line no-inline-assembly
        assembly {
            instance := create2(
                value,
                add(creationcode, 0x20),
                mload(creationcode),
                0
            )
        }
        if (instance == address(0)) {
            revert CreateFail();
        }
    }

    /// @notice Computes the address of a clone created using CREATE2
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @return instance The address of the clone
    function addressOfClone2(
        address implementation,
        bytes memory data
    ) internal view returns (address payable instance) {
        bytes memory creationcode = getCreationBytecode(implementation, data);
        bytes32 bytecodeHash = keccak256(creationcode);
        instance = payable(
            address(
                uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                bytes1(0xff),
                                address(this),
                                bytes32(0),
                                bytecodeHash
                            )
                        )
                    )
                )
            )
        );
    }

    /// @notice Computes bytecode for a clone
    /// @dev data cannot exceed 65535 bytes, since 2 bytes are used to store the data length
    /// @param implementation The implementation contract to clone
    /// @param data Encoded immutable args
    /// @return ret Creation bytecode for the clone contract
    function getCreationBytecode(
        address implementation,
        bytes memory data
    ) internal pure returns (bytes memory ret) {
        // unrealistic for memory ptr or data length to exceed 256 bits
        unchecked {
            uint256 extraLength = data.length + 2; // +2 bytes for telling how much data there is appended to the call
            uint256 creationSize = 0x41 + extraLength;
            uint256 runSize = creationSize - 10;
            uint256 dataPtr;
            uint256 ptr;

            // solhint-disable-next-line no-inline-assembly
            assembly {
                ret := mload(0x40)
                mstore(ret, creationSize)
                mstore(0x40, add(ret, creationSize))
                ptr := add(ret, 0x20)

                // -------------------------------------------------------------------------------------------------------------
                // CREATION (10 bytes)
                // -------------------------------------------------------------------------------------------------------------

                // 61 runtime  | PUSH2 runtime (r)     | r                             | –
                mstore(
                    ptr,
                    0x6100000000000000000000000000000000000000000000000000000000000000
                )
                mstore(add(ptr, 0x01), shl(240, runSize)) // size of the contract running bytecode (16 bits)

                // creation size = 0a
                // 3d          | RETURNDATASIZE        | 0 r                           | –
                // 81          | DUP2                  | r 0 r                         | –
                // 60 creation | PUSH1 creation (c)    | c r 0 r                       | –
                // 3d          | RETURNDATASIZE        | 0 c r 0 r                     | –
                // 39          | CODECOPY              | 0 r                           | [0-runSize): runtime code
                // f3          | RETURN                |                               | [0-runSize): runtime code

                // -------------------------------------------------------------------------------------------------------------
                // RUNTIME (55 bytes + extraLength)
                // -------------------------------------------------------------------------------------------------------------

                // 3d          | RETURNDATASIZE        | 0                             | –
                // 3d          | RETURNDATASIZE        | 0 0                           | –
                // 3d          | RETURNDATASIZE        | 0 0 0                         | –
                // 3d          | RETURNDATASIZE        | 0 0 0 0                       | –
                // 36          | CALLDATASIZE          | cds 0 0 0 0                   | –
                // 3d          | RETURNDATASIZE        | 0 cds 0 0 0 0                 | –
                // 3d          | RETURNDATASIZE        | 0 0 cds 0 0 0 0               | –
                // 37          | CALLDATACOPY          | 0 0 0 0                       | [0, cds) = calldata
                // 61          | PUSH2 extra           | extra 0 0 0 0                 | [0, cds) = calldata
                mstore(
                    add(ptr, 0x03),
                    0x3d81600a3d39f33d3d3d3d363d3d376100000000000000000000000000000000
                )
                mstore(add(ptr, 0x13), shl(240, extraLength))

                // 60 0x37     | PUSH1 0x37            | 0x37 extra 0 0 0 0            | [0, cds) = calldata // 0x37 (55) is runtime size - data
                // 36          | CALLDATASIZE          | cds 0x37 extra 0 0 0 0        | [0, cds) = calldata
                // 39          | CODECOPY              | 0 0 0 0                       | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 36          | CALLDATASIZE          | cds 0 0 0 0                   | [0, cds) = calldata, [cds, cds+extra) = extraData
                // 61 extra    | PUSH2 extra           | extra cds 0 0 0 0             | [0, cds) = calldata, [cds, cds+extra) = extraData
                mstore(
                    add(ptr, 0x15),
                    0x6037363936610000000000000000000000000000000000000000000000000000
                )
                mstor

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xd98b5b2e4d5d6cd554115de19efb7a9084beddd1|verified:true|block:23747529|tx:0xcf420d5e105ec89d61b23b4e246f49181ff5f555a49e62296b938ccdf029a626|first_check:1762528453

Submitted on: 2025-11-07 16:14:14

Comments

Log in to comment.

No comments yet.