PositionTokenRenderer

Description:

Governance contract for decentralized decision-making.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/modules/DEPOS/PositionTokenRenderer.sol": {
      "content": "// SPDX-License-Identifier: MIT
/// forge-lint: disable-start(mixed-case-function)
pragma solidity >=0.8.15;

// Interfaces
import {IPositionTokenRenderer} from "src/modules/DEPOS/IPositionTokenRenderer.sol";
import {IDepositPositionManager} from "src/modules/DEPOS/IDepositPositionManager.sol";
import {IERC165} from "@openzeppelin-5.3.0/utils/introspection/IERC165.sol";

// Libraries
import {ERC20} from "@solmate-6.2.0/tokens/ERC20.sol";
import {Strings} from "@openzeppelin-5.3.0/utils/Strings.sol";
import {Base64} from "@openzeppelin-5.3.0/utils/Base64.sol";
import {Timestamp} from "src/libraries/Timestamp.sol";
import {DecimalString} from "src/libraries/DecimalString.sol";
import {FullMath} from "src/libraries/FullMath.sol";

// solhint-disable quotes

/// @title  Position Token Renderer
/// @notice Implementation of the IPositionTokenRenderer interface
///         This contract implements a custom token renderer
///         for the Olympus Deposit Position Manager
contract PositionTokenRenderer is IPositionTokenRenderer {
    // ========== STATE VARIABLES ========== //

    /// @notice The number of decimal places to display when rendering values as decimal strings
    uint8 public constant DISPLAY_DECIMALS = 2;

    uint8 public constant OHM_DECIMALS = 9;

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

    /// @inheritdoc IPositionTokenRenderer
    function tokenURI(
        address positionManager_,
        uint256 positionId_
    ) external view override returns (string memory) {
        // Validate that the position manager supports the IDepositPositionManager interface
        if (
            !IERC165(positionManager_).supportsInterface(type(IDepositPositionManager).interfaceId)
        ) {
            revert PositionTokenRenderer_InvalidAddress();
        }

        // Get the position data from the position manager
        IDepositPositionManager.Position memory position = IDepositPositionManager(positionManager_)
            .getPosition(positionId_);

        // Get the decimals of the deposit token
        uint8 depositDecimals = ERC20(position.asset).decimals();
        string memory cdSymbol = ERC20(position.asset).symbol();

        // Check if the position is convertible
        bool positionIsConvertible = IDepositPositionManager(positionManager_).isConvertible(
            positionId_
        );

        // Generate the JSON metadata
        string memory jsonContent = string.concat(
            "{",
            '"name": "Olympus Deposit Position",',
            '"symbol": "ODP",',
            '"attributes": [',
            string.concat(
                '{"trait_type": "Position ID", "value": ',
                Strings.toString(positionId_),
                "},"
            ),
            string.concat(
                '{"trait_type": "Deposit Asset", "value": "',
                Strings.toHexString(position.asset),
                '"},'
            ),
            string.concat(
                '{"trait_type": "Deposit Period", "value": ',
                Strings.toString(position.periodMonths),
                "},"
            ),
            string.concat(
                '{"trait_type": "Expiry", "display_type": "date", "value": ',
                Strings.toString(position.expiry),
                "},"
            ),
            positionIsConvertible
                ? string.concat(
                    '{"trait_type": "Conversion Price", "value": ',
                    DecimalString.toDecimalString(
                        position.conversionPrice,
                        depositDecimals,
                        DISPLAY_DECIMALS
                    ),
                    "},"
                )
                : "",
            string.concat(
                '{"trait_type": "Remaining Deposit", "value": ',
                DecimalString.toDecimalString(
                    position.remainingDeposit,
                    depositDecimals,
                    DISPLAY_DECIMALS
                ),
                "}"
            ),
            "],",
            string.concat(
                '"image": "',
                "data:image/svg+xml;base64,",
                Base64.encode(bytes(_renderSVG(position, cdSymbol, positionIsConvertible))),
                '"'
            ),
            "}"
        );

        return string.concat("data:application/json;base64,", Base64.encode(bytes(jsonContent)));
    }

    // ========== INTERNAL FUNCTIONS ========== //

    function _getTimeString(uint48 time_) internal pure returns (string memory) {
        (string memory year, string memory month, string memory day) = Timestamp.toPaddedString(
            time_
        );

        return string.concat(year, "-", month, "-", day);
    }

    function _renderSVG(
        IDepositPositionManager.Position memory position_,
        string memory cdSymbol_,
        bool positionIsConvertible_
    ) internal view returns (string memory) {
        return
            string.concat(
                '<svg width="500" height="600" viewBox="0 0 500 600" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect width="500" height="600" rx="18" fill="#141722" /><rect x="73.0154" y="63" width="353" height="353" rx="176.5" fill="#708B96" /><path id="Vector" d="M233.546 286.32C213.544 281.175 197.413 260.273 197.413 236.476C197.413 209.142 221.287 185.346 249.676 185.346C280.002 185.989 301.617 209.464 301.617 236.476C301.617 260.273 287.1 278.924 265.807 286.32V317.513H324.523V294.36H284.196V298.862C310.65 290.501 324.845 263.81 324.845 236.476C324.845 195.958 291.939 162.193 249.676 162.193C208.705 162.193 174.185 195.958 174.185 236.476C174.185 263.81 188.057 289.214 213.544 299.183L214.189 294.36H174.185V317.513H233.546V286.32Z" fill="#EEE9E2" />',
                string.concat(
                    '<text xml:space="preserve" class="heading"><tspan x="32" y="465.504">',
                    positionIsConvertible_
                        ? string.concat(cdSymbol_, "-OHM Convertible Deposit")
                        : string.concat(cdSymbol_, " Yield-Bearing Deposit"),
                    "</tspan></text>"
                ),
                '<rect x="33" y="480" width="434" height="110" rx="9" fill="#2C2E37" />',
                string.concat(
                    '<text xml:space="preserve" class="standard-text"><tspan x="42" y="503">Expiry</tspan><tspan x="457" y="503" text-anchor="end">',
                    _getTimeString(position_.expiry),
                    "</tspan></text>"
                ),
                string.concat(
                    '<text xml:space="preserve" class="standard-text"><tspan x="42" y="527">Deposit Period</tspan><tspan x="457" y="527" text-anchor="end">',
                    Strings.toString(position_.periodMonths),
                    " months",
                    "</tspan></text>"
                ),
                string.concat(
                    '<text xml:space="preserve" class="standard-text"><tspan x="42" y="551">',
                    positionIsConvertible_ ? "Remaining Deposit" : "Deposit",
                    '</tspan><tspan x="457" y="551" text-anchor="end">',
                    DecimalString.toDecimalString(
                        position_.remainingDeposit,
                        ERC20(position_.asset).decimals(),
                        DISPLAY_DECIMALS
                    ),
                    " ",
                    cdSymbol_,
                    "</tspan></text>"
                ),
                positionIsConvertible_
                    ? string.concat(
                        '<text xml:space="preserve" class="standard-text"><tspan x="42" y="575">Convertible To</tspan><tspan x="457" y="575" text-anchor="end">',
                        DecimalString.toDecimalString(
                            FullMath.mulDiv(
                                position_.remainingDeposit,
                                10 ** OHM_DECIMALS,
                                position_.conversionPrice
                            ),
                            OHM_DECIMALS,
                            DISPLAY_DECIMALS
                        ),
                        " OHM</tspan></text>"
                    )
                    : "",
                '<defs><style type="text/css">.heading{fill:#F8CC82;font-family:"Helvetica Neue",Helvetica,-apple-system,BlinkMacSystemFont,Ubuntu,Jost,"DM Sans",sans-serif;font-size:24px;font-weight:500;letter-spacing:0em;white-space:pre;}.standard-text{fill:#EEE9E2;font-family:"Helvetica Neue",Helvetica,-apple-system,BlinkMacSystemFont,Ubuntu,Jost,"DM Sans",sans-serif;font-size:15px;font-weight:500;letter-spacing:0em;white-space:pre;}</style></defs></svg>'
            );
    }

    // ========== ERC165 SUPPORT ========== //

    function supportsInterface(bytes4 interfaceId_) external pure returns (bool) {
        return
            interfaceId_ == type(IERC165).interfaceId ||
            interfaceId_ == type(IPositionTokenRenderer).interfaceId;
    }
}
/// forge-lint: disable-end(mixed-case-function)
"
    },
    "src/modules/DEPOS/IPositionTokenRenderer.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;

/// @title  IPositionTokenRenderer
/// @notice Interface for a contract that can render token URIs for deposit positions
interface IPositionTokenRenderer {
    // ========== ERRORS ========== //

    error PositionTokenRenderer_InvalidAddress();

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

    /// @notice Renders the token URI for a given position
    /// @dev    This function should return a valid JSON metadata string that follows the ERC721 metadata standard
    ///
    /// @param  positionManager_ The address of the position manager contract
    /// @param  positionId_      The ID of the position to render
    /// @return uri              The token URI as a string
    function tokenURI(
        address positionManager_,
        uint256 positionId_
    ) external view returns (string memory uri);
}
"
    },
    "src/modules/DEPOS/IDepositPositionManager.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;

/// @title  IDepositPositionManager
/// @notice This interface defines the functions for the DEPOS module.
///         The objective of this module is to track the terms of a deposit position.
interface IDepositPositionManager {
    // ========== DATA STRUCTURES ========== //

    /// @notice Data structure for the terms of a deposit position
    ///
    /// @param  operator                Address of the operator/creator of the position
    /// @param  owner                   Address of the owner of the position
    /// @param  asset                   Address of the asset
    /// @param  periodMonths            The period of the deposit
    /// @param  remainingDeposit        Amount of reserve tokens remaining to be converted
    /// @param  conversionPrice         The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
    /// @param  expiry                  Timestamp of the position expiry
    /// @param  wrapped                 Whether the term is wrapped
    /// @param  additionalData          Additional data for the position
    struct Position {
        address operator;
        address owner;
        address asset;
        uint8 periodMonths;
        uint256 remainingDeposit;
        uint256 conversionPrice;
        uint48 expiry;
        bool wrapped;
        bytes additionalData;
    }

    /// @notice Parameters for the {mint} function
    ///
    /// @param  owner                   Address of the owner of the position
    /// @param  asset                   Address of the asset
    /// @param  periodMonths            The period of the deposit
    /// @param  remainingDeposit        Amount of reserve tokens remaining to be converted
    /// @param  conversionPrice         The amount of asset tokens required to receive 1 OHM (scale: asset token decimals)
    /// @param  expiry                  Timestamp of the position expiry
    /// @param  wrapPosition            Whether the position should be wrapped
    /// @param  additionalData          Additional data for the position
    struct MintParams {
        address owner;
        address asset;
        uint8 periodMonths;
        uint256 remainingDeposit;
        uint256 conversionPrice;
        uint48 expiry;
        bool wrapPosition;
        bytes additionalData;
    }

    // ========== EVENTS ========== //

    /// @notice Emitted when a position is created
    event PositionCreated(
        uint256 indexed positionId,
        address indexed owner,
        address indexed asset,
        uint8 periodMonths,
        uint256 remainingDeposit,
        uint256 conversionPrice,
        uint48 expiry,
        bool wrapped
    );

    /// @notice Emitted when a position's remaining deposit is updated
    event PositionRemainingDepositUpdated(uint256 indexed positionId, uint256 remainingDeposit);

    /// @notice Emitted when a position's additional data is updated
    event PositionAdditionalDataUpdated(uint256 indexed positionId, bytes additionalData);

    /// @notice Emitted when a position is split
    event PositionSplit(
        uint256 indexed positionId,
        uint256 indexed newPositionId,
        address indexed asset,
        uint8 periodMonths,
        uint256 amount,
        address to,
        bool wrap
    );

    /// @notice Emitted when a position is wrapped
    event PositionWrapped(uint256 indexed positionId);

    /// @notice Emitted when a position is unwrapped
    event PositionUnwrapped(uint256 indexed positionId);

    /// @notice Emitted when the token renderer is set
    event TokenRendererSet(address indexed renderer);

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

    /// @notice Error thrown when the caller is not the owner of the position
    error DEPOS_NotOwner(uint256 positionId_);

    /// @notice Error thrown when the caller is not the operator of the position
    error DEPOS_NotOperator(uint256 positionId_);

    /// @notice Error thrown when an invalid position ID is provided
    error DEPOS_InvalidPositionId(uint256 id_);

    /// @notice Error thrown when a position has already been wrapped
    error DEPOS_AlreadyWrapped(uint256 positionId_);

    /// @notice Error thrown when a position has not been wrapped
    error DEPOS_NotWrapped(uint256 positionId_);

    /// @notice Error thrown when an invalid parameter is provided
    error DEPOS_InvalidParams(string reason_);

    /// @notice Error thrown when a position does not support conversion
    error DEPOS_NotConvertible(uint256 positionId_);

    /// @notice Error thrown when the renderer contract does not implement the required interface
    error DEPOS_InvalidRenderer(address renderer_);

    // ========== WRAPPING ========== //

    /// @notice Wraps a position into an ERC721 token
    ///         This is useful if the position owner wants a tokenized representation of their position. It is functionally equivalent to the position itself.
    ///
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is the owner of the position
    ///         - Validate that the position is not already wrapped
    ///         - Mint an ERC721 token to the position owner
    ///
    /// @param  positionId_ The ID of the position to wrap
    function wrap(uint256 positionId_) external;

    /// @notice Unwraps/burns an ERC721 position token
    ///         This is useful if the position owner wants to unwrap their token back into the position.
    ///
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is the owner of the position
    ///         - Validate that the position is already wrapped
    ///         - Burn the ERC721 token
    ///
    /// @param  positionId_ The ID of the position to unwrap
    function unwrap(uint256 positionId_) external;

    // ========== POSITION MANAGEMENT =========== //

    /// @notice Creates a new deposit position
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is permissioned
    ///         - Validate that the owner is not the zero address
    ///         - Validate that the convertible deposit token is not the zero address
    ///         - Validate that the remaining deposit is greater than 0
    ///         - Validate that the conversion price is greater than 0
    ///         - Validate that the expiry is in the future
    ///         - Create the position record
    ///         - Wrap the position if requested
    ///
    /// @param  params_                     The parameters for the position creation
    /// @return _positionId                 The ID of the new position
    function mint(MintParams calldata params_) external returns (uint256 _positionId);

    /// @notice Updates the remaining deposit of a position
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is permissioned
    ///         - Validate that the position ID is valid
    ///         - Update the remaining deposit of the position
    ///
    /// @param  positionId_ The ID of the position to update
    /// @param  amount_     The new amount of the position
    function setRemainingDeposit(uint256 positionId_, uint256 amount_) external;

    /// @notice Updates the additional data of a position
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is permissioned
    ///         - Validate that the position ID is valid
    ///         - Update the additional data of the position
    ///
    /// @param  positionId_         The ID of the position to update
    /// @param  additionalData_     The new additional data of the position
    function setAdditionalData(uint256 positionId_, bytes calldata additionalData_) external;

    /// @notice Splits the specified amount of the position into a new position
    ///         This is useful if the position owner wants to split their position into multiple smaller positions.
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is the owner of the position
    ///         - Validate that the amount is greater than 0
    ///         - Validate that the amount is less than or equal to the remaining deposit
    ///         - Validate that `to_` is not the zero address
    ///         - Update the remaining deposit of the original position
    ///         - Create the new position record
    ///         - Wrap the new position if requested
    ///
    /// @param  positionId_     The ID of the position to split
    /// @param  amount_         The amount of the position to split
    /// @param  to_             The address to split the position to
    /// @param  wrap_           Whether the new position should be wrapped
    /// @return newPositionId   The ID of the new position
    function split(
        uint256 positionId_,
        uint256 amount_,
        address to_,
        bool wrap_
    ) external returns (uint256 newPositionId);

    // ========== POSITION INFORMATION ========== //

    /// @notice Get the total number of positions
    ///
    /// @return _count The total number of positions
    function getPositionCount() external view returns (uint256 _count);

    /// @notice Get the IDs of all positions for a given user
    ///
    /// @param  user_           The address of the user
    /// @return _positionIds    An array of position IDs
    function getUserPositionIds(
        address user_
    ) external view returns (uint256[] memory _positionIds);

    /// @notice Get the position for a given ID
    ///
    /// @param  positionId_ The ID of the position
    /// @return _position   The position for the given ID
    function getPosition(uint256 positionId_) external view returns (Position memory _position);

    /// @notice Check if a position is expired
    ///
    /// @param  positionId_ The ID of the position
    /// @return _expired    Whether the position is expired
    function isExpired(uint256 positionId_) external view returns (bool _expired);

    /// @notice Check if a position is convertible
    ///
    /// @param  positionId_     The ID of the position
    /// @return _convertible    Whether the position is convertible
    function isConvertible(uint256 positionId_) external view returns (bool _convertible);

    /// @notice Preview the amount of OHM that would be received for a given amount of convertible deposit tokens
    ///
    /// @param  positionId_ The ID of the position
    /// @param  amount_     The amount of convertible deposit tokens to convert
    /// @return _ohmOut     The amount of OHM that would be received
    function previewConvert(
        uint256 positionId_,
        uint256 amount_
    ) external view returns (uint256 _ohmOut);

    // ========== TOKEN URI RENDERER ========== //

    /// @notice Set the token renderer contract
    /// @dev    The implementing function should do the following:
    ///         - Validate that the caller is permissioned
    ///         - Validate that the renderer contract implements the required interface
    ///         - Set the renderer contract
    ///         - Emit an event
    ///
    /// @param  renderer_ The address of the renderer contract
    function setTokenRenderer(address renderer_) external;

    /// @notice Get the current token renderer contract
    ///
    /// @return _renderer The address of the current renderer contract (or zero address if not set)
    function getTokenRenderer() external view returns (address _renderer);
}
"
    },
    "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);
}
"
    },
    "dependencies/solmate-6.2.0/src/tokens/ERC20.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/utils/Strings.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SafeCast} from "./math/SafeCast.sol";
import {SignedMath} from "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    using SafeCast for *;

    bytes16 private constant HEX_DIGITS = "0123456789abcdef";
    uint8 private constant ADDRESS_LENGTH = 20;
    uint256 private constant SPECIAL_CHARS_LOOKUP =
        (1 << 0x08) | // backspace
            (1 << 0x09) | // tab
            (1 << 0x0a) | // newline
            (1 << 0x0c) | // form feed
            (1 << 0x0d) | // carriage return
            (1 << 0x22) | // double quote
            (1 << 0x5c); // backslash

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @dev The string being parsed contains characters that are not in scope of the given base.
     */
    error StringsInvalidChar();

    /**
     * @dev The string being parsed is not a properly formatted address.
     */
    error StringsInvalidAddressFormat();

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            assembly ("memory-safe") {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                assembly ("memory-safe") {
                    mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
     * representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal
     * representation, according to EIP-55.
     */
    function toChecksumHexString(address addr) internal pure returns (string memory) {
        bytes memory buffer = bytes(toHexString(addr));

        // hash the hex part of buffer (skip length + 2 bytes, length 40)
        uint256 hashValue;
        assembly ("memory-safe") {
            hashValue := shr(96, keccak256(add(buffer, 0x22), 40))
        }

        for (uint256 i = 41; i > 1; --i) {
            // possible values for buffer[i] are 48 (0) to 57 (9) and 97 (a) to 102 (f)
            if (hashValue & 0xf > 7 && uint8(buffer[i]) > 96) {
                // case shift by xoring with 0x20
                buffer[i] ^= 0x20;
            }
            hashValue >>= 4;
        }
        return string(buffer);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }

    /**
     * @dev Parse a decimal string and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input) internal pure returns (uint256) {
        return parseUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[0-9]*`
     * - The result must fit into an `uint256` type
     */
    function parseUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseUint-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        uint256 result = 0;
        for (uint256 i = begin; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 9) return (false, 0);
            result *= 10;
            result += chr;
        }
        return (true, result);
    }

    /**
     * @dev Parse a decimal string and returns the value as a `int256`.
     *
     * Requirements:
     * - The string must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input) internal pure returns (int256) {
        return parseInt(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseInt-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `[-+]?[0-9]*`
     * - The result must fit in an `int256` type.
     */
    function parseInt(string memory input, uint256 begin, uint256 end) internal pure returns (int256) {
        (bool success, int256 value) = tryParseInt(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseInt-string} that returns false if the parsing fails because of an invalid character or if
     * the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(string memory input) internal pure returns (bool success, int256 value) {
        return _tryParseIntUncheckedBounds(input, 0, bytes(input).length);
    }

    uint256 private constant ABS_MIN_INT256 = 2 ** 255;

    /**
     * @dev Variant of {parseInt-string-uint256-uint256} that returns false if the parsing fails because of an invalid
     * character or if the result does not fit in a `int256`.
     *
     * NOTE: This function will revert if the absolute value of the result does not fit in a `uint256`.
     */
    function tryParseInt(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, int256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseIntUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseInt-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseIntUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, int256 value) {
        bytes memory buffer = bytes(input);

        // Check presence of a negative sign.
        bytes1 sign = begin == end ? bytes1(0) : bytes1(_unsafeReadBytesOffset(buffer, begin)); // don't do out-of-bound (possibly unsafe) read if sub-string is empty
        bool positiveSign = sign == bytes1("+");
        bool negativeSign = sign == bytes1("-");
        uint256 offset = (positiveSign || negativeSign).toUint();

        (bool absSuccess, uint256 absValue) = tryParseUint(input, begin + offset, end);

        if (absSuccess && absValue < ABS_MIN_INT256) {
            return (true, negativeSign ? -int256(absValue) : int256(absValue));
        } else if (absSuccess && negativeSign && absValue == ABS_MIN_INT256) {
            return (true, type(int256).min);
        } else return (false, 0);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input) internal pure returns (uint256) {
        return parseHexUint(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]*`
     * - The result must fit in an `uint256` type.
     */
    function parseHexUint(string memory input, uint256 begin, uint256 end) internal pure returns (uint256) {
        (bool success, uint256 value) = tryParseHexUint(input, begin, end);
        if (!success) revert StringsInvalidChar();
        return value;
    }

    /**
     * @dev Variant of {parseHexUint-string} that returns false if the parsing fails because of an invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(string memory input) internal pure returns (bool success, uint256 value) {
        return _tryParseHexUintUncheckedBounds(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseHexUint-string-uint256-uint256} that returns false if the parsing fails because of an
     * invalid character.
     *
     * NOTE: This function will revert if the result does not fit in a `uint256`.
     */
    function tryParseHexUint(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, uint256 value) {
        if (end > bytes(input).length || begin > end) return (false, 0);
        return _tryParseHexUintUncheckedBounds(input, begin, end);
    }

    /**
     * @dev Implementation of {tryParseHexUint-string-uint256-uint256} that does not check bounds. Caller should make sure that
     * `begin <= end <= input.length`. Other inputs would result in undefined behavior.
     */
    function _tryParseHexUintUncheckedBounds(
        string memory input,
        uint256 begin,
        uint256 end
    ) private pure returns (bool success, uint256 value) {
        bytes memory buffer = bytes(input);

        // skip 0x prefix if present
        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
        uint256 offset = hasPrefix.toUint() * 2;

        uint256 result = 0;
        for (uint256 i = begin + offset; i < end; ++i) {
            uint8 chr = _tryParseChr(bytes1(_unsafeReadBytesOffset(buffer, i)));
            if (chr > 15) return (false, 0);
            result *= 16;
            unchecked {
                // Multiplying by 16 is equivalent to a shift of 4 bits (with additional overflow check).
                // This guarantees that adding a value < 16 will not cause an overflow, hence the unchecked.
                result += chr;
            }
        }
        return (true, result);
    }

    /**
     * @dev Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`.
     *
     * Requirements:
     * - The string must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input) internal pure returns (address) {
        return parseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string} that parses a substring of `input` located between position `begin` (included) and
     * `end` (excluded).
     *
     * Requirements:
     * - The substring must be formatted as `(0x)?[0-9a-fA-F]{40}`
     */
    function parseAddress(string memory input, uint256 begin, uint256 end) internal pure returns (address) {
        (bool success, address value) = tryParseAddress(input, begin, end);
        if (!success) revert StringsInvalidAddressFormat();
        return value;
    }

    /**
     * @dev Variant of {parseAddress-string} that returns false if the parsing fails because the input is not a properly
     * formatted address. See {parseAddress-string} requirements.
     */
    function tryParseAddress(string memory input) internal pure returns (bool success, address value) {
        return tryParseAddress(input, 0, bytes(input).length);
    }

    /**
     * @dev Variant of {parseAddress-string-uint256-uint256} that returns false if the parsing fails because input is not a properly
     * formatted address. See {parseAddress-string-uint256-uint256} requirements.
     */
    function tryParseAddress(
        string memory input,
        uint256 begin,
        uint256 end
    ) internal pure returns (bool success, address value) {
        if (end > bytes(input).length || begin > end) return (false, address(0));

        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
        uint256 expectedLength = 40 + hasPrefix.toUint() * 2;

        // check that input is the correct length
        if (end - begin == expectedLength) {
            // length guarantees that this does not overflow, and value is at most type(uint160).max
            (bool s, uint256 v) = _tryParseHexUintUncheckedBounds(input, begin, end);
            return (s, address(uint160(v)));
        } else {
            return (false, address(0));
        }
    }

    function _tryParseChr(bytes1 chr) private pure returns (uint8) {
        uint8 value = uint8(chr);

        // Try to parse `chr`:
        // - Case 1: [0-9]
        // - Case 2: [a-f]
        // - Case 3: [A-F]
        // - otherwise not supported
        unchecked {
            if (value > 47 && value < 58) value -= 48;
            else if (value > 96 && value < 103) value -= 87;
            else if (value > 64 && value < 71) value -= 55;
            else return type(uint8).max;
        }

        return value;
    }

    /**
     * @dev Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata.
     *
     * WARNING: This function should only be used in double quoted JSON strings. Single quotes are not escaped.
     *
     * NOTE: This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of
     * RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode
     * characters that are not in this range, but other tooling may provide different results.
     */
    function escapeJSON(string memory input) internal pure returns (string memory) {
        bytes memory buffer = bytes(input);
        bytes memory output = new bytes(2 * buffer.length); // worst case scenario
        uint256 outputLength = 0;

        for (uint256 i; i < buffer.length; ++i) {
            bytes1 char = bytes1(_unsafeReadBytesOffset(buffer, i));
            if (((SPECIAL_CHARS_LOOKUP & (1 << uint8(char))) != 0)) {
                output[outputLength++] = "\\";
                if (char == 0x08) output[outputLength++] = "b";
                else if (char == 0x09) output[outputLength++] = "t";
                else if (char == 0x0a) output[outputLength++] = "n";
                else if (char == 0x0c) output[outputLength++] = "f";
                else if (char == 0x0d) output[outputLength++] = "r";
                else if (char == 0x5c) output[outputLength++] = "\\";
                else if (char == 0x22) {
                    // solhint-disable-next-line quotes
                    output[outputLength++] = '"';
                }
            } else {
                output[outputLength++] = char;
            }
        }
        // write the actual length and deallocate unused memory
        assembly ("memory-safe") {
            mstore(output, outputLength)
            mstore(0x40, add(output, shl(5, shr(5, add(outputLength, 63)))))
        }

        return string(output);
    }

    /**
     * @dev Reads a bytes32 from a bytes array without bounds checking.
     *
     * NOTE: making this function internal would mean it could be used with memory unsafe offset, and marking the
     * assembly block as such would prevent some optimizations.
     */
    function _unsafeReadBytesOffset(bytes memory buffer, uint256 offset) private pure returns (bytes32 value) {
        // This is not memory safe in the general case, but all calls to this private function are within bounds.
        assembly ("memory-safe") {
            value := mload(add(buffer, add(0x20, offset)))
        }
    }
}
"
    },
    "dependencies/openzeppelin-new-5.3.0/contracts/utils/Base64.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Base64.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     * See sections 4 and 5 of https://datatracker.ietf.org/doc/html/rfc4648
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    string internal constant _TABLE_URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        return _encode(data, _TABLE, true);
    }

    /**
     * @dev Converts a `bytes` to its Bytes64Url `string` representation.
     * Output is not padded with `=` as specified in https://www.rfc-editor.org/rfc/rfc4648[rfc4648].
     */
    function encodeURL(bytes memory data) internal pure returns (string memory) {
        return _encode(data, _TABLE_URL, false);
    }

    /**
     * @dev Internal table-agnostic conversion
     */
    function _encode(bytes memory data, string memory table, bool withPadding) private pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // If padding is enabled, the final length should be `bytes` data length divided by 3 rounded up and then
        // multiplied by 4 so that it leaves room for padding the last chunk
        // - `data.length + 2`  -> Prepare for division rounding up
        // - `/ 3`              -> Number of 3-bytes chunks (rounded up)
        // - `4 *`              -> 4 characters for each chunk
        // This is equivalent to: 4 * Math.ceil(data.length / 3)
        //
        // If padding is disabled, the final length should be `bytes` data length multiplied by 4/3 rounded up as
        // opposed to when padding is required to fill the last chunk.
        // - `4 * data.length`  -> 4 characters for each chunk
        // - ` + 2`             -> Prepare for division rounding up
        // - `/ 3`              -> Number of 3-bytes chunks (rounded up)
        // This is equivalent to: Math.ceil((4 * data.length) / 3)
        uint256 resultLength = withPadding ? 4 * ((data.length + 2) / 3) : (4 * data.length + 2) / 3;

        string memory result = new string(resultLength);

        assembly ("memory-safe") {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 0x20)
            let dataPtr := data
            let endPtr := add(data, mload(data))

            // In some cases, the last iteration will read bytes after the end of the data. We cache the value, and
            // set it to zero to make sure no dirty bytes are read in that section.
            let afterPtr := add(endPtr, 0x20)
            let afterCache := mload(afterPtr)
            mstore(afterPtr, 0x00)

            // Run over the input, 3 bytes at a time
            for {

            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 byte (24 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F to bitmask the least significant 6 bits.
                // Use this as an index into the lookup table, mload an entire word
                // so the desired character is in the least significant byte, and
                // mstore8 this least significant byte into the result and continue.

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // Reset the value that was cached
            mstore(afterPtr, afterCache)

            if withPadding {
                // When data `bytes` is not exactly 3 bytes long
                // it is padded with `=` characters at the end
                switch mod(mload(data), 3)
                case 1 {
                    mstore8(sub(resultPtr, 1), 0x3d)
                    mstore8(sub(resultPtr, 2), 0x3d)
                }
                case 2 {
                    mstore8(sub(resultPtr, 1), 0x3d)
                }
            }
        }

        return result;
    }
}
"
    },
    "src/libraries/Timestamp.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;

import {uint2str} from "./Uint2Str.sol";

library Timestamp {
    /// @notice Convert a timestamp to a padded string of the form "YYYY-MM-DD"
    /// @dev    This has only been tested up to year 2345.
    ///
    /// @param timestamp    The timestamp to convert
    /// @return year        Year as a zero-padded string
    /// @return month       Month as a zero-padded string
    /// @return day         Day as a zero-padded string
    function toPaddedString(
        uint48 timestamp
    ) internal pure returns (string memory, string memory, string memory) {
        // Convert a number of days into a human-readable date, courtesy of BokkyPooBah.
        // Source: https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary/blob/master/contracts/BokkyPooBahsDateTimeLibrary.sol

        uint256 year;
        uint256 month;
        uint256 day;
        {
            int256 __days = int256(uint256(timestamp) / 1 days);

            int256 num1 = __days + 68_569 + 2_440_588; // 2440588 = OFFSET19700101
            int256 num2 = (4 * num1) / 146_097;
            num1 = num1 - (146_097 * num2 + 3) / 4;
            int256 _year = (4000 * (num1 + 1)) / 1_461_001;
            num1 = num1 - (1461 * _year) / 4 + 31;
            int256 _month = (80 * num1) / 2447;
            int256 _day = num1 - (2447 * _month) / 80;
            num1 = _month / 11;
            _month = _month + 2 - 12 * num1;
            _year = 100 * (num2 - 49) + _year + num1;

            /// forge-lint: disable-start(unsafe-typecast)
            year = uint256(_year);
            month = uint256(_month);
            day = uint256(_day);
            /// forge-lint: disable-end(unsafe-typecast)
        }

        string memory yearStr = uint2str(year % 10_000);
        string memory monthStr = month < 10
            ? string(abi.encodePacked("0", uint2str(month)))
            : uint2str(month);
        string memory dayStr = day < 10
            ? string(abi.encodePacked("0", uint2str(day)))
            : uint2str(day);

        return (yearStr, monthStr, dayStr);
    }
}
"
    },
    "src/libraries/DecimalString.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8;

import {uint2str} from "./Uint2Str.sol";

library DecimalString {
    /// @notice Converts a uint256 value to a string with a specified number of decimal places.
    ///         The value is adjusted by the scale factor and then formatted to the specified number of decimal places.
    ///         The decimal places are not zero-padded, so the result is not always the same length.
    /// @dev    This is inspired by code in [FixedStrikeOptionTeller](https://github.com/Bond-Protocol/option-contracts/blob/b8ce2ca2bae3bd06f0e7665c3aa8d827e4d8ca2c/src/fixed-strike/FixedStrikeOptionTeller.sol#L722).
    ///
    /// @param  value_            The uint256 value to convert to a string.
    /// @param  valueDecimals_    The scale factor of the value.
    /// @param  decimalPlaces_    The number of decimal places to format the value to.
    /// @return result            A string representation of the value with the specified number of decimal places.
    function toDecimalString(
        uint256 value_,
        uint8 valueDecimals_,
        uint8 decimalPlaces_
    ) internal pure returns (string memory) {
        // Handle zero case
        if (value_ == 0) return "0";

        // Convert the entire number to string first
        string memory str = uint2str(value_);
        bytes memory bStr = bytes(str);

        // If no decimal places requested, just handle the scaling and return
        if (decimalPlaces_ == 0) {
            if (bStr.length <= valueDecimals_) return "0";
            return uint2str(value_ / (10 ** valueDecimals_));
        }

        // If value is a whole number, return as-is
        if (valueDecimals_ == 0) return str;

        // Calculate decimal places to show (limited by request and available decimals)
        uint256 maxDecimalPlaces = valueDecimals_ > decimalPlaces_
            ? decimalPlaces_
            : valueDecimals_;

        // Handle numbers smaller than 1
        if (bStr.length <= valueDecimals_) {
            bytes memory smallResult = new bytes(2 + maxDecimalPlaces);
            smallResult[0] = "0";
            smallResult[1] = ".";

            uint256 leadingZeros = valueDecimals_ - bStr.length;
            uint256 zerosToAdd = leadingZeros > maxDecimalPlaces ? maxDecimalPlaces : leadingZeros;

            // Add leading zeros after decimal
            for (uint256 i = 0; i < zerosToAdd; i++) {
                smallResult[i + 2] = "0";
            }

            // Add available digits
            for (uint256 i = 0; i < maxDecimalPlaces - zerosToAdd && i < bStr.length; i++) {
                smallResult[i + 2 + zerosToAdd] = bStr[i];
            }

            return string(smallResult);
        }

        // Find decimal position and last significant digit
        uint256 decimalPosition = bStr.length - valueDecimals_;
        uint256 lastNonZeroPos = decimalPosition;
        for (uint256 i = 0; i < maxDecimalPlaces && i + decimalPosition < bStr.length; i++) {
            if (bStr[decimalPosition + i] != "0") {
                lastNonZeroPos = decimalPosition + i + 1;
            }
        }

        // Create and populate result
        bytes memory finalResult = new bytes(
            lastNonZeroPos - decimalPosition > 0 ? lastNonZeroPos + 1 : lastNonZeroPos
        );

        for (uint256 i = 0; i < decimalPosition; i++) {
            finalResult[i] = bStr[i];
        }

        if (lastNonZeroPos > decimalPosition) {
            finalResult[decimalPosition] = ".";
            for (uint256 i = 0; i < lastNonZeroPos - decimalPosition; i++) {
                finalResult[decimalPosition + 1 + i] = bStr[decimalPosition + i];
            }
        }

        return string(finalResult);
    }
}
"
    },
    "src/libraries/FullMath.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then use the Chinese Remainder Theorem to reconstruct
            // the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2**256 + prod0
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division
            if (prod1 == 0) {
                require(denominator > 0);
                assembly {
                    result := div(prod0, denominator)
                }
                return result;
            }

            // Make sure the result is less than 2**256.
            // Also prevents denominator == 0
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0]
            // Compute remainder using mulmod
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
            }
            // Subtract 256 bit number from 512 bit number
            assembly {
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator
            // Compute largest power of two divisor of denominator.
            // Always >= 1.
            uint256 twos = (type(uint256).max - denominator + 1) & denominator;
            // Divide denominator by power of two
            assembly {
                denominator := div(denominator, twos)
            }

            // Divide [prod1 prod0] by the factors of two
            assembly {
                prod0 := div(prod0, twos)
            }
            // Shift 

Tags:
ERC165, Governance, Voting, Factory, Oracle|addr:0x9c859dc91db65bd7375660341231227336daa134|verified:true|block:23747526|tx:0xea02f220e6c0f92a9c924ccf388e0565eda40b4f69681bf02d597abcd1b73699|first_check:1762528465

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

Comments

Log in to comment.

No comments yet.