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))
Submitted on: 2025-11-07 21:01:19
Comments
Log in to comment.
No comments yet.