TermMaxFactoryV2

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": {
    "contracts/v2/factory/TermMaxFactoryV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {GearingTokenWithERC20V2} from "../tokens/GearingTokenWithERC20V2.sol";
import {MarketInitialParams} from "../../v1/storage/TermMaxStorage.sol";
import {FactoryErrors} from "../../v1/errors/FactoryErrors.sol";
import {FactoryEvents} from "../../v1/events/FactoryEvents.sol";
import {ITermMaxMarket} from "../../v1/ITermMaxMarket.sol";
import {ITermMaxFactory} from "../../v1/factory/ITermMaxFactory.sol";
import {FactoryEventsV2} from "../events/FactoryEventsV2.sol";
import {VersionV2} from "../VersionV2.sol";

/**
 * @title TermMax Factory V2
 * @author Term Structure Labs
 * @notice Factory contract for creating TermMax V2 markets with enhanced functionality
 * @dev Manages market deployment, gearing token implementations, and market configuration validation
 * Inherits from V1 factory interface while adding V2-specific features for improved market creation
 */
contract TermMaxFactoryV2 is Ownable2Step, ITermMaxFactory, FactoryEventsV2, VersionV2 {
    /// @notice Constant key for the default ERC20 gearing token implementation
    bytes32 constant GT_ERC20 = keccak256("GearingTokenWithERC20");

    /// @notice The implementation of TermMax Market contract used as template for cloning
    /// @dev This is set once during construction and cannot be changed
    address public immutable TERMMAX_MARKET_IMPLEMENTATION;

    /// @notice Mapping of gearing token implementation names to their contract addresses
    /// @dev Based on the abstract GearingToken contract, different GearingTokens can be adapted
    /// to various collaterals, such as ERC20 tokens and ERC721 tokens
    /// @dev Keys are keccak256 hashes of implementation names for gas efficiency
    mapping(bytes32 => address) public gtImplements;

    /**
     * @notice Constructs the TermMax Factory V2 with initial configurations
     * @dev Sets up the factory with a market implementation and deploys the default ERC20 gearing token
     * @param admin The address that will have administrative privileges over the factory
     * @param TERMMAX_MARKET_IMPLEMENTATION_ The address of the TermMax market implementation contract
     * @custom:security Only the admin can create markets and manage gearing token implementations
     */
    constructor(address admin, address TERMMAX_MARKET_IMPLEMENTATION_) Ownable(admin) {
        if (TERMMAX_MARKET_IMPLEMENTATION_ == address(0)) {
            revert FactoryErrors.InvalidImplementation();
        }
        TERMMAX_MARKET_IMPLEMENTATION = TERMMAX_MARKET_IMPLEMENTATION_;

        // Deploy and register the default ERC20 gearing token implementation
        gtImplements[GT_ERC20] = address(new GearingTokenWithERC20V2());
    }

    /**
     * @notice Registers a new gearing token implementation with a given name
     * @dev Allows the factory to support different types of gearing tokens for various collateral types
     * @param gtImplementName The string name of the gearing token implementation
     * @param gtImplement The contract address of the gearing token implementation
     * @custom:access Only the factory owner can register new implementations
     * @custom:events Emits SetGtImplement event for tracking implementation changes
     */
    function setGtImplement(string memory gtImplementName, address gtImplement) external onlyOwner {
        bytes32 key = keccak256(abi.encodePacked(gtImplementName));
        gtImplements[key] = gtImplement;
        emit FactoryEvents.SetGtImplement(key, gtImplement);
    }

    /**
     * @notice Predicts the address where a market will be deployed before actual creation
     * @dev Uses CREATE2 deterministic deployment to calculate the future market address
     * @param deployer The address that will deploy the market (msg.sender during createMarket)
     * @param collateral The address of the collateral token for the market
     * @param debtToken The address of the debt token for the market
     * @param maturity The maturity timestamp of the market
     * @param salt Additional salt value for address generation uniqueness
     * @return market The predicted address where the market will be deployed
     * @custom:view This is a view function that doesn't modify state
     */
    function predictMarketAddress(
        address deployer,
        address collateral,
        address debtToken,
        uint64 maturity,
        uint256 salt
    ) external view returns (address market) {
        return Clones.predictDeterministicAddress(
            TERMMAX_MARKET_IMPLEMENTATION, keccak256(abi.encode(deployer, collateral, debtToken, maturity, salt))
        );
    }

    /**
     * @notice Creates a new TermMax market with specified parameters
     * @dev Clones the market implementation and initializes it with the provided parameters
     * @param gtKey The key identifying which gearing token implementation to use
     * @param params The initial parameters for market configuration including collateral, debt token, and settings
     * @param salt Additional entropy for deterministic address generation
     * @return market The address of the newly created market contract
     * @custom:access Only the factory owner can create new markets
     * @custom:validation Validates that the requested gearing token implementation exists
     * @custom:events Emits CreateMarket event with market details for indexing and monitoring
     */
    function createMarket(bytes32 gtKey, MarketInitialParams memory params, uint256 salt)
        external
        onlyOwner
        returns (address market)
    {
        // Retrieve the gearing token implementation for the requested key
        params.gtImplementation = gtImplements[gtKey];
        if (params.gtImplementation == address(0)) {
            revert FactoryErrors.CantNotFindGtImplementation();
        }

        // Deploy market using CREATE2 for deterministic addressing
        market = Clones.cloneDeterministic(
            TERMMAX_MARKET_IMPLEMENTATION,
            keccak256(abi.encode(msg.sender, params.collateral, params.debtToken, params.marketConfig.maturity, salt))
        );

        // Initialize the newly deployed market with provided parameters
        ITermMaxMarket(market).initialize(params);

        // Emit event for market creation tracking
        emit FactoryEventsV2.MarketCreated(market, params.collateral, params.debtToken, params);
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-5.2.0/access/Ownable2Step.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {Ownable} from "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        return _pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-5.2.0/proxy/Clones.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

import {Create2} from "../utils/Create2.sol";
import {Errors} from "../utils/Errors.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    error CloneArgumentsTooLong();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        return clone(implementation, 0);
    }

    /**
     * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
     * to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function clone(address implementation, uint256 value) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(value, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple times will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        return cloneDeterministic(implementation, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
     * a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministic(
        address implementation,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(value, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create opcode, which should never revert.
     */
    function cloneWithImmutableArgs(address implementation, bytes memory args) internal returns (address instance) {
        return cloneWithImmutableArgs(implementation, args, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneWithImmutableArgs-address-bytes-}[cloneWithImmutableArgs], but with a `value`
     * parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneWithImmutableArgs(
        address implementation,
        bytes memory args,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        assembly ("memory-safe") {
            instance := create(value, add(bytecode, 0x20), mload(bytecode))
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation` with custom
     * immutable arguments. These are provided through `args` and cannot be changed after deployment. To
     * access the arguments within the implementation, use {fetchCloneArgs}.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same
     * `implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice
     * at the same address.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal returns (address instance) {
        return cloneDeterministicWithImmutableArgs(implementation, args, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-}[cloneDeterministicWithImmutableArgs],
     * but with a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministicWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.deploy(value, salt, bytecode);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        bytes memory bytecode = _cloneCodeWithImmutableArgs(implementation, args);
        return Create2.computeAddress(salt, keccak256(bytecode), deployer);
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministicWithImmutableArgs}.
     */
    function predictDeterministicAddressWithImmutableArgs(
        address implementation,
        bytes memory args,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddressWithImmutableArgs(implementation, args, salt, address(this));
    }

    /**
     * @dev Get the immutable args attached to a clone.
     *
     * - If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this
     *   function will return an empty array.
     * - If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or
     *   `cloneDeterministicWithImmutableArgs`, this function will return the args array used at
     *   creation.
     * - If `instance` is NOT a clone deployed using this library, the behavior is undefined. This
     *   function should only be used to check addresses that are known to be clones.
     */
    function fetchCloneArgs(address instance) internal view returns (bytes memory) {
        bytes memory result = new bytes(instance.code.length - 45); // revert if length is too short
        assembly ("memory-safe") {
            extcodecopy(instance, add(result, 32), 45, mload(result))
        }
        return result;
    }

    /**
     * @dev Helper that prepares the initcode of the proxy with immutable args.
     *
     * An assembly variant of this function requires copying the `args` array, which can be efficiently done using
     * `mcopy`. Unfortunately, that opcode is not available before cancun. A pure solidity implementation using
     * abi.encodePacked is more expensive but also more portable and easier to review.
     *
     * NOTE: https://eips.ethereum.org/EIPS/eip-170[EIP-170] limits the length of the contract code to 24576 bytes.
     * With the proxy code taking 45 bytes, that limits the length of the immutable args to 24531 bytes.
     */
    function _cloneCodeWithImmutableArgs(
        address implementation,
        bytes memory args
    ) private pure returns (bytes memory) {
        if (args.length > 24531) revert CloneArgumentsTooLong();
        return
            abi.encodePacked(
                hex"61",
                uint16(args.length + 45),
                hex"3d81600a3d39f3363d3d373d3d3d363d73",
                implementation,
                hex"5af43d82803e903d91602b57fd5bf3",
                args
            );
    }
}
"
    },
    "contracts/v2/tokens/GearingTokenWithERC20V2.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "./AbstractGearingTokenV2.sol";

/**
 * @title TermMax Gearing Token, using ERC20 token as collateral
 * @author Term Structure Labs
 */
contract GearingTokenWithERC20V2 is AbstractGearingTokenV2 {
    using SafeCast for uint256;
    using SafeCast for int256;
    using TransferUtils for IERC20;
    using TransferUtils for IERC20Metadata;
    using Math for *;

    /// @notice The operation failed because the collateral capacity is exceeded
    error CollateralCapacityExceeded();

    /// @notice The operation failed because the amount can not be uint256 max
    error AmountCanNotBeUint256Max();

    /// @notice Emitted when the collateral capacity is updated
    event CollateralCapacityUpdated(uint256 newCapacity);

    /// @notice The max capacity of collateral token
    uint256 public collateralCapacity;

    uint256 private collateralDenominator;

    constructor() {
        _disableInitializers();
    }

    function __GearingToken_Implement_init(bytes memory initalParams) internal override onlyInitializing {
        _updateConfig(initalParams);
        collateralDenominator = 10 ** IERC20Metadata(_config.collateral).decimals();
    }

    function _updateConfig(bytes memory configData) internal virtual override {
        collateralCapacity = abi.decode(configData, (uint256));
        emit CollateralCapacityUpdated(collateralCapacity);
    }

    function _checkBeforeMint(uint128, bytes memory collateralData) internal virtual override {
        if (IERC20(_config.collateral).balanceOf(address(this)) + _decodeAmount(collateralData) > collateralCapacity) {
            revert CollateralCapacityExceeded();
        }
    }

    function _delivery(uint256 proportion) internal view virtual override returns (bytes memory deliveryData) {
        uint256 collateralReserve = IERC20(_config.collateral).balanceOf(address(this));
        uint256 amount = collateralReserve.mulDiv(proportion, Constants.DECIMAL_BASE_SQ);
        deliveryData = abi.encode(amount);
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _mergeCollateral(bytes memory collateralDataA, bytes memory collateralDataB)
        internal
        virtual
        override
        returns (bytes memory collateralData)
    {
        uint256 total = _decodeAmount(collateralDataA) + _decodeAmount(collateralDataB);
        collateralData = abi.encode(total);
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _transferCollateralFrom(address from, address to, bytes memory collateralData) internal virtual override {
        uint256 amount = _decodeAmount(collateralData);
        if (amount != 0) {
            IERC20(_config.collateral).safeTransferFrom(from, to, amount);
        }
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _transferCollateral(address to, bytes memory collateralData) internal virtual override {
        uint256 amount = _decodeAmount(collateralData);
        if (amount != 0) {
            IERC20(_config.collateral).safeTransfer(to, amount);
        }
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _getCollateralValue(bytes memory collateralData, bytes memory priceData)
        internal
        view
        virtual
        override
        returns (uint256)
    {
        uint256 collateralAmt = _decodeAmount(collateralData);
        (uint256 price, uint256 priceDenominator, uint256 collateralDemonimator) =
            abi.decode(priceData, (uint256, uint256, uint256));
        return collateralAmt.mulDiv(price * Constants.DECIMAL_BASE, priceDenominator * collateralDemonimator);
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _getCollateralPriceData(GtConfig memory config)
        internal
        view
        virtual
        override
        returns (bytes memory priceData)
    {
        (uint256 price, uint8 decimals) = config.loanConfig.oracle.getPrice(config.collateral);
        uint256 priceDenominator = 10 ** decimals;

        priceData = abi.encode(price, priceDenominator, collateralDenominator);
    }

    /// @notice Encode amount to collateral data
    function _decodeAmount(bytes memory collateralData) internal pure returns (uint256 amount) {
        amount = abi.decode(collateralData, (uint256));
        if (amount == type(uint256).max) {
            revert AmountCanNotBeUint256Max();
        }
    }

    /// @notice Decode amount from collateral data
    function _encodeAmount(uint256 amount) internal pure returns (bytes memory) {
        return abi.encode(amount);
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _removeCollateral(LoanInfo memory loan, bytes memory collateralData)
        internal
        virtual
        override
        returns (bytes memory)
    {
        uint256 amount = _decodeAmount(loan.collateralData) - _decodeAmount(collateralData);
        return _encodeAmount(amount);
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _addCollateral(LoanInfo memory loan, bytes memory collateralData)
        internal
        virtual
        override
        returns (bytes memory)
    {
        uint256 amount = _decodeAmount(loan.collateralData) + _decodeAmount(collateralData);
        return _encodeAmount(amount);
    }

    /**
     * @inheritdoc AbstractGearingTokenV2
     */
    function _calcLiquidationResult(LoanInfo memory loan, uint128 repayAmt, ValueAndPrice memory valueAndPrice)
        internal
        virtual
        override
        returns (bytes memory, bytes memory, bytes memory)
    {
        uint256 collateralAmt = _decodeAmount(loan.collateralData);

        uint256 removedCollateralAmt;
        uint256 cEqualRepayAmt;
        uint256 rewardToLiquidator;
        uint256 rewardToProtocol;

        if (loan.debtAmt != 0) {
            (uint256 collateralPrice, uint256 cPriceDenominator, uint256 cTokenDenominator) =
                abi.decode(valueAndPrice.collateralPriceData, (uint256, uint256, uint256));

            /* DP := debt token price (valueAndPrice.debtPrice)
             * DPD := debt token price decimal (valueAndPrice.priceDenominator)
             * CP := collateral token price (collateralPrice)
             * CPD := collateral token price decimal (cPriceDenominator)
             * liquidate value = repayAmt * DP / debt token decimals
             * collateral amount to remove = liquidate value * collateral decimals * cpd / (CP * DPD)
             */
            uint256 liquidateValueInPriceScale = repayAmt.mulDiv(valueAndPrice.debtPrice, valueAndPrice.debtDenominator);

            cEqualRepayAmt = liquidateValueInPriceScale.mulDiv(
                cPriceDenominator * cTokenDenominator, collateralPrice * valueAndPrice.priceDenominator
            );

            rewardToLiquidator =
                cEqualRepayAmt.mulDiv(GearingTokenConstants.REWARD_TO_LIQUIDATOR, Constants.DECIMAL_BASE);
            rewardToProtocol = cEqualRepayAmt.mulDiv(GearingTokenConstants.REWARD_TO_PROTOCOL, Constants.DECIMAL_BASE);

            removedCollateralAmt = cEqualRepayAmt + rewardToLiquidator + rewardToProtocol;
            removedCollateralAmt = removedCollateralAmt.min(collateralAmt.mulDiv(repayAmt, loan.debtAmt));
        }
        uint256 cToLiquidatorAmount = removedCollateralAmt.min(cEqualRepayAmt + rewardToLiquidator);
        removedCollateralAmt -= cToLiquidatorAmount;
        uint256 cToTreasurerAmount = removedCollateralAmt.min(rewardToProtocol);
        uint256 remainingCollateralAmt = collateralAmt - cToLiquidatorAmount - cToTreasurerAmount;
        return (abi.encode(cToLiquidatorAmount), abi.encode(cToTreasurerAmount), abi.encode(remainingCollateralAmt));
    }
}
"
    },
    "contracts/v1/storage/TermMaxStorage.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IOracle} from "../oracle/IOracle.sol";
import {ISwapCallback} from "../ISwapCallback.sol";

/**
 * @title The data struct of token pair
 * @author Term Structure Labs
 */
struct CurveCut {
    uint256 xtReserve;
    uint256 liqSquare;
    int256 offset;
}

struct FeeConfig {
    /// @notice The lending fee ratio taker
    ///         i.e. 0.01e8 means 1%
    uint32 lendTakerFeeRatio;
    /// @notice The lending fee ratio for maker
    ///         i.e. 0.01e8 means 1%
    uint32 lendMakerFeeRatio;
    /// @notice The borrowing fee ratio for taker
    ///         i.e. 0.01e8 means 1%
    uint32 borrowTakerFeeRatio;
    /// @notice The borrowing fee ratio for maker
    ///         i.e. 0.01e8 means 1%
    uint32 borrowMakerFeeRatio;
    /// @notice The fee ratio when minting GT tokens by collateral
    ///         i.e. 0.01e8 means 1%
    uint32 mintGtFeeRatio;
    /// @notice The fee ref when minting GT tokens by collateral
    ///         i.e. 0.01e8 means 1%
    uint32 mintGtFeeRef;
}

struct CurveCuts {
    /// @notice The curve cuts of the market to lend
    CurveCut[] lendCurveCuts;
    /// @notice The curve cuts of the market to borrow
    CurveCut[] borrowCurveCuts;
}

struct MarketConfig {
    /// @notice The treasurer's address, which will receive protocol fee
    address treasurer;
    /// @notice The unix time of maturity date
    uint64 maturity;
    /// @notice The fee ratio when tradings with the market and orders
    FeeConfig feeConfig;
}

struct LoanConfig {
    /// @notice The oracle aggregator
    IOracle oracle;
    /// @notice The debt liquidation threshold
    ///         If the loan to collateral is greater than or equal to this value,
    ///         it will be liquidated
    ///         i.e. 0.9e8 means debt value is the 90% of collateral value
    uint32 liquidationLtv;
    /// @notice Maximum loan to collateral when borrowing
    ///         i.e. 0.85e8 means debt value is the 85% of collateral value
    uint32 maxLtv;
    /// @notice The flag to indicate debt is liquidatable or not
    /// @dev    If liquidatable is false, the collateral can only be delivered after maturity
    bool liquidatable;
}

/// @notice Data of Gearing Token's configuturation
struct GtConfig {
    /// @notice The address of collateral token
    address collateral;
    /// @notice The debtToken(debt) token
    IERC20Metadata debtToken;
    /// @notice The bond token
    IERC20 ft;
    /// @notice The treasurer's address, which will receive protocol reward while liquidation
    address treasurer;
    /// @notice The unix time of maturity date
    uint64 maturity;
    /// @notice The configuration of oracle, ltv and liquidation
    LoanConfig loanConfig;
}

struct OrderConfig {
    CurveCuts curveCuts;
    uint256 gtId;
    uint256 maxXtReserve;
    ISwapCallback swapTrigger;
    FeeConfig feeConfig;
}

struct MarketInitialParams {
    /// @notice The address of collateral token
    address collateral;
    /// @notice The debtToken(debt) token
    IERC20Metadata debtToken;
    /// @notice The admin address
    address admin;
    /// @notice The implementation of TermMax Gearing Token contract
    address gtImplementation;
    /// @notice The configuration of market
    MarketConfig marketConfig;
    /// @notice The configuration of loan
    LoanConfig loanConfig;
    /// @notice The encoded parameters to initialize GT implementation contract
    bytes gtInitalParams;
    string tokenName;
    string tokenSymbol;
}

struct VaultInitialParams {
    address admin;
    address curator;
    uint256 timelock;
    IERC20 asset;
    uint256 maxCapacity;
    string name;
    string symbol;
    uint64 performanceFeeRate;
}
"
    },
    "contracts/v1/errors/FactoryErrors.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title Factory Errors Interface
 * @notice Custom errors for the TermMax factory operations
 */
interface FactoryErrors {
    /**
     * @notice Error thrown when attempting to initialize with an invalid implementation
     */
    error InvalidImplementation();

    /**
     * @notice Error thrown when a requested Gearing Token implementation cannot be found
     * @dev This occurs when trying to use a GT implementation that hasn't been registered
     */
    error CantNotFindGtImplementation();
}
"
    },
    "contracts/v1/events/FactoryEvents.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {VaultInitialParams} from "../storage/TermMaxStorage.sol";

/**
 * @title Factory Events Interface
 * @notice Events emitted by the TermMax factory contracts
 */
interface FactoryEvents {
    /**
     * @notice Emitted when a new Gearing Token implementation is set
     * @param key The unique identifier for the GT implementation
     * @param gtImplement The address of the GT implementation contract
     */
    event SetGtImplement(bytes32 key, address gtImplement);

    /**
     * @notice Emitted when a new market is created
     * @param market The address of the newly created market
     * @param collateral The address of the collateral token
     * @param debtToken The debt token interface
     */
    event CreateMarket(address indexed market, address indexed collateral, IERC20 indexed debtToken);

    /**
     * @notice Emitted when a new vault is created
     * @param vault The address of the newly created vault
     * @param creator The address of the vault creator
     * @param initialParams The initial parameters used to configure the vault
     */
    event CreateVault(address indexed vault, address indexed creator, VaultInitialParams indexed initialParams);
}
"
    },
    "contracts/v1/ITermMaxMarket.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IMintableERC20, IERC20} from "./tokens/IMintableERC20.sol";
import {IGearingToken} from "./tokens/IGearingToken.sol";
import {ITermMaxOrder} from "./ITermMaxOrder.sol";
import {MarketConfig, MarketInitialParams, CurveCuts, FeeConfig} from "./storage/TermMaxStorage.sol";
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {ISwapCallback} from "./ISwapCallback.sol";

/**
 * @title TermMax Market interface
 * @author Term Structure Labs
 */
interface ITermMaxMarket {
    /// @notice Initialize the token and configuration of the market
    function initialize(MarketInitialParams memory params) external;

    /// @notice Return the configuration
    function config() external view returns (MarketConfig memory);

    /// @notice Set the market configuration
    function updateMarketConfig(MarketConfig calldata newConfig) external;

    /// @notice Return the tokens in TermMax Market
    /// @return ft Fixed-rate Token(bond token). Earning Fixed Income with High Certainty
    /// @return xt Intermediary Token for Collateralization and Leveragin
    /// @return gt Gearing Token
    /// @return collateral Collateral token
    /// @return underlying Underlying Token(debt)
    function tokens()
        external
        view
        returns (IMintableERC20 ft, IMintableERC20 xt, IGearingToken gt, address collateral, IERC20 underlying);

    /// @notice Mint FT and XT tokens by underlying token.
    ///         No price slippage or handling fees.
    /// @param debtTokenAmt Amount of underlying token want to lock
    function mint(address recipient, uint256 debtTokenAmt) external;

    /// @notice Burn FT and XT to get underlying token.
    ///         No price slippage or handling fees.
    /// @param debtTokenAmt Amount of underlying token want to get
    function burn(address recipient, uint256 debtTokenAmt) external;

    /// @notice Using collateral to issue FT tokens.
    ///         Caller will get FT(bond) tokens equal to the debt amount subtract issue fee
    /// @param debt The amount of debt, unit by underlying token
    /// @param collateralData The encoded data of collateral
    /// @return gtId The id of Gearing Token
    ///
    function issueFt(address recipient, uint128 debt, bytes calldata collateralData)
        external
        returns (uint256 gtId, uint128 ftOutAmt);

    /// @notice Return the issue fee ratio
    function mintGtFeeRatio() external view returns (uint256);

    /// @notice Using collateral to issue FT tokens.
    ///         Caller will get FT(bond) tokens equal to the debt amount subtract issue fee
    /// @param recipient Who will receive Gearing Token
    /// @param debt The amount of debt, unit by underlying token
    /// @param gtId The id of Gearing Token
    /// @return ftOutAmt The amount of FT issued
    ///
    function issueFtByExistedGt(address recipient, uint128 debt, uint256 gtId) external returns (uint128 ftOutAmt);

    /// @notice Flash loan underlying token for leverage
    /// @param recipient Who will receive Gearing Token
    /// @param xtAmt The amount of XT token.
    ///              The caller will receive an equal amount of underlying token by flash loan.
    /// @param callbackData The data of flash loan callback
    /// @return gtId The id of Gearing Token
    function leverageByXt(address recipient, uint128 xtAmt, bytes calldata callbackData)
        external
        returns (uint256 gtId);

    /// @notice Preview the redeem amount and delivery data
    /// @param ftAmount The amount of FT want to redeem
    /// @return debtTokenAmt The amount of debt token
    /// @return deliveryData The delivery data
    function previewRedeem(uint256 ftAmount) external view returns (uint256 debtTokenAmt, bytes memory deliveryData);

    /// @notice Redeem underlying tokens after maturity
    /// @param ftAmount The amount of FT want to redeem
    /// @param recipient Who will receive the underlying tokens
    /// @return debtTokenAmt The amount of debt token
    /// @return deliveryData The delivery data
    function redeem(uint256 ftAmount, address recipient)
        external
        returns (uint256 debtTokenAmt, bytes memory deliveryData);

    /// @notice Set the configuration of Gearing Token
    function updateGtConfig(bytes memory configData) external;

    /// @notice Set the fee rate of order
    function updateOrderFeeRate(ITermMaxOrder order, FeeConfig memory newFeeConfig) external;

    /// @notice Create a new order
    function createOrder(address maker, uint256 maxXtReserve, ISwapCallback swapTrigger, CurveCuts memory curveCuts)
        external
        returns (ITermMaxOrder order);
}
"
    },
    "contracts/v1/factory/ITermMaxFactory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {MarketInitialParams} from "../storage/TermMaxStorage.sol";

/**
 * @title The TermMax factory interface
 * @author Term Structure Labs
 */
interface ITermMaxFactory {
    function TERMMAX_MARKET_IMPLEMENTATION() external view returns (address);

    function gtImplements(bytes32 gtKey) external view returns (address gtImplement);

    /// @notice Set the implementations of TermMax Gearing Token contract
    function setGtImplement(string memory gtImplementName, address gtImplement) external;

    /// @notice Predict the address of token pair
    function predictMarketAddress(
        address deployer,
        address collateral,
        address debtToken,
        uint64 maturity,
        uint256 salt
    ) external view returns (address market);

    /// @notice Deploy a new market
    function createMarket(bytes32 gtKey, MarketInitialParams memory params, uint256 salt)
        external
        returns (address market);
}
"
    },
    "contracts/v2/events/FactoryEventsV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {MarketInitialParams} from "../../v1/storage/TermMaxStorage.sol";
import {VaultInitialParamsV2} from "../storage/TermMaxStorageV2.sol";

/**
 * @title Factory Events Interface V2
 * @notice Events emitted by the TermMax factory contracts
 */
interface FactoryEventsV2 {
    /**
     * @notice Emitted when a new market is created
     * @param market The address of the newly created market
     * @param collateral The address of the collateral token
     * @param debtToken The debt token interface
     * @param params The initial parameters for the market
     */
    event MarketCreated(
        address indexed market, address indexed collateral, IERC20 indexed debtToken, MarketInitialParams params
    );

    /**
     * @notice Emitted when a new vault is created
     * @param vault The address of the newly created vault
     * @param creator The address of the vault creator
     * @param initialParams The initial parameters used to configure the vault
     */
    event VaultCreated(address indexed vault, address indexed creator, VaultInitialParamsV2 initialParams);

    /**
     * @notice Emitted when a new price feed is created
     * @param priceFeed The address of the newly created price feed contract
     */
    event PriceFeedCreated(address indexed priceFeed);

    // Events from TermMax4626Factory
    /**
     * @notice Emitted when TermMax4626Factory is initialized
     * @param aavePool The Aave pool address
     * @param aaveReferralCode The Aave referral code
     * @param stableERC4626For4626Implementation The stable ERC4626For4626 implementation address
     * @param stableERC4626ForAaveImplementation The stable ERC4626ForAave implementation address
     * @param variableERC4626ForAaveImplementation The variable ERC4626ForAave implementation address
     */
    event TermMax4626FactoryInitialized(
        address indexed aavePool,
        uint16 aaveReferralCode,
        address stableERC4626For4626Implementation,
        address stableERC4626ForAaveImplementation,
        address variableERC4626ForAaveImplementation
    );

    /**
     * @notice Emitted when a new StableERC4626For4626 is created
     * @param caller The address that called the creation function
     * @param stableERC4626For4626 The address of the created StableERC4626For4626
     */
    event StableERC4626For4626Created(address indexed caller, address indexed stableERC4626For4626);

    /**
     * @notice Emitted when a new StableERC4626ForAave is created
     * @param caller The address that called the creation function
     * @param stableERC4626ForAave The address of the created StableERC4626ForAave
     */
    event StableERC4626ForAaveCreated(address indexed caller, address indexed stableERC4626ForAave);

    /**
     * @notice Emitted when a new VariableERC4626ForAave is created
     * @param caller The address that called the creation function
     * @param variableERC4626ForAave The address of the created VariableERC4626ForAave
     */
    event VariableERC4626ForAaveCreated(address indexed caller, address indexed variableERC4626ForAave);
}
"
    },
    "contracts/v2/VersionV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract VersionV2 {
    // Function to get the version number
    function getVersion() public pure virtual returns (string memory) {
        return "2.0.0";
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-5.2.0/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-5.2.0/utils/Create2.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Create2.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
 * `CREATE2` can be used to compute in advance the address where a smart
 * contract will be deployed, which allows for interesting new mechanisms known
 * as 'counterfactual interactions'.
 *
 * See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
 * information.
 */
library Create2 {
    /**
     * @dev There's no code to deploy.
     */
    error Create2EmptyBytecode();

    /**
     * @dev Deploys a contract using `CREATE2`. The address where the contract
     * will be deployed can be known in advance via {computeAddress}.
     *
     * The bytecode for a contract can be obtained from Solidity with
     * `type(contractName).creationCode`.
     *
     * Requirements:
     *
     * - `bytecode` must not be empty.
     * - `salt` must have not been used for `bytecode` already.
     * - the factory must have a balance of at least `amount`.
     * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
     */
    function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }
        if (bytecode.length == 0) {
            revert Create2EmptyBytecode();
        }
        assembly ("memory-safe") {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
            // if no address was created, and returndata is not empty, bubble revert
            if and(iszero(addr), not(iszero(returndatasize()))) {
                let p := mload(0x40)
                returndatacopy(p, 0, returndatasize())
                revert(p, returndatasize())
            }
        }
        if (addr == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
     * `bytecodeHash` or `salt` will result in a new destination address.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
        return computeAddress(salt, bytecodeHash, address(this));
    }

    /**
     * @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
     * `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
     */
    function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
        assembly ("memory-safe") {
            let ptr := mload(0x40) // Get free memory pointer

            // |                   | ↓ ptr ...  ↓ ptr + 0x0B (start) ...  ↓ ptr + 0x20 ...  ↓ ptr + 0x40 ...   |
            // |-------------------|---------------------------------------------------------------------------|
            // | bytecodeHash      |                                                        CCCCCCCCCCCCC...CC |
            // | salt              |                                      BBBBBBBBBBBBB...BB                   |
            // | deployer          | 000000...0000AAAAAAAAAAAAAAAAAAA...AA                                     |
            // | 0xFF              |            FF                                                             |
            // |-------------------|---------------------------------------------------------------------------|
            // | memory            | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
            // | keccak(start, 85) |            ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |

            mstore(add(ptr, 0x40), bytecodeHash)
            mstore(add(ptr, 0x20), salt)
            mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
            let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
            mstore8(start, 0xff)
            addr := and(keccak256(start, 85), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-5.2.0/utils/Errors.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}
"
    },
    "contracts/v2/tokens/AbstractGearingTokenV2.sol": {
      "content": "// SPDX-License-Identifier:  BUSL-1.1
pragma solidity ^0.8.27;

import {ERC721EnumerableUpgradeable} from
    "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {Constants} from "../../v1/lib/Constants.sol";
import {GearingTokenConstants} from "../../v1/lib/GearingTokenConstants.sol";
import {TransferUtils} from "../../v1/lib/TransferUtils.sol";
import {IFlashRepayer} from "../../v1/tokens/IFlashRepayer.sol";
import {IGearingToken, IERC20Metadata, IERC20} from "../../v1/tokens/IGearingToken.sol";
import {GearingTokenErrors} from "../../v1/errors/GearingTokenErrors.sol";
import {GearingTokenEvents} from "../../v1/events/GearingTokenEvents.sol";
import {GtConfig, IOracle} from "../../v1/storage/TermMaxStorage.sol";
import {IGearingTokenV2} from "./IGearingTokenV2.sol";
import {GearingTokenEventsV2} from "../events/GearingTokenEventsV2.sol";
import {GearingTokenErrorsV2} from "../errors/GearingTokenErrorsV2.sol";
import {VersionV2} from "../VersionV2.sol";
import {DelegateAble} from "../lib/DelegateAble.sol";

/**
 * @title TermMax Gearing Token
 * @author Term Structure Labs
 */
abstract contract AbstractGearingTokenV2 is
    OwnableUpgradeable,
    ERC721EnumerableUpgradeable,
    EIP712Upgradeable,
    ReentrancyGuardUpgradeable,
    IGearingToken,
    IGearingTokenV2,
    GearingTokenErrors,
    GearingTokenEvents,
    VersionV2,
    DelegateAble
{
    using SafeCast for uint256;
    using SafeCast for int256;
    using TransferUtils for IERC20;
    using TransferUtils for IERC20Metadata;
    using Math for *;

    struct LoanInfo {
        /// @notice Debt amount in debtToken token
        uint128 debtAmt;
        /// @notice Encoded collateral data
        bytes collateralData;
    }

    struct ValueAndPrice {
        /// @notice USD value of collateral
        uint256 collateralValue;
        /// @notice USD value of debt contains price and token decimals
        uint256 debtValueWithDecimals;
        /// @notice USD price of debt token
        uint256 debtPrice;
        /// @notice Denominator of USD price, e.g. 10**priceDecimals
        uint256 priceDenominator;
        /// @notice Denominator of debt token, e.g. 10**debtToken.decimals()
        uint256 debtDenominator;
        /// @notice Encoded USD price of collateral token, e.g. priceData is
        ///         abi.encode(price, priceDenominator, collateralDenominator)
        ///         where gt is GearingTokenWithERC20
        bytes collateralPriceData;
    }

    /// @notice Configuration of Gearing Token
    GtConfig internal _config;
    /// @notice Total supply of Gearing Token
    uint256 internal totalIds;
    /// @notice Denominator of debt token
    uint256 internal debtDenominator;
    /// @notice Mapping relationship between Gearing Token id and loan
    mapping(uint256 => LoanInfo) internal loanMapping;

    modifier isOwnerOrDelegate(uint256 id, address msgSender) {
        _checkIsOwnerOrDelegate(id, msgSender);
        _;
    }

    function _checkIsOwnerOrDelegate(uint256 id, address msgSender) internal view {
        address owner = ownerOf(id);
        if (msgSender != owner && !isDelegate(owner, msgSender)) {
            revert GearingTokenErrors.AuthorizationFailed(id, msgSender);
        }
    }

    /**
     * @inheritdoc IGearingToken
     */
    function initialize(string memory name, string memory symbol, GtConfig memory config_, bytes memory initalParams)
        external
        virtual
        override
        initializer
    {
        __AbstractGearingToken_init(name, symbol, config_);
        __GearingToken_Implement_init(initalParams);
        emit GearingTokenEventsV2.GearingTokenInitialized(msg.sender, name, symbol, initalParams);
    }

    function __AbstractGearingToken_init(string memory name, string memory symbol, GtConfig memory config_)
        internal
        onlyInitializing
    {
        if (config_.loanConfig.liquidationLtv <= config_.loanConfig.maxLtv) {
            revert LiquidationLtvMustBeGreaterThanMaxLtv();
        }
        if (config_.loanConfig.liquidationLtv > Constants.DECIMAL_BASE) {
            revert GearingTokenErrorsV2.InvalidLiquidationLtv();
        }
        __ERC721_init_unchained(name, symbol);
        __EIP712_init_unchained(name, getVersion());
        __Ownable_init_unchained(msg.sender);
        _config = config_;
        debtDenominator = 10 ** _config.debtToken.decimals();
    }

    function __GearingToken_Implement_init(bytes memory initalParams) internal virtual;

    /**
     * @inheritdoc IGearingToken
     */
    function setTreasurer(address treasurer) external virtual onlyOwner {
        _config.treasurer = treasurer;
    }

    /**
     * @inheritdoc IGearingToken
     */
    function updateConfig(bytes memory configData) external virtual onlyOwner {
        _updateConfig(configData);
        emit UpdateConfig(configData);
    }

    function _updateConfig(bytes memory configData) internal virtual;

    /**
     * @inheritdoc IGearingToken
     */
    function getGtConfig() external view virtual override returns (GtConfig memory) {
        return _config;
    }

    /**
     * @inheritdoc IGearingToken
     */
    function marketAddr() public view override returns (address) {
        return owner();
    }

    /**
     * @inheritdoc IGearingToken
     */
    function liquidatable() external view virtual returns (bool) {
        return _config.loanConfig.liquidatable;
    }

    /**
     * @inheritdoc IGearingToken
     */
    function mint(address collateralProvider, address to, uint128 debtAmt, bytes memory collateralData)
        external
        virtual
        override
        nonReentrant
        onlyOwner
        returns (uint256 id)
    {
        _checkBeforeMint(debtAmt, collateralData);
        _transferCollateralFrom(collateralProvider, address(this), collateralData);
        id = _mintInternal(to, debtAmt, collateralData, _config);
    }

    /// @notice Check if the loan can be minted
    function _checkBeforeMint(uint128 debtAmt, bytes memory collateralData) internal virtual;

    function _mintInternal(address to, uint128 debtAmt, bytes memory collateralData, GtConfig memory config)
        internal
        returns (uint256 id)
    {
        LoanInfo memory loan = LoanInfo(debtAmt, collateralData);
        ValueAndPrice memory valueAndPrice = _getValueAndPrice(config, loan);
        uint128 ltv = _calculateLtv(valueAndPrice);
        if (ltv > config.loanConfig.maxLtv) {
            revert GtIsNotHealthy(0, to, ltv);
        }
        id = ++totalIds;
        loanMapping[id] = loan;
        _safeMint(to, id);
    }

    /**
     * @inheritdoc IGearingToken
     */
    function augmentDebt(address caller, uint256 id, uint256 ftAmt)
        external
        virtual
        override
        nonReentrant
        onlyOwner
        isOwnerOrDelegate(id, caller)
    {
        GtConfig memory config = _config;
        if (config.maturity <= block.timestamp) {
            revert GearingTokenErrorsV2.GtIsExpired();
        }

        LoanInfo memory loan = loanMapping[id];
        loan.debtAmt += ftAmt.toUint128();

        ValueAndPrice memory valueAndPrice = _getValueAndPrice(config, loan);
        uint128 ltv = _calculateLtv(valueAndPrice);
        if (ltv > config.loanConfig.maxLtv) {
            revert GtIsNotHealthy(id, msg.sender, ltv);
        }
        loanMapping[id] = loan;

        emit AugmentDebt(id, ftAmt);
    }

    /**
     * @inheritdoc IGearingToken
     */
    function loanInfo(uint256 id)
        external
        view
        virtual
        override
        returns (address owner, uint128 debtAmt, bytes memory collateralData)
    {
        owner = ownerOf(id);
        LoanInfo memory loan = loanMapping[id];
        debtAmt = loan.debtAmt;
        collateralData = loan.collateralData;
    }

    function _burnInternal(uint256 id) internal {
        _burn(id);
        delete loanMapping[id];
    }

    /**
     * @inheritdoc IGearingToken
     */
    function merge(uint256[] memory ids) external virtual nonReentrant returns (uint256 newId) {
        if (ids.length == 0) {
            revert GearingTokenErrorsV2.GtIdArrayIsEmpty();
        }
        GtConfig memory config = _config;
        if (config.maturity <= block.timestamp) {
            revert GearingTokenErrorsV2.GtIsExpired();
        }
        newId = ids[0];
        LoanInfo memory firstLoan = loanMapping[newId];

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            _checkIsOwnerOrDelegate(id, msg.sender);
            if (i != 0) {
                if (id == newId) {
                    revert GearingTokenErrorsV2.DuplicateIdInMerge(id);
                }
                LoanInfo memory loan = loanMapping[id];
                firstLoan.debtAmt += loan.debtAmt;
                firstLoan.collateralData = _mergeCollateral(firstLoan.collateralData, loan.collateralData);
                _burnInternal(id);
            }
        }
        loanMapping[newId] = firstLoan;
        emit MergeGts(msg.sender, newId, ids);
    }

    /**
     * @inheritdoc IGearingToken
     */
    function repay(uint256 id, uint128 repayAmt, bool byDebtToken) external virtual override nonReentrant {
        GtConfig memory config = _config;
        if (config.maturity <= block.timestamp) {
            revert GearingTokenErrorsV2.GtIsExpired();
        }
        (LoanInfo memory loan, bool repayAll, uint128 finalRepayAmt) = _repay(id, repayAmt);
        if (repayAll) {
            _transferCollateral(ownerOf(id), loan.collateralData);
            _burnInternal(id);
        } else {
            loanMapping[id] = loan;
        }
        if (byDebtToken) {
            config.debtToken.safeTransferFrom(msg.sender, marketAddr(), finalRepayAmt);
        } else {
            // Those ft tokens have been approved to market and will be burn after maturity

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Pausable, Non-Fungible, Swap, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x34bab42f022d0ad6d9da468c3672501a131a8906|verified:true|block:23479698|tx:0xa34756125f80dddb24b3bc6340ede5eff535a097b97a2748bc8bb54268a23eaf|first_check:1759318531

Submitted on: 2025-10-01 13:35:32

Comments

Log in to comment.

No comments yet.