MutantStrategy

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/MutantStrategy.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IMutantStrategyToken} from "./interfaces/IMutantStrategyToken.sol";
import {ISeaport} from "./interfaces/ISeaport.sol";
import {IERC721} from "openzeppelin-contracts/token/ERC721/IERC721.sol";
import {AuctionHouse} from "./AuctionHouse.sol";
import {PrisonNFT} from "./PrisonNFT.sol";
import {MutantTreasury} from "./MutantTreasury.sol";
import {PrisonRewards} from "./PrisonRewards.sol";

contract MutantStrategy is AuctionHouse {
    address public buyer;
    bool public initialized;

    IMutantStrategyToken public token;
    IERC721 public mutants;
    PrisonNFT public prisonNft;
    MutantTreasury public treasury;
    PrisonRewards public prisonRewards;
    ISeaport public constant SEAPORT = ISeaport(0x0000000000000068F116a894984e2DB1123eB395);

    uint256 public constant AUCTION_PRICE_MULTIPLIER = 4;

    event MutantBought(uint256 indexed mutantId, uint256 price);

    constructor(address _mutants, address _owner) {
        buyer = _owner;
        mutants = IERC721(_mutants);

        // Note: Deployer must call initialize(...) after deployment
    }

    function initialize(address _token, address _treasury, address _prisonRewards) external onlyBuyer {
        require(!initialized, "Already initialized");
        require(_token != address(0), "Invalid token");
        require(_treasury != address(0), "Invalid treasury");
        require(_prisonRewards != address(0), "Invalid prison rewards");

        token = IMutantStrategyToken(_token);
        treasury = MutantTreasury(payable(_treasury));
        prisonRewards = PrisonRewards(payable(_prisonRewards));

        // Deploy the Prison NFT contract
        prisonNft = new PrisonNFT(address(mutants), _token, address(this), buyer, address(prisonRewards));

        initialized = true;
    }

    function buyMutant(ISeaport.Order calldata order, uint256 maxPrice) external payable onlyBuyer {
        // Validate it's a Mutant Ape Yacht Club NFT
        require(
            order.parameters.offer.length > 0 && order.parameters.offer[0].token == address(mutants)
                && order.parameters.offer[0].itemType == ISeaport.ItemType.ERC721,
            "Not a Mutant"
        );

        // Calculate total ETH needed (sum all consideration items that are ETH)
        uint256 actualPrice = 0;
        for (uint256 i = 0; i < order.parameters.consideration.length; i++) {
            if (order.parameters.consideration[i].itemType == ISeaport.ItemType.NATIVE) {
                actualPrice += order.parameters.consideration[i].startAmount;
            }
        }

        // Slippage protection
        require(actualPrice <= maxPrice, "Price exceeds maximum");

        uint256 balance = address(this).balance;
        if (balance < actualPrice) {
            uint256 needed = actualPrice - balance;
            treasury.useSurplus(needed);
        }

        // Fulfill the order on Seaport v1.6
        bool fulfilled = SEAPORT.fulfillOrder{value: actualPrice}(order, bytes32(0));
        require(fulfilled, "Order not fulfilled");

        // Verify we received the NFT
        uint256 tokenId = order.parameters.offer[0].identifierOrCriteria;
        require(mutants.ownerOf(tokenId) == address(this), "Not owner");

        emit MutantBought(tokenId, actualPrice);

        // If auction is active, add to existing auction (prison mode)
        // Otherwise, start new auction
        if (auction.active) {
            _addMutantToActiveAuction(tokenId, actualPrice);
        } else {
            // Start new auction
            _startNewAuction(tokenId, actualPrice);
        }
    }

    function _startNewAuction(uint256 mutantId, uint256 actualPrice) internal {
        _prepareAuction(mutantId);

        uint256[] memory mutantIds = new uint256[](1);
        mutantIds[0] = mutantId;

        uint256 auctionId = _nextAuctionId++;

        auction = Auction({
            active: true,
            auctionId: auctionId,
            mutantIds: mutantIds,
            startTime: block.timestamp,
            startPrice: 0,
            costBasis: actualPrice,
            maxUnitPrice: actualPrice
        });

        // Calculate dynamic start price for prison of 1
        uint256 startPrice = _calculateStartPrice(actualPrice, 1, actualPrice, 0);
        auction.startPrice = startPrice;
        emit AuctionStarted(auctionId, mutantIds, startPrice);
    }

    function _addMutantToActiveAuction(uint256 tokenId, uint256 actualPrice) internal {
        // Add mutant to existing auction
        auction.mutantIds.push(tokenId);

        // Update cost basis tracking
        auction.costBasis += actualPrice;
        if (actualPrice > auction.maxUnitPrice) {
            auction.maxUnitPrice = actualPrice;
        }

        // Calculate new start price for prison, ensuring it never decreases
        uint256 prisonSize = auction.mutantIds.length;
        uint256 newStartPrice =
            _calculateStartPrice(auction.costBasis, prisonSize, auction.maxUnitPrice, auction.startPrice);

        // Reset auction with new price
        auction.startPrice = newStartPrice;
        auction.startTime = block.timestamp;

        emit MutantAddedToAuction(auction.auctionId, tokenId, newStartPrice);
    }

    receive() external payable {} // can receive ETH

    function _prepareAuction(uint256 mutantId) internal view override {
        require(mutants.ownerOf(mutantId) == address(this), "Not owner");
    }

    function _calculateStartPrice(
        uint256 costBasis,
        uint256 prisonSize,
        uint256 maxUnitPrice,
        uint256 previousStartPrice
    ) internal view override returns (uint256) {
        uint256 startPrice = previousStartPrice;

        // Price based on cumulative ETH deployed into this prison
        uint256 cumulativeMutantTokens = treasury.previewMint(costBasis);
        uint256 cumulativePricing = cumulativeMutantTokens * AUCTION_PRICE_MULTIPLIER;
        if (cumulativePricing > startPrice) {
            startPrice = cumulativePricing;
        }

        if (maxUnitPrice > 0 && prisonSize > 0) {
            // Ensure unit cost floor scales with the most expensive mutant inside the prison
            uint256 unitMutantTokens = treasury.previewMint(maxUnitPrice * prisonSize);
            uint256 unitPricing = unitMutantTokens * AUCTION_PRICE_MULTIPLIER;
            if (unitPricing > startPrice) {
                startPrice = unitPricing;
            }
        }

        return startPrice;
    }

    function _settleAuction(uint256[] memory mutantIds, address _buyer, uint256 price) internal override {
        // Lock tokens from buyer
        token.lock(price, _buyer);

        // Transfer all mutants to the prison NFT contract
        for (uint256 i = 0; i < mutantIds.length; i++) {
            mutants.transferFrom(address(this), address(prisonNft), mutantIds[i]);
        }

        // Mint prison NFT to the buyer
        uint256 prisonId = prisonNft.settlePrison(_buyer, mutantIds);
        prisonRewards.onPrisonCreated(prisonId, mutantIds.length, _buyer);
    }

    function setBuyer(address _buyer) external onlyBuyer {
        buyer = _buyer;
    }

    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }

    modifier onlyBuyer() {
        _onlyBuyer();
        _;
    }

    function _onlyBuyer() internal view {
        require(msg.sender == buyer, "Only buyer");
    }
}"
    },
    "src/interfaces/IMutantStrategyToken.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IMutantStrategyToken {
    // === ERC20 FUNCTIONS (from solady/tokens/ERC20.sol) ===
    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint8);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

    // === CUSTOM FUNCTIONS (from MutantStrategyToken.sol) ===
    function minter() external view returns (address);
    function strategy() external view returns (address);
    function prisonNft() external view returns (address);
    function protocolFeeRecipient() external view returns (address);
    function live() external view returns (bool);
    function isConfigured() external view returns (bool);
    function isPoolActive() external view returns (bool);
    function isTransferDisabled() external view returns (bool);
    function pools(address) external view returns (bool);
    function DEAD_ADDRESS() external view returns (address);

    function configure(address newStrategy, address newPrisonNft, address newProtocolFeeRecipient) external;
    function setMinter(address _minter) external;
    function start() external;
    function setPoolActive(bool _isPoolActive) external;
    function setTransferDisabled(bool _isTransferDisabled) external;
    function addPool(address pool) external;
    function removePool(address pool) external;
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
    function lock(uint256 amount, address from) external;
    function lockedSupply() external view returns (uint256);
    function effectiveSupply() external view returns (uint256);
}
"
    },
    "src/interfaces/ISeaport.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ISeaport {
    enum OrderType {
        FULL_OPEN,
        PARTIAL_OPEN,
        FULL_RESTRICTED,
        PARTIAL_RESTRICTED,
        CONTRACT
    }

    enum ItemType {
        NATIVE,
        ERC20,
        ERC721,
        ERC1155,
        ERC721_WITH_CRITERIA,
        ERC1155_WITH_CRITERIA
    }

    struct OfferItem {
        ItemType itemType;
        address token;
        uint256 identifierOrCriteria;
        uint256 startAmount;
        uint256 endAmount;
    }

    struct ConsiderationItem {
        ItemType itemType;
        address token;
        uint256 identifierOrCriteria;
        uint256 startAmount;
        uint256 endAmount;
        address payable recipient;
    }

    struct OrderParameters {
        address offerer;
        address zone;
        OfferItem[] offer;
        ConsiderationItem[] consideration;
        OrderType orderType;
        uint256 startTime;
        uint256 endTime;
        bytes32 zoneHash;
        uint256 salt;
        bytes32 conduitKey;
        uint256 totalOriginalConsiderationItems;
    }

    struct Order {
        OrderParameters parameters;
        bytes signature;
    }

    struct AdvancedOrder {
        OrderParameters parameters;
        uint120 numerator;
        uint120 denominator;
        bytes signature;
        bytes extraData;
    }

    struct CriteriaResolver {
        uint256 orderIndex;
        uint256 side;
        uint256 index;
        uint256 identifier;
        bytes32[] criteriaProof;
    }

    struct FulfillmentComponent {
        uint256 orderIndex;
        uint256 itemIndex;
    }

    struct Fulfillment {
        FulfillmentComponent[] offerComponents;
        FulfillmentComponent[] considerationComponents;
    }

    struct OrderComponents {
        address offerer;
        address zone;
        OfferItem[] offer;
        ConsiderationItem[] consideration;
        OrderType orderType;
        uint256 startTime;
        uint256 endTime;
        bytes32 zoneHash;
        uint256 salt;
        bytes32 conduitKey;
        uint256 counter;
    }

    /**
     * @notice Fulfill an order offering an ERC721 token by supplying Ether (or
     *         the native token for the given chain) as consideration for the order.
     */
    function fulfillOrder(Order calldata order, bytes32 fulfillerConduitKey) external payable returns (bool fulfilled);

    /**
     * @notice Fulfill an advanced order
     */
    function fulfillAdvancedOrder(
        AdvancedOrder calldata advancedOrder,
        CriteriaResolver[] calldata criteriaResolvers,
        bytes32 fulfillerConduitKey,
        address recipient
    ) external payable returns (bool fulfilled);

    /**
     * @notice Fulfill available advanced orders
     */
    function fulfillAvailableAdvancedOrders(
        AdvancedOrder[] calldata advancedOrders,
        CriteriaResolver[] calldata criteriaResolvers,
        FulfillmentComponent[][] calldata offerFulfillments,
        FulfillmentComponent[][] calldata considerationFulfillments,
        bytes32 fulfillerConduitKey,
        address recipient,
        uint256 maximumFulfilled
    ) external payable returns (bool[] memory availableOrders, Fulfillment[] memory fulfillments);

    /**
     * @notice Match an arbitrary number of orders
     */
    function matchOrders(Order[] calldata orders, Fulfillment[] calldata fulfillments)
        external
        payable
        returns (Fulfillment[] memory);

    /**
     * @notice Match an arbitrary number of advanced orders
     */
    function matchAdvancedOrders(
        AdvancedOrder[] calldata advancedOrders,
        CriteriaResolver[] calldata criteriaResolvers,
        Fulfillment[] calldata fulfillments,
        address recipient
    ) external payable returns (Fulfillment[] memory);

    /**
     * @notice Cancel an arbitrary number of orders
     */
    function cancel(OrderComponents[] calldata orders) external returns (bool cancelled);

    /**
     * @notice Validate an arbitrary number of orders
     */
    function validate(Order[] calldata orders) external returns (bool validated);

    /**
     * @notice Retrieve the order hash for a given order
     */
    function getOrderHash(OrderComponents calldata order) external view returns (bytes32 orderHash);

    /**
     * @notice Retrieve the status of a given order
     */
    function getOrderStatus(bytes32 orderHash)
        external
        view
        returns (bool isValidated, bool isCancelled, uint256 totalFilled, uint256 totalSize);

    /**
     * @notice Retrieve the current counter for a given offerer
     */
    function getCounter(address offerer) external view returns (uint256 counter);

    /**
     * @notice Retrieve configuration information for this contract
     */
    function information()
        external
        view
        returns (string memory version, bytes32 domainSeparator, address conduitController);

    /**
     * @notice Retrieve the name of this contract
     */
    function name() external pure returns (string memory contractName);
}"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/IERC721.sol)

pragma solidity >=0.6.2;

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

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
"
    },
    "src/AuctionHouse.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/utils/ReentrancyGuard.sol";

abstract contract AuctionHouse is ReentrancyGuard {
    // 12-hour half-life: ln(2) / 43200 seconds ≈ 1.604e-5
    // Expressed in WAD: 1.604e-5 * 1e18 = 1.604e13
    uint256 public constant AUCTION_DECAY_RATE = 16040000000000; // 1.604e13 in WAD

    event AuctionStarted(uint256 indexed auctionId, uint256[] mutantIds, uint256 startPrice);
    event AuctionSettled(uint256 indexed auctionId, uint256[] mutantIds, address indexed buyer, uint256 price);
    event MutantAddedToAuction(uint256 indexed auctionId, uint256 indexed mutantId, uint256 newStartPrice);

    Auction public auction;

    uint256 public _nextAuctionId;

    struct Auction {
        bool active;
        uint256 auctionId;
        uint256[] mutantIds;
        uint256 startTime;
        uint256 startPrice;
        uint256 costBasis;
        uint256 maxUnitPrice;
    }

    modifier whenAuctionActive() {
        _whenAuctionActive();
        _;
    }

    modifier whenAuctionNotActive() {
        _whenAuctionNotActive();
        _;
    }

    function _whenAuctionActive() internal view {
        require(auction.active, "Auction not active");
    }

    function _whenAuctionNotActive() internal view {
        require(!auction.active, "Auction already active");
    }

    function take(uint256 maxPrice) external whenAuctionActive nonReentrant {
        uint256 price = currentAuctionPrice();
        require(price <= maxPrice, "Price too high");

        auction.active = false;
        uint256[] memory mutantIds = auction.mutantIds;
        require(mutantIds.length > 0, "No mutants in auction");
        uint256 auctionId = auction.auctionId;
        _settleAuction(mutantIds, msg.sender, price);
        emit AuctionSettled(auctionId, mutantIds, msg.sender, price);
    }

    function currentAuction() external view returns (Auction memory) {
        return auction;
    }

    function isAuctionActive() external view returns (bool) {
        return auction.active;
    }

    function currentAuctionPrice() public view returns (uint256) {
        if (!auction.active) return 0;
        return _priceAt(block.timestamp - auction.startTime);
    }

    function _priceAt(uint256 t) internal view virtual returns (uint256 price) {
        // casting to int256 is safe because AUCTION_DECAY_RATE and t are uint256 values bounded by
        // reasonably small timestamps, so the product fits within int256 during auction lifetime.
        // forge-lint: disable-next-line(unsafe-typecast)
        int256 exp = -int256(AUCTION_DECAY_RATE) * int256(t);
        uint256 ratio = uint256(FixedPointMathLib.expWad(exp));

        if (ratio == 0) return 1;

        price = FixedPointMathLib.mulWadUp(auction.startPrice, ratio);
    }

    function _calculateStartPrice(
        uint256 costBasis,
        uint256 prisonSize,
        uint256 maxUnitPrice,
        uint256 previousStartPrice
    ) internal view virtual returns (uint256);
    function _prepareAuction(uint256 mutantId) internal virtual;
    function _settleAuction(uint256[] memory mutantIds, address buyer, uint256 price) internal virtual;
}"
    },
    "src/PrisonNFT.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC721} from "solady/tokens/ERC721.sol";
import {LibString} from "solady/utils/LibString.sol";
import {IERC721} from "openzeppelin-contracts/token/ERC721/IERC721.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/utils/ReentrancyGuard.sol";
import {IMutantStrategyToken} from "./interfaces/IMutantStrategyToken.sol";
import {PrisonRewards} from "./PrisonRewards.sol";

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

contract PrisonNFT is ERC721, ReentrancyGuard, Ownable {
    IERC721 public immutable MUTANTS;
    IMutantStrategyToken public immutable TOKEN;
    address public immutable AUCTION_CONTRACT;
    PrisonRewards public immutable prisonRewards;

    uint256 private _nextPrisonId;
    string private _baseTokenUri;

    // Maps prison token ID to array of mutant IDs
    mapping(uint256 => uint256[]) public prisonMutants;

    event PrisonMinted(uint256 indexed prisonId, address indexed owner, uint256[] mutantIds);
    event PrisonDisassembled(uint256 indexed prisonId, address indexed owner, uint256[] mutantIds);

    modifier onlyAuction() {
        _onlyAuction();
        _;
    }

    function _onlyAuction() internal view {
        require(msg.sender == AUCTION_CONTRACT, "Only auction contract");
    }

    constructor(address _mutants, address _token, address _auctionContract, address _owner, address _prisonRewards) {
        MUTANTS = IERC721(_mutants);
        TOKEN = IMutantStrategyToken(_token);
        AUCTION_CONTRACT = _auctionContract;
        prisonRewards = PrisonRewards(payable(_prisonRewards));
        _initializeOwner(_owner);
    }

    function name() public pure override returns (string memory) {
        return "Mutant Prison";
    }

    function symbol() public pure override returns (string memory) {
        return "PRISON";
    }

    function tokenURI(uint256 prisonId) public view override returns (string memory) {
        require(_ownerOf(prisonId) != address(0), "TokenDoesNotExist");
        string memory base = _baseTokenUri;
        return bytes(base).length == 0
            ? ""
            : string.concat(base, LibString.toString(prisonId));
    }

    function baseURI() external view returns (string memory) {
       return _baseTokenUri;
   }

    function setBaseURI(string calldata newBaseURI) external onlyOwner {
        _baseTokenUri = newBaseURI;
    }

    /// @notice Mint a new prison NFT containing the specified mutants
    /// @param to The address to mint the prison to
    /// @param mutantIds Array of mutant ape token IDs to include in the prison
    /// @return prisonId The ID of the newly minted prison NFT
    function settlePrison(address to, uint256[] calldata mutantIds) external onlyAuction returns (uint256 prisonId) {
        require(mutantIds.length > 0, "Empty prison");

        // Verify this contract owns all the mutants
        for (uint256 i = 0; i < mutantIds.length; i++) {
            require(MUTANTS.ownerOf(mutantIds[i]) == address(this), "Prison does not own mutant");
        }

        prisonId = _nextPrisonId++;

        // Store mutant IDs in the prison
        prisonMutants[prisonId] = mutantIds;

        // Mint the prison NFT
        _mint(to, prisonId);

        emit PrisonMinted(prisonId, to, mutantIds);
    }

    /// @notice Claim accumulated protocol fees for a given prison
    /// @param prisonId The prison whose fees to claim
    function claimFees(uint256 prisonId) external nonReentrant {
        address owner = ownerOf(prisonId);
        require(msg.sender == owner, "Not prison owner");
        prisonRewards.claimPrisonFees(prisonId, owner);
    }

    /// @notice Disassemble a prison, returning all mutants to the owner and burning the prison NFT
    /// @param prisonId The ID of the prison to disassemble
    function disassemble(uint256 prisonId) external nonReentrant {
        address owner = ownerOf(prisonId);
        require(msg.sender == owner, "Not prison owner");

        uint256[] memory mutantIds = prisonMutants[prisonId];
        require(mutantIds.length > 0, "Prison already disassembled");

        // Claim outstanding fees and remove prison weight from the strategy token
        prisonRewards.removePrison(prisonId, owner);

        // Transfer all mutants to the owner
        for (uint256 i = 0; i < mutantIds.length; i++) {
            MUTANTS.safeTransferFrom(address(this), owner, mutantIds[i]);
        }

        // Clear the prison data
        delete prisonMutants[prisonId];

        // Burn the prison NFT
        _burn(prisonId);

        emit PrisonDisassembled(prisonId, owner, mutantIds);
    }

    /// @notice Get the mutant IDs contained in a prison
    /// @param prisonId The ID of the prison
    /// @return Array of mutant ape token IDs
    function getPrisonMutants(uint256 prisonId) external view returns (uint256[] memory) {
        return prisonMutants[prisonId];
    }

    /// @notice Get the size (number of mutants) in a prison
    /// @param prisonId The ID of the prison
    /// @return Number of mutants in the prison
    function getPrisonSize(uint256 prisonId) external view returns (uint256) {
        return prisonMutants[prisonId].length;
    }

    /// @notice Required for receiving ERC721 tokens
    function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }
}"
    },
    "src/MutantTreasury.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IMutantStrategyToken} from "./interfaces/IMutantStrategyToken.sol";
import {IPrisonRewards} from "./interfaces/Interfaces.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {QuadraticCurve} from "./QuadraticCurve.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";
import {FeeConfig} from "./FeeConfig.sol";

contract MutantTreasury is ReentrancyGuard, Ownable {
    using QuadraticCurve for QuadraticCurve.Params;

    IMutantStrategyToken public token;
    IPrisonRewards public prisonRewards;

    QuadraticCurve.Params public curve;

    address public protocolFeeRecipient;
    address public strategy;
    bool public isPaused;

    event Mint(address indexed by, address indexed to, uint256 assets, uint256 tokens);
    event Redeem(address indexed by, address indexed from, address indexed to, uint256 assets, uint256 tokens);
    event SurplusUsed(address indexed strategy, uint256 amount);
    event ProtocolFeeRecipientUpdated(address indexed newRecipient);
    event StrategyUpdated(address indexed newStrategy);


    error Paused();
    error OnlyStrategy();

    receive() external payable {} // Can receive ETH

    modifier onlyStrategy() {
        _onlyStrategy();
        _;
    }

    function _onlyStrategy() internal view {
        if (msg.sender != strategy) revert OnlyStrategy();
    }

    constructor(
        address _token,
        address _prisonRewards,
        address _feeRecipient,
        address _strategy,
        QuadraticCurve.Params memory _curveParams
    ) {
        _initializeOwner(msg.sender);
        require(_token != address(0), "Invalid token");
        require(_prisonRewards != address(0), "Invalid rewards");
        require(_feeRecipient != address(0), "Invalid fee recipient");
        require(_strategy != address(0), "Invalid strategy");

        token = IMutantStrategyToken(_token);
        prisonRewards = IPrisonRewards(_prisonRewards);
        protocolFeeRecipient = _feeRecipient;
        strategy = _strategy;
        curve = _curveParams;
    }

    function setPaused(bool _isPaused) external onlyOwner {
        isPaused = _isPaused;
    }

    function setStrategy(address _strategy) external onlyOwner {
        require(_strategy != address(0), "Invalid strategy");
        strategy = _strategy;
        emit StrategyUpdated(_strategy);
    }

    function setProtocolFeeRecipient(address _recipient) external onlyOwner {
        require(_recipient != address(0), "Invalid recipient");
        protocolFeeRecipient = _recipient;
        emit ProtocolFeeRecipientUpdated(_recipient);
    }

    function _previewMint(uint256 ethAmount) internal view returns (uint256 tokenAmount, uint256 totalBuyFee) {
        totalBuyFee = (ethAmount * FeeConfig.BONDING_CURVE_FEE_RATE) / 1 ether;
        uint256 ethAmountForMinting = ethAmount - totalBuyFee;
        tokenAmount = curve.findTokenAmountForReserveIn(token.totalSupply(), ethAmountForMinting);
    }

    function previewMint(uint256 ethAmount) external view returns (uint256) {
        (uint256 tokenAmount, ) = _previewMint(ethAmount);
        return tokenAmount;
    }

    function quoteMint(uint256 amount) external view returns (uint256 ethRequired) {
        uint256 basePrice = _mintPrice(token.totalSupply(), amount);
        uint256 denominator = 1 ether - FeeConfig.BONDING_CURVE_FEE_RATE;
        ethRequired = (basePrice * 1 ether + denominator - 1) / denominator;
    }

    function mint(address receiver, uint256 minTokensOut) external payable nonReentrant {
        if (isPaused) revert Paused();
        require(msg.value > 0, "Must send ETH");

        (uint256 tokenAmount, uint256 totalBuyFee) = _previewMint(msg.value);
        uint256 supply = token.totalSupply();

        require(tokenAmount >= minTokensOut, "Insufficient output amount");
        require(supply + tokenAmount <= QuadraticCurve.MAX_SUPPLY, "Max supply reached");

        uint256 actualMintPrice = _mintPrice(supply, tokenAmount);
        _distributeFees(totalBuyFee);

        token.mint(receiver, tokenAmount);
        emit Mint(msg.sender, receiver, actualMintPrice, tokenAmount);

        uint256 excess = msg.value - totalBuyFee - actualMintPrice;
        if (excess > 0) {
            SafeTransferLib.safeTransferETH(msg.sender, excess);
        }
    }

    function previewRedeem(uint256 amount) external view returns (uint256) {
        uint256 baseEthOut = _redeemPrice(token.totalSupply(), amount);
        uint256 totalSellFee = (baseEthOut * FeeConfig.BONDING_CURVE_FEE_RATE) / 1 ether;
        return baseEthOut - totalSellFee;
    }

    function redeem(
        uint256 amount,
        address from,
        address receiver,
        uint256 minAmountOut
    )
    external
    nonReentrant
    {
        _redeem(amount, from, receiver, minAmountOut, msg.sender);
    }

    function _redeem(
        uint256 amount,
        address from,
        address receiver,
        uint256 minAmountOut,
        address redeemedBy
    )
    internal returns(uint256)
    {
        if (isPaused) revert Paused();
        require(amount > 0, "Amount must be > 0");

        uint256 supply = token.totalSupply();
        uint256 baseEthOut = _redeemPrice(supply, amount);
        uint256 totalSellFee = (baseEthOut * FeeConfig.BONDING_CURVE_FEE_RATE) / 1 ether;
        uint256 netEthOut = baseEthOut - totalSellFee;

        require(netEthOut >= minAmountOut, "Insufficient output amount");

        // The user must have approved THIS address (Treasury) to spend tokens on their behalf.
        token.lock(amount, from);
        emit Redeem(redeemedBy, from, receiver, netEthOut, amount);

        _distributeFees(totalSellFee);
        SafeTransferLib.safeTransferETH(receiver, netEthOut);
        return netEthOut;
    }

    // --- Treasury Logic ---
    function reserve() public view returns (uint256) {
        return _redeemPrice(token.totalSupply(), token.effectiveSupply());
    }

    function surplus() public view returns (uint256) {

        // Total assets = current balance + incoming ETH fees - outgoing fees.
        uint256 totalAssets = address(this).balance;

        // The required liability is for the supply AFTER the lock/burn.
        uint256 required = _redeemPrice(token.totalSupply(), token.effectiveSupply());

        if (totalAssets <= required) {
            return 0;
        }
        return totalAssets - required;
    }

    function useSurplus(uint256 amount) external onlyStrategy nonReentrant {
        require(amount <= surplus(), "Insufficient surplus");
        SafeTransferLib.safeTransferETH(strategy, amount);
        emit SurplusUsed(strategy, amount);
    }

    // --- Internal Helpers ---
    function _distributeFees(uint256 totalFeeAmount) internal {
        if (totalFeeAmount == 0) return;

        uint256 protocolFee = (totalFeeAmount * FeeConfig.PROTOCOL_FEE_BPS) / 10000;
        uint256 prisonFee = (totalFeeAmount * FeeConfig.PRISON_FEE_BPS) / 10000;

        if (protocolFee > 0) {
            SafeTransferLib.safeTransferETH(protocolFeeRecipient, protocolFee);
        }
        if (prisonFee > 0) {
            // Sends ETH to the rewards contract
            prisonRewards.addRewards{value: prisonFee}();
        }
    }

    function _mintPrice(uint256 supply, uint256 amount) internal view returns (uint256) {
        require(supply + amount <= QuadraticCurve.MAX_SUPPLY, "Max supply reached");
        return curve.reserveInForTokenOut(supply, amount);
    }

    function _redeemPrice(uint256 supply, uint256 amount) internal view returns (uint256) {
        require(supply >= amount, "Insufficient supply");
        return curve.reserveOutForTokenIn(supply, amount);
    }

    /// @dev Public view function for testing to get the expected ETH for a given amount of tokens.
    function getRedeemPrice(uint256 amount) public view returns (uint256) {
        return _redeemPrice(token.totalSupply(), amount);
    }
}"
    },
    "src/PrisonRewards.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {SafeTransferLib} from "lib/solady/src/utils/SafeTransferLib.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {ReentrancyGuard} from "openzeppelin-contracts/utils/ReentrancyGuard.sol";

contract PrisonRewards is ReentrancyGuard, Ownable {
    address public treasury;
    address public strategy;
    address public prisonNft;

    uint256 public prisonFeePool; // Accumulated fees awaiting distribution
    uint256 public totalPrisonWeight; // Sum of mutants across active prisons
    uint256 public prisonRewardsIndex; // Accumulated rewards per mutant (scaled by ACC_PRECISION)
    mapping(uint256 => uint256) public prisonRewardDebt; // prisonId => reward debt
    mapping(uint256 => uint256) public prisonWeight; // prisonId => mutant count

    uint256 public feesPendingCreation; // Fees accumulated before any prison is created
    bool public aPrisonHasBeenCreated; // Flag to check if a prison has ever been created

    uint256 private constant ACC_PRECISION = 1e18;

    event PrisonFeeClaimed(address indexed recipient, uint256 prisonId, uint256 amount);
    event AddressesSet(address indexed treasury, address indexed strategy, address indexed prisonNft);

    error OnlyTreasury();
    error OnlyStrategy();
    error OnlyPrisonNft();

    modifier onlyTreasury() {
        _onlyTreasury();
        _;
    }

    function _onlyTreasury() internal view {
        if (msg.sender != treasury) revert OnlyTreasury();
    }
    modifier onlyStrategy() {
        _onlyStrategy();
        _;
    }

    function _onlyStrategy() internal view {
        if (msg.sender != strategy) revert OnlyStrategy();
    }

    modifier onlyPrisonNft() {
        _onlyPrisonNft();
        _;
    }

    function _onlyPrisonNft() internal view {
        if (msg.sender != prisonNft) revert OnlyPrisonNft();
    }

    constructor() {
        _initializeOwner(msg.sender);
    }

    /// @dev Configure all external contract addresses.
    function setAddresses(address _treasury, address _strategy, address _prisonNft) external onlyOwner {
        require(_treasury != address(0) && _strategy != address(0) && _prisonNft != address(0), "Invalid address");
        treasury = _treasury;
        strategy = _strategy;
        prisonNft = _prisonNft;
        emit AddressesSet(_treasury, _strategy, _prisonNft);
    }

    /// @dev Adds ETH to the reward pool. Only callable by Treasury.
    function addRewards() external payable onlyTreasury {
        if (!aPrisonHasBeenCreated) {
            feesPendingCreation += msg.value;
        } else {
            prisonFeePool += msg.value;
        }
    }

    /// @dev Fallback to receive ETH from Treasury
    receive() external payable {
        if (msg.sender != treasury) revert OnlyTreasury();
        if (!aPrisonHasBeenCreated) {
            feesPendingCreation += msg.value;
        } else {
            prisonFeePool += msg.value;
        }
    }

    function pendingPrisonFees(uint256 prisonId) public view returns (uint256) {
        uint256 weight = prisonWeight[prisonId];
        if (weight == 0) return 0;

        uint256 accrued = weight * prisonRewardsIndex / ACC_PRECISION;
        uint256 debt = prisonRewardDebt[prisonId];

        return accrued > debt ? accrued - debt : 0;
    }

    function onPrisonCreated(uint256 prisonId, uint256 mutantCount, address owner) external onlyStrategy nonReentrant {
        if (!aPrisonHasBeenCreated) {
            aPrisonHasBeenCreated = true;
        }

        require(prisonWeight[prisonId] == 0, "Prison exists");

        uint256 oldIndex = prisonRewardsIndex;
        uint256 newTotalWeight = totalPrisonWeight + mutantCount;

        if (prisonFeePool > 0) {
            prisonRewardsIndex += ((prisonFeePool * ACC_PRECISION) / newTotalWeight);
            prisonFeePool = 0;
        }

        totalPrisonWeight = newTotalWeight;
        prisonWeight[prisonId] = mutantCount;

        uint256 initialDebt = (mutantCount * oldIndex) / ACC_PRECISION;
        prisonRewardDebt[prisonId] = initialDebt;

        _harvestPrison(prisonId, owner);
    }

    function claimPrisonFees(uint256 prisonId, address recipient)
    external
    onlyPrisonNft
    nonReentrant
    returns (uint256 amount)
    {
        require(recipient != address(0), "Invalid recipient");
        amount = _harvestPrison(prisonId, recipient);
    }

    function removePrison(uint256 prisonId, address recipient) external onlyPrisonNft nonReentrant {
        _harvestPrison(prisonId, recipient);

        uint256 weight = prisonWeight[prisonId];
        if (weight > 0) {
            totalPrisonWeight -= weight;
            delete prisonWeight[prisonId];
            delete prisonRewardDebt[prisonId];
        }
    }

    function _harvestPrison(uint256 prisonId, address recipient) internal returns (uint256 amount) {
        uint256 weight = prisonWeight[prisonId];
        if (weight == 0) {
            return 0;
        }

        if (prisonFeePool > 0) {
            prisonRewardsIndex += ((prisonFeePool * ACC_PRECISION) / totalPrisonWeight);
            prisonFeePool = 0;
        }

        uint256 accrued = (weight * prisonRewardsIndex) / ACC_PRECISION;
        uint256 debt = prisonRewardDebt[prisonId];

        if (accrued != debt) {
            prisonRewardDebt[prisonId] = accrued;
        }

        if (accrued <= debt) {
            return 0;
        }

        amount = accrued - debt;

        if (amount > 0 && recipient != address(0)) {
            SafeTransferLib.safeTransferETH(recipient, amount);
            emit PrisonFeeClaimed(recipient, prisonId, amount);
        }
    }

    /// @dev Allows the owner to claim fees accumulated before the first prison was created.
    function claimPendingFees(address recipient) external onlyOwner {
        require(recipient != address(0), "Invalid recipient");
        require(feesPendingCreation > 0, "No fees to claim");
        SafeTransferLib.safeTransferETH(recipient, feesPendingCreation);
        feesPendingCreation = 0;
    }
}"
    },
    "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @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);
}
"
    },
    "lib/solady/src/utils/FixedPointMathLib.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The operation failed, as the output exceeds the maximum value of uint256.
    error ExpOverflow();

    /// @dev The operation failed, as the output exceeds the maximum value of uint256.
    error FactorialOverflow();

    /// @dev The operation failed, due to an overflow.
    error RPowOverflow();

    /// @dev The mantissa is too big to fit.
    error MantissaOverflow();

    /// @dev The operation failed, due to an multiplication overflow.
    error MulWadFailed();

    /// @dev The operation failed, due to an multiplication overflow.
    error SMulWadFailed();

    /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
    error DivWadFailed();

    /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
    error SDivWadFailed();

    /// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
    error MulDivFailed();

    /// @dev The division failed, as the denominator is zero.
    error DivFailed();

    /// @dev The full precision multiply-divide operation failed, either due
    /// to the result being larger than 256 bits, or a division by a zero.
    error FullMulDivFailed();

    /// @dev The output is undefined, as the input is less-than-or-equal to zero.
    error LnWadUndefined();

    /// @dev The input outside the acceptable domain.
    error OutOfDomain();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The scalar of ETH and most ERC20s.
    uint256 internal constant WAD = 1e18;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*              SIMPLIFIED FIXED POINT OPERATIONS             */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Equivalent to `(x * y) / WAD` rounded down.
    function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
            if gt(x, div(not(0), y)) {
                if y {
                    mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            z := div(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded down.
    function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(x, y)
            // Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
            if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
                mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := sdiv(z, WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
    function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := div(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
    function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := sdiv(mul(x, y), WAD)
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded up.
    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(x, y)
            // Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
            if iszero(eq(div(z, y), x)) {
                if y {
                    mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
                    revert(0x1c, 0x04)
                }
            }
            z := add(iszero(iszero(mod(z, WAD))), div(z, WAD))
        }
    }

    /// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
    function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down.
    function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
            if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
                mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := div(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down.
    function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := mul(x, WAD)
            // Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
            if iszero(mul(y, eq(sdiv(z, WAD), x))) {
                mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := sdiv(z, y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
    function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := div(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
    function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := sdiv(mul(x, WAD), y)
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded up.
    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
            if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
                mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
                revert(0x1c, 0x04)
            }
            z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
        }
    }

    /// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
    function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
        }
    }

    /// @dev Equivalent to `x` to the power of `y`.
    /// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
    /// Note: This function is an approximation.
    function powWad(int256 x, int256 y) internal pure returns (int256) {
        // Using `ln(x)` means `x` must be greater than 0.
        return expWad((lnWad(x) * y) / int256(WAD));
    }

    /// @dev Returns `exp(x)`, denominated in `WAD`.
    /// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
    /// Note: This function is an approximation. Monotonically increasing.
    function expWad(int256 x) internal pure returns (int256 r) {
        unchecked {
            // When the result is less than 0.5 we return zero.
            // This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
            if (x <= -41446531673892822313) return r;

            /// @solidity memory-safe-assembly
            assembly {
                // When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
                // an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
                if iszero(slt(x, 135305999368893231589)) {
                    mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
                    revert(0x1c, 0x04)
                }
            }

            // `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
            // for more intermediate precision and a binary basis. This base conversion
            // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
            x = (x << 78) / 5 ** 18;

            // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
            // of two such that exp(x) = exp(x') * 2**k, where k is an integer.
            // Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
            int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
            x = x - k * 54916777467707473351141471128;

            // `k` is in the range `[-61, 195]`.

            // Evaluate using a (6, 7)-term rational approximation.
            // `p` is made monic, we'll multiply by a scale factor later.
            int256 y = x + 1346386616545796478920950773328;
            y = ((y * x) >> 96) + 57155421227552351082224309758442;
            int256 p = y + x - 94201549194550492254356042504812;
            p = ((p * y) >> 96) + 28719021644029726153956944680412240;
            p = p * x + (4385272521454847904659076985693276 << 96);

            // We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
            int256 q = x - 2855989394907223263936484059900;
            q = ((q * x) >> 96) + 50020603652535783019961831881945;
            q = ((q * x) >> 96) - 533845033583426703283633433725380;
            q = ((q * x) >> 96) + 3604857256930695427073651918091429;
            q = ((q * x) >> 96) - 14423608567350463180887372962807573;
            q = ((q * x) >> 96) + 26449188498355588339934803723976023;

            /// @solidity memory-safe-assembly
            assembly {
                // Div in assembly because solidity adds a zero check despite the unchecked.
                // The q polynomial won't have zeros in the domain as all its roots are complex.
                // No scaling is necessary because p is already `2**96` too large.
                r := sdiv(p, q)
            }

            // r should be in the range `(0.09, 0.25) * 2**96`.

            // We now need to multiply r by:
            // - The scale factor `s ≈ 6.031367120`.
            // - The `2**k` factor from the range reduction.
            // - The `1e18 / 2**96` factor for base conversion.
            // We do this all at once, with an intermediate result in `2**213`
            // basis, so the final right shift is always by a positive amount.
            r = int256(
                (uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
            );
        }
    }

    /// @dev Returns `ln(x)`, denominated in `WAD`.
    /// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
    /// Note: This function is an approximation. Monotonically increasing.
    function lnWad(int256 x) internal pure returns (int256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
            // We do this by multiplying by `2**96 / 10**18`. But since
            // `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
            // and add `ln(2**96 / 10**18)` at the end.

            // Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
            r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
            r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
            r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
            r := or(r, shl(4, lt(0xffff, shr(r, x))))
            r := or(r, shl(3, lt(0xff, shr(r, x))))
            // We place the check here for more optimal stack operations.
            if iszero(sgt(x, 0)) {
                mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
                revert(0x1c, 0x04)
            }
            // forgefmt: disable-next-item
            r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
                0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))

            // Reduce range of x to (1, 2) * 2**96
            // ln(2^k * x) = k * ln(2) + ln(x)
            x := shr(159, shl(r, x))

            // Evaluate using a (8, 8)-term rational approximation.
            // `p` is made monic, we will multiply by a scale factor later.
            // forgefmt: disable-next-item
            let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
                sar(96, mul(add(43456485725739037958740375743393,
                sar(96, mul(add(24828157081833163892658089445524,
                sar(96, mul(add(3273285459638523848632254066296,
                    x), x))), x))), x)), 11111509109440967052023855526967)
            p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
            p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
            p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
            // We leave `p` in `2**192` basis so we don't need to scale it back up for the division.

            // `q` is monic by convention.
            let q := add(5573035233440673466300451813936, x)
            q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
            q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
            q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
            q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
            q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
            q := add(909429971244387300277376558375, sar(96, mul(x, q)))

            // `p / q` is in the range `(0, 0.125) * 2**96`.

            // Finalization, we need to:
            // - Multiply by the scale factor `s = 5.549…`.
            // - Add `ln(2**96 / 10**18)`.
            // - Add `k * ln(2)`.
            // - Multiply by `10**18 / 2**96 = 5**18 >> 78`.

            // The q polynomial is known not to have zeros in the domain.
            // No scaling required because p is already `2**96` too large.
            p := sdiv(p, q)
            // Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
            p := mul(1677202110996718588342820967067443963516166, p)
            // Add `ln(2) * k * 5**18 * 2**192`.
            // forgefmt: disable-next-item
            p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
            // Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
            p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
            // Base conversion: mul `2**18 / 2**192`.
            r := sar(174, p)
        }
    }

    /// @dev Returns `W_0(x)`, denominated in `WAD`.
    /// See: https://en.wikipedia.org/wiki/Lambert_W_function
    /// a.k.a. Product log function. This is an approximation of the principal branch.
    /// Note: This function is an approximation. Monotonically increasing.
    function lambertW0Wad(int256 x) internal pure returns (int256 w) {
        // forgefmt: disable-next-item
        unchecked {
            if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
            (int256 wad, int256 p) = (int256(WAD), x);
            uint256 c; // Whether we need to avoid catastrophic cancellation.
            uint256 i = 4; // Number of iterations.
            if (w <= 0x1ffffffffffff) {
                if (-0x4000000000000 <= w) {
                    i = 1; // Inputs near zero only take one step to converge.
                } else if (w <= -0x3ffffffffffffff) {
                    i = 32; // Inputs near `-1/e` take very long to converge.
                }
            } else if (uint256(w >> 63) == uint256(0)) {
                /// @solidity memory-safe-assembly
                assembly {
                    // Inline log2 for more performance, since the range is small.
                    let v := shr(49, w)
                    let l := shl(3, lt(0xff, v))
                    l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
                        0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
                    w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
                    c := gt(l, 60)
                    i := add(2, add(gt(l, 53), c))
                }
            } else {
                int256 ll = lnWad(w = lnWad(w));
                /// @solidity memory-safe-assembly
                assembly {
                    // `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
                    w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
                    i := add(3, iszero(shr(68, x)))
                    c := iszero(shr(143, x))
                }
                if (c == uint256(0)) {
                    do { // If `x` is big, use Newton's so that intermediate values won't overflow.
                        int256 e = expWad(w);
                        /// @solidity memory-safe-assembly
                        assembly {
                            let t := mul(w, div(e, wad))
                            w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
                        }
                        if (p <= w) break;
                        p = w;
                    } while (--i != uint256(0));
                    /// @solidity memory-safe-assembly
                    assembly {
                        w := sub(w, sgt(w, 2))
                    }
                    return w;
                }
            }
            do { // Otherwise, use Halley's for faster convergence.
                int256 e = expWad(w);
                /// @solidity memory-safe-assembly
                assembly {
                    let t := add(w, wad)
                    let s := sub(mul(w, e), mul(x, wad))
                    w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
                }
                if (p <= w) break;
                p = w;
            } while (--i != c);
            /// @solidity memory-safe-assembly
            assembly {
                w := sub(w, sgt(w, 2))
            

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Non-Fungible, Upgradeable, Multi-Signature, Factory|addr:0xbd9d1ef34e9a275785f48a7347643b0f2502c02d|verified:true|block:23749588|tx:0xe9ff59a794e001db31bb9fd1c6ae50f823e18dc0730af9b4327aa3175f9a4604|first_check:1762545678

Submitted on: 2025-11-07 21:01:19

Comments

Log in to comment.

No comments yet.