ETH Price: $2,077.96 (-1.18%)

Contract

0x4eA57ef203E91AE8c7D9822aa09CC719a9c01aC6
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Redeem189963372024-01-13 6:59:23791 days ago1705129163IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0012796914.34390859
Redeem189264462024-01-03 11:09:59801 days ago1704280199IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0020562618.74781496
Redeem189183792024-01-02 8:01:11802 days ago1704182471IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0016520715.06265903
Redeem189182602024-01-02 7:37:23802 days ago1704181043IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0015664916.92047052
Redeem189182102024-01-02 7:27:23802 days ago1704180443IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0015166116.38166004
Redeem189182002024-01-02 7:25:23802 days ago1704180323IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0013276514.34067158
Redeem189181432024-01-02 7:13:59802 days ago1704179639IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0012913211.77360083
Redeem189177822024-01-02 6:00:11802 days ago1704175211IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0010921711.79712666
Redeem183681562023-10-17 5:57:35879 days ago1697522255IN
0x4eA57ef2...9a9c01aC6
0 ETH0.000538525.81688303
Redeem181505152023-09-16 18:36:47910 days ago1694889407IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0011883812.83628971
Redeem181436572023-09-15 19:24:11911 days ago1694805851IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0022211823.99204374
Redeem178262262023-08-02 8:49:35955 days ago1690966175IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0013689514.78677144
Redeem177768872023-07-26 11:14:35962 days ago1690370075IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0019883721.47739279
Redeem177746402023-07-26 3:41:47962 days ago1690342907IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0019011420.53518283
Redeem177698132023-07-25 11:28:35963 days ago1690284515IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0026323224
Redeem177397372023-07-21 6:29:35967 days ago1689920975IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0018968717.29466995
Redeem177257072023-07-19 7:19:35969 days ago1689751175IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0018865720.37780235
Redeem177232612023-07-18 23:04:23969 days ago1689721463IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0024628826.60274465
Redeem177232522023-07-18 23:02:35969 days ago1689721355IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0024468726.429823
Redeem177167052023-07-18 0:59:47970 days ago1689641987IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0015228613.88465739
Redeem176979382023-07-15 9:34:59973 days ago1689413699IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0012988614.02969825
Redeem176979212023-07-15 9:31:23973 days ago1689413483IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0017668916.10957727
Redeem176805502023-07-12 22:49:23975 days ago1689202163IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0015675116.93141594
Redeem176804812023-07-12 22:35:23975 days ago1689201323IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0016973918.33431675
Redeem176804182023-07-12 22:22:47975 days ago1689200567IN
0x4eA57ef2...9a9c01aC6
0 ETH0.0018841620.35173347
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Redeemer

Compiler Version
v0.8.16+commit.07a7930e

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/Marketplace.sol';

import 'src/lib/Safe.sol';
import 'src/lib/Maturities.sol';
import 'src/errors/Exception.sol';

import 'src/interfaces/IERC5095.sol';
import 'src/interfaces/IERC20.sol';
import 'src/interfaces/ITempus.sol';
import 'src/interfaces/ITempusToken.sol';
import 'src/interfaces/IAPWineController.sol';
import 'src/interfaces/IAPWineFutureVault.sol';
import 'src/interfaces/IAPWineToken.sol';
import 'src/interfaces/ISwivel.sol';
import 'src/interfaces/ISwivelToken.sol';
import 'src/interfaces/IElementToken.sol';
import 'src/interfaces/IYieldToken.sol';
import 'src/interfaces/INotional.sol';
import 'src/interfaces/IPendleToken.sol';
import 'src/interfaces/IPendleYieldToken.sol';
import 'src/interfaces/IPendleSYToken.sol';
import 'src/interfaces/ISensePeriphery.sol';
import 'src/interfaces/ISenseDivider.sol';
import 'src/interfaces/ISenseAdapter.sol';
import 'src/interfaces/IConverter.sol';

/// @title Redeemer
/// @author Sourabh Marathe, Julian Traversa, Rob Robbins
/// @notice The Redeemer contract is used to redeem the underlying lent capital of a loan.
/// @notice Users may redeem their ERC-5095 tokens for the underlying asset represented by that token after maturity.
contract Redeemer {
    /// @notice minimum wait before the admin may withdraw funds or change the fee rate
    uint256 public constant HOLD = 3 days;

    /// @notice address that is allowed to set fees and contracts, etc. It is commonly used in the authorized modifier.
    address public admin;
    /// @notice address of the MarketPlace contract, used to access the markets mapping
    address public marketPlace;
    /// @notice address that custodies principal tokens for all markets
    address public lender;
    /// @notice address that converts compounding tokens to their underlying
    address public converter;

    /// @notice third party contract needed to redeem Swivel PTs
    address public immutable swivelAddr;
    /// @notice third party contract needed to redeem Tempus PTs
    address public immutable tempusAddr;

    /// @notice this value determines the amount of fees paid on auto redemptions
    uint256 public feenominator;
    /// @notice represents a point in time where the feenominator may change
    uint256 public feeChange;
    /// @notice represents a minimum that the feenominator must exceed
    uint256 public MIN_FEENOMINATOR = 500;

    /// @notice mapping that indicates how much underlying has been redeemed by a market
    mapping(address => mapping(uint256 => uint256)) public holdings;
    /// @notice mapping that determines if a market's iPT can be redeemed
    mapping(address => mapping(uint256 => bool)) public paused;

    /// @notice emitted upon redemption of a loan
    event Redeem(
        uint8 principal,
        address indexed underlying,
        uint256 indexed maturity,
        uint256 amount,
        uint256 burned,
        address sender
    );
    /// @notice emitted upon changing the admin
    event SetAdmin(address indexed admin);
    /// @notice emitted upon changing the converter
    event SetConverter(address indexed converter);
    /// @notice emitted upon setting the fee rate
    event SetFee(uint256 indexed fee);
    /// @notice emitted upon scheduling a fee change
    event ScheduleFeeChange(uint256 when);
    /// @notice emitted upon pausing of Illuminate PTs
    event PauseRedemptions(
        address indexed underlying,
        uint256 maturity,
        bool state
    );

    /// @notice ensures that only a certain address can call the function
    /// @param a address that msg.sender must be to be authorized
    modifier authorized(address a) {
        if (msg.sender != a) {
            revert Exception(0, 0, 0, msg.sender, a);
        }
        _;
    }

    /// @notice reverts on all markets where the paused mapping returns true
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    modifier unpaused(address u, uint256 m) {
        if (paused[u][m] || ILender(lender).halted()) {
            revert Exception(17, m, 0, u, address(0));
        }
        _;
    }

    /// @notice Initializes the Redeemer contract
    /// @param l the lender contract
    /// @param s the Swivel contract
    /// @param t the Tempus contract
    constructor(address l, address s, address t) {
        admin = msg.sender;
        lender = l;
        swivelAddr = s;
        tempusAddr = t;
        feenominator = 4000;
    }

    /// @notice sets the admin address
    /// @param a Address of a new admin
    /// @return bool true if successful
    function setAdmin(address a) external authorized(admin) returns (bool) {
        admin = a;
        emit SetAdmin(a);
        return true;
    }

    /// @notice sets the address of the marketplace contract which contains the addresses of all the fixed rate markets
    /// @param m the address of the marketplace contract
    /// @return bool true if the address was set
    function setMarketPlace(
        address m
    ) external authorized(admin) returns (bool) {
        // MarketPlace may only be set once
        if (marketPlace != address(0)) {
            revert Exception(5, 0, 0, marketPlace, address(0));
        }

        marketPlace = m;
        return true;
    }

    /// @notice sets the converter address
    /// @param c address of the new converter
    /// @param i a list of interest bearing tokens the redeemer will approve
    /// @return bool true if successful
    function setConverter(
        address c,
        address[] memory i
    ) external authorized(admin) returns (bool) {
        // Set the new converter
        converter = c;

        // Have the redeemer approve the new converter
        for (uint256 x; x != i.length; ) {
            // Approve the new converter to transfer the relevant tokens
            Safe.approve(IERC20(i[x]), c, type(uint256).max);

            unchecked {
                x++;
            }
        }

        emit SetConverter(c);
        return true;
    }

    /// @notice sets the address of the lender contract which contains the addresses of all the fixed rate markets
    /// @param l the address of the lender contract
    /// @return bool true if the address was set
    function setLender(address l) external authorized(admin) returns (bool) {
        // Lender may only be set once
        if (lender != address(0)) {
            revert Exception(8, 0, 0, address(lender), address(0));
        }

        lender = l;
        return true;
    }

    /// @notice sets the feenominator to the given value
    /// @param f the new value of the feenominator, fees are not collected when the feenominator is 0
    /// @return bool true if successful
    function setFee(uint256 f) external authorized(admin) returns (bool) {
        // Cache the minimum timestamp for executing a fee rate change
        uint256 feeTime = feeChange;

        // Check that a fee rate change has been scheduled
        if (feeTime == 0) {
            revert Exception(23, 0, 0, address(0), address(0));

            // Check that the scheduled fee rate change time has been passed
        } else if (block.timestamp < feeTime) {
            revert Exception(
                24,
                block.timestamp,
                feeTime,
                address(0),
                address(0)
            );
            // Check the the new fee rate is not too high
        } else if (f < MIN_FEENOMINATOR) {
            revert Exception(25, 0, 0, address(0), address(0));
        }

        // Set the new fee rate
        feenominator = f;

        // Unschedule the fee rate change
        delete feeChange;

        emit SetFee(f);
        return true;
    }

    /// @notice allows the admin to schedule a change to the fee denominators
    function scheduleFeeChange() external authorized(admin) returns (bool) {
        // Calculate the timestamp that must be passed prior to setting thew new fee
        uint256 when = block.timestamp + HOLD;

        // Store the timestamp that must be passed to update the fee rate
        feeChange = when;

        emit ScheduleFeeChange(when);

        return true;
    }

    /// @notice allows admin to stop redemptions of Illuminate PTs for a given market
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param b true to pause, false to unpause
    function pauseRedemptions(
        address u,
        uint256 m,
        bool b
    ) external authorized(admin) {
        paused[u][m] = b;
        emit PauseRedemptions(u, m, b);
    }

    /// @notice approves the converter to spend the compounding asset
    /// @param i an interest bearing token that must be approved for conversion
    function approve(address i) external authorized(marketPlace) {
        if (i != address(0)) {
            Safe.approve(IERC20(i), address(converter), type(uint256).max);
        }
    }

    /// @notice redeem method for Yield, Element, Pendle, APWine, Tempus and Notional protocols
    /// @param p principal value according to the MarketPlace's Principals Enum
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @return bool true if the redemption was successful
    function redeem(
        uint8 p,
        address u,
        uint256 m
    ) external unpaused(u, m) returns (bool) {
        // Get the principal token that is being redeemed by the user
        address principal = IMarketPlace(marketPlace).markets(u, m, p);

        // Get the maturity for the given principal token
        uint256 maturity;
        if (p == uint8(MarketPlace.Principals.Yield)) {
            maturity = Maturities.yield(principal);
        } else if (p == uint8(MarketPlace.Principals.Element)) {
            maturity = Maturities.element(principal);
        } else if (p == uint8(MarketPlace.Principals.Pendle)) {
            maturity = Maturities.pendle(principal);
        } else if (p == uint8(MarketPlace.Principals.Tempus)) {
            maturity = Maturities.tempus(principal);
        } else if (p == uint8(MarketPlace.Principals.Apwine)) {
            maturity = Maturities.apwine(principal);
        } else if (p == uint8(MarketPlace.Principals.Notional)) {
            maturity = Maturities.notional(principal);
        } else {
            revert Exception(6, p, 0, address(0), address(0));
        }

        // Verify that the token has matured
        if (maturity > block.timestamp) {
            revert Exception(7, maturity, 0, address(0), address(0));
        }

        // Cache the lender to save gas on sload
        address cachedLender = lender;

        // Get the amount of principal tokens held by the lender
        uint256 amount = IERC20(principal).balanceOf(cachedLender);

        // For Pendle, we can transfer directly to the YT
        address destination = address(this);
        if (p == uint8(MarketPlace.Principals.Pendle)) {
            destination = IPendleToken(principal).YT();
        }

        // Receive the principal token from the lender contract
        Safe.transferFrom(IERC20(principal), cachedLender, destination, amount);

        // Get the starting balance of the underlying held by the redeemer
        uint256 starting = IERC20(u).balanceOf(address(this));

        if (p == uint8(MarketPlace.Principals.Yield)) {
            // Redeems principal tokens from Yield
            IYieldToken(principal).redeem(address(this), amount);
        } else if (p == uint8(MarketPlace.Principals.Element)) {
            // Redeems principal tokens from Element
            IElementToken(principal).withdrawPrincipal(amount, address(this));
        } else if (p == uint8(MarketPlace.Principals.Pendle)) {
            // Retrieve the YT for the PT
            address yt = IPendleToken(principal).YT();

            // Redeem the PTs to the SY token
            uint256 syRedeemed = IPendleYieldToken(yt).redeemPY(address(this));

            // Retreive the SY token from the PT
            address sy = IPendleToken(principal).SY();

            // Redeem the underlying by unwrapping the SY token
            IPendleSYToken(sy).redeem(address(this), syRedeemed, u, 0, false);
        } else if (p == uint8(MarketPlace.Principals.Tempus)) {
            // Retrieve the pool for the principal token
            address pool = ITempusToken(principal).pool();

            // Redeems principal tokens from Tempus
            ITempus(tempusAddr).redeemToBacking(pool, amount, 0, address(this));
        } else if (p == uint8(MarketPlace.Principals.Apwine)) {
            apwineWithdraw(principal, u, amount);
        } else if (p == uint8(MarketPlace.Principals.Notional)) {
            // Redeems principal tokens from Notional
            INotional(principal).redeem(
                IERC20(principal).balanceOf(address(this)),
                address(this),
                address(this)
            );
        }

        // Calculate how much underlying was redeemed
        uint256 redeemed = IERC20(u).balanceOf(address(this)) - starting;

        // Update the holding for this market
        holdings[u][m] = holdings[u][m] + redeemed;

        emit Redeem(p, u, m, redeemed, amount, msg.sender);
        return true;
    }

    /// @notice redeem method signature for Swivel
    /// @param p principal value according to the MarketPlace's Principals Enum
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @return bool true if the redemption was successful
    function redeem(
        uint8 p,
        address u,
        uint256 m,
        uint8 protocol
    ) external unpaused(u, m) returns (bool) {
        // Check the principal is Swivel
        if (p != uint8(MarketPlace.Principals.Swivel)) {
            revert Exception(6, p, 0, address(0), address(0));
        }

        // Get Swivel's principal token for this market
        address token = IMarketPlace(marketPlace).markets(u, m, p);

        // Get the maturity of the token
        uint256 maturity = ISwivelToken(token).maturity();

        // Verify that the token has matured
        if (maturity > block.timestamp) {
            revert Exception(7, maturity, 0, address(0), address(0));
        }

        // Cache the lender to save on SLOAD operations
        address cachedLender = lender;

        // Get the balance of tokens to be redeemed by the lenders
        uint256 amount = IERC20(token).balanceOf(cachedLender);

        // Transfer the lenders' tokens to the redeem contract
        Safe.transferFrom(IERC20(token), cachedLender, address(this), amount);

        // Get the starting balance to verify the amount received afterwards
        uint256 starting = IERC20(u).balanceOf(address(this));

        // Redeem principal tokens from Swivel
        if (!ISwivel(swivelAddr).redeemZcToken(protocol, u, maturity, amount)) {
            revert Exception(15, 0, 0, address(0), address(0));
        }

        // Retrieve unswapped premium from the Lender contract
        ILender(cachedLender).transferPremium(u, m);

        // Calculate how much underlying was redeemed
        uint256 redeemed = IERC20(u).balanceOf(address(this)) - starting;

        // Update the holding for this market
        holdings[u][m] = holdings[u][m] + redeemed;

        emit Redeem(p, u, m, redeemed, amount, msg.sender);
        return true;
    }

    /// @notice redeem method signature for Sense
    /// @param p principal value according to the MarketPlace's Principals Enum
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param s Sense's maturity is needed to extract the pt address
    /// @param a Sense's adapter index
    /// @param periphery Sense's periphery contract, used to get the verified adapter
    /// @return bool true if the redemption was successful
    function redeem(
        uint8 p,
        address u,
        uint256 m,
        uint256 s,
        uint256 a,
        address periphery
    ) external unpaused(u, m) returns (bool) {
        // Get Sense's principal token for this market
        IERC20 token = IERC20(
            IMarketPlace(marketPlace).markets(
                u,
                m,
                uint8(MarketPlace.Principals.Sense)
            )
        );

        // Confirm the periphery is verified by the lender
        if (IERC20(u).allowance(lender, periphery) == 0) {
            revert Exception(29, 0, 0, address(0), address(0));
        }

        // Cache the lender to save on SLOAD operations
        address cachedLender = lender;

        // Get the balance of tokens to be redeemed by the user
        uint256 amount = token.balanceOf(cachedLender);

        // Transfer the user's tokens to the redeem contract
        Safe.transferFrom(token, cachedLender, address(this), amount);

        // Calculate the balance of the redeemer contract
        uint256 redeemable = token.balanceOf(address(this));

        // Get the starting balance to verify the amount received afterwards
        uint256 starting = IERC20(u).balanceOf(address(this));

        // Get the existing balance of Sense PTs
        uint256 senseBalance = token.balanceOf(address(this));

        // Get the divider from the periphery
        ISenseDivider divider = ISenseDivider(
            ISensePeriphery(periphery).divider()
        );

        // Get the adapter from the divider
        address adapter = divider.adapterAddresses(a);

        // Redeem the tokens from the Sense contract
        ISenseDivider(divider).redeem(adapter, s, senseBalance);

        // Get the compounding token that is redeemed by Sense
        address compounding = ISenseAdapter(adapter).target();

        // Redeem the compounding token back to the underlying
        IConverter(converter).convert(
            compounding,
            u,
            IERC20(compounding).balanceOf(address(this))
        );

        // Get the amount received
        uint256 redeemed = IERC20(u).balanceOf(address(this)) - starting;

        // Update the holdings for this market
        holdings[u][m] = holdings[u][m] + redeemed;

        emit Redeem(p, u, m, redeemed, redeemable, msg.sender);
        return true;
    }

    /// @notice burns Illuminate principal tokens and sends underlying to user
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    function redeem(address u, uint256 m) external unpaused(u, m) {
        // Get Illuminate's principal token for this market
        IERC5095 token = IERC5095(
            IMarketPlace(marketPlace).markets(
                u,
                m,
                uint8(MarketPlace.Principals.Illuminate)
            )
        );

        // Verify the token has matured
        if (block.timestamp < token.maturity()) {
            revert Exception(7, block.timestamp, m, address(0), address(0));
        }

        // Get the amount of tokens to be redeemed from the sender
        uint256 amount = token.balanceOf(msg.sender);

        // Calculate how many tokens the user should receive
        uint256 redeemed = (amount * holdings[u][m]) / token.totalSupply();

        // Update holdings of underlying
        holdings[u][m] = holdings[u][m] - redeemed;

        // Burn the user's principal tokens
        token.authBurn(msg.sender, amount);

        // Transfer the original underlying token back to the user
        Safe.transfer(IERC20(u), msg.sender, redeemed);

        emit Redeem(0, u, m, redeemed, amount, msg.sender);
    }

    /// @notice implements the redeem method for the contract to fulfill the ERC-5095 interface
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param f address from where the underlying asset will be burned
    /// @param t address to where the underlying asset will be transferred
    /// @param a amount of the Illuminate PT to be burned and redeemed
    /// @return uint256 amount of the underlying asset that was burned
    function authRedeem(
        address u,
        uint256 m,
        address f,
        address t,
        uint256 a
    )
        external
        authorized(IMarketPlace(marketPlace).markets(u, m, 0))
        unpaused(u, m)
        returns (uint256)
    {
        // Get the principal token for the given market
        IERC5095 pt = IERC5095(IMarketPlace(marketPlace).markets(u, m, 0));

        // Make sure the market has matured
        uint256 maturity = pt.maturity();
        if (block.timestamp < maturity) {
            revert Exception(7, maturity, 0, address(0), address(0));
        }

        // Calculate the amount redeemed
        uint256 redeemed = (a * holdings[u][m]) / pt.totalSupply();

        // Update holdings of underlying
        holdings[u][m] = holdings[u][m] - redeemed;

        // Burn the user's principal tokens
        pt.authBurn(f, a);

        // Transfer the original underlying token back to the user
        Safe.transfer(IERC20(u), t, redeemed);

        emit Redeem(0, u, m, redeemed, a, msg.sender);
        return a;
    }

    /// @notice implements a redeem method to enable third-party redemptions
    /// @dev expects approvals from owners to redeemer
    /// @param u address of the underlying asset
    /// @param m maturity of the market
    /// @param f address from where the principal token will be burned
    /// @return uint256 amount of underlying yielded as a fee
    function autoRedeem(
        address u,
        uint256 m,
        address[] calldata f
    ) external unpaused(u, m) returns (uint256) {
        // Get the principal token for the given market
        IERC5095 pt = IERC5095(IMarketPlace(marketPlace).markets(u, m, 0));

        // Make sure the market has matured
        if (block.timestamp < pt.maturity()) {
            revert Exception(7, pt.maturity(), 0, address(0), address(0));
        }

        // Sum up the fees received by the caller
        uint256 incentiveFee;

        // Loop through the provided arrays and mature each individual position
        for (uint256 i; i != f.length; ) {
            // Fetch the allowance set by the holder of the principal tokens
            uint256 allowance = pt.allowance(f[i], address(this));

            // Get the amount of tokens held by the owner
            uint256 amount = pt.balanceOf(f[i]);

            // Calculate how many tokens the user should receive
            uint256 redeemed = (amount * holdings[u][m]) / pt.totalSupply();

            // Calculate the fees to be received
            uint256 fee = redeemed / feenominator;

            // Verify allowance
            if (allowance < amount) {
                revert Exception(20, allowance, amount, address(0), address(0));
            }

            // Burn the tokens from the user
            pt.authBurn(f[i], amount);

            // Reduce the allowance of the burned tokens
            pt.authApprove(f[i], address(this), 0);

            // Update the holdings for this market
            holdings[u][m] = holdings[u][m] - redeemed;

            // Transfer the underlying to the user
            Safe.transfer(IERC20(u), f[i], redeemed - fee);

            unchecked {
                // Track the fees gained by the caller
                incentiveFee += fee;

                ++i;
            }
        }

        // Transfer the fee to the caller
        Safe.transfer(IERC20(u), msg.sender, incentiveFee);

        return incentiveFee;
    }

    /// @notice Allows for external deposit of underlying for a market
    /// @notice This is to be used in emergency situations where the redeem method is not functioning for a market
    /// @param u address of the underlying asset
    /// @param m maturity of the market
    /// @param a amount of underlying to be deposited
    function depositHoldings(address u, uint256 m, uint256 a) external {
        // Receive the underlying asset from the admin
        Safe.transferFrom(IERC20(u), msg.sender, address(this), a);

        // Update the holdings
        holdings[u][m] += a;
    }

    /// @notice Execute the business logic for conducting an APWine redemption
    function apwineWithdraw(address p, address u, uint256 a) internal {
        // Retrieve the vault which executes the redemption in APWine
        address futureVault = IAPWineToken(p).futureVault();

        // Retrieve the controller that will execute the withdrawal
        address controller = IAPWineFutureVault(futureVault)
            .getControllerAddress();

        // Retrieve the next period index
        uint256 index = IAPWineFutureVault(futureVault).getCurrentPeriodIndex();

        // Get the FYT address for the current period
        address fyt = IAPWineFutureVault(futureVault).getFYTofPeriod(index);

        // Ensure there are sufficient FYTs to execute the redemption
        uint256 amount = IERC20(fyt).balanceOf(address(lender));

        // Get the minimum between the FYT and PT balance to redeem
        if (amount > a) {
            amount = a;
        }

        // Trigger claim to FYTs by executing transfer
        ILender(lender).transferFYTs(fyt, amount);

        // Redeem the underlying token from APWine to Illuminate
        IAPWineController(controller).withdraw(futureVault, amount);

        // Retrieve the interest bearing token
        address ibt = IAPWineFutureVault(futureVault).getIBTAddress();

        // Convert the interest bearing token to underlying
        IConverter(converter).convert(
            IAPWineFutureVault(futureVault).getIBTAddress(),
            u,
            IERC20(ibt).balanceOf(address(this))
        );
    }
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/tokens/ERC5095.sol';
import 'src/lib/Safe.sol';
import 'src/lib/RevertMsgExtractor.sol';
import 'src/errors/Exception.sol';

import 'src/interfaces/ILender.sol';
import 'src/interfaces/ICreator.sol';
import 'src/interfaces/IPool.sol';
import 'src/interfaces/IPendleToken.sol';
import 'src/interfaces/IAPWineToken.sol';
import 'src/interfaces/IAPWineFutureVault.sol';

/// @title MarketPlace
/// @author Sourabh Marathe, Julian Traversa, Rob Robbins
/// @notice This contract is in charge of managing the available principals for each loan market.
/// @notice In addition, this contract routes swap orders between Illuminate PTs and their respective underlying to YieldSpace pools.
contract MarketPlace {
    /// @notice the available principals
    /// @dev the order of this enum is used to select principals from the markets
    /// mapping (e.g. Illuminate => 0, Swivel => 1, and so on)
    enum Principals {
        Illuminate, // 0
        Swivel, // 1
        Yield, // 2
        Element, // 3
        Pendle, // 4
        Tempus, // 5
        Sense, // 6
        Apwine, // 7
        Notional // 8
    }

    /// @notice markets are defined by a tuple that points to a fixed length array of principal token addresses.
    mapping(address => mapping(uint256 => address[9])) public markets;

    /// @notice pools map markets to their respective YieldSpace pools for the MetaPrincipal token
    mapping(address => mapping(uint256 => address)) public pools;

    /// @notice address that is allowed to create markets, set pools, etc. It is commonly used in the authorized modifier.
    address public admin;
    /// @notice address of the deployed redeemer contract
    address public immutable redeemer;
    /// @notice address of the deployed lender contract
    address public immutable lender;
    /// @notice address of the deployed creator contract
    address public immutable creator;

    /// @notice emitted upon the creation of a new market
    event CreateMarket(
        address indexed underlying,
        uint256 indexed maturity,
        address[9] tokens,
        address element,
        address apwine
    );
    /// @notice emitted upon setting a principal token
    event SetPrincipal(
        address indexed underlying,
        uint256 indexed maturity,
        address indexed principal,
        uint8 protocol
    );
    /// @notice emitted upon swapping with the pool
    event Swap(
        address indexed underlying,
        uint256 indexed maturity,
        address sold,
        address bought,
        uint256 received,
        uint256 spent,
        address spender
    );
    /// @notice emitted upon minting tokens with the pool
    event Mint(
        address indexed underlying,
        uint256 indexed maturity,
        uint256 underlyingIn,
        uint256 principalTokensIn,
        uint256 minted,
        address minter
    );
    /// @notice emitted upon burning tokens with the pool
    event Burn(
        address indexed underlying,
        uint256 indexed maturity,
        uint256 tokensBurned,
        uint256 underlyingReceived,
        uint256 principalTokensReceived,
        address burner
    );
    /// @notice emitted upon changing the admin
    event SetAdmin(address indexed admin);
    /// @notice emitted upon setting a pool
    event SetPool(
        address indexed underlying,
        uint256 indexed maturity,
        address indexed pool
    );

    /// @notice ensures that only a certain address can call the function
    /// @param a address that msg.sender must be to be authorized
    modifier authorized(address a) {
        if (msg.sender != a) {
            revert Exception(0, 0, 0, msg.sender, a);
        }
        _;
    }

    /// @notice initializes the MarketPlace contract
    /// @param r address of the deployed redeemer contract
    /// @param l address of the deployed lender contract
    /// @param c address of the deployed creator contract
    constructor(address r, address l, address c) {
        admin = msg.sender;
        redeemer = r;
        lender = l;
        creator = c;
    }

    /// @notice creates a new market for the given underlying token and maturity
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param t principal token addresses for this market
    /// @param n name for the Illuminate token
    /// @param s symbol for the Illuminate token
    /// @param a address of the APWine router that corresponds to this market
    /// @param e address of the Element vault that corresponds to this market
    /// @param h address of a helper contract, used for Sense approvals if active in the market
    /// @param sensePeriphery address of the Sense periphery contract that must be approved by the lender
    /// @return bool true if successful
    function createMarket(
        address u,
        uint256 m,
        address[8] calldata t,
        string calldata n,
        string calldata s,
        address a,
        address e,
        address h,
        address sensePeriphery
    ) external authorized(admin) returns (bool) {
        {
            // Get the Illuminate principal token for this market (if one exists)
            address illuminate = markets[u][m][0];

            // If illuminate PT already exists, a new market cannot be created
            if (illuminate != address(0)) {
                revert Exception(9, 0, 0, illuminate, address(0));
            }
        }

        // Create an Illuminate principal token for the new market
        address illuminateToken = ICreator(creator).create(
            u,
            m,
            redeemer,
            lender,
            address(this),
            n,
            s
        );

        {
            // create the principal tokens array
            address[9] memory market = [
                illuminateToken, // Illuminate
                t[0], // Swivel
                t[1], // Yield
                t[2], // Element
                t[3], // Pendle
                t[4], // Tempus
                t[5], // Sense
                t[6], // APWine
                t[7] // Notional
            ];

            // Set the market
            markets[u][m] = market;

            // Have the lender contract approve the several contracts
            ILender(lender).approve(u, a, e, t[7], sensePeriphery);

            // Allow converter to spend interest bearing asset
            if (t[5] != address(0)) {
                IRedeemer(redeemer).approve(h);
            }

            // Approve interest bearing token conversion to underlying for APWine
            if (t[6] != address(0)) {
                address futureVault = IAPWineToken(t[6]).futureVault();
                address interestBearingToken = IAPWineFutureVault(futureVault)
                    .getIBTAddress();
                IRedeemer(redeemer).approve(interestBearingToken);
            }

            emit CreateMarket(u, m, market, e, a);
        }
        return true;
    }

    /// @notice allows the admin to set an individual market
    /// @param p principal value according to the MarketPlace's Principals Enum
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param a address of the new principal token
    /// @param h a supplementary address (apwine needs a router, element needs a vault, sense needs interest bearing asset)
    /// @param sensePeriphery address of the Sense periphery contract that must be approved by the lender
    /// @return bool true if the principal set, false otherwise
    function setPrincipal(
        uint8 p,
        address u,
        uint256 m,
        address a,
        address h,
        address sensePeriphery
    ) external authorized(admin) returns (bool) {
        // Set the principal token in the markets mapping
        markets[u][m][p] = a;

        if (p == uint8(Principals.Element)) {
            // Approve Element vault if setting Element's principal token
            ILender(lender).approve(u, address(0), h, address(0), address(0));
        } else if (p == uint8(Principals.Sense)) {
            // Approve converter to transfer yield token for Sense's redeem
            IRedeemer(redeemer).approve(h);

            // Approve Periphery to be used from Lender
            ILender(lender).approve(
                u,
                address(0),
                address(0),
                address(0),
                sensePeriphery
            );
        } else if (p == uint8(Principals.Apwine)) {
            // Approve converter to transfer yield token for APWine's redeem
            address futureVault = IAPWineToken(a).futureVault();
            address interestBearingToken = IAPWineFutureVault(futureVault)
                .getIBTAddress();
            IRedeemer(redeemer).approve(interestBearingToken);

            // Approve APWine's router if setting APWine's principal token
            ILender(lender).approve(u, h, address(0), address(0), address(0));
        } else if (p == uint8(Principals.Notional)) {
            // Principal token must be approved for Notional's lend
            ILender(lender).approve(u, address(0), address(0), a, address(0));
        }

        emit SetPrincipal(u, m, a, p);
        return true;
    }

    /// @notice sets the admin address
    /// @param a Address of a new admin
    /// @return bool true if the admin set, false otherwise
    function setAdmin(address a) external authorized(admin) returns (bool) {
        admin = a;
        emit SetAdmin(a);
        return true;
    }

    /// @notice sets the address for a pool
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param a address of the pool
    /// @return bool true if the pool set, false otherwise
    function setPool(
        address u,
        uint256 m,
        address a
    ) external authorized(admin) returns (bool) {
        // Set the pool
        pools[u][m] = a;

        // Get the principal token
        ERC5095 pt = ERC5095(markets[u][m][uint8(Principals.Illuminate)]);

        // Set the pool for the principal token
        pt.setPool(a);

        // Approve the marketplace to spend the principal and underlying tokens
        pt.approveMarketPlace();

        emit SetPool(u, m, a);
        return true;
    }

    /// @notice sells the PT for the underlying via the pool
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param a amount of PTs to sell
    /// @param s slippage cap, minimum amount of underlying that must be received
    /// @return uint128 amount of underlying bought
    function sellPrincipalToken(
        address u,
        uint256 m,
        uint128 a,
        uint128 s
    ) external returns (uint128) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Preview amount of underlying received by selling `a` PTs
        uint256 expected = pool.sellFYTokenPreview(a);

        // Verify that the amount needed does not exceed the slippage parameter
        if (expected < s) {
            revert Exception(16, expected, s, address(0), address(0));
        }

        // Transfer the principal tokens to the pool
        Safe.transferFrom(
            IERC20(address(pool.fyToken())),
            msg.sender,
            address(pool),
            a
        );

        // Execute the swap
        uint128 received = pool.sellFYToken(msg.sender, s);
        emit Swap(u, m, address(pool.fyToken()), u, received, a, msg.sender);

        return received;
    }

    /// @notice buys the PT for the underlying via the pool
    /// @notice determines how many underlying to sell by using the preview
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param a amount of PTs to be purchased
    /// @param s slippage cap, maximum number of underlying that can be sold
    /// @return uint128 amount of underlying sold
    function buyPrincipalToken(
        address u,
        uint256 m,
        uint128 a,
        uint128 s
    ) external returns (uint128) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Get the amount of base hypothetically required to purchase `a` PTs
        uint128 expected = pool.buyFYTokenPreview(a);

        // Verify that the amount needed does not exceed the slippage parameter
        if (expected > s) {
            revert Exception(16, expected, 0, address(0), address(0));
        }

        // Transfer the underlying tokens to the pool
        Safe.transferFrom(
            IERC20(pool.base()),
            msg.sender,
            address(pool),
            expected
        );

        // Execute the swap to purchase `a` base tokens
        uint128 spent = pool.buyFYToken(msg.sender, a, s);

        emit Swap(u, m, u, address(pool.fyToken()), a, spent, msg.sender);
        return spent;
    }

    /// @notice sells the underlying for the PT via the pool
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param a amount of underlying to sell
    /// @param s slippage cap, minimum number of PTs that must be received
    /// @return uint128 amount of PT purchased
    function sellUnderlying(
        address u,
        uint256 m,
        uint128 a,
        uint128 s
    ) external returns (uint128) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Get the number of PTs received for selling `a` underlying tokens
        uint128 expected = pool.sellBasePreview(a);

        // Verify slippage does not exceed the one set by the user
        if (expected < s) {
            revert Exception(16, expected, 0, address(0), address(0));
        }

        // Transfer the underlying tokens to the pool
        Safe.transferFrom(IERC20(pool.base()), msg.sender, address(pool), a);

        // Execute the swap
        uint128 received = pool.sellBase(msg.sender, s);

        emit Swap(u, m, u, address(pool.fyToken()), received, a, msg.sender);
        return received;
    }

    /// @notice buys the underlying for the PT via the pool
    /// @notice determines how many PTs to sell by using the preview
    /// @param u address of an underlying asset
    /// @param m maturity (timestamp) of the market
    /// @param a amount of underlying to be purchased
    /// @param s slippage cap, maximum number of PTs that can be sold
    /// @return uint128 amount of PTs sold
    function buyUnderlying(
        address u,
        uint256 m,
        uint128 a,
        uint128 s
    ) external returns (uint128) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Get the amount of PTs hypothetically required to purchase `a` underlying
        uint256 expected = pool.buyBasePreview(a);

        // Verify that the amount needed does not exceed the slippage parameter
        if (expected > s) {
            revert Exception(16, expected, 0, address(0), address(0));
        }

        // Transfer the principal tokens to the pool
        Safe.transferFrom(
            IERC20(address(pool.fyToken())),
            msg.sender,
            address(pool),
            expected
        );

        // Execute the swap to purchase `a` underlying tokens
        uint128 spent = pool.buyBase(msg.sender, a, s);

        emit Swap(u, m, address(pool.fyToken()), u, a, spent, msg.sender);
        return spent;
    }

    /// @notice mint liquidity tokens in exchange for adding underlying and PT
    /// @dev amount of liquidity tokens to mint is calculated from the amount of unaccounted for PT in this contract.
    /// @dev A proportional amount of underlying tokens need to be present in this contract, also unaccounted for.
    /// @param u the address of the underlying token
    /// @param m the maturity of the principal token
    /// @param b number of base tokens
    /// @param p the principal token amount being sent
    /// @param minRatio minimum ratio of LP tokens to PT in the pool.
    /// @param maxRatio maximum ratio of LP tokens to PT in the pool.
    /// @return uint256 number of base tokens passed to the method
    /// @return uint256 number of yield tokens passed to the method
    /// @return uint256 the amount of tokens minted.
    function mint(
        address u,
        uint256 m,
        uint256 b,
        uint256 p,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256, uint256) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Transfer the underlying tokens to the pool
        Safe.transferFrom(IERC20(pool.base()), msg.sender, address(pool), b);

        // Transfer the principal tokens to the pool
        Safe.transferFrom(
            IERC20(address(pool.fyToken())),
            msg.sender,
            address(pool),
            p
        );

        // Mint the tokens and return the leftover assets to the caller
        (uint256 underlyingIn, uint256 principalTokensIn, uint256 minted) = pool
            .mint(msg.sender, msg.sender, minRatio, maxRatio);

        emit Mint(u, m, underlyingIn, principalTokensIn, minted, msg.sender);
        return (underlyingIn, principalTokensIn, minted);
    }

    /// @notice Mint liquidity tokens in exchange for adding only underlying
    /// @dev amount of liquidity tokens is calculated from the amount of PT to buy from the pool,
    /// plus the amount of unaccounted for PT in this contract.
    /// @param u the address of the underlying token
    /// @param m the maturity of the principal token
    /// @param a the underlying amount being sent
    /// @param p amount of `PT` being bought in the Pool, from this we calculate how much underlying it will be taken in.
    /// @param minRatio minimum ratio of LP tokens to PT in the pool.
    /// @param maxRatio maximum ratio of LP tokens to PT in the pool.
    /// @return uint256 number of base tokens passed to the method
    /// @return uint256 number of yield tokens passed to the method
    /// @return uint256 the amount of tokens minted.
    function mintWithUnderlying(
        address u,
        uint256 m,
        uint256 a,
        uint256 p,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256, uint256) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Transfer the underlying tokens to the pool
        Safe.transferFrom(IERC20(pool.base()), msg.sender, address(pool), a);

        // Mint the tokens to the user
        (uint256 underlyingIn, , uint256 minted) = pool.mintWithBase(
            msg.sender,
            msg.sender,
            p,
            minRatio,
            maxRatio
        );

        emit Mint(u, m, underlyingIn, 0, minted, msg.sender);
        return (underlyingIn, 0, minted);
    }

    /// @notice burn liquidity tokens in exchange for underlying and PT.
    /// @param u the address of the underlying token
    /// @param m the maturity of the principal token
    /// @param a the amount of liquidity tokens to burn
    /// @param minRatio minimum ratio of LP tokens to PT in the pool
    /// @param maxRatio maximum ratio of LP tokens to PT in the pool
    /// @return uint256 amount of LP tokens burned
    /// @return uint256 amount of base tokens received
    /// @return uint256 amount of fyTokens received
    function burn(
        address u,
        uint256 m,
        uint256 a,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256, uint256) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Transfer the underlying tokens to the pool
        Safe.transferFrom(IERC20(address(pool)), msg.sender, address(pool), a);

        // Burn the tokens
        (
            uint256 tokensBurned,
            uint256 underlyingReceived,
            uint256 principalTokensReceived
        ) = pool.burn(msg.sender, msg.sender, minRatio, maxRatio);

        emit Burn(
            u,
            m,
            tokensBurned,
            underlyingReceived,
            principalTokensReceived,
            msg.sender
        );
        return (tokensBurned, underlyingReceived, principalTokensReceived);
    }

    /// @notice burn liquidity tokens in exchange for underlying.
    /// @param u the address of the underlying token
    /// @param m the maturity of the principal token
    /// @param a the amount of liquidity tokens to burn
    /// @param minRatio minimum ratio of LP tokens to PT in the pool.
    /// @param maxRatio minimum ratio of LP tokens to PT in the pool.
    /// @return uint256 amount of PT tokens sent to the pool
    /// @return uint256 amount of underlying tokens returned
    function burnForUnderlying(
        address u,
        uint256 m,
        uint256 a,
        uint256 minRatio,
        uint256 maxRatio
    ) external returns (uint256, uint256) {
        // Get the pool for the market
        IPool pool = IPool(pools[u][m]);

        // Transfer the underlying tokens to the pool
        Safe.transferFrom(IERC20(address(pool)), msg.sender, address(pool), a);

        // Burn the tokens in exchange for underlying tokens
        (uint256 tokensBurned, uint256 underlyingReceived) = pool.burnForBase(
            msg.sender,
            minRatio,
            maxRatio
        );

        emit Burn(u, m, tokensBurned, underlyingReceived, 0, msg.sender);
        return (tokensBurned, underlyingReceived);
    }

    /// @notice Allows batched call to self (this contract).
    /// @param c An array of inputs for each call.
    function batch(
        bytes[] calldata c
    ) external payable returns (bytes[] memory results) {
        results = new bytes[](c.length);
        for (uint256 i; i < c.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(
                c[i]
            );
            if (!success) revert(RevertMsgExtractor.getRevertMsg(result));
            results[i] = result;
        }
    }
}

File 3 of 40 : Exception.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

/// @dev A single custom error capable of indicating a wide range of detected errors by providing
/// an error code value whose string representation is documented in errors.txt, and any possible other values
/// that are pertinent to the error.
error Exception(uint8, uint256, uint256, address, address);

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IAPWineController {
    function getNextPeriodStart(uint256) external view returns (uint256);

    function withdraw(address, uint256) external;

    function createFYTDelegationTo(
        address,
        address,
        uint256
    ) external;
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IAPWineFutureVault {
    function PERIOD_DURATION() external view returns (uint256);

    function getControllerAddress() external view returns (address);

    function getCurrentPeriodIndex() external view returns (uint256);

    function getFYTofPeriod(uint256) external view returns (address);

    function getIBTAddress() external view returns (address);

    function startNewPeriod() external;
}

File 6 of 40 : IAPWineToken.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IAPWineToken {
    function futureVault() external view returns (address);
}

File 7 of 40 : IAny.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IAny {}

File 8 of 40 : IConverter.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IConverter {
    function convert(
        address,
        address,
        uint256
    ) external;
}

File 9 of 40 : ICreator.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ICreator {
    function create(
        address,
        uint256,
        address,
        address,
        address,
        string calldata,
        string calldata
    ) external returns (address);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount)
        external
        returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Returns the number of decimals the token uses - e.g. 8, means to
     * divide the token amount by 100000000 to get its user representation.
     */
    function decimals() external view returns (uint8);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC20.sol';

interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC20Metadata.sol';

/**
 * @dev Interface of the ERC2612 standard as defined in the EIP.
 *
 * Adds the {permit} method, which can be used to change one's
 * {IERC20-allowance} without having to send a transaction, by signing a
 * message. This allows users to spend tokens without having to hold Ether.
 *
 * See https://eips.ethereum.org/EIPS/eip-2612.
 */
interface IERC2612 is IERC20Metadata {
    /**
     * @dev Sets `amount` as the allowance of `spender` over `owner`'s tokens,
     * given `owner`'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current ERC2612 nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC2612.sol';

interface IERC5095 is IERC2612 {
    function maturity() external view returns (uint256);

    function underlying() external view returns (address);

    function convertToUnderlying(uint256) external view returns (uint256);

    function convertToShares(uint256) external view returns (uint256);

    function maxRedeem(address) external view returns (uint256);

    function previewRedeem(uint256) external view returns (uint256);

    function maxWithdraw(address) external view returns (uint256);

    function previewWithdraw(uint256) external view returns (uint256);

    function previewDeposit(uint256) external view returns (uint256);

    function withdraw(
        uint256,
        address,
        address
    ) external returns (uint256);

    function redeem(
        uint256,
        address,
        address
    ) external returns (uint256);

    function deposit(uint256, address) external returns (uint256);

    function mint(uint256, address) external returns (uint256);

    function authMint(address, uint256) external returns (bool);

    function authBurn(address, uint256) external returns (bool);

    function authApprove(
        address,
        address,
        uint256
    ) external returns (bool);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IElementToken {
    function unlockTimestamp() external view returns (uint256);

    function underlying() external returns (address);

    function withdrawPrincipal(uint256 amount, address destination)
        external
        returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ILender {
    function approve(
        address,
        address,
        address,
        address,
        address
    ) external;

    function transferFYTs(address, uint256) external;

    function transferPremium(address, uint256) external;

    function paused(uint8) external returns (bool);

    function halted() external returns (bool);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IMarketPlace {
    function markets(
        address,
        uint256,
        uint256
    ) external returns (address);

    function pools(address, uint256) external view returns (address);

    function sellPrincipalToken(
        address,
        uint256,
        uint128,
        uint128
    ) external returns (uint128);

    function buyPrincipalToken(
        address,
        uint256,
        uint128,
        uint128
    ) external returns (uint128);

    function sellUnderlying(
        address,
        uint256,
        uint128,
        uint128
    ) external returns (uint128);

    function buyUnderlying(
        address,
        uint256,
        uint128,
        uint128
    ) external returns (uint128);

    function redeemer() external view returns (address);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC20.sol';

interface INotional {
    function getUnderlyingToken() external view returns (IERC20, int256);

    function getMaturity() external view returns (uint40);

    function deposit(uint256, address) external returns (uint256);

    function maxRedeem(address) external returns (uint256);

    function redeem(
        uint256,
        address,
        address
    ) external returns (uint256);
}

File 18 of 40 : IPendleSYToken.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IPendleSYToken {
    function redeem(
        address,
        uint256,
        address,
        uint256,
        bool
    ) external returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IPendleToken {
    function SY() external view returns (address);

    function YT() external view returns (address);

    function expiry() external view returns (uint256);
}

File 20 of 40 : IPendleYieldToken.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IPendleYieldToken {
    function redeemPY(address) external returns (uint256);
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

import 'src/interfaces/IERC20.sol';
import 'src/interfaces/IERC5095.sol';

interface IPool {
    function ts() external view returns (int128);

    function g1() external view returns (int128);

    function g2() external view returns (int128);

    function maturity() external view returns (uint32);

    function scaleFactor() external view returns (uint96);

    function getCache()
        external
        view
        returns (
            uint112,
            uint112,
            uint32
        );

    // NOTE This will be deprecated
    function base() external view returns (IERC20);

    function baseToken() external view returns (address);

    function fyToken() external view returns (IERC5095);

    function getBaseBalance() external view returns (uint112);

    function getFYTokenBalance() external view returns (uint112);

    function retrieveBase(address) external returns (uint128 retrieved);

    function retrieveFYToken(address) external returns (uint128 retrieved);

    function sellBase(address, uint128) external returns (uint128);

    function buyBase(
        address,
        uint128,
        uint128
    ) external returns (uint128);

    function sellFYToken(address, uint128) external returns (uint128);

    function buyFYToken(
        address,
        uint128,
        uint128
    ) external returns (uint128);

    function sellBasePreview(uint128) external view returns (uint128);

    function buyBasePreview(uint128) external view returns (uint128);

    function sellFYTokenPreview(uint128) external view returns (uint128);

    function buyFYTokenPreview(uint128) external view returns (uint128);

    function mint(
        address,
        address,
        uint256,
        uint256
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function mintWithBase(
        address,
        address,
        uint256,
        uint256,
        uint256
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function burn(
        address,
        address,
        uint256,
        uint256
    )
        external
        returns (
            uint256,
            uint256,
            uint256
        );

    function burnForBase(
        address,
        uint256,
        uint256
    ) external returns (uint256, uint256);

    function cumulativeBalancesRatio() external view returns (uint256);

    function sync() external;
}

// SPDX-License-Identifier: MIT

pragma solidity 0.8.16;

interface IRedeemer {
    function authRedeem(
        address underlying,
        uint256 maturity,
        address from,
        address to,
        uint256 amount
    ) external returns (uint256);

    function approve(address p) external;

    function holdings(address u, uint256 m) external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ISenseAdapter {
    function underlying() external view returns (address);

    function divider() external view returns (address);

    function target() external view returns (address);

    function maxm() external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ISenseDivider {
    function redeem(
        address,
        uint256,
        uint256
    ) external returns (uint256);

    function pt(address, uint256) external view returns (address);

    // only used by integration tests
    function settleSeries(address, uint256) external;

    function adapterAddresses(uint256) external view returns (address);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ISensePeriphery {
    function divider() external view returns (address);

    function swapUnderlyingForPTs(
        address,
        uint256,
        uint256,
        uint256
    ) external returns (uint256);

    function verified(address) external view returns (bool);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/lib/Swivel.sol';

interface ISwivel {
    function initiate(
        Swivel.Order[] calldata,
        uint256[] calldata,
        Swivel.Components[] calldata
    ) external returns (bool);

    function redeemZcToken(
        uint8 p,
        address u,
        uint256 m,
        uint256 a
    ) external returns (bool);
}

File 27 of 40 : ISwivelToken.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ISwivelToken {
    function maturity() external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC20Metadata.sol';
import 'src/interfaces/IAny.sol';

interface ITempus {
    function depositAndFix(
        address,
        uint256,
        bool,
        uint256,
        uint256
    ) external;

    function redeemToBacking(
        address,
        uint256,
        uint256,
        address
    ) external;
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC20Metadata.sol';

interface ITempusPool {
    function maturityTime() external view returns (uint256);

    function backingToken() external view returns (address);

    function controller() external view returns (address);

    // Used for integration testing
    function principalShare() external view returns (address);

    function currentInterestRate() external view returns (uint256);

    function initialInterestRate() external view returns (uint256);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface ITempusToken {
    function balanceOf(address) external returns (uint256);

    function pool() external view returns (address);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC20.sol';

interface IYield {
    function maturity() external view returns (uint32);

    function base() external view returns (IERC20);

    function sellBase(address, uint128) external returns (uint128);

    function sellBasePreview(uint128) external view returns (uint128);

    function fyToken() external returns (address);

    function sellFYToken(address, uint128) external returns (uint128);

    function sellFYTokenPreview(uint128) external view returns (uint128);

    function buyBase(
        address,
        uint128,
        uint128
    ) external returns (uint128);

    function buyBasePreview(uint128) external view returns (uint128);

    function buyFYToken(
        address,
        uint128,
        uint128
    ) external returns (uint128);

    function buyFYTokenPreview(uint128) external view returns (uint128);
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

interface IYieldToken {
    function redeem(address, uint256) external returns (uint256);

    function underlying() external returns (address);

    function maturity() external view returns (uint256);
}

File 33 of 40 : Cast.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

library Cast {
    /// @dev Safely cast an uint256 to an uint128
    /// @param n the u256 to cast to u128
    function u128(uint256 n) internal pure returns (uint128) {
        if (n > type(uint128).max) {
            revert();
        }
        return uint128(n);
    }
}

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.16;

import 'src/interfaces/IERC5095.sol';
import 'src/interfaces/ISwivelToken.sol';
import 'src/interfaces/IYieldToken.sol';
import 'src/interfaces/IElementToken.sol';
import 'src/interfaces/IPendleToken.sol';
import 'src/interfaces/ITempusToken.sol';
import 'src/interfaces/ITempusPool.sol';
import 'src/interfaces/IAPWineToken.sol';
import 'src/interfaces/IAPWineFutureVault.sol';
import 'src/interfaces/IAPWineController.sol';
import 'src/interfaces/INotional.sol';

library Maturities {
    /// @notice returns the maturity for an Illumiante principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function illuminate(address p) internal view returns (uint256) {
        return IERC5095(p).maturity();
    }

    /// @notice returns the maturity for a Swivel principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function swivel(address p) internal view returns (uint256) {
        return ISwivelToken(p).maturity();
    }

    function yield(address p) internal view returns (uint256) {
        return IYieldToken(p).maturity();
    }

    /// @notice returns the maturity for an Element principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function element(address p) internal view returns (uint256) {
        return IElementToken(p).unlockTimestamp();
    }

    /// @notice returns the maturity for a Pendle principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function pendle(address p) internal view returns (uint256) {
        return IPendleToken(p).expiry();
    }

    /// @notice returns the maturity for a Tempus principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function tempus(address p) internal view returns (uint256) {
        return ITempusPool(ITempusToken(p).pool()).maturityTime();
    }

    /// @notice returns the maturity for a APWine principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function apwine(address p) internal view returns (uint256) {
        address futureVault = IAPWineToken(p).futureVault();

        address controller = IAPWineFutureVault(futureVault)
            .getControllerAddress();

        uint256 duration = IAPWineFutureVault(futureVault).PERIOD_DURATION();

        return IAPWineController(controller).getNextPeriodStart(duration);
    }

    /// @notice returns the maturity for a Notional principal token
    /// @param p address of the principal token contract
    /// @return uint256 maturity of the principal token
    function notional(address p) internal view returns (uint256) {
        return INotional(p).getMaturity();
    }
}

// SPDX-License-Identifier: MIT
// Taken from https://github.com/sushiswap/BoringSolidity/blob/441e51c0544cf2451e6116fe00515e71d7c42e2c/contracts/BoringBatchable.sol

pragma solidity >=0.6.0;

library RevertMsgExtractor {
    /// @dev Helper function to extract a useful revert message from a failed call.
    /// If the returned data is malformed or not correctly abi encoded then this call can fail itself.
    function getRevertMsg(bytes memory returnData)
        internal
        pure
        returns (string memory)
    {
        // If the _res length is less than 68, then the transaction failed silently (without a revert message)
        if (returnData.length < 68) return 'Transaction reverted silently';

        assembly {
            // Slice the sighash.
            returnData := add(returnData, 0x04)
        }
        return abi.decode(returnData, (string)); // All that remains is the revert string
    }
}

// SPDX-License-Identifier: UNLICENSED
// Adapted from: https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol

pragma solidity ^0.8.13;

import 'src/interfaces/IERC20.sol';

/**
  @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
  @author Modified from Gnosis (https://github.com/gnosis/gp-v2-contracts/blob/main/src/contracts/libraries/GPv2SafeERC20.sol)
  @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
*/

library Safe {
    /// @param e Erc20 token to execute the call with
    /// @param t To address
    /// @param a Amount being transferred
    function transfer(
        IERC20 e,
        address t,
        uint256 a
    ) internal {
        bool result;

        assembly {
            // Get a pointer to some free memory.
            let pointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                pointer,
                0xa9059cbb00000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(pointer, 4),
                and(t, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(pointer, 36), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            result := call(gas(), e, 0, pointer, 68, 0, 0)
        }

        require(success(result), 'transfer failed');
    }

    /// @param e Erc20 token to execute the call with
    /// @param f From address
    /// @param t To address
    /// @param a Amount being transferred
    function transferFrom(
        IERC20 e,
        address f,
        address t,
        uint256 a
    ) internal {
        bool result;

        assembly {
            // Get a pointer to some free memory.
            let pointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                pointer,
                0x23b872dd00000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(pointer, 4),
                and(f, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "from" argument.
            mstore(
                add(pointer, 36),
                and(t, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(pointer, 68), a) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 100 because the calldata length is 4 + 32 * 3.
            result := call(gas(), e, 0, pointer, 100, 0, 0)
        }

        require(success(result), 'transfer from failed');
    }

    /// @notice normalize the acceptable values of true or null vs the unacceptable value of false (or something malformed)
    /// @param r Return value from the assembly `call()` to Erc20['selector']
    function success(bool r) private pure returns (bool) {
        bool result;

        assembly {
            // Get how many bytes the call returned.
            let returnDataSize := returndatasize()

            // If the call reverted:
            if iszero(r) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }

            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                result := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                result := 1
            }
            default {
                // It returned some malformed input.
                result := 0
            }
        }

        return result;
    }

    function approve(
        IERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool callStatus;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata to memory piece by piece:
            mstore(
                freeMemoryPointer,
                0x095ea7b300000000000000000000000000000000000000000000000000000000
            ) // Begin with the function selector.
            mstore(
                add(freeMemoryPointer, 4),
                and(to, 0xffffffffffffffffffffffffffffffffffffffff)
            ) // Mask and append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Finally append the "amount" argument. No mask as it's a full 32 byte value.

            // Call the token and store if it succeeded or not.
            // We use 68 because the calldata length is 4 + 32 * 2.
            callStatus := call(gas(), token, 0, freeMemoryPointer, 68, 0, 0)
        }

        require(didLastOptionalReturnCallSucceed(callStatus), 'APPROVE_FAILED');
    }

    /*///////////////////////////////////////////////////////////////
                         INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function didLastOptionalReturnCallSucceed(bool callStatus)
        private
        pure
        returns (bool)
    {
        bool result;
        assembly {
            // Get how many bytes the call returned.
            let returnDataSize := returndatasize()

            // If the call reverted:
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }

            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                result := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                result := 1
            }
            default {
                // It returned some malformed input.
                result := 0
            }
        }

        return result;
    }
}

File 37 of 40 : Swivel.sol
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

library Swivel {
    // the components of a ECDSA signature
    struct Components {
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    struct Order {
        bytes32 key;
        uint8 protocol;
        address maker;
        address underlying;
        bool vault;
        bool exit;
        uint256 principal;
        uint256 premium;
        uint256 maturity;
        uint256 expiry;
    }
}

// SPDX-License-Identifier: MIT
// Inspired on token.sol from DappHub. Natspec adpated from OpenZeppelin.
pragma solidity 0.8.16;

import 'src/interfaces/IERC20Metadata.sol';

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * We have followed general OpenZeppelin guidelines: functions revert instead
 * of returning `false` on failure. This behavior is nonetheless conventional
 * and does not conflict with the expectations of ERC20 applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Calls to {transferFrom} do not check for allowance if the caller is the owner
 * of the funds. This allows to reduce the number of approvals that are necessary.
 *
 * Finally, {transferFrom} does not decrease the allowance if it is set to
 * type(uint256).max. This reduces the gas costs without any likely impact.
 */
contract ERC20 is IERC20Metadata {
    uint256 internal _totalSupply;
    mapping(address => uint256) internal _balanceOf;
    mapping(address => mapping(address => uint256)) internal _allowance;
    string public override name = '???';
    string public override symbol = '???';
    uint8 public override decimals = 18;

    /**
     *  @dev Sets the values for {name}, {symbol} and {decimals}.
     */
    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_
    ) {
        name = name_;
        symbol = symbol_;
        decimals = decimals_;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address guy)
        external
        view
        virtual
        override
        returns (uint256)
    {
        return _balanceOf[guy];
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender)
        external
        view
        virtual
        override
        returns (uint256)
    {
        return _allowance[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     */
    function approve(address spender, uint256 wad)
        external
        virtual
        override
        returns (bool)
    {
        return _setAllowance(msg.sender, spender, wad);
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - the caller must have a balance of at least `wad`.
     */
    function transfer(address dst, uint256 wad)
        external
        virtual
        override
        returns (bool)
    {
        return _transfer(msg.sender, dst, wad);
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * Requirements:
     *
     * - `src` must have a balance of at least `wad`.
     * - the caller is not `src`, it must have allowance for ``src``'s tokens of at least
     * `wad`.
     */
    /// if_succeeds {:msg "TransferFrom - decrease allowance"} msg.sender != src ==> old(_allowance[src][msg.sender]) >= wad;
    function transferFrom(
        address src,
        address dst,
        uint256 wad
    ) external virtual override returns (bool) {
        _decreaseAllowance(src, wad);

        return _transfer(src, dst, wad);
    }

    /**
     * @dev Moves tokens `wad` from `src` to `dst`.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `src` must have a balance of at least `amount`.
     */
    /// if_succeeds {:msg "Transfer - src decrease"} old(_balanceOf[src]) >= _balanceOf[src];
    /// if_succeeds {:msg "Transfer - dst increase"} _balanceOf[dst] >= old(_balanceOf[dst]);
    /// if_succeeds {:msg "Transfer - supply"} old(_balanceOf[src]) + old(_balanceOf[dst]) == _balanceOf[src] + _balanceOf[dst];
    function _transfer(
        address src,
        address dst,
        uint256 wad
    ) internal virtual returns (bool) {
        require(_balanceOf[src] >= wad, 'ERC20: Insufficient balance');
        unchecked {
            _balanceOf[src] = _balanceOf[src] - wad;
        }
        _balanceOf[dst] = _balanceOf[dst] + wad;

        emit Transfer(src, dst, wad);

        return true;
    }

    /**
     * @dev Sets the allowance granted to `spender` by `owner`.
     *
     * Emits an {Approval} event indicating the updated allowance.
     */
    function _setAllowance(
        address owner,
        address spender,
        uint256 wad
    ) internal virtual returns (bool) {
        _allowance[owner][spender] = wad;
        emit Approval(owner, spender, wad);

        return true;
    }

    /**
     * @dev Decreases the allowance granted to the caller by `src`, unless src == msg.sender or _allowance[src][msg.sender] == MAX
     *
     * Emits an {Approval} event indicating the updated allowance, if the allowance is updated.
     *
     * Requirements:
     *
     * - `spender` must have allowance for the caller of at least
     * `wad`, unless src == msg.sender
     */
    /// if_succeeds {:msg "Decrease allowance - underflow"} old(_allowance[src][msg.sender]) <= _allowance[src][msg.sender];
    function _decreaseAllowance(address src, uint256 wad)
        internal
        virtual
        returns (bool)
    {
        if (src != msg.sender) {
            uint256 allowed = _allowance[src][msg.sender];
            if (allowed != type(uint256).max) {
                require(allowed >= wad, 'ERC20: Insufficient approval');
                unchecked {
                    _setAllowance(src, msg.sender, allowed - wad);
                }
            }
        }

        return true;
    }

    /** @dev Creates `wad` tokens and assigns them to `dst`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     */
    /// if_succeeds {:msg "Mint - balance overflow"} old(_balanceOf[dst]) >= _balanceOf[dst];
    /// if_succeeds {:msg "Mint - supply overflow"} old(_totalSupply) >= _totalSupply;
    function _mint(address dst, uint256 wad) internal virtual returns (bool) {
        _balanceOf[dst] = _balanceOf[dst] + wad;
        _totalSupply = _totalSupply + wad;
        emit Transfer(address(0), dst, wad);

        return true;
    }

    /**
     * @dev Destroys `wad` tokens from `src`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `src` must have at least `wad` tokens.
     */
    /// if_succeeds {:msg "Burn - balance underflow"} old(_balanceOf[src]) <= _balanceOf[src];
    /// if_succeeds {:msg "Burn - supply underflow"} old(_totalSupply) <= _totalSupply;
    function _burn(address src, uint256 wad) internal virtual returns (bool) {
        unchecked {
            require(_balanceOf[src] >= wad, 'ERC20: Insufficient balance');
            _balanceOf[src] = _balanceOf[src] - wad;
            _totalSupply = _totalSupply - wad;
            emit Transfer(src, address(0), wad);
        }

        return true;
    }
}

// SPDX-License-Identifier: MIT
// Adapted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/53516bc555a454862470e7860a9b5254db4d00f5/contracts/token/ERC20/ERC20Permit.sol
pragma solidity 0.8.16;

import 'src/tokens/ERC20.sol';
import 'src/interfaces/IERC2612.sol';

/**
 * @dev Extension of {ERC20} that allows token holders to use their tokens
 * without sending any transactions by setting {IERC20-allowance} with a
 * signature using the {permit} method, and then spend them via
 * {IERC20-transferFrom}.
 *
 * The {permit} signature mechanism conforms to the {IERC2612} interface.
 */
abstract contract ERC20Permit is ERC20, IERC2612 {
    mapping(address => uint256) public override nonces;

    bytes32 public immutable PERMIT_TYPEHASH =
        keccak256(
            'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'
        );
    bytes32 private immutable _DOMAIN_SEPARATOR;
    uint256 public immutable deploymentChainId;

    constructor(
        string memory name_,
        string memory symbol_,
        uint8 decimals_
    ) ERC20(name_, symbol_, decimals_) {
        deploymentChainId = block.chainid;
        _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
    }

    /// @dev Calculate the DOMAIN_SEPARATOR.
    function _calculateDomainSeparator(uint256 chainId)
        private
        view
        returns (bytes32)
    {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
                    ),
                    keccak256(bytes(name)),
                    keccak256(bytes(version())),
                    chainId,
                    address(this)
                )
            );
    }

    /// @dev Return the DOMAIN_SEPARATOR.
    function DOMAIN_SEPARATOR() external view returns (bytes32) {
        return
            block.chainid == deploymentChainId
                ? _DOMAIN_SEPARATOR
                : _calculateDomainSeparator(block.chainid);
    }

    /// @dev Setting the version as a function so that it can be overriden
    function version() public pure virtual returns (string memory) {
        return '1';
    }

    /**
     * @dev See {IERC2612-permit}.
     *
     * In cases where the free option is not a concern, deadline can simply be
     * set to uint(-1), so it should be seen as an optional parameter
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual override {
        require(deadline >= block.timestamp, 'ERC20Permit: expired deadline');

        bytes32 hashStruct = keccak256(
            abi.encode(
                PERMIT_TYPEHASH,
                owner,
                spender,
                amount,
                nonces[owner]++,
                deadline
            )
        );

        bytes32 hash = keccak256(
            abi.encodePacked(
                '\x19\x01',
                block.chainid == deploymentChainId
                    ? _DOMAIN_SEPARATOR
                    : _calculateDomainSeparator(block.chainid),
                hashStruct
            )
        );

        address signer = ecrecover(hash, v, r, s);
        require(
            signer != address(0) && signer == owner,
            'ERC20Permit: invalid signature'
        );

        _setAllowance(owner, spender, amount);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import 'src/tokens/ERC20Permit.sol';
import 'src/interfaces/IERC5095.sol';
import 'src/interfaces/IRedeemer.sol';
import 'src/interfaces/IMarketPlace.sol';
import 'src/interfaces/IYield.sol';
import 'src/errors/Exception.sol';

import 'src/lib/Cast.sol';
import 'src/lib/Safe.sol';

contract ERC5095 is ERC20Permit, IERC5095 {
    /// @dev unix timestamp when the ERC5095 token can be redeemed
    uint256 public immutable override maturity;
    /// @dev address of the ERC20 token that is returned on ERC5095 redemption
    address public immutable override underlying;
    /// @dev address of the minting authority
    address public immutable lender;
    /// @dev address of the "marketplace" YieldSpace AMM router
    address public immutable marketplace;
    ///@dev Interface to interact with the pool
    address public pool;

    /// @dev address and interface for an external custody contract (necessary for some project's backwards compatability)
    address public immutable redeemer;

    /// @notice ensures that only a certain address can call the function
    /// @param a address that msg.sender must be to be authorized
    modifier authorized(address a) {
        if (msg.sender != a) {
            revert Exception(0, 0, 0, msg.sender, a);
        }
        _;
    }

    constructor(
        address _underlying,
        uint256 _maturity,
        address _redeemer,
        address _lender,
        address _marketplace,
        string memory name_,
        string memory symbol_,
        uint8 decimals_
    ) ERC20Permit(name_, symbol_, decimals_) {
        underlying = _underlying;
        maturity = _maturity;
        redeemer = _redeemer;
        lender = _lender;
        marketplace = _marketplace;
        pool = address(0);
    }

    /// @notice Allows the marketplace to set the pool
    /// @param p Address of the pool
    /// @return bool True if successful
    function setPool(address p)
        external
        authorized(marketplace)
        returns (bool)
    {
        pool = p;
        return true;
    }

    /// @notice Allows the marketplace to spend underlying, principal tokens held by the token
    /// @dev This is necessary when MarketPlace calls pool methods to swap tokens
    /// @return True if successful
    function approveMarketPlace()
        external
        authorized(marketplace)
        returns (bool)
    {
        // Approve the marketplace to spend the token's underlying
        Safe.approve(IERC20(underlying), marketplace, type(uint256).max);

        // Approve the marketplace to spend illuminate PTs
        Safe.approve(IERC20(address(this)), marketplace, type(uint256).max);

        return true;
    }

    /// @notice Post or at maturity, converts an amount of principal tokens to an amount of underlying that would be returned.
    /// @param s The amount of principal tokens to convert
    /// @return uint256 The amount of underlying tokens returned by the conversion
    function convertToUnderlying(uint256 s)
        external
        view
        override
        returns (uint256)
    {
        if (block.timestamp < maturity) {
            return previewRedeem(s);
        }
        return s;
    }

    /// @notice Post or at maturity, converts a desired amount of underlying tokens returned to principal tokens needed.
    /// @param a The amount of underlying tokens to convert
    /// @return uint256 The amount of principal tokens returned by the conversion
    function convertToShares(uint256 a)
        external
        view
        override
        returns (uint256)
    {
        if (block.timestamp < maturity) {
            return previewWithdraw(a);
        }
        return a;
    }

    /// @notice Returns user's PT balance
    /// @param o The address of the owner for which redemption is calculated
    /// @return uint256 The maximum amount of principal tokens that `owner` can redeem.
    function maxRedeem(address o) external view override returns (uint256) {
        return _balanceOf[o];
    }

    /// @notice Post or at maturity, returns user's PT balance. Prior to maturity, returns a previewRedeem for owner's PT balance.
    /// @param  o The address of the owner for which withdrawal is calculated
    /// @return uint256 maximum amount of underlying tokens that `owner` can withdraw.
    function maxWithdraw(address o) external view override returns (uint256) {
        if (block.timestamp < maturity) {
            return previewRedeem(_balanceOf[o]);
        }
        return _balanceOf[o];
    }

    /// @notice After maturity, returns 0. Prior to maturity, returns the amount of `shares` when spending `a` in underlying on a YieldSpace AMM.
    /// @param a The amount of underlying spent
    /// @return uint256 The amount of PT purchased by spending `a` of underlying
    function previewDeposit(uint256 a) public view returns (uint256) {
        if (block.timestamp < maturity) {
            return IYield(pool).sellBasePreview(Cast.u128(a));
        }
        return 0;
    }

    /// @notice After maturity, returns 0. Prior to maturity, returns the amount of `assets` in underlying spent on a purchase of `s` in PT on a YieldSpace AMM.
    /// @param s The amount of principal tokens bought in the simulation
    /// @return uint256 The amount of underlying required to purchase `s` of PT
    function previewMint(uint256 s) public view returns (uint256) {
        if (block.timestamp < maturity) {
            return IYield(pool).buyFYTokenPreview(Cast.u128(s));
        }
        return 0;
    }

    /// @notice Post or at maturity, simulates the effects of redemption. Prior to maturity, returns the amount of `assets` from a sale of `s` PTs on a YieldSpace AMM.
    /// @param s The amount of principal tokens redeemed in the simulation
    /// @return uint256 The amount of underlying returned by `s` of PT redemption
    function previewRedeem(uint256 s) public view override returns (uint256) {
        if (block.timestamp >= maturity) {
            // After maturity, the amount redeemed is based on the Redeemer contract's holdings of the underlying
            return
                Cast.u128(
                    s *
                        Cast.u128(
                            IRedeemer(redeemer).holdings(underlying, maturity)
                        )
                ) / _totalSupply;
        }

        // Prior to maturity, return a a preview of a swap on the pool
        return IYield(pool).sellFYTokenPreview(Cast.u128(s));
    }

    /// @notice Post or at maturity, simulates the effects of withdrawal at the current block. Prior to maturity, simulates the amount of PTs necessary to receive `a` in underlying from the sale of PTs on a YieldSpace AMM.
    /// @param a The amount of underlying tokens withdrawn in the simulation
    /// @return uint256 The amount of principal tokens required for the withdrawal of `a`
    function previewWithdraw(uint256 a) public view override returns (uint256) {
        if (block.timestamp >= maturity) {
            // After maturity, the amount redeemed is based on the Redeemer contract's holdings of the underlying
            return
                (a * _totalSupply) /
                IRedeemer(redeemer).holdings(underlying, maturity);
        }

        // Prior to maturity, return a a preview of a swap on the pool
        return IYield(pool).buyBasePreview(Cast.u128(a));
    }

    /// @notice Before maturity spends `a` of underlying, and sends PTs to `r`. Post or at maturity, reverts.
    /// @param a The amount of underlying tokens deposited
    /// @param r The receiver of the principal tokens
    /// @param m Minimum number of shares that the user will receive
    /// @return uint256 The amount of principal tokens purchased
    function deposit(
        uint256 a,
        address r,
        uint256 m
    ) external returns (uint256) {
        // Execute the deposit
        return _deposit(r, a, m);
    }

    /// @notice Before maturity spends `assets` of underlying, and sends `shares` of PTs to `receiver`. Post or at maturity, reverts.
    /// @param a The amount of underlying tokens deposited
    /// @param r The receiver of the principal tokens
    /// @return uint256 The amount of principal tokens burnt by the withdrawal
    function deposit(uint256 a, address r) external override returns (uint256) {
        // Execute the deposit
        return _deposit(r, a, 0);
    }

    /// @notice Before maturity mints `s` of PTs to `r` by spending underlying. Post or at maturity, reverts.
    /// @param s The amount of shares being minted
    /// @param r The receiver of the underlying tokens being withdrawn
    /// @param m Maximum amount of underlying that the user will spend
    /// @return uint256 The amount of principal tokens purchased
    function mint(
        uint256 s,
        address r,
        uint256 m
    ) external returns (uint256) {
        // Execute the mint
        return _mint(r, s, m);
    }

    /// @notice Before maturity mints `shares` of PTs to `receiver` by spending underlying. Post or at maturity, reverts.
    /// @param s The amount of shares being minted
    /// @param r The receiver of the underlying tokens being withdrawn
    /// @return uint256 The amount of principal tokens purchased
    function mint(uint256 s, address r) external override returns (uint256) {
        // Execute the mint
        return _mint(r, s, type(uint128).max);
    }

    /// @notice At or after maturity, burns PTs from owner and sends `a` underlying to `r`. Before maturity, sends `a` by selling shares of PT on a YieldSpace AMM.
    /// @param a The amount of underlying tokens withdrawn
    /// @param r The receiver of the underlying tokens being withdrawn
    /// @param o The owner of the underlying tokens
    /// @param m Maximum amount of PTs to be sold
    /// @return uint256 The amount of principal tokens burnt by the withdrawal
    function withdraw(
        uint256 a,
        address r,
        address o,
        uint256 m
    ) external returns (uint256) {
        // Execute the withdrawal
        return _withdraw(a, r, o, m);
    }

    /// @notice At or after maturity, burns PTs from owner and sends `a` underlying to `r`. Before maturity, sends `a` by selling shares of PT on a YieldSpace AMM.
    /// @param a The amount of underlying tokens withdrawn
    /// @param r The receiver of the underlying tokens being withdrawn
    /// @param o The owner of the underlying tokens
    /// @return uint256 The amount of principal tokens burnt by the withdrawal
    function withdraw(
        uint256 a,
        address r,
        address o
    ) external override returns (uint256) {
        // Execute the withdrawal
        return _withdraw(a, r, o, type(uint128).max);
    }

    /// @notice At or after maturity, burns exactly `s` of Principal Tokens from `o` and sends underlying tokens to `r`. Before maturity, sends underlying by selling `s` of PT on a YieldSpace AMM.
    /// @param s The number of shares to be burned in exchange for the underlying asset
    /// @param r The receiver of the underlying tokens being withdrawn
    /// @param o Address of the owner of the shares being burned
    /// @param m Minimum amount of underlying that must be received
    /// @return uint256 The amount of underlying tokens distributed by the redemption
    function redeem(
        uint256 s,
        address r,
        address o,
        uint256 m
    ) external returns (uint256) {
        // Execute the redemption
        return _redeem(s, r, o, m);
    }

    /// @notice At or after maturity, burns exactly `shares` of Principal Tokens from `owner` and sends `assets` of underlying tokens to `receiver`. Before maturity, sells `s` of PT on a YieldSpace AMM.
    /// @param s The number of shares to be burned in exchange for the underlying asset
    /// @param r The receiver of the underlying tokens being withdrawn
    /// @param o Address of the owner of the shares being burned
    /// @return uint256 The amount of underlying tokens distributed by the redemption
    function redeem(
        uint256 s,
        address r,
        address o
    ) external override returns (uint256) {
        // Execute the redemption
        return _redeem(s, r, o, 0);
    }

    /// @param f Address to burn from
    /// @param a Amount to burn
    /// @return bool true if successful
    function authBurn(address f, uint256 a)
        external
        authorized(redeemer)
        returns (bool)
    {
        _burn(f, a);
        return true;
    }

    /// @param t Address recieving the minted amount
    /// @param a The amount to mint
    /// @return bool True if successful
    function authMint(address t, uint256 a)
        external
        authorized(lender)
        returns (bool)
    {
        _mint(t, a);
        return true;
    }

    /// @param o Address of the owner of the tokens
    /// @param s Address of the spender
    /// @param a Amount to be approved
    function authApprove(
        address o,
        address s,
        uint256 a
    ) external authorized(redeemer) returns (bool) {
        _allowance[o][s] = a;
        return true;
    }

    function _deposit(
        address r,
        uint256 a,
        uint256 m
    ) internal returns (uint256) {
        // Revert if called at or after maturity
        if (block.timestamp >= maturity) {
            revert Exception(
                21,
                block.timestamp,
                maturity,
                address(0),
                address(0)
            );
        }

        // Receive the funds from the sender
        Safe.transferFrom(IERC20(underlying), msg.sender, address(this), a);

        // Sell the underlying assets for PTs
        uint128 returned = IMarketPlace(marketplace).sellUnderlying(
            underlying,
            maturity,
            Cast.u128(a),
            Cast.u128(m)
        );

        // Pass the received shares onto the intended receiver
        _transfer(address(this), r, returned);

        return returned;
    }

    function _mint(
        address r,
        uint256 s,
        uint256 m
    ) internal returns (uint256) {
        // Revert if called at or after maturity
        if (block.timestamp >= maturity) {
            revert Exception(
                21,
                block.timestamp,
                maturity,
                address(0),
                address(0)
            );
        }

        // Determine how many underlying tokens are needed to mint the shares
        uint256 required = IYield(pool).buyFYTokenPreview(Cast.u128(s));

        // Transfer the underlying to the token
        Safe.transferFrom(
            IERC20(underlying),
            msg.sender,
            address(this),
            required
        );

        // Swap the underlying for principal tokens via the pool
        uint128 sold = IMarketPlace(marketplace).buyPrincipalToken(
            underlying,
            maturity,
            Cast.u128(s),
            Cast.u128(m)
        );

        // Transfer the principal tokens to the desired receiver
        _transfer(address(this), r, s);

        return sold;
    }

    function _withdraw(
        uint256 a,
        address r,
        address o,
        uint256 m
    ) internal returns (uint256) {
        // Determine how many principal tokens are needed to purchase the underlying
        uint256 needed = previewWithdraw(a);

        // Pre maturity
        if (block.timestamp < maturity) {
            // Receive the shares from the caller
            _transfer(o, address(this), needed);

            // If owner is the sender, sell PT without allowance check
            if (o == msg.sender) {
                uint128 returned = IMarketPlace(marketplace).buyUnderlying(
                    underlying,
                    maturity,
                    Cast.u128(a),
                    Cast.u128(m)
                );

                // Transfer the underlying to the desired receiver
                Safe.transfer(IERC20(underlying), r, a);

                return returned;
            } else {
                // Else, sell PT with allowance check
                // Get the allowance of the user spending the tokens
                uint256 allowance = _allowance[o][msg.sender];

                // Check for sufficient allowance
                if (allowance < needed) {
                    revert Exception(20, allowance, a, address(0), address(0));
                }

                // Update the caller's allowance
                _allowance[o][msg.sender] = allowance - needed;

                // Sell the principal tokens for underlying
                uint128 returned = IMarketPlace(marketplace).buyUnderlying(
                    underlying,
                    maturity,
                    Cast.u128(a),
                    Cast.u128(m)
                );

                // Transfer the underlying to the desired receiver
                Safe.transfer(IERC20(underlying), r, returned);

                return returned;
            }
        }
        // Post maturity
        else {
            // If owner is the sender, redeem PT without allowance check
            if (o == msg.sender) {
                // Execute the redemption to the desired receiver
                return
                    IRedeemer(redeemer).authRedeem(
                        underlying,
                        maturity,
                        msg.sender,
                        r,
                        needed
                    );
            } else {
                // Get the allowance of the user spending the tokens
                uint256 allowance = _allowance[o][msg.sender];

                // Check for sufficient allowance
                if (allowance < needed) {
                    revert Exception(
                        20,
                        allowance,
                        needed,
                        address(0),
                        address(0)
                    );
                }

                // Update the callers's allowance
                _allowance[o][msg.sender] = allowance - needed;

                // Execute the redemption to the desired receiver
                return
                    IRedeemer(redeemer).authRedeem(
                        underlying,
                        maturity,
                        o,
                        r,
                        needed
                    );
            }
        }
    }

    function _redeem(
        uint256 s,
        address r,
        address o,
        uint256 m
    ) internal returns (uint256) {
        // Pre-maturity
        if (block.timestamp < maturity) {
            // Receive the funds from the user
            _transfer(o, address(this), s);

            // If owner is the sender, sell PT without allowance check
            if (o == msg.sender) {
                // Swap principal tokens for the underlying asset
                uint128 returned = IMarketPlace(marketplace).sellPrincipalToken(
                    underlying,
                    maturity,
                    Cast.u128(s),
                    Cast.u128(m)
                );

                // Transfer underlying to the desired receiver
                Safe.transfer(IERC20(underlying), r, returned);
                return returned;
                // Else, sell PT with allowance check
            } else {
                // Get the allowance of the user spending the tokens
                uint256 allowance = _allowance[o][msg.sender];

                // Check for sufficient allowance
                if (allowance < s) {
                    revert Exception(20, allowance, s, address(0), address(0));
                }

                // Update the caller's allowance
                _allowance[o][msg.sender] = allowance - s;

                // Sell the principal tokens for the underlying
                uint128 returned = IMarketPlace(marketplace).sellPrincipalToken(
                    underlying,
                    maturity,
                    Cast.u128(s),
                    Cast.u128(m)
                );

                // Transfer the underlying to the desired receiver
                Safe.transfer(IERC20(underlying), r, returned);
                return returned;
            }
            // Post-maturity
        } else {
            // If owner is the sender, redeem PT without allowance check
            if (o == msg.sender) {
                // Execute the redemption to the desired receiver
                return
                    IRedeemer(redeemer).authRedeem(
                        underlying,
                        maturity,
                        msg.sender,
                        r,
                        s
                    );
            } else {
                // Get the allowance of the user spending the tokens
                uint256 allowance = _allowance[o][msg.sender];

                // Check for sufficient allowance
                if (allowance < s) {
                    revert Exception(20, allowance, s, address(0), address(0));
                }

                // Update the caller's allowance
                _allowance[o][msg.sender] = allowance - s;

                // Execute the redemption to the desired receiver
                return
                    IRedeemer(redeemer).authRedeem(
                        underlying,
                        maturity,
                        o,
                        r,
                        s
                    );
            }
        }
    }
}

Settings
{
  "remappings": [
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "viaIR": true,
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"l","type":"address"},{"internalType":"address","name":"s","type":"address"},{"internalType":"address","name":"t","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint8","name":"","type":"uint8"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"Exception","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"underlying","type":"address"},{"indexed":false,"internalType":"uint256","name":"maturity","type":"uint256"},{"indexed":false,"internalType":"bool","name":"state","type":"bool"}],"name":"PauseRedemptions","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"principal","type":"uint8"},{"indexed":true,"internalType":"address","name":"underlying","type":"address"},{"indexed":true,"internalType":"uint256","name":"maturity","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"burned","type":"uint256"},{"indexed":false,"internalType":"address","name":"sender","type":"address"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"when","type":"uint256"}],"name":"ScheduleFeeChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"SetAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"converter","type":"address"}],"name":"SetConverter","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"SetFee","type":"event"},{"inputs":[],"name":"HOLD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_FEENOMINATOR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"i","type":"address"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"address","name":"f","type":"address"},{"internalType":"address","name":"t","type":"address"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"authRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"address[]","name":"f","type":"address[]"}],"name":"autoRedeem","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"converter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"uint256","name":"a","type":"uint256"}],"name":"depositHoldings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeChange","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feenominator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"holdings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"marketPlace","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"bool","name":"b","type":"bool"}],"name":"pauseRedemptions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"}],"name":"redeem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"p","type":"uint8"},{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"uint8","name":"protocol","type":"uint8"}],"name":"redeem","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"p","type":"uint8"},{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"},{"internalType":"uint256","name":"s","type":"uint256"},{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"address","name":"periphery","type":"address"}],"name":"redeem","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint8","name":"p","type":"uint8"},{"internalType":"address","name":"u","type":"address"},{"internalType":"uint256","name":"m","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"scheduleFeeChange","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"setAdmin","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"c","type":"address"},{"internalType":"address[]","name":"i","type":"address[]"}],"name":"setConverter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"f","type":"uint256"}],"name":"setFee","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"l","type":"address"}],"name":"setLender","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"m","type":"address"}],"name":"setMarketPlace","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"swivelAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tempusAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

60c034620000d157601f62003e2d38819003918201601f19168301916001600160401b03831184841017620000d657808492606094604052833981010312620000d1576200004d81620000ec565b906200006a60406200006260208401620000ec565b9201620000ec565b916101f460065560018060a01b0319903382600054161760005560018060a01b031690600254161760025560805260a052610fa0600455604051613d2b90816200010282396080518181816110760152611d4a015260a05181818161103001526117da0152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b0382168203620000d15756fe60806040526004361015610013575b600080fd5b60003560e01c80630d3f53521461022f5780631177ec30146102265780631e9a69501461021d5780632e25d2a61461021457806330568a8d1461020b57806335197f9e1461020257806346e368d4146101f957806369fe0e2d146101f05780636a97d9ce146101e7578063704b6c02146101de57806370a03ced146101d5578063769065b9146101cc57806380252724146101c357806391b46a91146101ba5780639e6b5173146101b1578063a1b1138c146101a8578063a4ad51151461019f578063ad86b83e14610196578063bcead63e1461018d578063bd38837b14610184578063c01199f41461017b578063d0886f9714610172578063daea85c514610169578063de1d3cb514610160578063ea08c03114610157578063f38961311461014e5763f851a4401461014657600080fd5b61000e6110f6565b5061000e6110a5565b5061000e61105f565b5061000e611019565b5061000e610fb1565b5061000e610f92565b5061000e610f26565b5061000e610efc565b5061000e610ed2565b5061000e610e66565b5061000e610dc2565b5061000e610c99565b5061000e610c7a565b5061000e610c2e565b5061000e610b00565b5061000e6109db565b5061000e6108b2565b5061000e610816565b5061000e610750565b5061000e6105cc565b5061000e61053a565b5061000e61051b565b5061000e610481565b5061000e610457565b5061000e610314565b5061000e610262565b5061000e610243565b600091031261000e57565b503461000e57600036600319011261000e576020600654604051908152f35b503461000e57600036600319011261000e576000546001600160a01b0316338190036102e1577ff339d7864b1b8839e8a8870c012fc6eb9a89844861a87a26ce35979018603a1b60206203f48042018042116102d4575b80600555604051908152a160405160018152602090f35b0390f35b6102dc61121a565b6102b9565b604051636d4c6c8960e01b81529081906102ff903360048401611120565b0390fd5b6001600160a01b0381160361000e57565b503461000e57604036600319011261000e5760043561033281610303565b60243561036e610367826103588560018060a01b03166000526008602052604060002090565b90600052602052604060002090565b5460ff1690565b80156103c7575b61038457610382916125bc565b005b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b03929092166064820152608481019190915260a490fd5b506002546000906020906103f1906103e5906001600160a01b031681565b6001600160a01b031690565b60046040518094819363b9b8af0b60e01b83525af190811561044a575b60009161041c575b50610375565b61043d915060203d8111610443575b610435818361072e565b810190611248565b38610416565b503d61042b565b610452611260565b61040e565b503461000e57600036600319011261000e576001546040516001600160a01b039091168152602090f35b503461000e57602036600319011261000e5760043561049f81610303565b6000546001600160a01b03908116338190036102e1575060015416806104e757600180546001600160a01b0319166001600160a01b0384161790555b60405160018152602090f35b60a49060405190636d4c6c8960e01b8252600560048301526000602483015260006044830152606482015260006084820152fd5b503461000e57600036600319011261000e576020600554604051908152f35b503461000e57602036600319011261000e5760043561055881610303565b6000546001600160a01b03908116338190036102e15750600254168061059857600280546001600160a01b0319166001600160a01b0384161790556104db565b60a49060405190636d4c6c8960e01b8252600860048301526000602483015260006044830152606482015260006084820152fd5b503461000e57602036600319011261000e57600054600435906001600160a01b0316338190036102e157506005548061063457604051636d4c6c8960e01b8152601760048201526000602482018190526044820181905260648201819052608482015260a490fd5b80421060001461067357604051636d4c6c8960e01b8152601860048201524260248201526044810191909152600060648201819052608482015260a490fd5b5060065481106106c15761068681600455565b6106906000600555565b604051907e172ddfc5ae88d08b3de01a5a187667c37a5a53989e8c175055cb6c993792a7600083a260018152602090f35b604051636d4c6c8960e01b8152601960048201526000602482018190526044820181905260648201819052608482015260a490fd5b50634e487b7160e01b600052604160045260246000fd5b67ffffffffffffffff811161072157604052565b6107296106f6565b604052565b90601f8019910116810190811067ffffffffffffffff82111761072157604052565b503461000e57604036600319011261000e5760043561076e81610303565b6024359067ffffffffffffffff80831161000e573660238401121561000e578260040135908111610809575b8060051b92604051936020926107b28483018761072e565b85526024838601918301019136831161000e57602401905b8282106107f0576102d06107de878761114e565b60405190151581529081906020820190565b83809183356107fe81610303565b8152019101906107ca565b6108116106f6565b61079a565b503461000e57602036600319011261000e5760043561083481610303565b6000546001600160a01b03808216923384900361089657602093501680916bffffffffffffffffffffffff60a01b1617600055604051907f5a272403b402d892977df56625f4164ccaf70ca3863991c43ecfe76a6905b0a1600083a260018152f35b604051636d4c6c8960e01b8152806102ff863360048401611120565b503461000e5760a036600319011261000e576004356108d081610303565b6024356044356108df81610303565b606435916108ec83610303565b600154610903906103e5906001600160a01b031681565b60405163125cf47f60e01b81526001600160a01b0386166004820152602481018390526000604482018190529091602091839160649183915af19081156109ae575b600091610980575b506001600160a01b03811633036102e1576102d06109706084358686868a6128da565b6040519081529081906020820190565b6109a1915060203d81116109a7575b610999818361072e565b81019061126d565b3861094d565b503d61098f565b6109b6611260565b610945565b6004359060ff8216820361000e57565b6064359060ff8216820361000e57565b503461000e57608036600319011261000e576109f56109bb565b602435610a0181610303565b60443590610a0d6109cb565b92610a31610367846103588560018060a01b03166000526008602052604060002090565b8015610a8c575b610a4b57916102d093916107de93611bb1565b50604051636d4c6c8960e01b81526011600482015260248101929092526000604483018190526001600160a01b03919091166064830152608482015260a490fd5b50600254600090602090610aaa906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115610af3575b600091610ad5575b50610a38565b610aed915060203d811161044357610435818361072e565b38610acf565b610afb611260565b610ac7565b503461000e5760c036600319011261000e57610b1a6109bb565b60243590610b2782610303565b60a43590604435610b3783610303565b610b5a610367826103588760018060a01b03166000526008602052604060002090565b8015610bba575b610b7c57926107de92916102d0946084359260643592611fc5565b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b0385166064830152608482015260a490fd5b50600254600090602090610bd8906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115610c21575b600091610c03575b50610b61565b610c1b915060203d811161044357610435818361072e565b38610bfd565b610c29611260565b610bf5565b503461000e57604036600319011261000e57600435610c4c81610303565b60018060a01b0316600052600760205260406000206024356000526020526020604060002054604051908152f35b503461000e57600036600319011261000e576020600454604051908152f35b503461000e57606036600319011261000e57610cb36109bb565b60243590610cc082610303565b60443590610ce7610367836103588660018060a01b03166000526008602052604060002090565b8015610d44575b610d0057906102d0926107de9261129e565b50604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b03929092166064820152608481019190915260a490fd5b50600254600090602090610d62906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115610dab575b600091610d8d575b50610cee565b610da5915060203d811161044357610435818361072e565b38610d87565b610db3611260565b610d7f565b8015150361000e57565b503461000e57606036600319011261000e57600435610de081610303565b604435602435610def82610db8565b6000805490936001600160a01b03918216338190036102e15750916040917f1258883257f202b4bfb5c92d9effcc9b5e7775dcbb52c2c31f39f2dff27d3c2d93169384865260086020528286208287526020528286209015159060ff1981541660ff831617905582519182526020820152a2604051f35b503461000e57606036600319011261000e57600435610e8481610303565b60443567ffffffffffffffff80821161000e573660238301121561000e57816004013590811161000e573660248260051b8401011161000e576102d092602461097093019060243590612c9d565b503461000e57600036600319011261000e576002546040516001600160a01b039091168152602090f35b503461000e57600036600319011261000e576003546040516001600160a01b039091168152602090f35b503461000e57606036600319011261000e57600435610f4481610303565b604435906001600160a01b0316610f5d82303384613ba8565b6000526007602052604060002060243560005260205260406000208054918201809211610f875755005b610f8f61121a565b55005b503461000e57600036600319011261000e5760206040516203f4808152f35b503461000e57602036600319011261000e57600435610fcf81610303565b6001546001600160a01b039081169133839003610ffd57811680610fef57005b610382916003541690613c5e565b604051636d4c6c8960e01b8152806102ff853360048401611120565b503461000e57600036600319011261000e576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461000e57600036600319011261000e576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b503461000e57604036600319011261000e576004356110c381610303565b60018060a01b031660005260086020526040600020602435600052602052602060ff604060002054166040519015158152f35b503461000e57600036600319011261000e576000546040516001600160a01b039091168152602090f35b60008082526020820181905260408201526001600160a01b0391821660608201529116608082015260a00190565b6000549092916001600160a01b03918216338190036102e15750818416806bffffffffffffffffffffffff60a01b600354161760035560005b82518082146111d057906001918110156111c3575b6020600582901b850101516111bd90889087166001600160a01b0316613c5e565b01611187565b6111cb611203565b61119c565b505092935050507fc06343c9448e37c4ed257861469b4b35c140991c9cf08a4c38d335a1bc6a75d36000604051a2600190565b50634e487b7160e01b600052603260045260246000fd5b50634e487b7160e01b600052601160045260246000fd5b9190820180921161123e57565b61124661121a565b565b9081602091031261000e575161125d81610db8565b90565b506040513d6000823e3d90fd5b9081602091031261000e575161125d81610303565b9081602091031261000e575190565b9190820391821161123e57565b6001546112b5906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0385166004808301919091526024820187905260ff8516604483015260209591949193919291868680606481010381600080955af1958615611ba4575b8196611b85575b5060ff84169660028814928315611af057611328886137e3565b428111611ab557506002546001600160a01b031685516370a0823160e01b8082526001600160a01b03838116858401908152929b919282821692908e9088908f9081906020010381875afa9d8e15611aa8575b899e611a85575b508d61139991883091149788611a2f575b86613ba8565b818c169d8e958b519a868c52898c806113c3308d8301919091602081019260018060a01b03169052565b03818b5afa9b8c15611a22575b8b9c611a03575b501561155e57505089516301e9a69560e41b815230818801908152602081018f9052600080516020613cd68339815191529e9d9c6114f29c909b909a61147b9a919990986114629892978897509195509391928691839182908c90829060400103925af18015611551575b611534575b505b8951908152309281019283529485928391829160200190565b03915afa928315611527575b926114fa575b5050611291565b936114c9896103586114ad886114a7846103588860018060a01b03166000526007602052604060002090565b54611231565b6001600160a01b03909416600090815260076020526040902090565b555160ff9091168152602081019290925260408201929092523360608201529081906080820190565b0390a3600190565b6115199250803d10611520575b611511818361072e565b810190611282565b3880611474565b503d611507565b61152f611260565b61146e565b61154a90853d871161152057611511818361072e565b5038611447565b611559611260565b611442565b600382036115ff575050895163884e17f360e01b81528087018e8152306020820152600080516020613cd68339815191529e9d9c6114f29c909b909a61147b9a919990986114629892978897509195509391928691839182908c90829060400103925af180156115f2575b6115d5575b505b611449565b6115eb90853d871161152057611511818361072e565b50386115ce565b6115fa611260565b6115c9565b9293921561179f5750509484879461146294611708600080516020613cd68339815191529f9e9d9b988e8697916114f29f9d9288938f61147b9f9086849281611688845163781c18db60e01b815282818b81875afa908115611792575b8891611775575b50855163bcb7ea5d60e01b815230818c0190815290998a9384928b9291849160200190565b0393165af1958615611768575b8596611749575b50825163afd27bf560e01b815296879182905afa94851561173c575b839561171d575b505163769f8e5d60e01b8152308b820190815260208101949094526001600160a01b03909516604084015260006060840181905260808401529395869485939091849160a00190565b0393165af180156115f2576115d55750611449565b611735919550873d89116109a757610999818361072e565b93386116bf565b611744611260565b6116b8565b611761919650823d841161152057611511818361072e565b943861169c565b611770611260565b611695565b61178c9150833d85116109a757610999818361072e565b38611663565b61179a611260565b61165c565b6005819693949596146000146118d957505050908484928951938480926316f0115b60e01b82525afa9182156118cc575b86926118ad575b507f000000000000000000000000000000000000000000000000000000000000000016803b156118a95793808b9a9997948d889561186e600080516020613cd68339815191529f9a6114629761147b9b6114f29f9d838f51809681958294636c8d4fa160e01b845230918d850191939290606091608084019560018060a01b03809316855260208501526000604085015216910152565b03925af1801561189c575b6118835750611449565b806118906118969261070d565b80610238565b386115ce565b6118a4611260565b611879565b8580fd5b6118c5919250853d87116109a757610999818361072e565b90386117d7565b6118d4611260565b6117d0565b61146294509188956114f29b99600080516020613cd68339815191529f9e9d9b98938f908f908c80998961147b9f9a60071460001461191f575050506115d0935061332e565b91945092506008915014611936575b505050611449565b8c51848152308782019081526119959291908f908590839081906020010381865afa9182156119f6575b86926119d7575b5051635d043b2960e11b81528881019182523060208301819052604083015294859384928391606090910190565b03925af180156119ca575b6119ad575b84889161192e565b6119c390853d871161152057611511818361072e565b50386119a5565b6119d2611260565b6119a0565b6119ef919250853d871161152057611511818361072e565b9038611967565b6119fe611260565b611960565b611a1b919c508a3d8c1161152057611511818361072e565b9a386113d7565b611a2a611260565b6113d0565b8d5163781c18db60e01b81529091508a818b818a5afa908115611a78575b8c91611a5b575b5090611393565b611a7291508b3d8d116109a757610999818361072e565b38611a54565b611a80611260565b611a4d565b611399919e50611aa190893d8b1161152057611511818361072e565b9d90611382565b611ab0611260565b61137b565b8551636d4c6c8960e01b81526007928101928352602083019190915260006040830181905260608301819052608083015290819060a0010390fd5b60038903611b0657611b018861383e565b611328565b888103611b1657611b0188613873565b60058903611b2757611b01886138a8565b60078903611b3857611b0188613958565b60088903611b4957611b0188613aa1565b8451636d4c6c8960e01b8152600691810191825260ff8716602083015260006040830181905260608301819052608083015290819060a0010390fd5b611b9d919650873d89116109a757610999818361072e565b943861130e565b611bac611260565b611307565b939293600160ff821603611f8c57600154611bd6906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0385166004808301919091526024820187905260ff8516604483015290979093926020808a80606481010381600080965af1998a15611f7f575b829a611f60575b50835163204f83f960e01b81526001600160a01b039a9096908b1682888381845afa978815611f53575b8498611f34575b50428811611efb5760025486516370a0823160e01b8082526001600160a01b039092168482018181529d9e9c9d9b9c919b8d9b9394929391929087908e9081900360200181845afa9c8d15611eee575b889d611ecb575b508c611cc291843091613ba8565b828b169c8d988d888c51809c8982528180611cef8d30908301919091602081019260018060a01b03169052565b03915afa9a8b15611ebe575b8a9b611e95575b508b51632c5a8cf360e21b815260ff9092168883019081526001600160a01b039093166020840152604083019390935260608201929092528690829081906080015b03818a877f0000000000000000000000000000000000000000000000000000000000000000165af1908115611e88575b8791611e6b575b5015611e325716803b15611e2e578361147b948a9997948d8f95611de5600080516020613cd68339815191529f9e9a8098611462986114f29f9d838f51809681958294636b42450d60e01b84528c840160209093929193604081019460018060a01b031681520152565b03925af18015611e21575b611e0e57508951908152309281019283529485928391829160200190565b80611890611e1b9261070d565b38611447565b611e29611260565b611df0565b8480fd5b8751636d4c6c8960e01b8152600f81860190815260006020820181905260408201819052606082018190526080820152819060a0010390fd5b611e829150863d881161044357610435818361072e565b38611d7b565b611e90611260565b611d74565b899392919b50611eb4611d4491853d871161152057611511818361072e565b9b91929350611d02565b611ec6611260565b611cfb565b611cc2919d50611ee790883d8a1161152057611511818361072e565b9c90611cb4565b611ef6611260565b611cad565b8551636d4c6c8960e01b81526007818401908152602081018a9052600060408201819052606082018190526080820152819060a0010390fd5b611f4c919850833d851161152057611511818361072e565b9638611c5d565b611f5b611260565b611c56565b81611f78929b503d8c116109a757610999818361072e565b9838611c2c565b611f87611260565b611c25565b604051636d4c6c8960e01b81526006600482015260ff91909116602482015260006044820181905260648201819052608482015260a490fd5b90919493611fe06103e56103e560015460018060a01b031690565b6040805163125cf47f60e01b81526001600160a01b038616600480830191909152602482018a90526006604483015260209792949092888380606481010381600080995af1928315612570575b8593612551575b5060018060a01b03809316918a856120918c878d169e8f8c61205d60025460018060a01b031690565b9051636eb1769f60e11b81526001600160a01b03808316978201978852909316602087015294919384928391829160400190565b03915afa908115612544575b8991612527575b50156124ee57928b95928795926121018f9a99968c51908a82806120e4846370a0823160e01b9d8e84528301919091602081019260018060a01b03169052565b03818a5afa9182156124e1575b8c926124c2575b50309087613ba8565b8a5186815230888201908152909e908f9081906020010381875afa9d8e156124b5575b899e612494575b508a5186815230888201908152909a8c92918a918d918290819060200103915afa9a8b15612487575b8a9b61243d575b5088936121ed938b8a858a8996878e839d9a519a8b9182528180612190308a8301919091602081019260018060a01b03169052565b03915afa988915612430575b8599612409575b5090808880938686518094819363378efa3760e01b8352165afa9081156123fc575b86916123df575b508351630f922e0760e31b81528581019788529a9116958a91829160200190565b0381875afa9788156123d2575b83986123a9575b5051632b83cccd60e01b81526001600160a01b0388169181019182526020820194909452604081019590955291938492839182906060015b03925af1801561239c575b61237f575b50858a5180948193636a5c1cc960e11b8352165afa908115612372575b8691612355575b50600354612285906103e5906001600160a01b031681565b8851848152308682019081529093919291879185919082908190602001039185165afa928315612348575b8793612329575b50813b1561232557948a9997948d8895611de5600080516020613cd68339815191529f9e9a6114629786976114f29f9d838f9e61147b9f519687958694859363248391ff60e01b85528d850160409194939294606082019560018060a01b0380921683521660208201520152565b8680fd5b612341919350863d881161152057611511818361072e565b91386122b7565b612350611260565b6122b0565b61236c9150853d87116109a757610999818361072e565b3861226d565b61237a611260565b612266565b61239590833d851161152057611511818361072e565b5038612249565b6123a4611260565b612244565b61223998506123c89087949392943d89116109a757610999818361072e565b9792909192612201565b6123da611260565b6121fa565b6123f69150833d85116109a757610999818361072e565b386121cc565b612404611260565b6121c5565b8291995091886124268194823d841161152057611511818361072e565b9a929350506121a3565b612438611260565b61219c565b899b509387938b8a899895898e99968a97883d8a11612480575b612461818361072e565b810161246c91611282565b9f509650509598505050935093989a61215b565b503d612457565b61248f611260565b612154565b8b919e506124ae90893d8b1161152057611511818361072e565b9d9061212b565b6124bd611260565b612124565b6124da9192508b3d8d1161152057611511818361072e565b90386120f8565b6124e9611260565b6120f1565b8851636d4c6c8960e01b8152601d81890190815260006020820181905260408201819052606082018190526080820152819060a0010390fd5b61253e91508d803d1061152057611511818361072e565b386120a4565b61254c611260565b61209d565b612569919350893d8b116109a757610999818361072e565b9138612034565b612578611260565b61202d565b8060001904821181151516612590570290565b61259861121a565b0290565b81156125a6570490565b634e487b7160e01b600052601260045260246000fd5b6001546125d3906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b038416600480830191909152602482018690526000604483015291939190602090818180606481010381600080995af19081156128cd575b85916128b0575b50855163204f83f960e01b81526001600160a01b03918216949083818681895afa9081156128a3575b8791612886575b50421061284e5786516370a0823160e01b8152338582019081529094908490869081906020010381895afa948515612841575b8795612820575b5083906126ea6126c16126ba8c6103588760018060a01b03166000526007602052604060002090565b548861257d565b8a516318160ddd60e01b815290848285818d5afa918215612813575b8b926127f4575b5061259c565b96878b6127098660018060a01b03166000526007602052604060002090565b600091825260205260409020549061272091611291565b6001600160a01b03851660009081526007602052604090208c906000918252602052604090205589516388ba33df60e01b81523392810192835260208301889052988992839190829060400103925af1600080516020613cd6833981519152966127c4949180156127e7575b6127c9575b505016946127a0843388613b37565b51600081526020810193909352604083019190915233606083015281906080820190565b0390a3565b816127df92903d1061044357610435818361072e565b503880612791565b6127ef611260565b61278c565b61280c919250853d871161152057611511818361072e565b90386126e4565b61281b611260565b6126dd565b8491955061283a90823d841161152057611511818361072e565b9490612691565b612849611260565b61268a565b8651636d4c6c8960e01b81526007818601908152426020820152604081018a90526000606082018190526080820152819060a0010390fd5b61289d9150843d861161152057611511818361072e565b38612657565b6128ab611260565b612650565b6128c79150823d84116109a757610999818361072e565b38612627565b6128d5611260565b612620565b6001600160a01b03811660009081526008602052604090209095949392919081600052602052612910604060002060ff90541690565b8015612963575b6129255761125d94956129d7565b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b0387166064830152608482015260a490fd5b50600254600090602090612981906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af19081156129ca575b6000916129ac575b50612917565b6129c4915060203d811161044357610435818361072e565b386129a6565b6129d2611260565b61299e565b600154919392916129f2906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0384166004820152602481018790526000604482018190529193909290916020918291859160649183915af1928315612c90575b600093612c71575b50835163204f83f960e01b81526001600160a01b03938416908281600481855afa908115612c64575b600091612c47575b50804210612c125750600080516020613cd683398151915295938383612b7e612b98958d8d96612af6612acc612ac58a612bbc9f9d6103589060018060a01b03166000526007602052604060002090565b548461257d565b8b516318160ddd60e01b8152908582600481875afa918215612c05575b600092612bed575061259c565b9a88612b46612b268e612b20819d6103588d60018060a01b03166000526007602052604060002090565b54611291565b6001600160a01b038a166000908152600760205260409020909290610358565b558a516388ba33df60e01b81526001600160a01b03909116600482015260248101929092529092839190829060009082906044820190565b03925af18015612be0575b612bc2575b5050169687613b37565b51600081526020810191909152604081018690523360608201529081906080820190565b0390a390565b81612bd892903d1061044357610435818361072e565b503880612b8e565b612be8611260565b612b89565b61280c919250863d881161152057611511818361072e565b612c0d611260565b612ae9565b8551636d4c6c8960e01b815260076004820152602481019190915260006044820181905260648201819052608482015260a490fd5b612c5e9150833d851161152057611511818361072e565b38612a74565b612c6c611260565b612a6c565b81612c899294503d85116109a757610999818361072e565b9138612a43565b612c98611260565b612a3b565b6001600160a01b038116600090815260086020526040902090949392919081600052602052612cd2604060002060ff90541690565b8015612d25575b612ce75761125d9394612dc1565b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b0386166064830152608482015260a490fd5b50600254600090602090612d43906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115612d8c575b600091612d6e575b50612cd9565b612d86915060203d811161044357610435818361072e565b38612d68565b612d94611260565b612d60565b9190811015612daa575b60051b0190565b612db2611203565b612da3565b3561125d81610303565b600154909290612ddb906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0386166004820152602481018590526000604482015291959193909290602085806064810103816000809b5af1948515613321575b8795613300575b50835163204f83f960e01b808252906020816004816001600160a01b038b165afa9081156132f3575b89916132d4575b50421061323f5750869493929190855b838703612e92575061125d968896503395506001600160a01b03169350613b3792505050565b909192939495612ee68199848a6020612eb4612eaf878c8c612d99565b612db7565b8b51636eb1769f60e11b81526001600160a01b0390911660048201523060248201529485908d90829081906044820190565b03916001600160a01b03165afa938415613232575b8694613204575b506020612f18612eaf612f449596978c8c612d99565b8b516370a0823160e01b81526001600160a01b0390911660048201529384908d90829081906024820190565b03916001600160a01b03165afa9283156131f7575b86936131c1575b506001600160a01b03166000908152600760205260409020612fbd91600491612f8c91612ac591610358565b60208c8c51938480926318160ddd60e01b825260018060a01b03165afa9182156131b4575b879261319a575061259c565b90612fca6004548361259c565b9381811061316457508392916130f26001956130ec8f8f908f918f8f8f612eaf958f93613067612eaf886131009f96866130476020809a8c613013612eaf888861309f9f612d99565b8a516388ba33df60e01b81526001600160a01b03909116600482015260248101929092529092839190829081906044820190565b03916001600160a01b038d165af18015613157575b61313a575b50612d99565b90516326ce41a160e21b81526001600160a01b0390911660048201523060248201526000604482015293849283919082906064820190565b03926001600160a01b03165af1801561312d575b61310e575b506130e48d6103586114ad8a612b20846103588860018060a01b03166000526007602052604060002090565b558d8d612d99565b92611291565b90858060a01b038d16613b37565b019801959493929190612e6c565b6131269060203d60201161044357610435818361072e565b50386130b8565b613135611260565b6130b3565b613150908a3d8c1161044357610435818361072e565b5038613061565b61315f611260565b61305c565b8951636d4c6c8960e01b81526014600482015260248101919091526044810191909152600060648201819052608482015260a490fd5b61280c91925060203d60201161152057611511818361072e565b6131bc611260565b612fb1565b6004919350612ac5612fbd936103586131eb612f8c9460203d60201161152057611511818361072e565b96945050935050612f60565b6131ff611260565b612f59565b612f44939450612f18612eaf613228602093843d861161152057611511818361072e565b9695505050612f02565b61323a611260565b612efb565b84519081526102ff90889086906020816004816001600160a01b038d165afa9283156132c7575b926132a6575b5051636d4c6c8960e01b815260076004820152602481019190915260006044820181905260648201819052608482015290819060a4820190565b6132c091925060203d60201161152057611511818361072e565b908361326c565b6132cf611260565b613266565b6132ed915060203d60201161152057611511818361072e565b38612e5c565b6132fb611260565b612e55565b61331a91955060203d6020116109a757610999818361072e565b9338612e2c565b613329611260565b612e25565b60405163247c579160e21b815292600492916020908590859082906001600160a01b03165afa9384156137d6575b6000946137b5575b506040516362b9c05160e11b815260208185816001600160a01b0389165afa9081156137a8575b600091613789575b50604051635a7ad10d60e11b81526133e790602090818188816001600160a01b038c165afa90811561377c575b60009161375f575b50604051809381926371a5d76160e01b83528983019190602083019252565b03816001600160a01b038a165afa908115613752575b600091613733575b506002546001600160a01b03166040516370a0823160e01b81526001600160a01b03821687820190815290949060209086908190830103816001600160a01b0387165afa948515613726575b600095613705575b508085116136fd575b506001600160a01b0316803b1561000e5760408051633ad4d8b960e11b81526001600160a01b0390931687840190815260208101869052600092849290918391859183910103925af180156136f0575b6136dd575b506001600160a01b0316803b1561000e576040805163f3fef3a360e01b81526001600160a01b038716868201908152602081019490945292600092849290918391859183910103925af180156136d0575b6136bd575b5060405163f018ae6960e01b808252909360208585816001600160a01b0385165afa9485156136b0575b60009561368f575b5060035460209061355a906103e5906001600160a01b031681565b60405193845292918290869082906001600160a01b03165afa908115613682575b600091613663575b506040516370a0823160e01b815230858201908152909560209187919082908190850103916001600160a01b03165afa948515613656575b600095613635575b50813b1561000e576040805163248391ff60e01b81526001600160a01b039283169581019586529390911660208501528301939093529160009183919082908490829060600103925af18015613628575b61361b5750565b806118906112469261070d565b613630611260565b613614565b61364f91955060203d60201161152057611511818361072e565b93386135c3565b61365e611260565b6135bb565b61367c915060203d6020116109a757610999818361072e565b38613583565b61368a611260565b61357b565b6136a991955060203d6020116109a757610999818361072e565b933861353f565b6136b8611260565b613537565b806118906136ca9261070d565b3861350d565b6136d8611260565b613508565b806118906136ea9261070d565b386134b7565b6136f8611260565b6134b2565b935038613462565b61371f91955060203d60201161152057611511818361072e565b9338613459565b61372e611260565b613451565b61374c915060203d6020116109a757610999818361072e565b38613405565b61375a611260565b6133fd565b6137769150823d841161152057611511818361072e565b386133c8565b613784611260565b6133c0565b6137a2915060203d6020116109a757610999818361072e565b38613393565b6137b0611260565b61338b565b6137cf91945060203d6020116109a757610999818361072e565b9238613364565b6137de611260565b61335c565b60405163204f83f960e01b815290602090829060049082906001600160a01b03165afa908115613831575b600091613819575090565b61125d915060203d811161152057611511818361072e565b613839611260565b61380e565b60405163aa082a9d60e01b815290602090829060049082906001600160a01b03165afa90811561383157600091613819575090565b6040516370c264df60e11b815290602090829060049082906001600160a01b03165afa90811561383157600091613819575090565b6040516316f0115b60e01b81526020916001600160a01b0391839182908290600490829087165afa90811561394b575b60009161392e575b50600460405180948193632745fed560e11b8352165afa918215613921575b60009261390b57505090565b61125d9250803d1061152057611511818361072e565b613929611260565b6138ff565b6139459150823d84116109a757610999818361072e565b386138e0565b613953611260565b6138d8565b60405163247c579160e21b81526020916001600160a01b03918391613a0891849184908290600490829086165afa908115613a94575b600091613a77575b50166040516362b9c05160e11b8152838082600481865afa918215613a6a575b600092613a4a575b50604051636558954f60e01b81529192829060049082905afa908115613a3d575b600091613a20575b506040519485809481936220e10960e51b8352600483019190602083019252565b0392165afa9182156139215760009261390b57505090565b613a379150843d861161152057611511818361072e565b386139e7565b613a45611260565b6139df565b60049250613a6490823d84116109a757610999818361072e565b916139be565b613a72611260565b6139b6565b613a8e9150843d86116109a757610999818361072e565b38613996565b613a9c611260565b61398e565b60405163e16695b560e01b815290602090829060049082906001600160a01b03165afa908115613b2a575b600091613adf575b5064ffffffffff1690565b6020813d8211613b22575b81613af76020938361072e565b81010312613b1e57519064ffffffffff82168203613b1b575064ffffffffff613ad4565b80fd5b5080fd5b3d9150613aea565b613b32611260565b613acc565b6044600092838093613b6a966040519363a9059cbb60e01b855260018060a01b0316600485015260248401525af1613c27565b15613b7157565b60405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b6044820152606490fd5b60009283606492613be4968295604051946323b872dd60e01b865260018060a01b03809216600487015216602485015260448401525af1613c27565b15613beb57565b60405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b6044820152606490fd5b6000903d9015613c57579081602014613c4a575015613c4557600090565b600190565b9050602081803e51151590565b908181803efd5b60405163095ea7b360e01b81526001600160a01b0390921660048301526000196024830152613c9891600091829160449183905af1613c27565b15613c9f57565b60405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606490fdfef9820069613a0f200b0791623bec3ed14a17256cf61c3fc2160fdadbbcdda463a26469706673582212207234f4d02766bbc48118cc207dfdebcc367022e3d70cc9a1355fb4874657444a64736f6c63430008100033000000000000000000000000429b47c4aeadd42bbcb118651c8984086bfc4551000000000000000000000000373a06bd3067f8da90239a47f316f09312b7800f000000000000000000000000db5fd0678eed82246b599da6bc36b56157e4bed8

Deployed Bytecode

0x60806040526004361015610013575b600080fd5b60003560e01c80630d3f53521461022f5780631177ec30146102265780631e9a69501461021d5780632e25d2a61461021457806330568a8d1461020b57806335197f9e1461020257806346e368d4146101f957806369fe0e2d146101f05780636a97d9ce146101e7578063704b6c02146101de57806370a03ced146101d5578063769065b9146101cc57806380252724146101c357806391b46a91146101ba5780639e6b5173146101b1578063a1b1138c146101a8578063a4ad51151461019f578063ad86b83e14610196578063bcead63e1461018d578063bd38837b14610184578063c01199f41461017b578063d0886f9714610172578063daea85c514610169578063de1d3cb514610160578063ea08c03114610157578063f38961311461014e5763f851a4401461014657600080fd5b61000e6110f6565b5061000e6110a5565b5061000e61105f565b5061000e611019565b5061000e610fb1565b5061000e610f92565b5061000e610f26565b5061000e610efc565b5061000e610ed2565b5061000e610e66565b5061000e610dc2565b5061000e610c99565b5061000e610c7a565b5061000e610c2e565b5061000e610b00565b5061000e6109db565b5061000e6108b2565b5061000e610816565b5061000e610750565b5061000e6105cc565b5061000e61053a565b5061000e61051b565b5061000e610481565b5061000e610457565b5061000e610314565b5061000e610262565b5061000e610243565b600091031261000e57565b503461000e57600036600319011261000e576020600654604051908152f35b503461000e57600036600319011261000e576000546001600160a01b0316338190036102e1577ff339d7864b1b8839e8a8870c012fc6eb9a89844861a87a26ce35979018603a1b60206203f48042018042116102d4575b80600555604051908152a160405160018152602090f35b0390f35b6102dc61121a565b6102b9565b604051636d4c6c8960e01b81529081906102ff903360048401611120565b0390fd5b6001600160a01b0381160361000e57565b503461000e57604036600319011261000e5760043561033281610303565b60243561036e610367826103588560018060a01b03166000526008602052604060002090565b90600052602052604060002090565b5460ff1690565b80156103c7575b61038457610382916125bc565b005b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b03929092166064820152608481019190915260a490fd5b506002546000906020906103f1906103e5906001600160a01b031681565b6001600160a01b031690565b60046040518094819363b9b8af0b60e01b83525af190811561044a575b60009161041c575b50610375565b61043d915060203d8111610443575b610435818361072e565b810190611248565b38610416565b503d61042b565b610452611260565b61040e565b503461000e57600036600319011261000e576001546040516001600160a01b039091168152602090f35b503461000e57602036600319011261000e5760043561049f81610303565b6000546001600160a01b03908116338190036102e1575060015416806104e757600180546001600160a01b0319166001600160a01b0384161790555b60405160018152602090f35b60a49060405190636d4c6c8960e01b8252600560048301526000602483015260006044830152606482015260006084820152fd5b503461000e57600036600319011261000e576020600554604051908152f35b503461000e57602036600319011261000e5760043561055881610303565b6000546001600160a01b03908116338190036102e15750600254168061059857600280546001600160a01b0319166001600160a01b0384161790556104db565b60a49060405190636d4c6c8960e01b8252600860048301526000602483015260006044830152606482015260006084820152fd5b503461000e57602036600319011261000e57600054600435906001600160a01b0316338190036102e157506005548061063457604051636d4c6c8960e01b8152601760048201526000602482018190526044820181905260648201819052608482015260a490fd5b80421060001461067357604051636d4c6c8960e01b8152601860048201524260248201526044810191909152600060648201819052608482015260a490fd5b5060065481106106c15761068681600455565b6106906000600555565b604051907e172ddfc5ae88d08b3de01a5a187667c37a5a53989e8c175055cb6c993792a7600083a260018152602090f35b604051636d4c6c8960e01b8152601960048201526000602482018190526044820181905260648201819052608482015260a490fd5b50634e487b7160e01b600052604160045260246000fd5b67ffffffffffffffff811161072157604052565b6107296106f6565b604052565b90601f8019910116810190811067ffffffffffffffff82111761072157604052565b503461000e57604036600319011261000e5760043561076e81610303565b6024359067ffffffffffffffff80831161000e573660238401121561000e578260040135908111610809575b8060051b92604051936020926107b28483018761072e565b85526024838601918301019136831161000e57602401905b8282106107f0576102d06107de878761114e565b60405190151581529081906020820190565b83809183356107fe81610303565b8152019101906107ca565b6108116106f6565b61079a565b503461000e57602036600319011261000e5760043561083481610303565b6000546001600160a01b03808216923384900361089657602093501680916bffffffffffffffffffffffff60a01b1617600055604051907f5a272403b402d892977df56625f4164ccaf70ca3863991c43ecfe76a6905b0a1600083a260018152f35b604051636d4c6c8960e01b8152806102ff863360048401611120565b503461000e5760a036600319011261000e576004356108d081610303565b6024356044356108df81610303565b606435916108ec83610303565b600154610903906103e5906001600160a01b031681565b60405163125cf47f60e01b81526001600160a01b0386166004820152602481018390526000604482018190529091602091839160649183915af19081156109ae575b600091610980575b506001600160a01b03811633036102e1576102d06109706084358686868a6128da565b6040519081529081906020820190565b6109a1915060203d81116109a7575b610999818361072e565b81019061126d565b3861094d565b503d61098f565b6109b6611260565b610945565b6004359060ff8216820361000e57565b6064359060ff8216820361000e57565b503461000e57608036600319011261000e576109f56109bb565b602435610a0181610303565b60443590610a0d6109cb565b92610a31610367846103588560018060a01b03166000526008602052604060002090565b8015610a8c575b610a4b57916102d093916107de93611bb1565b50604051636d4c6c8960e01b81526011600482015260248101929092526000604483018190526001600160a01b03919091166064830152608482015260a490fd5b50600254600090602090610aaa906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115610af3575b600091610ad5575b50610a38565b610aed915060203d811161044357610435818361072e565b38610acf565b610afb611260565b610ac7565b503461000e5760c036600319011261000e57610b1a6109bb565b60243590610b2782610303565b60a43590604435610b3783610303565b610b5a610367826103588760018060a01b03166000526008602052604060002090565b8015610bba575b610b7c57926107de92916102d0946084359260643592611fc5565b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b0385166064830152608482015260a490fd5b50600254600090602090610bd8906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115610c21575b600091610c03575b50610b61565b610c1b915060203d811161044357610435818361072e565b38610bfd565b610c29611260565b610bf5565b503461000e57604036600319011261000e57600435610c4c81610303565b60018060a01b0316600052600760205260406000206024356000526020526020604060002054604051908152f35b503461000e57600036600319011261000e576020600454604051908152f35b503461000e57606036600319011261000e57610cb36109bb565b60243590610cc082610303565b60443590610ce7610367836103588660018060a01b03166000526008602052604060002090565b8015610d44575b610d0057906102d0926107de9261129e565b50604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b03929092166064820152608481019190915260a490fd5b50600254600090602090610d62906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115610dab575b600091610d8d575b50610cee565b610da5915060203d811161044357610435818361072e565b38610d87565b610db3611260565b610d7f565b8015150361000e57565b503461000e57606036600319011261000e57600435610de081610303565b604435602435610def82610db8565b6000805490936001600160a01b03918216338190036102e15750916040917f1258883257f202b4bfb5c92d9effcc9b5e7775dcbb52c2c31f39f2dff27d3c2d93169384865260086020528286208287526020528286209015159060ff1981541660ff831617905582519182526020820152a2604051f35b503461000e57606036600319011261000e57600435610e8481610303565b60443567ffffffffffffffff80821161000e573660238301121561000e57816004013590811161000e573660248260051b8401011161000e576102d092602461097093019060243590612c9d565b503461000e57600036600319011261000e576002546040516001600160a01b039091168152602090f35b503461000e57600036600319011261000e576003546040516001600160a01b039091168152602090f35b503461000e57606036600319011261000e57600435610f4481610303565b604435906001600160a01b0316610f5d82303384613ba8565b6000526007602052604060002060243560005260205260406000208054918201809211610f875755005b610f8f61121a565b55005b503461000e57600036600319011261000e5760206040516203f4808152f35b503461000e57602036600319011261000e57600435610fcf81610303565b6001546001600160a01b039081169133839003610ffd57811680610fef57005b610382916003541690613c5e565b604051636d4c6c8960e01b8152806102ff853360048401611120565b503461000e57600036600319011261000e576040517f000000000000000000000000db5fd0678eed82246b599da6bc36b56157e4bed86001600160a01b03168152602090f35b503461000e57600036600319011261000e576040517f000000000000000000000000373a06bd3067f8da90239a47f316f09312b7800f6001600160a01b03168152602090f35b503461000e57604036600319011261000e576004356110c381610303565b60018060a01b031660005260086020526040600020602435600052602052602060ff604060002054166040519015158152f35b503461000e57600036600319011261000e576000546040516001600160a01b039091168152602090f35b60008082526020820181905260408201526001600160a01b0391821660608201529116608082015260a00190565b6000549092916001600160a01b03918216338190036102e15750818416806bffffffffffffffffffffffff60a01b600354161760035560005b82518082146111d057906001918110156111c3575b6020600582901b850101516111bd90889087166001600160a01b0316613c5e565b01611187565b6111cb611203565b61119c565b505092935050507fc06343c9448e37c4ed257861469b4b35c140991c9cf08a4c38d335a1bc6a75d36000604051a2600190565b50634e487b7160e01b600052603260045260246000fd5b50634e487b7160e01b600052601160045260246000fd5b9190820180921161123e57565b61124661121a565b565b9081602091031261000e575161125d81610db8565b90565b506040513d6000823e3d90fd5b9081602091031261000e575161125d81610303565b9081602091031261000e575190565b9190820391821161123e57565b6001546112b5906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0385166004808301919091526024820187905260ff8516604483015260209591949193919291868680606481010381600080955af1958615611ba4575b8196611b85575b5060ff84169660028814928315611af057611328886137e3565b428111611ab557506002546001600160a01b031685516370a0823160e01b8082526001600160a01b03838116858401908152929b919282821692908e9088908f9081906020010381875afa9d8e15611aa8575b899e611a85575b508d61139991883091149788611a2f575b86613ba8565b818c169d8e958b519a868c52898c806113c3308d8301919091602081019260018060a01b03169052565b03818b5afa9b8c15611a22575b8b9c611a03575b501561155e57505089516301e9a69560e41b815230818801908152602081018f9052600080516020613cd68339815191529e9d9c6114f29c909b909a61147b9a919990986114629892978897509195509391928691839182908c90829060400103925af18015611551575b611534575b505b8951908152309281019283529485928391829160200190565b03915afa928315611527575b926114fa575b5050611291565b936114c9896103586114ad886114a7846103588860018060a01b03166000526007602052604060002090565b54611231565b6001600160a01b03909416600090815260076020526040902090565b555160ff9091168152602081019290925260408201929092523360608201529081906080820190565b0390a3600190565b6115199250803d10611520575b611511818361072e565b810190611282565b3880611474565b503d611507565b61152f611260565b61146e565b61154a90853d871161152057611511818361072e565b5038611447565b611559611260565b611442565b600382036115ff575050895163884e17f360e01b81528087018e8152306020820152600080516020613cd68339815191529e9d9c6114f29c909b909a61147b9a919990986114629892978897509195509391928691839182908c90829060400103925af180156115f2575b6115d5575b505b611449565b6115eb90853d871161152057611511818361072e565b50386115ce565b6115fa611260565b6115c9565b9293921561179f5750509484879461146294611708600080516020613cd68339815191529f9e9d9b988e8697916114f29f9d9288938f61147b9f9086849281611688845163781c18db60e01b815282818b81875afa908115611792575b8891611775575b50855163bcb7ea5d60e01b815230818c0190815290998a9384928b9291849160200190565b0393165af1958615611768575b8596611749575b50825163afd27bf560e01b815296879182905afa94851561173c575b839561171d575b505163769f8e5d60e01b8152308b820190815260208101949094526001600160a01b03909516604084015260006060840181905260808401529395869485939091849160a00190565b0393165af180156115f2576115d55750611449565b611735919550873d89116109a757610999818361072e565b93386116bf565b611744611260565b6116b8565b611761919650823d841161152057611511818361072e565b943861169c565b611770611260565b611695565b61178c9150833d85116109a757610999818361072e565b38611663565b61179a611260565b61165c565b6005819693949596146000146118d957505050908484928951938480926316f0115b60e01b82525afa9182156118cc575b86926118ad575b507f000000000000000000000000db5fd0678eed82246b599da6bc36b56157e4bed816803b156118a95793808b9a9997948d889561186e600080516020613cd68339815191529f9a6114629761147b9b6114f29f9d838f51809681958294636c8d4fa160e01b845230918d850191939290606091608084019560018060a01b03809316855260208501526000604085015216910152565b03925af1801561189c575b6118835750611449565b806118906118969261070d565b80610238565b386115ce565b6118a4611260565b611879565b8580fd5b6118c5919250853d87116109a757610999818361072e565b90386117d7565b6118d4611260565b6117d0565b61146294509188956114f29b99600080516020613cd68339815191529f9e9d9b98938f908f908c80998961147b9f9a60071460001461191f575050506115d0935061332e565b91945092506008915014611936575b505050611449565b8c51848152308782019081526119959291908f908590839081906020010381865afa9182156119f6575b86926119d7575b5051635d043b2960e11b81528881019182523060208301819052604083015294859384928391606090910190565b03925af180156119ca575b6119ad575b84889161192e565b6119c390853d871161152057611511818361072e565b50386119a5565b6119d2611260565b6119a0565b6119ef919250853d871161152057611511818361072e565b9038611967565b6119fe611260565b611960565b611a1b919c508a3d8c1161152057611511818361072e565b9a386113d7565b611a2a611260565b6113d0565b8d5163781c18db60e01b81529091508a818b818a5afa908115611a78575b8c91611a5b575b5090611393565b611a7291508b3d8d116109a757610999818361072e565b38611a54565b611a80611260565b611a4d565b611399919e50611aa190893d8b1161152057611511818361072e565b9d90611382565b611ab0611260565b61137b565b8551636d4c6c8960e01b81526007928101928352602083019190915260006040830181905260608301819052608083015290819060a0010390fd5b60038903611b0657611b018861383e565b611328565b888103611b1657611b0188613873565b60058903611b2757611b01886138a8565b60078903611b3857611b0188613958565b60088903611b4957611b0188613aa1565b8451636d4c6c8960e01b8152600691810191825260ff8716602083015260006040830181905260608301819052608083015290819060a0010390fd5b611b9d919650873d89116109a757610999818361072e565b943861130e565b611bac611260565b611307565b939293600160ff821603611f8c57600154611bd6906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0385166004808301919091526024820187905260ff8516604483015290979093926020808a80606481010381600080965af1998a15611f7f575b829a611f60575b50835163204f83f960e01b81526001600160a01b039a9096908b1682888381845afa978815611f53575b8498611f34575b50428811611efb5760025486516370a0823160e01b8082526001600160a01b039092168482018181529d9e9c9d9b9c919b8d9b9394929391929087908e9081900360200181845afa9c8d15611eee575b889d611ecb575b508c611cc291843091613ba8565b828b169c8d988d888c51809c8982528180611cef8d30908301919091602081019260018060a01b03169052565b03915afa9a8b15611ebe575b8a9b611e95575b508b51632c5a8cf360e21b815260ff9092168883019081526001600160a01b039093166020840152604083019390935260608201929092528690829081906080015b03818a877f000000000000000000000000373a06bd3067f8da90239a47f316f09312b7800f165af1908115611e88575b8791611e6b575b5015611e325716803b15611e2e578361147b948a9997948d8f95611de5600080516020613cd68339815191529f9e9a8098611462986114f29f9d838f51809681958294636b42450d60e01b84528c840160209093929193604081019460018060a01b031681520152565b03925af18015611e21575b611e0e57508951908152309281019283529485928391829160200190565b80611890611e1b9261070d565b38611447565b611e29611260565b611df0565b8480fd5b8751636d4c6c8960e01b8152600f81860190815260006020820181905260408201819052606082018190526080820152819060a0010390fd5b611e829150863d881161044357610435818361072e565b38611d7b565b611e90611260565b611d74565b899392919b50611eb4611d4491853d871161152057611511818361072e565b9b91929350611d02565b611ec6611260565b611cfb565b611cc2919d50611ee790883d8a1161152057611511818361072e565b9c90611cb4565b611ef6611260565b611cad565b8551636d4c6c8960e01b81526007818401908152602081018a9052600060408201819052606082018190526080820152819060a0010390fd5b611f4c919850833d851161152057611511818361072e565b9638611c5d565b611f5b611260565b611c56565b81611f78929b503d8c116109a757610999818361072e565b9838611c2c565b611f87611260565b611c25565b604051636d4c6c8960e01b81526006600482015260ff91909116602482015260006044820181905260648201819052608482015260a490fd5b90919493611fe06103e56103e560015460018060a01b031690565b6040805163125cf47f60e01b81526001600160a01b038616600480830191909152602482018a90526006604483015260209792949092888380606481010381600080995af1928315612570575b8593612551575b5060018060a01b03809316918a856120918c878d169e8f8c61205d60025460018060a01b031690565b9051636eb1769f60e11b81526001600160a01b03808316978201978852909316602087015294919384928391829160400190565b03915afa908115612544575b8991612527575b50156124ee57928b95928795926121018f9a99968c51908a82806120e4846370a0823160e01b9d8e84528301919091602081019260018060a01b03169052565b03818a5afa9182156124e1575b8c926124c2575b50309087613ba8565b8a5186815230888201908152909e908f9081906020010381875afa9d8e156124b5575b899e612494575b508a5186815230888201908152909a8c92918a918d918290819060200103915afa9a8b15612487575b8a9b61243d575b5088936121ed938b8a858a8996878e839d9a519a8b9182528180612190308a8301919091602081019260018060a01b03169052565b03915afa988915612430575b8599612409575b5090808880938686518094819363378efa3760e01b8352165afa9081156123fc575b86916123df575b508351630f922e0760e31b81528581019788529a9116958a91829160200190565b0381875afa9788156123d2575b83986123a9575b5051632b83cccd60e01b81526001600160a01b0388169181019182526020820194909452604081019590955291938492839182906060015b03925af1801561239c575b61237f575b50858a5180948193636a5c1cc960e11b8352165afa908115612372575b8691612355575b50600354612285906103e5906001600160a01b031681565b8851848152308682019081529093919291879185919082908190602001039185165afa928315612348575b8793612329575b50813b1561232557948a9997948d8895611de5600080516020613cd68339815191529f9e9a6114629786976114f29f9d838f9e61147b9f519687958694859363248391ff60e01b85528d850160409194939294606082019560018060a01b0380921683521660208201520152565b8680fd5b612341919350863d881161152057611511818361072e565b91386122b7565b612350611260565b6122b0565b61236c9150853d87116109a757610999818361072e565b3861226d565b61237a611260565b612266565b61239590833d851161152057611511818361072e565b5038612249565b6123a4611260565b612244565b61223998506123c89087949392943d89116109a757610999818361072e565b9792909192612201565b6123da611260565b6121fa565b6123f69150833d85116109a757610999818361072e565b386121cc565b612404611260565b6121c5565b8291995091886124268194823d841161152057611511818361072e565b9a929350506121a3565b612438611260565b61219c565b899b509387938b8a899895898e99968a97883d8a11612480575b612461818361072e565b810161246c91611282565b9f509650509598505050935093989a61215b565b503d612457565b61248f611260565b612154565b8b919e506124ae90893d8b1161152057611511818361072e565b9d9061212b565b6124bd611260565b612124565b6124da9192508b3d8d1161152057611511818361072e565b90386120f8565b6124e9611260565b6120f1565b8851636d4c6c8960e01b8152601d81890190815260006020820181905260408201819052606082018190526080820152819060a0010390fd5b61253e91508d803d1061152057611511818361072e565b386120a4565b61254c611260565b61209d565b612569919350893d8b116109a757610999818361072e565b9138612034565b612578611260565b61202d565b8060001904821181151516612590570290565b61259861121a565b0290565b81156125a6570490565b634e487b7160e01b600052601260045260246000fd5b6001546125d3906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b038416600480830191909152602482018690526000604483015291939190602090818180606481010381600080995af19081156128cd575b85916128b0575b50855163204f83f960e01b81526001600160a01b03918216949083818681895afa9081156128a3575b8791612886575b50421061284e5786516370a0823160e01b8152338582019081529094908490869081906020010381895afa948515612841575b8795612820575b5083906126ea6126c16126ba8c6103588760018060a01b03166000526007602052604060002090565b548861257d565b8a516318160ddd60e01b815290848285818d5afa918215612813575b8b926127f4575b5061259c565b96878b6127098660018060a01b03166000526007602052604060002090565b600091825260205260409020549061272091611291565b6001600160a01b03851660009081526007602052604090208c906000918252602052604090205589516388ba33df60e01b81523392810192835260208301889052988992839190829060400103925af1600080516020613cd6833981519152966127c4949180156127e7575b6127c9575b505016946127a0843388613b37565b51600081526020810193909352604083019190915233606083015281906080820190565b0390a3565b816127df92903d1061044357610435818361072e565b503880612791565b6127ef611260565b61278c565b61280c919250853d871161152057611511818361072e565b90386126e4565b61281b611260565b6126dd565b8491955061283a90823d841161152057611511818361072e565b9490612691565b612849611260565b61268a565b8651636d4c6c8960e01b81526007818601908152426020820152604081018a90526000606082018190526080820152819060a0010390fd5b61289d9150843d861161152057611511818361072e565b38612657565b6128ab611260565b612650565b6128c79150823d84116109a757610999818361072e565b38612627565b6128d5611260565b612620565b6001600160a01b03811660009081526008602052604090209095949392919081600052602052612910604060002060ff90541690565b8015612963575b6129255761125d94956129d7565b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b0387166064830152608482015260a490fd5b50600254600090602090612981906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af19081156129ca575b6000916129ac575b50612917565b6129c4915060203d811161044357610435818361072e565b386129a6565b6129d2611260565b61299e565b600154919392916129f2906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0384166004820152602481018790526000604482018190529193909290916020918291859160649183915af1928315612c90575b600093612c71575b50835163204f83f960e01b81526001600160a01b03938416908281600481855afa908115612c64575b600091612c47575b50804210612c125750600080516020613cd683398151915295938383612b7e612b98958d8d96612af6612acc612ac58a612bbc9f9d6103589060018060a01b03166000526007602052604060002090565b548461257d565b8b516318160ddd60e01b8152908582600481875afa918215612c05575b600092612bed575061259c565b9a88612b46612b268e612b20819d6103588d60018060a01b03166000526007602052604060002090565b54611291565b6001600160a01b038a166000908152600760205260409020909290610358565b558a516388ba33df60e01b81526001600160a01b03909116600482015260248101929092529092839190829060009082906044820190565b03925af18015612be0575b612bc2575b5050169687613b37565b51600081526020810191909152604081018690523360608201529081906080820190565b0390a390565b81612bd892903d1061044357610435818361072e565b503880612b8e565b612be8611260565b612b89565b61280c919250863d881161152057611511818361072e565b612c0d611260565b612ae9565b8551636d4c6c8960e01b815260076004820152602481019190915260006044820181905260648201819052608482015260a490fd5b612c5e9150833d851161152057611511818361072e565b38612a74565b612c6c611260565b612a6c565b81612c899294503d85116109a757610999818361072e565b9138612a43565b612c98611260565b612a3b565b6001600160a01b038116600090815260086020526040902090949392919081600052602052612cd2604060002060ff90541690565b8015612d25575b612ce75761125d9394612dc1565b604051636d4c6c8960e01b81526011600482015260248101919091526000604482018190526001600160a01b0386166064830152608482015260a490fd5b50600254600090602090612d43906103e5906001600160a01b031681565b60046040518094819363b9b8af0b60e01b83525af1908115612d8c575b600091612d6e575b50612cd9565b612d86915060203d811161044357610435818361072e565b38612d68565b612d94611260565b612d60565b9190811015612daa575b60051b0190565b612db2611203565b612da3565b3561125d81610303565b600154909290612ddb906103e5906001600160a01b031681565b6040805163125cf47f60e01b81526001600160a01b0386166004820152602481018590526000604482015291959193909290602085806064810103816000809b5af1948515613321575b8795613300575b50835163204f83f960e01b808252906020816004816001600160a01b038b165afa9081156132f3575b89916132d4575b50421061323f5750869493929190855b838703612e92575061125d968896503395506001600160a01b03169350613b3792505050565b909192939495612ee68199848a6020612eb4612eaf878c8c612d99565b612db7565b8b51636eb1769f60e11b81526001600160a01b0390911660048201523060248201529485908d90829081906044820190565b03916001600160a01b03165afa938415613232575b8694613204575b506020612f18612eaf612f449596978c8c612d99565b8b516370a0823160e01b81526001600160a01b0390911660048201529384908d90829081906024820190565b03916001600160a01b03165afa9283156131f7575b86936131c1575b506001600160a01b03166000908152600760205260409020612fbd91600491612f8c91612ac591610358565b60208c8c51938480926318160ddd60e01b825260018060a01b03165afa9182156131b4575b879261319a575061259c565b90612fca6004548361259c565b9381811061316457508392916130f26001956130ec8f8f908f918f8f8f612eaf958f93613067612eaf886131009f96866130476020809a8c613013612eaf888861309f9f612d99565b8a516388ba33df60e01b81526001600160a01b03909116600482015260248101929092529092839190829081906044820190565b03916001600160a01b038d165af18015613157575b61313a575b50612d99565b90516326ce41a160e21b81526001600160a01b0390911660048201523060248201526000604482015293849283919082906064820190565b03926001600160a01b03165af1801561312d575b61310e575b506130e48d6103586114ad8a612b20846103588860018060a01b03166000526007602052604060002090565b558d8d612d99565b92611291565b90858060a01b038d16613b37565b019801959493929190612e6c565b6131269060203d60201161044357610435818361072e565b50386130b8565b613135611260565b6130b3565b613150908a3d8c1161044357610435818361072e565b5038613061565b61315f611260565b61305c565b8951636d4c6c8960e01b81526014600482015260248101919091526044810191909152600060648201819052608482015260a490fd5b61280c91925060203d60201161152057611511818361072e565b6131bc611260565b612fb1565b6004919350612ac5612fbd936103586131eb612f8c9460203d60201161152057611511818361072e565b96945050935050612f60565b6131ff611260565b612f59565b612f44939450612f18612eaf613228602093843d861161152057611511818361072e565b9695505050612f02565b61323a611260565b612efb565b84519081526102ff90889086906020816004816001600160a01b038d165afa9283156132c7575b926132a6575b5051636d4c6c8960e01b815260076004820152602481019190915260006044820181905260648201819052608482015290819060a4820190565b6132c091925060203d60201161152057611511818361072e565b908361326c565b6132cf611260565b613266565b6132ed915060203d60201161152057611511818361072e565b38612e5c565b6132fb611260565b612e55565b61331a91955060203d6020116109a757610999818361072e565b9338612e2c565b613329611260565b612e25565b60405163247c579160e21b815292600492916020908590859082906001600160a01b03165afa9384156137d6575b6000946137b5575b506040516362b9c05160e11b815260208185816001600160a01b0389165afa9081156137a8575b600091613789575b50604051635a7ad10d60e11b81526133e790602090818188816001600160a01b038c165afa90811561377c575b60009161375f575b50604051809381926371a5d76160e01b83528983019190602083019252565b03816001600160a01b038a165afa908115613752575b600091613733575b506002546001600160a01b03166040516370a0823160e01b81526001600160a01b03821687820190815290949060209086908190830103816001600160a01b0387165afa948515613726575b600095613705575b508085116136fd575b506001600160a01b0316803b1561000e5760408051633ad4d8b960e11b81526001600160a01b0390931687840190815260208101869052600092849290918391859183910103925af180156136f0575b6136dd575b506001600160a01b0316803b1561000e576040805163f3fef3a360e01b81526001600160a01b038716868201908152602081019490945292600092849290918391859183910103925af180156136d0575b6136bd575b5060405163f018ae6960e01b808252909360208585816001600160a01b0385165afa9485156136b0575b60009561368f575b5060035460209061355a906103e5906001600160a01b031681565b60405193845292918290869082906001600160a01b03165afa908115613682575b600091613663575b506040516370a0823160e01b815230858201908152909560209187919082908190850103916001600160a01b03165afa948515613656575b600095613635575b50813b1561000e576040805163248391ff60e01b81526001600160a01b039283169581019586529390911660208501528301939093529160009183919082908490829060600103925af18015613628575b61361b5750565b806118906112469261070d565b613630611260565b613614565b61364f91955060203d60201161152057611511818361072e565b93386135c3565b61365e611260565b6135bb565b61367c915060203d6020116109a757610999818361072e565b38613583565b61368a611260565b61357b565b6136a991955060203d6020116109a757610999818361072e565b933861353f565b6136b8611260565b613537565b806118906136ca9261070d565b3861350d565b6136d8611260565b613508565b806118906136ea9261070d565b386134b7565b6136f8611260565b6134b2565b935038613462565b61371f91955060203d60201161152057611511818361072e565b9338613459565b61372e611260565b613451565b61374c915060203d6020116109a757610999818361072e565b38613405565b61375a611260565b6133fd565b6137769150823d841161152057611511818361072e565b386133c8565b613784611260565b6133c0565b6137a2915060203d6020116109a757610999818361072e565b38613393565b6137b0611260565b61338b565b6137cf91945060203d6020116109a757610999818361072e565b9238613364565b6137de611260565b61335c565b60405163204f83f960e01b815290602090829060049082906001600160a01b03165afa908115613831575b600091613819575090565b61125d915060203d811161152057611511818361072e565b613839611260565b61380e565b60405163aa082a9d60e01b815290602090829060049082906001600160a01b03165afa90811561383157600091613819575090565b6040516370c264df60e11b815290602090829060049082906001600160a01b03165afa90811561383157600091613819575090565b6040516316f0115b60e01b81526020916001600160a01b0391839182908290600490829087165afa90811561394b575b60009161392e575b50600460405180948193632745fed560e11b8352165afa918215613921575b60009261390b57505090565b61125d9250803d1061152057611511818361072e565b613929611260565b6138ff565b6139459150823d84116109a757610999818361072e565b386138e0565b613953611260565b6138d8565b60405163247c579160e21b81526020916001600160a01b03918391613a0891849184908290600490829086165afa908115613a94575b600091613a77575b50166040516362b9c05160e11b8152838082600481865afa918215613a6a575b600092613a4a575b50604051636558954f60e01b81529192829060049082905afa908115613a3d575b600091613a20575b506040519485809481936220e10960e51b8352600483019190602083019252565b0392165afa9182156139215760009261390b57505090565b613a379150843d861161152057611511818361072e565b386139e7565b613a45611260565b6139df565b60049250613a6490823d84116109a757610999818361072e565b916139be565b613a72611260565b6139b6565b613a8e9150843d86116109a757610999818361072e565b38613996565b613a9c611260565b61398e565b60405163e16695b560e01b815290602090829060049082906001600160a01b03165afa908115613b2a575b600091613adf575b5064ffffffffff1690565b6020813d8211613b22575b81613af76020938361072e565b81010312613b1e57519064ffffffffff82168203613b1b575064ffffffffff613ad4565b80fd5b5080fd5b3d9150613aea565b613b32611260565b613acc565b6044600092838093613b6a966040519363a9059cbb60e01b855260018060a01b0316600485015260248401525af1613c27565b15613b7157565b60405162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c8819985a5b1959608a1b6044820152606490fd5b60009283606492613be4968295604051946323b872dd60e01b865260018060a01b03809216600487015216602485015260448401525af1613c27565b15613beb57565b60405162461bcd60e51b81526020600482015260146024820152731d1c985b9cd9995c88199c9bdb4819985a5b195960621b6044820152606490fd5b6000903d9015613c57579081602014613c4a575015613c4557600090565b600190565b9050602081803e51151590565b908181803efd5b60405163095ea7b360e01b81526001600160a01b0390921660048301526000196024830152613c9891600091829160449183905af1613c27565b15613c9f57565b60405162461bcd60e51b815260206004820152600e60248201526d1054141493d59157d1905253115160921b6044820152606490fdfef9820069613a0f200b0791623bec3ed14a17256cf61c3fc2160fdadbbcdda463a26469706673582212207234f4d02766bbc48118cc207dfdebcc367022e3d70cc9a1355fb4874657444a64736f6c63430008100033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000429b47c4aeadd42bbcb118651c8984086bfc4551000000000000000000000000373a06bd3067f8da90239a47f316f09312b7800f000000000000000000000000db5fd0678eed82246b599da6bc36b56157e4bed8

-----Decoded View---------------
Arg [0] : l (address): 0x429B47C4AEADD42BBcB118651C8984086Bfc4551
Arg [1] : s (address): 0x373a06bD3067f8DA90239a47f316F09312b7800F
Arg [2] : t (address): 0xdB5fD0678eED82246b599da6BC36B56157E4beD8

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 000000000000000000000000429b47c4aeadd42bbcb118651c8984086bfc4551
Arg [1] : 000000000000000000000000373a06bd3067f8da90239a47f316f09312b7800f
Arg [2] : 000000000000000000000000db5fd0678eed82246b599da6bc36b56157e4bed8


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.