ETH Price: $2,430.82 (+0.28%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x60806040160922952022-12-01 20:19:11703 days ago1669925951IN
 Create: Incentivizer
0 ETH0.0682849513.41923147

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Incentivizer

Compiler Version
v0.8.15+commit.e14f2714

Optimization Enabled:
No with 1000000 runs

Other Settings:
default evmVersion, Apache-2.0 license
File 1 of 43 : Incentivizer.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "@equilibria/root/control/unstructured/UInitializable.sol";
import "@equilibria/root/control/unstructured/UReentrancyGuard.sol";
import "../interfaces/IIncentivizer.sol";
import "../interfaces/IController.sol";
import "../controller/UControllerProvider.sol";
import "./types/ProductManager.sol";

/**
 * @title Incentivizer
 * @notice Manages logic and state for all incentive programs in the protocol.
 */
contract Incentivizer is IIncentivizer, UInitializable, UControllerProvider, UReentrancyGuard {
    /// @dev Product management state
    mapping(IProduct => ProductManager) private _products;

    /// @dev Fees that have been collected, but remain unclaimed
    mapping(Token18 => UFixed18) public fees;

    /**
     * @notice Initializes the contract state
     * @dev Must be called atomically as part of the upgradeable proxy deployment to
     *      avoid front-running
     * @param controller_ Factory contract address
     */
    function initialize(IController controller_) external initializer(1) {
        __UControllerProvider__initialize(controller_);
        __UReentrancyGuard__initialize();
    }

    /**
     * @notice Creates a new incentive program
     * @dev Must be called as the product or protocol owner
     * @param product The product to create the new program on
     * @param programInfo Parameters for the new program
     * @return programId New program's ID
     */
    function create(IProduct product, ProgramInfo calldata programInfo)
    external
    nonReentrant
    isProduct(product)
    notPaused
    onlyOwner(programInfo.coordinatorId)
    returns (uint256 programId) {
        IController _controller = controller();

        // Validate
        if (programInfo.coordinatorId != 0 && programInfo.coordinatorId != _controller.coordinatorFor(product))
            revert IncentivizerNotAllowedError(product);
        if (active(product) >= _controller.programsPerProduct())
            revert IncentivizerTooManyProgramsError();
        ProgramInfoLib.validate(programInfo);

        // Take fee
        (ProgramInfo memory newProgramInfo, UFixed18 programFeeAmount) = ProgramInfoLib.deductFee(programInfo, _controller.incentivizationFee());
        fees[newProgramInfo.token] = fees[newProgramInfo.token].add(programFeeAmount);

        // Register program
        programId = _products[product].register(newProgramInfo);

        // Charge creator
        newProgramInfo.token.pull(msg.sender, programInfo.amount.sum());

        emit ProgramCreated(
            product,
            programId,
            newProgramInfo,
            programFeeAmount
        );
    }

    /**
     * @notice Completes an in-progress program early
     * @dev Must be called as the program owner
     * @param product Product that the program is running on
     * @param programId Program to complete early
     */
    function complete(IProduct product, uint256 programId)
    external
    nonReentrant
    isProgram(product, programId)
    notPaused
    onlyProgramOwner(product, programId)
    {
        ProductManagerLib.SyncResult memory syncResult = _products[product].complete(product, programId);
        _handleSyncResult(product, syncResult);
    }

    /**
     * @notice Starts and completes programs as they become available
     * @dev Called every settle() from each product
     * @param currentOracleVersion The preloaded current oracle version
     */
    function sync(IOracleProvider.OracleVersion memory currentOracleVersion) external onlyProduct {
        IProduct product = IProduct(msg.sender);

        ProductManagerLib.SyncResult[] memory syncResults = _products[product].sync(product, currentOracleVersion);
        for (uint256 i = 0; i < syncResults.length; i++) {
            _handleSyncResult(product, syncResults[i]);
        }
    }

    /**
     * @notice Handles refunding and event emitting on program start and completion
     * @param product Product that the program is running on
     * @param syncResult The data from the sync event to handle
     */
    function _handleSyncResult(IProduct product, ProductManagerLib.SyncResult memory syncResult) private {
        uint256 programId = syncResult.programId;
        if (!syncResult.refundAmount.isZero())
            _products[product].token(programId).push(treasury(product, programId), syncResult.refundAmount);
        if (syncResult.versionStarted != 0)
            emit ProgramStarted(product, programId, syncResult.versionStarted);
        if (syncResult.versionComplete != 0)
            emit ProgramComplete(product, programId, syncResult.versionComplete);
    }

    /**
     * @notice Settles unsettled balance for `account`
     * @dev Called immediately proceeding a position update in the corresponding product
     * @param account Account to sync
     * @param currentOracleVersion The preloaded current oracle version
     */
    function syncAccount(
        address account,
        IOracleProvider.OracleVersion memory currentOracleVersion
    ) external onlyProduct {
        IProduct product = IProduct(msg.sender);
        _products[product].syncAccount(product, account, currentOracleVersion);
    }

    /**
     * @notice Claims all of `msg.sender`'s rewards for `product` programs
     * @param product Product to claim rewards for
     * @param programIds Programs to claim rewards for
     */
    function claim(IProduct product, uint256[] calldata programIds)
    external
    nonReentrant
    {
        _claimProduct(msg.sender, product, programIds);
    }

    /**
     * @notice Claims all of `account`'s rewards for `product` programs
     * @param account Account to claim rewards for
     * @param product Product to claim rewards for
     * @param programIds Programs to claim rewards for
     */
    function claimFor(address account, IProduct product, uint256[] calldata programIds)
    external
    nonReentrant
    onlyAccountOrMultiInvoker(account)
    {
        _claimProduct(account, product, programIds);
    }

    /**
     * @notice Claims all of `msg.sender`'s rewards for a specific program
     * @param products Products to claim rewards for
     * @param programIds Programs to claim rewards for
     */
    function claim(IProduct[] calldata products, uint256[][] calldata programIds)
    external
    nonReentrant
    {
        if (products.length != programIds.length) revert IncentivizerBatchClaimArgumentMismatchError();
        for (uint256 i; i < products.length; i++) {
            _claimProduct(msg.sender, products[i], programIds[i]);
        }
    }

    /**
     * @notice Claims all of `msg.sender`'s rewards for `product` programs
     * @dev Internal helper with validation checks
     * @param account Account to claim rewards for
     * @param product Product to claim rewards for
     * @param programIds Programs to claim rewards for
     */
    function _claimProduct(address account, IProduct product, uint256[] memory programIds)
    private
    isProduct(product)
    notPaused
    settleForAccount(account, product)
    {
        for (uint256 i; i < programIds.length; i++) {
            _claimProgram(account, product, programIds[i]);
        }
    }

    /**
     * @notice Claims all of `msg.sender`'s rewards for `programId` on `product`
     * @dev Internal helper with validation checks
     * @param account Account to claim rewards for
     * @param product Product to claim rewards for
     * @param programId Program to claim rewards for
     */
    function _claimProgram(address account, IProduct product, uint256 programId)
    private
    isProgram(product, programId)
    {
        ProductManager storage productManager = _products[product];
        UFixed18 claimAmount = productManager.claim(account, programId);
        productManager.token(programId).push(account, claimAmount);
        emit Claim(product, account, programId, claimAmount);
    }

    /**
     * @notice Claims all `tokens` fees to the protocol treasury
     * @param tokens Tokens to claim fees for
     */
    function claimFee(Token18[] calldata tokens) external notPaused {
        for(uint256 i; i < tokens.length; i++) {
            Token18 token = tokens[i];
            UFixed18 amount = fees[token];

            fees[token] = UFixed18Lib.ZERO;
            token.push(controller().treasury(), amount);

            emit FeeClaim(token, amount);
        }
    }

    /**
     * @notice Returns the quantity of active programs for a given product
     * @param product Product to check for
     * @return Number of active programs
     */
    function active(IProduct product) public view returns (uint256) {
        return _products[product].active();
    }

    /**
     * @notice Returns the quantity of programs for a given product
     * @param product Product to check for
     * @return Number of programs (inactive or active)
     */
    function count(IProduct product) external view returns (uint256) {
        return _products[product].programInfos.length;
    }

    /**
     * @notice Returns program info for program `programId`
     * @param product Product to return for
     * @param programId Program to return for
     * @return Program info
     */
    function programInfos(IProduct product, uint256 programId) external view returns (ProgramInfo memory) {
        return _products[product].programInfos[programId];
    }

    /**
     * @notice Returns `account`'s total unclaimed rewards for a specific program
     * @param product Product to return for
     * @param account Account to return for
     * @param programId Program to return for
     * @return `account`'s total unclaimed rewards for `programId`
     */
    function unclaimed(IProduct product, address account, uint256 programId) external view returns (UFixed18) {
        return _products[product].unclaimed(account, programId);
    }

    /**
     * @notice Returns available rewards for a specific program
     * @param product Product to return for
     * @param programId Program to return for
     * @return Available rewards for `programId`
     */
    function available(IProduct product, uint256 programId) external view returns (UFixed18) {
        return _products[product].programs[programId].available;
    }

    /**
     * @notice Returns the version started for a specific program
     * @param product Product to return for
     * @param programId Program to return for
     * @return The version started for `programId`
     */
    function versionStarted(IProduct product, uint256 programId) external view returns (uint256) {
        return _products[product].programs[programId].versionStarted;
    }

    /**
     * @notice Returns the version completed for a specific program
     * @param product Product to return for
     * @param programId Program to return for
     * @return The version completed for `programId`
     */
    function versionComplete(IProduct product, uint256 programId) external view returns (uint256) {
        return _products[product].programs[programId].versionComplete;
    }

    /**
     * @notice Returns the owner of a specific program
     * @param product Product to return for
     * @param programId Program to return for
     * @return The owner of `programId`
     */
    function owner(IProduct product, uint256 programId) public view returns (address) {
        return controller().owner(_products[product].programInfos[programId].coordinatorId);
    }

    /**
     * @notice Returns the treasury of a specific program
     * @param product Product to return for
     * @param programId Program to return for
     * @return The treasury of `programId`
     */
    function treasury(IProduct product, uint256 programId) public view returns (address) {
        return controller().treasury(_products[product].programInfos[programId].coordinatorId);
    }

    /// @dev Helper to fully settle an account's state
    modifier settleForAccount(address account, IProduct product) {
        product.settleAccount(account);

        _;
    }

    /// @dev Only allow the owner of `programId` to call
    modifier onlyProgramOwner(IProduct product, uint256 programId) {
        if (msg.sender != owner(product, programId)) revert IncentivizerNotProgramOwnerError(product, programId);

        _;
    }

    /// @dev Only allow a valid `programId`
    modifier isProgram(IProduct product, uint256 programId) {
        if (!_products[product].valid(programId)) revert IncentivizerInvalidProgramError(product, programId);

        _;
    }
}

File 2 of 43 : Batcher.sol
//SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";
import "@equilibria/root/token/types/Token18.sol";
import "@equilibria/root/token/types/Token6.sol";
import "@equilibria/root/control/unstructured/UOwnable.sol";
import "../interfaces/IBatcher.sol";

abstract contract Batcher is IBatcher, UOwnable {
    using UFixed18Lib for UFixed18;
    using Token18Lib for Token18;
    using Token6Lib for Token6;

    IEmptySetReserve public immutable RESERVE;
    Token18 public immutable DSU;
    Token6 public immutable USDC;

    constructor(IEmptySetReserve reserve, Token18 dsu, Token6 usdc) {
        RESERVE = reserve;
        DSU = dsu;
        USDC = usdc;

        DSU.approve(address(RESERVE));
        USDC.approve(address(RESERVE));

        __UOwnable__initialize();
    }

    function totalBalance() public view returns (UFixed18) {
        return DSU.balanceOf().add(USDC.balanceOf());
    }

    function wrap(UFixed18 amount, address to) external {
        _wrap(amount, to);
        emit Wrap(to, amount);
    }

    function _wrap(UFixed18 amount, address to) virtual internal {
        USDC.pull(msg.sender, amount, true);
        DSU.push(to, amount);
    }

    function unwrap(UFixed18 amount, address to) external {
        _unwrap(amount, to);
        emit Unwrap(to, amount);
    }

    function _unwrap(UFixed18 amount, address to) virtual internal {
        DSU.pull(msg.sender, amount);
        USDC.push(to, amount);
    }

    function rebalance() external {
        (UFixed18 usdcBalance, UFixed18 dsuBalance) = (USDC.balanceOf(), DSU.balanceOf());

        _rebalance(USDC.balanceOf(), DSU.balanceOf());

        (UFixed18 oldBalance, UFixed18 newBalance) = (usdcBalance.add(dsuBalance), totalBalance());
        if (!oldBalance.eq(newBalance)) revert BatcherBalanceMismatchError(oldBalance, newBalance);

        emit Rebalance(usdcBalance, UFixed18Lib.ZERO);
    }

    function _rebalance(UFixed18 usdcBalance, UFixed18 dsuBalance) virtual internal;

    function close() external onlyOwner {
        UFixed18 usdcBalance = USDC.balanceOf();
        if (!usdcBalance.isZero()) RESERVE.mint(usdcBalance);

        UFixed18 dsuBalance = DSU.balanceOf();
        UFixed18 repayAmount = UFixed18Lib.min(RESERVE.debt(address(this)), dsuBalance);
        UFixed18 returnAmount = dsuBalance.sub(repayAmount);

        RESERVE.repay(address(this), repayAmount);
        if (!returnAmount.isZero()) DSU.push(address(RESERVE), dsuBalance.sub(repayAmount));

        emit Close(dsuBalance);
    }
}

interface IEmptySetReserve {
    function debt(address borrower) external view returns (UFixed18);
    function repay(address borrower, UFixed18 amount) external;
    function mint(UFixed18 amount) external;
    function redeem(UFixed18 amount) external;
}

File 3 of 43 : IBatcher.sol
//SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";

interface IBatcher {
    event Wrap(address indexed to, UFixed18 amount);
    event Unwrap(address indexed to, UFixed18 amount);
    event Rebalance(UFixed18 newMinted, UFixed18 newRedeemed);
    event Close(UFixed18 amount);

    error BatcherNotImplementedError();
    error BatcherBalanceMismatchError(UFixed18 oldBalance, UFixed18 newBalance);

    function totalBalance() external view returns (UFixed18);
    function wrap(UFixed18 amount, address to) external;
    function unwrap(UFixed18 amount, address to) external;
    function rebalance() external;
}

File 4 of 43 : IOracleProvider.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/Fixed18.sol";

interface IOracleProvider {
    /// @dev A singular oracle version with its corresponding data
    struct OracleVersion {
        /// @dev The iterative version
        uint256 version;

        /// @dev the timestamp of the oracle update
        uint256 timestamp;

        /// @dev The oracle price of the corresponding version
        Fixed18 price;
    }

    function sync() external returns (OracleVersion memory);
    function currentVersion() external view returns (OracleVersion memory);
    function atVersion(uint256 oracleVersion) external view returns (OracleVersion memory);
}

File 5 of 43 : UInitializable.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/Address.sol";
import "../../storage/UStorage.sol";

/**
 * @title UInitializable
 * @notice Library to manage the initialization lifecycle of upgradeable contracts
 * @dev `UInitializable` allows the creation of pseudo-constructors for upgradeable contracts. One
 *      `initializer` should be declared per top-level contract. Child contracts can use the `onlyInitializer`
 *      modifier to tag their internal initialization functions to ensure that they can only be called
 *      from a top-level `initializer` or a constructor.
 */
abstract contract UInitializable {
    error UInitializableZeroVersionError();
    error UInitializableAlreadyInitializedError(uint256 version);
    error UInitializableNotInitializingError();

    event Initialized(uint256 version);

    /// @dev The initialized flag
    Uint256Storage private constant _version = Uint256Storage.wrap(keccak256("equilibria.root.UInitializable.version"));

    /// @dev The initializing flag
    BoolStorage private constant _initializing = BoolStorage.wrap(keccak256("equilibria.root.UInitializable.initializing"));

    /// @dev Can only be called once per version, `version` is 1-indexed
    modifier initializer(uint256 version) {
        if (version == 0) revert UInitializableZeroVersionError();
        if (_version.read() >= version) revert UInitializableAlreadyInitializedError(version);

        _version.store(version);
        _initializing.store(true);

        _;

        _initializing.store(false);
        emit Initialized(version);
    }

    /// @dev Can only be called from an initializer or constructor
    modifier onlyInitializer() {
        if (!_constructing() && !_initializing.read()) revert UInitializableNotInitializingError();
        _;
    }

    /**
     * @notice Returns whether the contract is currently being constructed
     * @dev {Address.isContract} returns false for contracts currently in the process of being constructed
     * @return Whether the contract is currently being constructed
     */
    function _constructing() private view returns (bool) {
        return !Address.isContract(address(this));
    }
}

File 6 of 43 : UOwnable.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "./UInitializable.sol";
import "../../storage/UStorage.sol";

/**
 * @title UOwnable
 * @notice Library to manage the ownership lifecycle of upgradeable contracts.
 * @dev This contract has been extended from the Open Zeppelin library to include an
 *      unstructured storage pattern so that it can be safely mixed in with upgradeable
 *      contracts without affecting their storage patterns through inheritance.
 */
abstract contract UOwnable is UInitializable {
    event OwnerUpdated(address indexed newOwner);
    event PendingOwnerUpdated(address indexed newPendingOwner);

    error UOwnableNotOwnerError(address sender);
    error UOwnableNotPendingOwnerError(address sender);

    /// @dev The owner address
    AddressStorage private constant _owner = AddressStorage.wrap(keccak256("equilibria.root.UOwnable.owner"));
    function owner() public view returns (address) { return _owner.read(); }

    /// @dev The pending owner address
    AddressStorage private constant _pendingOwner = AddressStorage.wrap(keccak256("equilibria.root.UOwnable.pendingOwner"));
    function pendingOwner() public view returns (address) { return _pendingOwner.read(); }

    /**
     * @notice Initializes the contract setting `msg.sender` as the initial owner
     */
    function __UOwnable__initialize() internal onlyInitializer {
        _updateOwner(msg.sender);
    }

    /**
     * @notice Updates the new pending owner
     * @dev Can only be called by the current owner
     *      New owner does not take affect until that address calls `acceptOwner()`
     * @param newPendingOwner New pending owner address
     */
    function updatePendingOwner(address newPendingOwner) public onlyOwner {
        _pendingOwner.store(newPendingOwner);
        emit PendingOwnerUpdated(newPendingOwner);
    }

    /**
     * @notice Accepts and transfers the ownership of the contract to the pending owner
     * @dev Can only be called by the pending owner to ensure correctness
     */
    function acceptOwner() external {
        if (msg.sender != pendingOwner()) revert UOwnableNotPendingOwnerError(msg.sender);

        _updateOwner(pendingOwner());
        updatePendingOwner(address(0));
    }

    /**
     * @notice Updates the owner address
     * @param newOwner New owner address
     */
    function _updateOwner(address newOwner) private {
        _owner.store(newOwner);
        emit OwnerUpdated(newOwner);
    }

    /// @dev Throws if called by any account other than the owner
    modifier onlyOwner() {
        if (owner() != msg.sender) revert UOwnableNotOwnerError(msg.sender);
        _;
    }
}

File 7 of 43 : UReentrancyGuard.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "./UInitializable.sol";
import "../../storage/UStorage.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 *
 * NOTE: This contract has been extended from the Open Zeppelin library to include an
 *       unstructured storage pattern, so that it can be safely mixed in with upgradeable
 *       contracts without affecting their storage patterns through inheritance.
 */
abstract contract UReentrancyGuard is UInitializable {
    error UReentrancyGuardReentrantCallError();

    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    /**
     * @dev unstructured storage slot for the reentrancy status
     */
    Uint256Storage private constant _status = Uint256Storage.wrap(keccak256("equilibria.root.UReentrancyGuard.status"));

    /**
     * @dev Initializes the contract setting the status to _NOT_ENTERED.
     */
    function __UReentrancyGuard__initialize() internal onlyInitializer {
        _status.store(_NOT_ENTERED);
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        if (_status.read() == _ENTERED) revert UReentrancyGuardReentrantCallError();

        // Any calls to nonReentrant after this point will fail
        _status.store(_ENTERED);

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status.store(_NOT_ENTERED);
    }
}

File 8 of 43 : CurveMath.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "../number/types/UFixed18.sol";
import "../number/types/Fixed18.sol";

/**
 * @title CurveMath
 * @notice Library for managing math operations for utilization curves.
 */
library CurveMath {
    error CurveMathOutOfBoundsError();

    /**
     * @notice Computes a linear interpolation between two points
     * @param startX First point's x-coordinate
     * @param startY First point's y-coordinate
     * @param endX Second point's x-coordinate
     * @param endY Second point's y-coordinate
     * @param targetX x-coordinate to interpolate
     * @return y-coordinate for `targetX` along the line from (`startX`, `startY`) -> (`endX`, `endY`)
     */
    function linearInterpolation(
        UFixed18 startX,
        Fixed18 startY,
        UFixed18 endX,
        Fixed18 endY,
        UFixed18 targetX
    ) internal pure returns (Fixed18) {
        if (targetX.lt(startX) || targetX.gt(endX)) revert CurveMathOutOfBoundsError();

        UFixed18 xRange = endX.sub(startX);
        Fixed18 yRange = endY.sub(startY);
        UFixed18 xRatio = targetX.sub(startX).div(xRange);
        return yRange.mul(Fixed18Lib.from(xRatio)).add(startY);
    }
}

File 9 of 43 : JumpRateUtilizationCurve.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "../CurveMath.sol";
import "../../number/types/PackedUFixed18.sol";
import "../../number/types/PackedFixed18.sol";

/// @dev JumpRateUtilizationCurve type
struct JumpRateUtilizationCurve {
    PackedFixed18 minRate;
    PackedFixed18 maxRate;
    PackedFixed18 targetRate;
    PackedUFixed18 targetUtilization;
}
using JumpRateUtilizationCurveLib for JumpRateUtilizationCurve global;
type JumpRateUtilizationCurveStorage is bytes32;
using JumpRateUtilizationCurveStorageLib for JumpRateUtilizationCurveStorage global;

/**
 * @title JumpRateUtilizationCurveLib
 * @notice Library for the Jump Rate utilization curve type
 */
library JumpRateUtilizationCurveLib {
    /**
     * @notice Computes the corresponding rate for a utilization ratio
     * @param utilization The utilization ratio
     * @return The corresponding rate
     */
    function compute(JumpRateUtilizationCurve memory self, UFixed18 utilization) internal pure returns (Fixed18) {
        UFixed18 targetUtilization = self.targetUtilization.unpack();
        if (utilization.lt(targetUtilization)) {
            return CurveMath.linearInterpolation(
                UFixed18Lib.ZERO,
                self.minRate.unpack(),
                targetUtilization,
                self.targetRate.unpack(),
                utilization
            );
        }
        if (utilization.lt(UFixed18Lib.ONE)) {
            return CurveMath.linearInterpolation(
                targetUtilization,
                self.targetRate.unpack(),
                UFixed18Lib.ONE,
                self.maxRate.unpack(),
                utilization
            );
        }
        return self.maxRate.unpack();
    }
}

library JumpRateUtilizationCurveStorageLib {
    function read(JumpRateUtilizationCurveStorage self) internal view returns (JumpRateUtilizationCurve memory) {
        return _storagePointer(self);
    }

    function store(JumpRateUtilizationCurveStorage self, JumpRateUtilizationCurve memory value) internal {
        JumpRateUtilizationCurve storage storagePointer = _storagePointer(self);

        storagePointer.minRate = value.minRate;
        storagePointer.maxRate = value.maxRate;
        storagePointer.targetRate = value.targetRate;
        storagePointer.targetUtilization = value.targetUtilization;
    }

    function _storagePointer(JumpRateUtilizationCurveStorage self)
    private pure returns (JumpRateUtilizationCurve storage pointer) {
        assembly { pointer.slot := self }
    }
}

File 10 of 43 : Fixed18.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/math/SignedMath.sol";
import "./UFixed18.sol";
import "./PackedFixed18.sol";

/// @dev Fixed18 type
type Fixed18 is int256;
using Fixed18Lib for Fixed18 global;
type Fixed18Storage is bytes32;
using Fixed18StorageLib for Fixed18Storage global;

/**
 * @title Fixed18Lib
 * @notice Library for the signed fixed-decimal type.
 */
library Fixed18Lib {
    error Fixed18OverflowError(uint256 value);
    error Fixed18PackingOverflowError(int256 value);
    error Fixed18PackingUnderflowError(int256 value);

    int256 private constant BASE = 1e18;
    Fixed18 public constant ZERO = Fixed18.wrap(0);
    Fixed18 public constant ONE = Fixed18.wrap(BASE);
    Fixed18 public constant NEG_ONE = Fixed18.wrap(-1 * BASE);
    Fixed18 public constant MAX = Fixed18.wrap(type(int256).max);
    Fixed18 public constant MIN = Fixed18.wrap(type(int256).min);

    /**
     * @notice Creates a signed fixed-decimal from an unsigned fixed-decimal
     * @param a Unsigned fixed-decimal
     * @return New signed fixed-decimal
     */
    function from(UFixed18 a) internal pure returns (Fixed18) {
        uint256 value = UFixed18.unwrap(a);
        if (value > uint256(type(int256).max)) revert Fixed18OverflowError(value);
        return Fixed18.wrap(int256(value));
    }

    /**
     * @notice Creates a signed fixed-decimal from a sign and an unsigned fixed-decimal
     * @param s Sign
     * @param m Unsigned fixed-decimal magnitude
     * @return New signed fixed-decimal
     */
    function from(int256 s, UFixed18 m) internal pure returns (Fixed18) {
        if (s > 0) return from(m);
        if (s < 0) return Fixed18.wrap(-1 * Fixed18.unwrap(from(m)));
        return ZERO;
    }

    /**
     * @notice Creates a signed fixed-decimal from a signed integer
     * @param a Signed number
     * @return New signed fixed-decimal
     */
    function from(int256 a) internal pure returns (Fixed18) {
        return Fixed18.wrap(a * BASE);
    }

    /**
     * @notice Creates a packed signed fixed-decimal from an signed fixed-decimal
     * @param a signed fixed-decimal
     * @return New packed signed fixed-decimal
     */
    function pack(Fixed18 a) internal pure returns (PackedFixed18) {
        int256 value = Fixed18.unwrap(a);
        if (value > type(int128).max) revert Fixed18PackingOverflowError(value);
        if (value < type(int128).min) revert Fixed18PackingUnderflowError(value);
        return PackedFixed18.wrap(int128(value));
    }

    /**
     * @notice Returns whether the signed fixed-decimal is equal to zero.
     * @param a Signed fixed-decimal
     * @return Whether the signed fixed-decimal is zero.
     */
    function isZero(Fixed18 a) internal pure returns (bool) {
        return Fixed18.unwrap(a) == 0;
    }

    /**
     * @notice Adds two signed fixed-decimals `a` and `b` together
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Resulting summed signed fixed-decimal
     */
    function add(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(Fixed18.unwrap(a) + Fixed18.unwrap(b));
    }

    /**
     * @notice Subtracts signed fixed-decimal `b` from `a`
     * @param a Signed fixed-decimal to subtract from
     * @param b Signed fixed-decimal to subtract
     * @return Resulting subtracted signed fixed-decimal
     */
    function sub(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(Fixed18.unwrap(a) - Fixed18.unwrap(b));
    }

    /**
     * @notice Multiplies two signed fixed-decimals `a` and `b` together
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Resulting multiplied signed fixed-decimal
     */
    function mul(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(Fixed18.unwrap(a) * Fixed18.unwrap(b) / BASE);
    }

    /**
     * @notice Divides signed fixed-decimal `a` by `b`
     * @param a Signed fixed-decimal to divide
     * @param b Signed fixed-decimal to divide by
     * @return Resulting divided signed fixed-decimal
     */
    function div(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(Fixed18.unwrap(a) * BASE / Fixed18.unwrap(b));
    }

    /**
     * @notice Divides unsigned fixed-decimal `a` by `b`
     * @dev Does not revert on divide-by-0, instead returns `ONE` for `0/0`, `MAX` for `n/0`, and `MIN` for `-n/0`.
     * @param a Unsigned fixed-decimal to divide
     * @param b Unsigned fixed-decimal to divide by
     * @return Resulting divided unsigned fixed-decimal
     */
    function unsafeDiv(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        if (isZero(b)) {
            if (gt(a, ZERO)) return MAX;
            if (lt(a, ZERO)) return MIN;
            return ONE;
        } else {
            return div(a, b);
        }
    }

    /**
     * @notice Computes a * b / c without loss of precision due to BASE conversion
     * @param a First signed fixed-decimal
     * @param b Signed number to multiply by
     * @param c Signed number to divide by
     * @return Resulting computation
     */
    function muldiv(Fixed18 a, int256 b, int256 c) internal pure returns (Fixed18) {
        return muldiv(a, Fixed18.wrap(b), Fixed18.wrap(c));
    }

    /**
     * @notice Computes a * b / c without loss of precision due to BASE conversion
     * @param a First signed fixed-decimal
     * @param b Signed fixed-decimal to multiply by
     * @param c Signed fixed-decimal to divide by
     * @return Resulting computation
     */
    function muldiv(Fixed18 a, Fixed18 b, Fixed18 c) internal pure returns (Fixed18) {
        return Fixed18.wrap(Fixed18.unwrap(a) * Fixed18.unwrap(b) / Fixed18.unwrap(c));
    }

    /**
     * @notice Returns whether signed fixed-decimal `a` is equal to `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Whether `a` is equal to `b`
     */
    function eq(Fixed18 a, Fixed18 b) internal pure returns (bool) {
        return compare(a, b) == 1;
    }

    /**
     * @notice Returns whether signed fixed-decimal `a` is greater than `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Whether `a` is greater than `b`
     */
    function gt(Fixed18 a, Fixed18 b) internal pure returns (bool) {
        return compare(a, b) == 2;
    }

    /**
     * @notice Returns whether signed fixed-decimal `a` is less than `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Whether `a` is less than `b`
     */
    function lt(Fixed18 a, Fixed18 b) internal pure returns (bool) {
        return compare(a, b) == 0;
    }

    /**
     * @notice Returns whether signed fixed-decimal `a` is greater than or equal to `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Whether `a` is greater than or equal to `b`
     */
    function gte(Fixed18 a, Fixed18 b) internal pure returns (bool) {
        return gt(a, b) || eq(a, b);
    }

    /**
     * @notice Returns whether signed fixed-decimal `a` is less than or equal to `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Whether `a` is less than or equal to `b`
     */
    function lte(Fixed18 a, Fixed18 b) internal pure returns (bool) {
        return lt(a, b) || eq(a, b);
    }

    /**
     * @notice Compares the signed fixed-decimals `a` and `b`
     * @dev Returns: 2 for greater than
     *               1 for equal to
     *               0 for less than
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Compare result of `a` and `b`
     */
    function compare(Fixed18 a, Fixed18 b) internal pure returns (uint256) {
        (int256 au, int256 bu) = (Fixed18.unwrap(a), Fixed18.unwrap(b));
        if (au > bu) return 2;
        if (au < bu) return 0;
        return 1;
    }

    /**
     * @notice Returns a signed fixed-decimal representing the ratio of `a` over `b`
     * @param a First signed number
     * @param b Second signed number
     * @return Ratio of `a` over `b`
     */
    function ratio(int256 a, int256 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(a * BASE / b);
    }

    /**
     * @notice Returns the minimum of signed fixed-decimals `a` and `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Minimum of `a` and `b`
     */
    function min(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(SignedMath.min(Fixed18.unwrap(a), Fixed18.unwrap(b)));
    }

    /**
     * @notice Returns the maximum of signed fixed-decimals `a` and `b`
     * @param a First signed fixed-decimal
     * @param b Second signed fixed-decimal
     * @return Maximum of `a` and `b`
     */
    function max(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
        return Fixed18.wrap(SignedMath.max(Fixed18.unwrap(a), Fixed18.unwrap(b)));
    }

    /**
     * @notice Converts the signed fixed-decimal into an integer, truncating any decimal portion
     * @param a Signed fixed-decimal
     * @return Truncated signed number
     */
    function truncate(Fixed18 a) internal pure returns (int256) {
        return Fixed18.unwrap(a) / BASE;
    }

    /**
     * @notice Returns the sign of the signed fixed-decimal
     * @dev Returns: -1 for negative
     *                0 for zero
     *                1 for positive
     * @param a Signed fixed-decimal
     * @return Sign of the signed fixed-decimal
     */
    function sign(Fixed18 a) internal pure returns (int256) {
        if (Fixed18.unwrap(a) > 0) return 1;
        if (Fixed18.unwrap(a) < 0) return -1;
        return 0;
    }

    /**
     * @notice Returns the absolute value of the signed fixed-decimal
     * @param a Signed fixed-decimal
     * @return Absolute value of the signed fixed-decimal
     */
    function abs(Fixed18 a) internal pure returns (UFixed18) {
        return UFixed18.wrap(SignedMath.abs(Fixed18.unwrap(a)));
    }
}

library Fixed18StorageLib {
    function read(Fixed18Storage self) internal view returns (Fixed18 value) {
        assembly {
            value := sload(self)
        }
    }

    function store(Fixed18Storage self, Fixed18 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

File 11 of 43 : PackedFixed18.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "./Fixed18.sol";

/// @dev PackedFixed18 type
type PackedFixed18 is int128;
using PackedFixed18Lib for PackedFixed18 global;

/**
 * @title PackedFixed18Lib
 * @dev A packed version of the Fixed18 which takes up half the storage space (two PackedFixed18 can be packed
 *      into a single slot). Only valid within the range -1.7014118e+20 <= x <= 1.7014118e+20.
 * @notice Library for the packed signed fixed-decimal type.
 */
library PackedFixed18Lib {
    PackedFixed18 public constant MAX = PackedFixed18.wrap(type(int128).max);
    PackedFixed18 public constant MIN = PackedFixed18.wrap(type(int128).min);

    /**
     * @notice Creates an unpacked signed fixed-decimal from a packed signed fixed-decimal
     * @param self packed signed fixed-decimal
     * @return New unpacked signed fixed-decimal
     */
    function unpack(PackedFixed18 self) internal pure returns (Fixed18) {
        return Fixed18.wrap(int256(PackedFixed18.unwrap(self)));
    }
}

File 12 of 43 : PackedUFixed18.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "./UFixed18.sol";

/// @dev PackedUFixed18 type
type PackedUFixed18 is uint128;
using PackedUFixed18Lib for PackedUFixed18 global;

/**
 * @title PackedUFixed18Lib
 * @dev A packed version of the UFixed18 which takes up half the storage space (two PackedUFixed18 can be packed
 *      into a single slot). Only valid within the range 0 <= x <= 3.4028237e+20.
 * @notice Library for the packed unsigned fixed-decimal type.
 */
library PackedUFixed18Lib {
    PackedUFixed18 public constant MAX = PackedUFixed18.wrap(type(uint128).max);

    /**
     * @notice Creates an unpacked unsigned fixed-decimal from a packed unsigned fixed-decimal
     * @param self packed unsigned fixed-decimal
     * @return New unpacked unsigned fixed-decimal
     */
    function unpack(PackedUFixed18 self) internal pure returns (UFixed18) {
        return UFixed18.wrap(uint256(PackedUFixed18.unwrap(self)));
    }
}

File 13 of 43 : UFixed18.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/math/Math.sol";
import "./Fixed18.sol";
import "./PackedUFixed18.sol";

/// @dev UFixed18 type
type UFixed18 is uint256;
using UFixed18Lib for UFixed18 global;
type UFixed18Storage is bytes32;
using UFixed18StorageLib for UFixed18Storage global;

/**
 * @title UFixed18Lib
 * @notice Library for the unsigned fixed-decimal type.
 */
library UFixed18Lib {
    error UFixed18UnderflowError(int256 value);
    error UFixed18PackingOverflowError(uint256 value);

    uint256 private constant BASE = 1e18;
    UFixed18 public constant ZERO = UFixed18.wrap(0);
    UFixed18 public constant ONE = UFixed18.wrap(BASE);
    UFixed18 public constant MAX = UFixed18.wrap(type(uint256).max);

    /**
     * @notice Creates a unsigned fixed-decimal from a signed fixed-decimal
     * @param a Signed fixed-decimal
     * @return New unsigned fixed-decimal
     */
    function from(Fixed18 a) internal pure returns (UFixed18) {
        int256 value = Fixed18.unwrap(a);
        if (value < 0) revert UFixed18UnderflowError(value);
        return UFixed18.wrap(uint256(value));
    }

    /**
     * @notice Creates a unsigned fixed-decimal from a unsigned integer
     * @param a Unsigned number
     * @return New unsigned fixed-decimal
     */
    function from(uint256 a) internal pure returns (UFixed18) {
        return UFixed18.wrap(a * BASE);
    }

    /**
     * @notice Creates a packed unsigned fixed-decimal from an unsigned fixed-decimal
     * @param a unsigned fixed-decimal
     * @return New packed unsigned fixed-decimal
     */
    function pack(UFixed18 a) internal pure returns (PackedUFixed18) {
        uint256 value = UFixed18.unwrap(a);
        if (value > type(uint128).max) revert UFixed18PackingOverflowError(value);
        return PackedUFixed18.wrap(uint128(value));
    }

    /**
     * @notice Returns whether the unsigned fixed-decimal is equal to zero.
     * @param a Unsigned fixed-decimal
     * @return Whether the unsigned fixed-decimal is zero.
     */
    function isZero(UFixed18 a) internal pure returns (bool) {
        return UFixed18.unwrap(a) == 0;
    }

    /**
     * @notice Adds two unsigned fixed-decimals `a` and `b` together
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Resulting summed unsigned fixed-decimal
     */
    function add(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(UFixed18.unwrap(a) + UFixed18.unwrap(b));
    }

    /**
     * @notice Subtracts unsigned fixed-decimal `b` from `a`
     * @param a Unsigned fixed-decimal to subtract from
     * @param b Unsigned fixed-decimal to subtract
     * @return Resulting subtracted unsigned fixed-decimal
     */
    function sub(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(UFixed18.unwrap(a) - UFixed18.unwrap(b));
    }

    /**
     * @notice Multiplies two unsigned fixed-decimals `a` and `b` together
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Resulting multiplied unsigned fixed-decimal
     */
    function mul(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(UFixed18.unwrap(a) * UFixed18.unwrap(b) / BASE);
    }

    /**
     * @notice Divides unsigned fixed-decimal `a` by `b`
     * @param a Unsigned fixed-decimal to divide
     * @param b Unsigned fixed-decimal to divide by
     * @return Resulting divided unsigned fixed-decimal
     */
    function div(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(UFixed18.unwrap(a) * BASE / UFixed18.unwrap(b));
    }

    /**
     * @notice Divides unsigned fixed-decimal `a` by `b`
     * @dev Does not revert on divide-by-0, instead returns `ONE` for `0/0` and `MAX` for `n/0`.
     * @param a Unsigned fixed-decimal to divide
     * @param b Unsigned fixed-decimal to divide by
     * @return Resulting divided unsigned fixed-decimal
     */
    function unsafeDiv(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        if (isZero(b)) {
            return isZero(a) ? ONE : MAX;
        } else {
            return div(a, b);
        }
    }

    /**
     * @notice Computes a * b / c without loss of precision due to BASE conversion
     * @param a First unsigned fixed-decimal
     * @param b Unsigned number to multiply by
     * @param c Unsigned number to divide by
     * @return Resulting computation
     */
    function muldiv(UFixed18 a, uint256 b, uint256 c) internal pure returns (UFixed18) {
        return muldiv(a, UFixed18.wrap(b), UFixed18.wrap(c));
    }

    /**
     * @notice Computes a * b / c without loss of precision due to BASE conversion
     * @param a First unsigned fixed-decimal
     * @param b Unsigned fixed-decimal to multiply by
     * @param c Unsigned fixed-decimal to divide by
     * @return Resulting computation
     */
    function muldiv(UFixed18 a, UFixed18 b, UFixed18 c) internal pure returns (UFixed18) {
        return UFixed18.wrap(UFixed18.unwrap(a) * UFixed18.unwrap(b) / UFixed18.unwrap(c));
    }

    /**
     * @notice Returns whether unsigned fixed-decimal `a` is equal to `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Whether `a` is equal to `b`
     */
    function eq(UFixed18 a, UFixed18 b) internal pure returns (bool) {
        return compare(a, b) == 1;
    }

    /**
     * @notice Returns whether unsigned fixed-decimal `a` is greater than `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Whether `a` is greater than `b`
     */
    function gt(UFixed18 a, UFixed18 b) internal pure returns (bool) {
        return compare(a, b) == 2;
    }

    /**
     * @notice Returns whether unsigned fixed-decimal `a` is less than `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Whether `a` is less than `b`
     */
    function lt(UFixed18 a, UFixed18 b) internal pure returns (bool) {
        return compare(a, b) == 0;
    }

    /**
     * @notice Returns whether unsigned fixed-decimal `a` is greater than or equal to `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Whether `a` is greater than or equal to `b`
     */
    function gte(UFixed18 a, UFixed18 b) internal pure returns (bool) {
        return gt(a, b) || eq(a, b);
    }

    /**
     * @notice Returns whether unsigned fixed-decimal `a` is less than or equal to `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Whether `a` is less than or equal to `b`
     */
    function lte(UFixed18 a, UFixed18 b) internal pure returns (bool) {
        return lt(a, b) || eq(a, b);
    }

    /**
     * @notice Compares the unsigned fixed-decimals `a` and `b`
     * @dev Returns: 2 for greater than
     *               1 for equal to
     *               0 for less than
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Compare result of `a` and `b`
     */
    function compare(UFixed18 a, UFixed18 b) internal pure returns (uint256) {
        (uint256 au, uint256 bu) = (UFixed18.unwrap(a), UFixed18.unwrap(b));
        if (au > bu) return 2;
        if (au < bu) return 0;
        return 1;
    }

    /**
     * @notice Returns a unsigned fixed-decimal representing the ratio of `a` over `b`
     * @param a First unsigned number
     * @param b Second unsigned number
     * @return Ratio of `a` over `b`
     */
    function ratio(uint256 a, uint256 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(a * BASE / b);
    }

    /**
     * @notice Returns the minimum of unsigned fixed-decimals `a` and `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Minimum of `a` and `b`
     */
    function min(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(Math.min(UFixed18.unwrap(a), UFixed18.unwrap(b)));
    }

    /**
     * @notice Returns the maximum of unsigned fixed-decimals `a` and `b`
     * @param a First unsigned fixed-decimal
     * @param b Second unsigned fixed-decimal
     * @return Maximum of `a` and `b`
     */
    function max(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
        return UFixed18.wrap(Math.max(UFixed18.unwrap(a), UFixed18.unwrap(b)));
    }

    /**
     * @notice Converts the unsigned fixed-decimal into an integer, truncating any decimal portion
     * @param a Unsigned fixed-decimal
     * @return Truncated unsigned number
     */
    function truncate(UFixed18 a) internal pure returns (uint256) {
        return UFixed18.unwrap(a) / BASE;
    }
}

library UFixed18StorageLib {
    function read(UFixed18Storage self) internal view returns (UFixed18 value) {
        assembly {
            value := sload(self)
        }
    }

    function store(UFixed18Storage self, UFixed18 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

File 14 of 43 : UStorage.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "../number/types/UFixed18.sol";

/// @dev Stored boolean slot
type BoolStorage is bytes32;
using BoolStorageLib for BoolStorage global;

/// @dev Stored uint256 slot
type Uint256Storage is bytes32;
using Uint256StorageLib for Uint256Storage global;

/// @dev Stored int256 slot
type Int256Storage is bytes32;
using Int256StorageLib for Int256Storage global;

/// @dev Stored address slot
type AddressStorage is bytes32;
using AddressStorageLib for AddressStorage global;

/// @dev Stored bytes32 slot
type Bytes32Storage is bytes32;
using Bytes32StorageLib for Bytes32Storage global;

/**
 * @title BoolStorageLib
 * @notice Library to manage storage and retrival of a boolean at a fixed storage slot
 */
library BoolStorageLib {
    /**
     * @notice Retrieves the stored value
     * @param self Storage slot
     * @return value Stored bool value
     */
    function read(BoolStorage self) internal view returns (bool value) {
        assembly {
            value := sload(self)
        }
    }

    /**
     * @notice Stores the value at the specific slot
     * @param self Storage slot
     * @param value boolean value to store
     */
    function store(BoolStorage self, bool value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

/**
 * @title Uint256StorageLib
 * @notice Library to manage storage and retrival of an uint256 at a fixed storage slot
 */
library Uint256StorageLib {
    /**
     * @notice Retrieves the stored value
     * @param self Storage slot
     * @return value Stored uint256 value
     */
    function read(Uint256Storage self) internal view returns (uint256 value) {
        assembly {
            value := sload(self)
        }
    }

    /**
     * @notice Stores the value at the specific slot
     * @param self Storage slot
     * @param value uint256 value to store
     */
    function store(Uint256Storage self, uint256 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

/**
 * @title Int256StorageLib
 * @notice Library to manage storage and retrival of an int256 at a fixed storage slot
 */
library Int256StorageLib {
    /**
     * @notice Retrieves the stored value
     * @param self Storage slot
     * @return value Stored int256 value
     */
    function read(Int256Storage self) internal view returns (int256 value) {
        assembly {
            value := sload(self)
        }
    }

    /**
     * @notice Stores the value at the specific slot
     * @param self Storage slot
     * @param value int256 value to store
     */
    function store(Int256Storage self, int256 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

/**
 * @title AddressStorageLib
 * @notice Library to manage storage and retrival of an address at a fixed storage slot
 */
library AddressStorageLib {
    /**
     * @notice Retrieves the stored value
     * @param self Storage slot
     * @return value Stored address value
     */
    function read(AddressStorage self) internal view returns (address value) {
        assembly {
            value := sload(self)
        }
    }

    /**
     * @notice Stores the value at the specific slot
     * @param self Storage slot
     * @param value address value to store
     */
    function store(AddressStorage self, address value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

/**
 * @title Bytes32StorageLib
 * @notice Library to manage storage and retrival of a bytes32 at a fixed storage slot
 */
library Bytes32StorageLib {
    /**
     * @notice Retrieves the stored value
     * @param self Storage slot
     * @return value Stored bytes32 value
     */
    function read(Bytes32Storage self) internal view returns (bytes32 value) {
        assembly {
            value := sload(self)
        }
    }

    /**
     * @notice Stores the value at the specific slot
     * @param self Storage slot
     * @param value bytes32 value to store
     */
    function store(Bytes32Storage self, bytes32 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

File 15 of 43 : Token18.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../../number/types/UFixed18.sol";

/// @dev Token18
type Token18 is address;
using Token18Lib for Token18 global;
type Token18Storage is bytes32;
using Token18StorageLib for Token18Storage global;

/**
 * @title Token18Lib
 * @notice Library to manage 18-decimal ERC20s that is compliant with the fixed-decimal types.
 * @dev Maintains significant gas savings over other Token implementations since no conversion take place
 */
library Token18Lib {
    using SafeERC20 for IERC20;

    Token18 public constant ZERO = Token18.wrap(address(0));

    /**
     * @notice Returns whether a token is the zero address
     * @param self Token to check for
     * @return Whether the token is the zero address
     */
    function isZero(Token18 self) internal pure returns (bool) {
        return Token18.unwrap(self) == Token18.unwrap(ZERO);
    }

    /**
     * @notice Returns whether the two tokens are equal
     * @param a First token to compare
     * @param b Second token to compare
     * @return Whether the two tokens are equal
     */
    function eq(Token18 a, Token18 b) internal pure returns (bool) {
        return Token18.unwrap(a) ==  Token18.unwrap(b);
    }

    /**
     * @notice Approves `grantee` to spend infinite tokens from the caller
     * @param self Token to transfer
     * @param grantee Address to allow spending
     */
    function approve(Token18 self, address grantee) internal {
        IERC20(Token18.unwrap(self)).safeApprove(grantee, type(uint256).max);
    }

    /**
     * @notice Approves `grantee` to spend `amount` tokens from the caller
     * @dev There are important race conditions to be aware of when using this function
            with values other than 0. This will revert if moving from non-zero to non-zero amounts
            See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/a55b7d13722e7ce850b626da2313f3e66ca1d101/contracts/token/ERC20/IERC20.sol#L57
     * @param self Token to transfer
     * @param grantee Address to allow spending
     * @param amount Amount of tokens to approve to spend
     */
    function approve(Token18 self, address grantee, UFixed18 amount) internal {
        IERC20(Token18.unwrap(self)).safeApprove(grantee, UFixed18.unwrap(amount));
    }

    /**
     * @notice Transfers all held tokens from the caller to the `recipient`
     * @param self Token to transfer
     * @param recipient Address to receive the tokens
     */
    function push(Token18 self, address recipient) internal {
        push(self, recipient, balanceOf(self, address(this)));
    }

    /**
     * @notice Transfers `amount` tokens from the caller to the `recipient`
     * @param self Token to transfer
     * @param recipient Address to transfer tokens to
     * @param amount Amount of tokens to transfer
     */
    function push(Token18 self, address recipient, UFixed18 amount) internal {
        IERC20(Token18.unwrap(self)).safeTransfer(recipient, UFixed18.unwrap(amount));
    }

    /**
     * @notice Transfers `amount` tokens from the `benefactor` to the caller
     * @dev Reverts if trying to pull Ether
     * @param self Token to transfer
     * @param benefactor Address to transfer tokens from
     * @param amount Amount of tokens to transfer
     */
    function pull(Token18 self, address benefactor, UFixed18 amount) internal {
        IERC20(Token18.unwrap(self)).safeTransferFrom(benefactor, address(this), UFixed18.unwrap(amount));
    }

    /**
     * @notice Transfers `amount` tokens from the `benefactor` to `recipient`
     * @dev Reverts if trying to pull Ether
     * @param self Token to transfer
     * @param benefactor Address to transfer tokens from
     * @param recipient Address to transfer tokens to
     * @param amount Amount of tokens to transfer
     */
    function pullTo(Token18 self, address benefactor, address recipient, UFixed18 amount) internal {
        IERC20(Token18.unwrap(self)).safeTransferFrom(benefactor, recipient, UFixed18.unwrap(amount));
    }

    /**
     * @notice Returns the name of the token
     * @param self Token to check for
     * @return Token name
     */
    function name(Token18 self) internal view returns (string memory) {
        return IERC20Metadata(Token18.unwrap(self)).name();
    }

    /**
     * @notice Returns the symbol of the token
     * @param self Token to check for
     * @return Token symbol
     */
    function symbol(Token18 self) internal view returns (string memory) {
        return IERC20Metadata(Token18.unwrap(self)).symbol();
    }

    /**
     * @notice Returns the `self` token balance of the caller
     * @param self Token to check for
     * @return Token balance of the caller
     */
    function balanceOf(Token18 self) internal view returns (UFixed18) {
        return balanceOf(self, address(this));
    }

    /**
     * @notice Returns the `self` token balance of `account`
     * @param self Token to check for
     * @param account Account to check
     * @return Token balance of the account
     */
    function balanceOf(Token18 self, address account) internal view returns (UFixed18) {
        return UFixed18.wrap(IERC20(Token18.unwrap(self)).balanceOf(account));
    }
}

library Token18StorageLib {
    function read(Token18Storage self) internal view returns (Token18 value) {
        assembly {
            value := sload(self)
        }
    }

    function store(Token18Storage self, Token18 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

File 16 of 43 : Token6.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "../../number/types/UFixed18.sol";

/// @dev Token6
type Token6 is address;
using Token6Lib for Token6 global;
type Token6Storage is bytes32;
using Token6StorageLib for Token6Storage global;

/**
 * @title Token6Lib
 * @notice Library to manage 6-decimal ERC20s that is compliant with the fixed-decimal types.
 * @dev Automatically converts from Base-6 token amounts to Base-18 UFixed18 amounts, with optional rounding
 */
library Token6Lib {
    using SafeERC20 for IERC20;

    Token6 public constant ZERO = Token6.wrap(address(0));

    uint256 private constant OFFSET = 1e12;

    /**
     * @notice Returns whether a token is the zero address
     * @param self Token to check for
     * @return Whether the token is the zero address
     */
    function isZero(Token6 self) internal pure returns (bool) {
        return Token6.unwrap(self) == Token6.unwrap(ZERO);
    }

    /**
     * @notice Returns whether the two tokens are equal
     * @param a First token to compare
     * @param b Second token to compare
     * @return Whether the two tokens are equal
     */
    function eq(Token6 a, Token6 b) internal pure returns (bool) {
        return Token6.unwrap(a) ==  Token6.unwrap(b);
    }

    /**
     * @notice Approves `grantee` to spend infinite tokens from the caller
     * @param self Token to transfer
     * @param grantee Address to allow spending
     */
    function approve(Token6 self, address grantee) internal {
        IERC20(Token6.unwrap(self)).safeApprove(grantee, type(uint256).max);
    }

    /**
     * @notice Approves `grantee` to spend `amount` tokens from the caller
     * @dev There are important race conditions to be aware of when using this function
            with values other than 0. This will revert if moving from non-zero to non-zero amounts
            See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/a55b7d13722e7ce850b626da2313f3e66ca1d101/contracts/token/ERC20/IERC20.sol#L57
     * @param self Token to transfer
     * @param grantee Address to allow spending
     * @param amount Amount of tokens to approve to spend
     */
    function approve(Token6 self, address grantee, UFixed18 amount) internal {
        IERC20(Token6.unwrap(self)).safeApprove(grantee, toTokenAmount(amount, false));
    }

    /**
     * @notice Approves `grantee` to spend `amount` tokens from the caller
     * @dev There are important race conditions to be aware of when using this function
            with values other than 0. This will revert if moving from non-zero to non-zero amounts
            See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/a55b7d13722e7ce850b626da2313f3e66ca1d101/contracts/token/ERC20/IERC20.sol#L57
     * @param self Token to transfer
     * @param grantee Address to allow spending
     * @param amount Amount of tokens to approve to spend
     * @param roundUp Whether to round decimal token amount up to the next unit
     */
    function approve(Token6 self, address grantee, UFixed18 amount, bool roundUp) internal {
        IERC20(Token6.unwrap(self)).safeApprove(grantee, toTokenAmount(amount, roundUp));
    }

    /**
     * @notice Transfers all held tokens from the caller to the `recipient`
     * @param self Token to transfer
     * @param recipient Address to receive the tokens
     */
    function push(Token6 self, address recipient) internal {
        push(self, recipient, balanceOf(self, address(this)));
    }

    /**
     * @notice Transfers `amount` tokens from the caller to the `recipient`
     * @param self Token to transfer
     * @param recipient Address to transfer tokens to
     * @param amount Amount of tokens to transfer
     */
    function push(Token6 self, address recipient, UFixed18 amount) internal {
        IERC20(Token6.unwrap(self)).safeTransfer(recipient, toTokenAmount(amount, false));
    }

    /**
     * @notice Transfers `amount` tokens from the caller to the `recipient`
     * @param self Token to transfer
     * @param recipient Address to transfer tokens to
     * @param amount Amount of tokens to transfer
     * @param roundUp Whether to round decimal token amount up to the next unit
     */
    function push(Token6 self, address recipient, UFixed18 amount, bool roundUp) internal {
        IERC20(Token6.unwrap(self)).safeTransfer(recipient, toTokenAmount(amount, roundUp));
    }

    /**
     * @notice Transfers `amount` tokens from the `benefactor` to the caller
     * @dev Reverts if trying to pull Ether
     * @param self Token to transfer
     * @param benefactor Address to transfer tokens from
     * @param amount Amount of tokens to transfer
     */
    function pull(Token6 self, address benefactor, UFixed18 amount) internal {
        IERC20(Token6.unwrap(self)).safeTransferFrom(benefactor, address(this), toTokenAmount(amount, false));
    }

    /**
     * @notice Transfers `amount` tokens from the `benefactor` to the caller
     * @dev Reverts if trying to pull Ether
     * @param self Token to transfer
     * @param benefactor Address to transfer tokens from
     * @param amount Amount of tokens to transfer
     * @param roundUp Whether to round decimal token amount up to the next unit
     */
    function pull(Token6 self, address benefactor, UFixed18 amount, bool roundUp) internal {
        IERC20(Token6.unwrap(self)).safeTransferFrom(benefactor, address(this), toTokenAmount(amount, roundUp));
    }

    /**
     * @notice Transfers `amount` tokens from the `benefactor` to `recipient`
     * @dev Reverts if trying to pull Ether
     * @param self Token to transfer
     * @param benefactor Address to transfer tokens from
     * @param recipient Address to transfer tokens to
     * @param amount Amount of tokens to transfer
     */
    function pullTo(Token6 self, address benefactor, address recipient, UFixed18 amount) internal {
        IERC20(Token6.unwrap(self)).safeTransferFrom(benefactor, recipient, toTokenAmount(amount, false));
    }

    /**
     * @notice Transfers `amount` tokens from the `benefactor` to `recipient`
     * @dev Reverts if trying to pull Ether
     * @param self Token to transfer
     * @param benefactor Address to transfer tokens from
     * @param recipient Address to transfer tokens to
     * @param amount Amount of tokens to transfer
     * @param roundUp Whether to round decimal token amount up to the next unit
     */
    function pullTo(Token6 self, address benefactor, address recipient, UFixed18 amount, bool roundUp) internal {
        IERC20(Token6.unwrap(self)).safeTransferFrom(benefactor, recipient, toTokenAmount(amount, roundUp));
    }

    /**
     * @notice Returns the name of the token
     * @param self Token to check for
     * @return Token name
     */
    function name(Token6 self) internal view returns (string memory) {
        return IERC20Metadata(Token6.unwrap(self)).name();
    }

    /**
     * @notice Returns the symbol of the token
     * @param self Token to check for
     * @return Token symbol
     */
    function symbol(Token6 self) internal view returns (string memory) {
        return IERC20Metadata(Token6.unwrap(self)).symbol();
    }

    /**
     * @notice Returns the `self` token balance of the caller
     * @param self Token to check for
     * @return Token balance of the caller
     */
    function balanceOf(Token6 self) internal view returns (UFixed18) {
        return balanceOf(self, address(this));
    }

    /**
     * @notice Returns the `self` token balance of `account`
     * @param self Token to check for
     * @param account Account to check
     * @return Token balance of the account
     */
    function balanceOf(Token6 self, address account) internal view returns (UFixed18) {
        return fromTokenAmount(IERC20(Token6.unwrap(self)).balanceOf(account));
    }

    /**
     * @notice Converts the unsigned fixed-decimal amount into the token amount according to
     *         it's defined decimals
     * @dev Provides the ability to "round up" the token amount which is useful in situations where
     *      are swapping one token for another and don't want to give away "free" units due to rounding
     *      errors in the favor of the user.
     * @param amount Amount to convert
     * @param roundUp Whether to round decimal token amount up to the next unit
     * @return Normalized token amount
     */
    function toTokenAmount(UFixed18 amount, bool roundUp) private pure returns (uint256) {
        return roundUp ? Math.ceilDiv(UFixed18.unwrap(amount), OFFSET) : UFixed18.unwrap(amount) / OFFSET;
    }

    /**
     * @notice Converts the token amount into the unsigned fixed-decimal amount according to
     *         it's defined decimals
     * @param amount Token amount to convert
     * @return Normalized unsigned fixed-decimal amount
     */
    function fromTokenAmount(uint256 amount) private pure returns (UFixed18) {
        return UFixed18.wrap(amount * OFFSET);
    }
}

library Token6StorageLib {
    function read(Token6Storage self) internal view returns (Token6 value) {
        assembly {
            value := sload(self)
        }
    }

    function store(Token6Storage self, Token6 value) internal {
        assembly {
            sstore(self, value)
        }
    }
}

File 17 of 43 : IBeacon.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)

pragma solidity ^0.8.0;

/**
 * @dev This is the interface that {BeaconProxy} expects of its beacon.
 */
interface IBeacon {
    /**
     * @dev Must return an address that can be used as a delegate call target.
     *
     * {BeaconProxy} will check that this address is a contract.
     */
    function implementation() external view returns (address);
}

File 18 of 43 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @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);

    /**
     * @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 `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, 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 `from` to `to` 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 from,
        address to,
        uint256 amount
    ) external returns (bool);
}

File 19 of 43 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
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);
}

File 20 of 43 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 21 of 43 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 22 of 43 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a / b + (a % b == 0 ? 0 : 1);
    }
}

File 23 of 43 : SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

File 24 of 43 : EnumerableSet.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/structs/EnumerableSet.sol)

pragma solidity ^0.8.0;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position of the value in the `values` array, plus 1 because index 0
        // means a value is not in the set.
        mapping(bytes32 => uint256) _indexes;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastValue;
                // Update the index for the moved value
                set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the index for the deleted slot
            delete set._indexes[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._indexes[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        return _values(set._inner);
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        assembly {
            result := store
        }

        return result;
    }
}

File 25 of 43 : UControllerProvider.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "@equilibria/root/control/unstructured/UInitializable.sol";
import "@equilibria/root/storage/UStorage.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../interfaces/IController.sol";
import "../interfaces/IProduct.sol";

/**
 * @title UControllerProvider
 * @notice Mix-in that manages a controller pointer and associated permissioning modifiers.
 * @dev Uses unstructured storage so that it is safe to mix-in to upgreadable contracts without modifying
 *      their storage layout.
 */
abstract contract UControllerProvider is UInitializable {
    error NotOwnerError(uint256 coordinatorId);
    error NotProductError(IProduct product);
    error NotCollateralError();
    error PausedError();
    error InvalidControllerError();
    error NotAccountOrMultiInvokerError(address account, address operator);

    /// @dev The controller contract address
    AddressStorage private constant _controller = AddressStorage.wrap(keccak256("equilibria.perennial.UControllerProvider.controller"));
    function controller() public view returns (IController) { return IController(_controller.read()); }

    /**
     * @notice Initializes the contract state
     * @param controller_ Protocol Controller contract address
     */
    // solhint-disable-next-line func-name-mixedcase
    function __UControllerProvider__initialize(IController controller_) internal onlyInitializer {
        if (!Address.isContract(address(controller_))) revert InvalidControllerError();
        _controller.store(address(controller_));
    }

    /// @dev Only allow a valid product contract to call
    modifier onlyProduct {
        if (!controller().isProduct(IProduct(msg.sender))) revert NotProductError(IProduct(msg.sender));

        _;
    }

    /// @dev Verify that `product` is a valid product contract
    modifier isProduct(IProduct product) {
        if (!controller().isProduct(product)) revert NotProductError(product);

        _;
    }

    /// @dev Only allow the Collateral contract to call
    modifier onlyCollateral {
        if (msg.sender != address(controller().collateral())) revert NotCollateralError();

        _;
    }

    /// @dev Only allow the coordinator owner to call
    modifier onlyOwner(uint256 coordinatorId) {
        if (msg.sender != controller().owner(coordinatorId)) revert NotOwnerError(coordinatorId);

        _;
    }

    /// @dev Only allow if the protocol is currently unpaused
    modifier notPaused() {
        if (controller().paused()) revert PausedError();

        _;
    }

    /// @dev Ensure the `msg.sender` is ether the `account` or the Controller's multiInvoker
    modifier onlyAccountOrMultiInvoker(address account) {
        if (!(msg.sender == account || msg.sender == address(controller().multiInvoker()))) {
            revert NotAccountOrMultiInvokerError(account, msg.sender);
        }
        _;
    }
}

File 26 of 43 : ProductManager.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "./Program.sol";

/// @dev ProductManager type
struct ProductManager {
    /// @dev Static program state
    ProgramInfo[] programInfos;

    /// @dev Dynamic program state
    mapping(uint256 => Program) programs;

    /// @dev Mapping of all active programs for each product
    EnumerableSet.UintSet activePrograms;

    /// @dev Mapping of all active programs for each user
    mapping(address => EnumerableSet.UintSet) activeProgramsFor;

    /// @dev Mapping of the next program to watch for for each user
    mapping(address => uint256) nextProgramFor;
}
using ProductManagerLib for ProductManager global;

/**
 * @title ProductManagerLib
 * @notice Library that manages each product's incentivization state and logic.
 */
library ProductManagerLib {
    using EnumerableSet for EnumerableSet.UintSet;

    /// @dev Result data for a sync event
    struct SyncResult {
        /// @dev The programId that was updated
        uint256 programId;

        /// @dev If non-zero, the new versionStart value of the program
        uint256 versionStarted;

        /// @dev If non-zero, the new versionComplete value of the program
        uint256 versionComplete;

        /// @dev If non-zero, the amount to refund due to completion
        UFixed18 refundAmount;
    }

    /**
     * @notice Registers a new program on this product
     * @param self The Product manager to operate on
     * @param programInfo The static program info
     * @return programId The new program's ID
     */
    function register(
        ProductManager storage self,
        ProgramInfo memory programInfo
    ) internal returns (uint256 programId) {
        programId = self.programInfos.length;
        self.programInfos.push(programInfo);
        self.programs[programId].initialize(programInfo);
        self.activePrograms.add(programId);
    }

    /**
     * @notice Syncs this product with the latest data
     * @param self The Program manager to operate on
     * @param product This Product
     * @param currentOracleVersion The preloaded current oracle version
     */
    function sync(
        ProductManager storage self,
        IProduct product,
        IOracleProvider.OracleVersion memory currentOracleVersion
    ) internal returns (SyncResult[] memory results) {

        uint256[] memory activeProgramIds = self.activePrograms.values();
        results = new SyncResult[](activeProgramIds.length);

        for (uint256 i; i < activeProgramIds.length; i++) {
            // Load program
            uint256 programId = activeProgramIds[i];
            ProgramInfo memory programInfo = self.programInfos[programId];
            Program storage program = self.programs[programId];

            // If timestamp-started, grab current version (first version after start)
            uint256 versionStarted;
            if (program.versionStarted == 0 && programInfo.isStarted(currentOracleVersion.timestamp)) {
                versionStarted = _start(self, programId, currentOracleVersion);
            }

            // If timestamp-completed, grab previous version (last version before completion)
            uint256 versionComplete;
            UFixed18 refundAmount;
            if (program.versionComplete == 0 && programInfo.isComplete(currentOracleVersion.timestamp)) {
                (versionComplete, refundAmount) = _complete(self, product, programId);
            }

            // Save result
            results[i] = SyncResult(programId, versionStarted, versionComplete, refundAmount);
        }
    }

    /**
     * @notice Syncs an account for this product with the latest data
     * @dev Assumes that sync() has already been called as part of the transaction flow
     * @param self The Program manager to operate on
     * @param product This Product
     * @param account The account to sync
     * @param currentOracleVersion The preloaded current oracle version
     */
    function syncAccount(
        ProductManager storage self,
        IProduct product,
        address account,
        IOracleProvider.OracleVersion memory currentOracleVersion
    ) internal {

        // Add any unseen programs
        uint256 fromProgramId = self.nextProgramFor[account];
        uint256 toProgramId = self.programInfos.length;
        for (uint256 programId = fromProgramId; programId < toProgramId; programId++) {
            self.activeProgramsFor[account].add(programId);
        }
        self.nextProgramFor[account] = toProgramId;

        // Settle programs
        uint256[] memory activeProgramIds = self.activeProgramsFor[account].values();
        for (uint256 i; i < activeProgramIds.length; i++) {
            uint256 programId = activeProgramIds[i];
            Program storage program = self.programs[programId];
            program.settle(product, self.programInfos[programId], account, currentOracleVersion);
            if (!self.activePrograms.contains(programId) && currentOracleVersion.version >= program.versionComplete) {
                self.activeProgramsFor[account].remove(programId);
            }
        }
    }

    /**
     * @notice Returns the quantity of active programs for this product
     * @param self The Program manager to operate on
     * @return The quantity of active programs
     */
    function active(ProductManager storage self) internal view returns (uint256) {
        return self.activePrograms.length();
    }

    /**
     * @notice Forces the specified program to complete if it hasn't already
     * @param self The Program manager to operate on
     * @param product The Product to operate on
     * @param programId The Program to complete
     * @return result The sync result data from completion
     */
    function complete(
        ProductManager storage self,
        IProduct product,
        uint256 programId
    ) internal returns (SyncResult memory result) {
        Program storage program = self.programs[programId];

        // If not started, start first
        if (program.versionStarted == 0) {
            result.versionStarted = _start(self, programId, product.currentVersion());
        }

        // If not completed already, complete
        if (program.versionComplete == 0) {
            (result.versionComplete, result.refundAmount) = _complete(self, product, programId);
        }
    }

    /**
     * @notice Starts the program
     * @dev Rewards do not start accruing until the program has started
     *      Internal helper, does not prevent incorrectly-timed starting
     * @param self The Program manager to operate on
     * @param programId The Program to start
     * @param currentOracleVersion The effective starting oracle version
     * @return versionStarted The version that the program started
     */
    function _start(
        ProductManager storage self,
        uint256 programId,
        IOracleProvider.OracleVersion memory currentOracleVersion
    ) internal returns (uint256 versionStarted) {
        versionStarted = currentOracleVersion.version;
        self.programs[programId].start(currentOracleVersion.version);
    }

    /**
     * @notice Completes the program
     * @dev Completion stops rewards from accruing
     *      Internal helper, does not prevent incorrectly-timed completion
     * @param self The Program manager to operate on
     * @param product The Product to operate on
     * @param programId The Program to complete
     * @return versionComplete The version that the program complete
     * @return refundAmount The refunded token amount
     */
    function _complete(
        ProductManager storage self,
        IProduct product,
        uint256 programId
    ) internal returns (uint256 versionComplete, UFixed18 refundAmount) {
        (versionComplete, refundAmount) = self.programs[programId].complete(product, self.programInfos[programId]);
        self.activePrograms.remove(programId);
    }

    /**
     * @notice Claims all of `account`'s rewards for a specific program
     * @param self The Program manager to operate on
     * @param account Account to claim rewards for
     * @param programId Program to claim rewards for
     * @return Amount claimed
     */
    function claim(ProductManager storage self, address account, uint256 programId) internal returns (UFixed18) {
        return self.programs[programId].claim(account);
    }

    /**
     * @notice Returns the total amount of unclaimed rewards for account `account`
     * @param self The Program manager to operate on
     * @param account The account to check for
     * @param programId The Program to check for
     * @return Total amount of unclaimed rewards for account
     */
    function unclaimed(ProductManager storage self, address account, uint256 programId) internal view returns (UFixed18) {
        if (!valid(self, programId)) return (UFixed18Lib.ZERO);
        return self.programs[programId].settled[account];
    }

    /**
     * @notice Returns the token denominatino of the program's rewards
     * @param self The Program manager to operate on
     * @param programId The Program to check for
     * @return The token for the program
     */
    function token(ProductManager storage self, uint256 programId) internal view returns (Token18) {
        return self.programInfos[programId].token;
    }

    /**
     * @notice Returns whether the supplied programId is valid
     * @param self The Program manager to operate on
     * @param programId The Program to check for
     * @return Whether the supplied programId is valid
     */
    function valid(ProductManager storage self, uint256 programId) internal view returns (bool) {
        return programId < self.programInfos.length;
    }
}

File 27 of 43 : Program.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "../../interfaces/types/ProgramInfo.sol";

/// @dev Program type
struct Program {
    /// @dev Mapping of latest rewards settled for each account
    mapping(address => UFixed18) settled;

    /// @dev Total amount of rewards yet to be claimed
    UFixed18 available;

    /// @dev Oracle version that the program started, 0 when hasn't started
    uint256 versionStarted;

    /// @dev Oracle version that the program completed, 0 is still ongoing
    uint256 versionComplete;
}
using ProgramLib for Program global;

/**
 * @title ProgramLib
 * @notice Library that manages all of the mutable state for a single incentivization program.
 */
library ProgramLib {
    /**
     * @notice Initializes the program state
     * @param self The Program to operate on
     * @param programInfo Static program information
     */
    function initialize(Program storage self, ProgramInfo memory programInfo) internal {
        self.available = programInfo.amount.sum();
    }

    /**
     * @notice Starts the program
     * @dev Rewards do not start accruing until the program has started accruing
     *      Does not stop double-starting
     * @param self The Program to operate on
     * @param oracleVersion The effective starting oracle version
     */
    function start(Program storage self, uint256 oracleVersion) internal {
        self.versionStarted = oracleVersion;
    }

    /**
     * @notice Completes the program
     * @dev Completion stops rewards from accruing
     *      Does not prevent double-completion
     * @param self The Program to operate on
     * @param product The Product to operate on
     * @param programInfo Static program information
     * @return versionComplete The version that the program completed on
     * @return refundAmount The refund amount from the program
     */
    function complete(
        Program storage self,
        IProduct product,
        ProgramInfo memory programInfo
    ) internal returns (uint256 versionComplete, UFixed18 refundAmount) {
        uint256 versionStarted = self.versionStarted;
        versionComplete = Math.max(versionStarted, product.latestVersion());
        self.versionComplete = versionComplete;

        IOracleProvider.OracleVersion memory fromOracleVersion = product.atVersion(versionStarted);
        IOracleProvider.OracleVersion memory toOracleVersion = product.atVersion(versionComplete);

        uint256 inactiveDuration = programInfo.duration - (toOracleVersion.timestamp - fromOracleVersion.timestamp);
        refundAmount = programInfo.amount.sum().muldiv(inactiveDuration, programInfo.duration);
        self.available = self.available.sub(refundAmount);
    }

    /**
     * @notice Settles unclaimed rewards for account `account`
     * @param self The Program to operate on
     * @param product The Product to operate on
     * @param programInfo Static program information
     * @param account The account to settle for
     * @param currentOracleVersion The preloaded current oracle version
     */
    function settle(
        Program storage self,
        IProduct product,
        ProgramInfo memory programInfo,
        address account,
        IOracleProvider.OracleVersion memory currentOracleVersion
    ) internal {
        UFixed18 unsettledAmount = _unsettled(self, product, programInfo, account, currentOracleVersion);
        self.settled[account] = self.settled[account].add(unsettledAmount);
        self.available = self.available.sub(unsettledAmount);
    }

    /**
     * @notice Claims settled rewards for account `account`
     * @param self The Program to operate on
     * @param account The account to claim for
     */
    function claim(Program storage self, address account) internal returns (UFixed18 claimedAmount) {
        claimedAmount = self.settled[account];
        self.settled[account] = UFixed18Lib.ZERO;
    }

    /**
     * @notice Returns the unsettled amount of unclaimed rewards for account `account`
     * @dev Clears when a program is closed
     *      Assumes that position is unchanged since last settlement, must be settled prior to user position update
     * @param self The Program to operate on
     * @param product The Product to operate on
     * @param programInfo Static program information
     * @param account The account to claim for
     * @param currentOracleVersion Current oracle version
     * @return amount Amount of unsettled rewards for account
     */
    function _unsettled(
        Program storage self,
        IProduct product,
        ProgramInfo memory programInfo,
        address account,
        IOracleProvider.OracleVersion memory currentOracleVersion
    ) private view returns (UFixed18 amount) {
        // program stage overview
        //
        // V = latest user settle version, V' = current user settle version
        // S = versionStarted, E = versionEnded
        //
        // (1) V   V' S           E        program not yet started
        // (2)   V    S     V'    E        use versionStarted -> V' for userShareDelta
        // (3)        S  V     V' E        use V -> V' for userShareDelta
        // (4)        S     V     E   V'   use V -> versionComplete for userShareDelta
        // (5)        S           E V   V' program already completed
        // (6)   V    S           E   V'   use versionStarted -> versionComplete for userShareDelta
        //
        // NOTE: V == S and V' == E both default to the inner case

        (uint256 _versionStarted, uint256 _versionComplete) = (
            self.versionStarted == 0 ? currentOracleVersion.version : self.versionStarted, // start must be no earlier than current version
            self.versionComplete == 0 ? type(uint256).max : self.versionComplete           // we don't know when completion occurs
        );

        // accruing must start between self.versionStarted and self.versionComplete
        uint256 fromVersion = Math.min(_versionComplete, Math.max(_versionStarted, product.latestVersion(account)));
        // accruing must complete between self.versionStarted and self.versionComplete, we know self.versionStarted must be no earlier than current version
        uint256 toVersion = Math.min(_versionComplete, currentOracleVersion.version);

        Accumulator memory globalShareDelta = product.shareAtVersion(toVersion).sub(product.shareAtVersion(fromVersion));
        Accumulator memory computedUserShareDelta = product.position(account).mul(globalShareDelta);
        amount = UFixed18Lib.from(programInfo.amountPerShare().mul(computedUserShareDelta).sum());
    }
}

File 28 of 43 : ICollateral.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";
import "@equilibria/root/number/types/Fixed18.sol";
import "@equilibria/root/token/types/Token18.sol";
import "./IController.sol";
import "./IProduct.sol";

interface ICollateral {
    event Deposit(address indexed user, IProduct indexed product, UFixed18 amount);
    event Withdrawal(address indexed user, IProduct indexed product, UFixed18 amount);
    event AccountSettle(IProduct indexed product, address indexed account, Fixed18 amount, UFixed18 newShortfall);
    event ProductSettle(IProduct indexed product, UFixed18 protocolFee, UFixed18 productFee);
    event Liquidation(address indexed user, IProduct indexed product, address liquidator, UFixed18 fee);
    event ShortfallResolution(IProduct indexed product, UFixed18 amount);
    event FeeClaim(address indexed account, UFixed18 amount);

    error CollateralCantLiquidate(UFixed18 totalMaintenance, UFixed18 totalCollateral);
    error CollateralInsufficientCollateralError();
    error CollateralUnderLimitError();
    error CollateralZeroAddressError();

    function token() external view returns (Token18);
    function fees(address account) external view returns (UFixed18);
    function initialize(IController controller_) external;
    function depositTo(address account, IProduct product, UFixed18 amount) external;
    function withdrawTo(address receiver, IProduct product, UFixed18 amount) external;
    function withdrawFrom(address account, address receiver, IProduct product, UFixed18 amount) external;
    function liquidate(address account, IProduct product) external;
    function settleAccount(address account, Fixed18 amount) external;
    function settleProduct(UFixed18 amount) external;
    function collateral(address account, IProduct product) external view returns (UFixed18);
    function collateral(IProduct product) external view returns (UFixed18);
    function shortfall(IProduct product) external view returns (UFixed18);
    function liquidatable(address account, IProduct product) external view returns (bool);
    function liquidatableNext(address account, IProduct product) external view returns (bool);
    function resolveShortfall(IProduct product, UFixed18 amount) external;
    function claimFee() external;
}

File 29 of 43 : IContractPayoffProvider.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/Fixed18.sol";

interface IContractPayoffProvider {
    function payoff(Fixed18 price) external view returns (Fixed18 payoff);
}

File 30 of 43 : IController.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";
import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol";
import "./ICollateral.sol";
import "./IIncentivizer.sol";
import "./IProduct.sol";
import "./IMultiInvoker.sol";
import "./types/PayoffDefinition.sol";

interface IController {
    /// @dev Coordinator of a one or many products
    struct Coordinator {
        /// @dev Pending owner of the product, can accept ownership
        address pendingOwner;

        /// @dev Owner of the product, allowed to update select parameters
        address owner;

        /// @dev Treasury of the product, collects fees
        address treasury;
    }

    event CollateralUpdated(ICollateral newCollateral);
    event IncentivizerUpdated(IIncentivizer newIncentivizer);
    event ProductBeaconUpdated(IBeacon newProductBeacon);
    event MultiInvokerUpdated(IMultiInvoker newMultiInvoker);
    event ProtocolFeeUpdated(UFixed18 newProtocolFee);
    event MinFundingFeeUpdated(UFixed18 newMinFundingFee);
    event LiquidationFeeUpdated(UFixed18 newLiquidationFee);
    event IncentivizationFeeUpdated(UFixed18 newIncentivizationFee);
    event MinCollateralUpdated(UFixed18 newMinCollateral);
    event ProgramsPerProductUpdated(uint256 newProgramsPerProduct);
    event PauserUpdated(address newPauser);
    event PausedUpdated(bool newPaused);
    event CoordinatorPendingOwnerUpdated(uint256 indexed coordinatorId, address newPendingOwner);
    event CoordinatorOwnerUpdated(uint256 indexed coordinatorId, address newOwner);
    event CoordinatorTreasuryUpdated(uint256 indexed coordinatorId, address newTreasury);
    event CoordinatorCreated(uint256 indexed coordinatorId, address owner);
    event ProductCreated(IProduct indexed product, IProduct.ProductInfo productInfo);

    error ControllerNoZeroCoordinatorError();
    error ControllerNotPauserError();
    error ControllerNotOwnerError(uint256 controllerId);
    error ControllerNotPendingOwnerError(uint256 controllerId);
    error ControllerInvalidProtocolFeeError();
    error ControllerInvalidMinFundingFeeError();
    error ControllerInvalidLiquidationFeeError();
    error ControllerInvalidIncentivizationFeeError();
    error ControllerNotContractAddressError();

    function collateral() external view returns (ICollateral);
    function incentivizer() external view returns (IIncentivizer);
    function productBeacon() external view returns (IBeacon);
    function multiInvoker() external view returns (IMultiInvoker);
    function coordinators(uint256 collateralId) external view returns (Coordinator memory);
    function coordinatorFor(IProduct product) external view returns (uint256);
    function protocolFee() external view returns (UFixed18);
    function minFundingFee() external view returns (UFixed18);
    function liquidationFee() external view returns (UFixed18);
    function incentivizationFee() external view returns (UFixed18);
    function minCollateral() external view returns (UFixed18);
    function programsPerProduct() external view returns (uint256);
    function pauser() external view returns (address);
    function paused() external view returns (bool);
    function initialize(ICollateral collateral_, IIncentivizer incentivizer_, IBeacon productBeacon_) external;
    function createCoordinator() external returns (uint256);
    function updateCoordinatorPendingOwner(uint256 coordinatorId, address newPendingOwner) external;
    function acceptCoordinatorOwner(uint256 coordinatorId) external;
    function updateCoordinatorTreasury(uint256 coordinatorId, address newTreasury) external;
    function createProduct(uint256 coordinatorId, IProduct.ProductInfo calldata productInfo) external returns (IProduct);
    function updateCollateral(ICollateral newCollateral) external;
    function updateIncentivizer(IIncentivizer newIncentivizer) external;
    function updateProductBeacon(IBeacon newProductBeacon) external;
    function updateMultiInvoker(IMultiInvoker newMultiInvoker) external;
    function updateProtocolFee(UFixed18 newProtocolFee) external;
    function updateMinFundingFee(UFixed18 newMinFundingFee) external;
    function updateLiquidationFee(UFixed18 newLiquidationFee) external;
    function updateIncentivizationFee(UFixed18 newIncentivizationFee) external;
    function updateMinCollateral(UFixed18 newMinCollateral) external;
    function updateProgramsPerProduct(uint256 newProductsPerProduct) external;
    function updatePauser(address newPauser) external;
    function updatePaused(bool newPaused) external;
    function isProduct(IProduct product) external view returns (bool);
    function owner() external view returns (address);
    function owner(uint256 coordinatorId) external view returns (address);
    function owner(IProduct product) external view returns (address);
    function treasury() external view returns (address);
    function treasury(uint256 coordinatorId) external view returns (address);
    function treasury(IProduct product) external view returns (address);
}

File 31 of 43 : IIncentivizer.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/token/types/Token18.sol";
import "@equilibria/root/number/types/UFixed18.sol";
import "@equilibria/perennial-oracle/contracts/interfaces/IOracleProvider.sol";
import "./types/ProgramInfo.sol";
import "./IController.sol";
import "./IProduct.sol";

interface IIncentivizer {
    event ProgramCreated(IProduct indexed product, uint256 indexed programId, ProgramInfo programInfo, UFixed18 programFeeAmount);
    event ProgramStarted(IProduct indexed product, uint256 indexed programId, uint256 version);
    event ProgramComplete(IProduct indexed product, uint256 indexed programId, uint256 version);
    event Claim(IProduct indexed product, address indexed account, uint256 indexed programId, UFixed18 amount);
    event FeeClaim(Token18 indexed token, UFixed18 amount);

    error IncentivizerNotAllowedError(IProduct product);
    error IncentivizerTooManyProgramsError();
    error IncentivizerNotProgramOwnerError(IProduct product, uint256 programId);
    error IncentivizerInvalidProgramError(IProduct product, uint256 programId);
    error IncentivizerBatchClaimArgumentMismatchError();

    function programInfos(IProduct product, uint256 programId) external view returns (ProgramInfo memory);
    function fees(Token18 token) external view returns (UFixed18);
    function initialize(IController controller_) external;
    function create(IProduct product, ProgramInfo calldata info) external returns (uint256);
    function complete(IProduct product, uint256 programId) external;
    function sync(IOracleProvider.OracleVersion memory currentOracleVersion) external;
    function syncAccount(address account, IOracleProvider.OracleVersion memory currentOracleVersion) external;
    function claim(IProduct product, uint256[] calldata programIds) external;
    function claimFor(address account, IProduct product, uint256[] calldata programIds) external;
    function claim(IProduct[] calldata products, uint256[][] calldata programIds) external;
    function claimFee(Token18[] calldata tokens) external;
    function active(IProduct product) external view returns (uint256);
    function count(IProduct product) external view returns (uint256);
    function unclaimed(IProduct product, address account, uint256 programId) external view returns (UFixed18);
    function available(IProduct product, uint256 programId) external view returns (UFixed18);
    function versionStarted(IProduct product, uint256 programId) external view returns (uint256);
    function versionComplete(IProduct product, uint256 programId) external view returns (uint256);
    function owner(IProduct product, uint256 programId) external view returns (address);
    function treasury(IProduct product, uint256 programId) external view returns (address);
}

File 32 of 43 : IMultiInvoker.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "@equilibria/root/token/types/Token6.sol";
import "@equilibria/emptyset-batcher/batcher/Batcher.sol";

import "./IController.sol";
import "./ICollateral.sol";
import "./IProduct.sol";

interface IMultiInvoker {
    /// @dev Core protocol actions that can be composed
    enum PerennialAction {
        NO_OP,
        DEPOSIT,
        WITHDRAW,
        OPEN_TAKE,
        CLOSE_TAKE,
        OPEN_MAKE,
        CLOSE_MAKE,
        CLAIM,
        WRAP,
        UNWRAP,
        WRAP_AND_DEPOSIT,
        WITHDRAW_AND_UNWRAP
    }

    /// @dev Struct for action invocation
    struct Invocation {
        PerennialAction action;
        bytes args;
    }

    function initialize() external;
    function USDC() external view returns (Token6); // solhint-disable-line func-name-mixedcase
    function DSU() external view returns (Token18); // solhint-disable-line func-name-mixedcase
    function batcher() external view returns (Batcher);
    function controller() external view returns (IController);
    function collateral() external view returns (ICollateral);
    function reserve() external view returns (IEmptySetReserve);
    function invoke(Invocation[] calldata invocations) external;
}

File 33 of 43 : IParamProvider.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";
import "@equilibria/root/curve/types/JumpRateUtilizationCurve.sol";
import "./types/PendingFeeUpdates.sol";

interface IParamProvider {
    event MaintenanceUpdated(UFixed18 newMaintenance, uint256 version);
    event FundingFeeUpdated(UFixed18 newFundingFee, uint256 version);
    event MakerFeeUpdated(UFixed18 newMakerFee, uint256 version);
    event PendingMakerFeeUpdated(UFixed18 newMakerFee);
    event TakerFeeUpdated(UFixed18 newTakerFee, uint256 version);
    event PendingTakerFeeUpdated(UFixed18 newTakerFee);
    event PositionFeeUpdated(UFixed18 newPositionFee, uint256 version);
    event PendingPositionFeeUpdated(UFixed18 newPositionFee);
    event MakerLimitUpdated(UFixed18 newMakerLimit, uint256 version);
    event JumpRateUtilizationCurveUpdated(
        JumpRateUtilizationCurve,
        uint256 version
    );

    error ParamProviderInvalidMakerFee();
    error ParamProviderInvalidTakerFee();
    error ParamProviderInvalidPositionFee();
    error ParamProviderInvalidFundingFee();

    function maintenance() external view returns (UFixed18);
    function updateMaintenance(UFixed18 newMaintenance) external;
    function fundingFee() external view returns (UFixed18);
    function updateFundingFee(UFixed18 newFundingFee) external;
    function makerFee() external view returns (UFixed18);
    function updateMakerFee(UFixed18 newMakerFee) external;
    function takerFee() external view returns (UFixed18);
    function updateTakerFee(UFixed18 newTakerFee) external;
    function positionFee() external view returns (UFixed18);
    function updatePositionFee(UFixed18 newPositionFee) external;
    function makerLimit() external view returns (UFixed18);
    function updateMakerLimit(UFixed18 newMakerLimit) external;
    function utilizationCurve() external view returns (JumpRateUtilizationCurve memory);
    function updateUtilizationCurve(JumpRateUtilizationCurve memory newUtilizationCurve) external;
    function pendingFeeUpdates() external view returns (PendingFeeUpdates memory);
}

File 34 of 43 : IPayoffProvider.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/Fixed18.sol";
import "@equilibria/perennial-oracle/contracts/interfaces/IOracleProvider.sol";
import "./types/PayoffDefinition.sol";

interface IPayoffProvider {
    error PayoffProviderInvalidOracle();
    error PayoffProviderInvalidPayoffDefinitionError();

    function oracle() external view returns (IOracleProvider);
    function payoffDefinition() external view returns (PayoffDefinition memory);
    function currentVersion() external view returns (IOracleProvider.OracleVersion memory);
    function atVersion(uint256 oracleVersion) external view returns (IOracleProvider.OracleVersion memory);
}

File 35 of 43 : IProduct.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";
import "@equilibria/root/curve/types/JumpRateUtilizationCurve.sol";
import "./IPayoffProvider.sol";
import "./IParamProvider.sol";
import "./types/PayoffDefinition.sol";
import "./types/Position.sol";
import "./types/PrePosition.sol";
import "./types/Accumulator.sol";

interface IProduct is IPayoffProvider, IParamProvider {
    /// @dev Product Creation parameters
    struct ProductInfo {
        /// @dev name of the product
        string name;

        /// @dev symbol of the product
        string symbol;

        /// @dev product payoff definition
        PayoffDefinition payoffDefinition;

        /// @dev oracle address
        IOracleProvider oracle;

        /// @dev product maintenance ratio
        UFixed18 maintenance;

        /// @dev product funding fee
        UFixed18 fundingFee;

        /// @dev product maker fee
        UFixed18 makerFee;

        /// @dev product taker fee
        UFixed18 takerFee;

        /// @dev product position fee share
        UFixed18 positionFee;

        /// @dev product maker limit
        UFixed18 makerLimit;

        /// @dev utulization curve definition
        JumpRateUtilizationCurve utilizationCurve;
    }

    event Settle(uint256 preVersion, uint256 toVersion);
    event AccountSettle(address indexed account, uint256 preVersion, uint256 toVersion);
    event MakeOpened(address indexed account, uint256 version, UFixed18 amount);
    event TakeOpened(address indexed account, uint256 version, UFixed18 amount);
    event MakeClosed(address indexed account, uint256 version, UFixed18 amount);
    event TakeClosed(address indexed account, uint256 version, UFixed18 amount);
    event ClosedUpdated(bool indexed newClosed, uint256 version);

    error ProductInsufficientLiquidityError(UFixed18 socializationFactor);
    error ProductDoubleSidedError();
    error ProductOverClosedError();
    error ProductInsufficientCollateralError();
    error ProductInLiquidationError();
    error ProductMakerOverLimitError();
    error ProductOracleBootstrappingError();
    error ProductNotOwnerError();
    error ProductInvalidOracle();
    error ProductClosedError();

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function initialize(ProductInfo calldata productInfo_) external;
    function settle() external;
    function settleAccount(address account) external;
    function openTake(UFixed18 amount) external;
    function openTakeFor(address account, UFixed18 amount) external;
    function closeTake(UFixed18 amount) external;
    function closeTakeFor(address account, UFixed18 amount) external;
    function openMake(UFixed18 amount) external;
    function openMakeFor(address account, UFixed18 amount) external;
    function closeMake(UFixed18 amount) external;
    function closeMakeFor(address account, UFixed18 amount) external;
    function closeAll(address account) external;
    function maintenance(address account) external view returns (UFixed18);
    function maintenanceNext(address account) external view returns (UFixed18);
    function isClosed(address account) external view returns (bool);
    function isLiquidating(address account) external view returns (bool);
    function position(address account) external view returns (Position memory);
    function pre(address account) external view returns (PrePosition memory);
    function latestVersion() external view returns (uint256);
    function positionAtVersion(uint256 oracleVersion) external view returns (Position memory);
    function pre() external view returns (PrePosition memory);
    function valueAtVersion(uint256 oracleVersion) external view returns (Accumulator memory);
    function shareAtVersion(uint256 oracleVersion) external view returns (Accumulator memory);
    function latestVersion(address account) external view returns (uint256);
    function rate(Position memory position) external view returns (Fixed18);
    function closed() external view returns (bool);
    function updateClosed(bool newClosed) external;
}

File 36 of 43 : Accumulator.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/Fixed18.sol";
import "./PackedAccumulator.sol";

/// @dev Accumulator type
struct Accumulator {
    /// @dev maker accumulator per share
    Fixed18 maker;
    /// @dev taker accumulator per share
    Fixed18 taker;
}
using AccumulatorLib for Accumulator global;

/**
 * @title AccountAccumulatorLib
 * @notice Library that surfaces math operations for the Accumulator type.
 * @dev Accumulators track the cumulative change in position value over time for the maker and taker positions
 *      respectively. Account-level accumulators can then use two of these values `a` and `a'` to compute the
 *      change in position value since last sync. This change in value is then used to compute P&L and fees.
 */
library AccumulatorLib {
    /**
     * @notice Creates a packed accumulator from an accumulator
     * @param self an accumulator
     * @return New packed accumulator
     */
    function pack(Accumulator memory self) internal pure returns (PackedAccumulator memory) {
        return PackedAccumulator({maker: self.maker.pack(), taker: self.taker.pack()});
    }

    /**
     * @notice Adds two accumulators together
     * @param a The first accumulator to sum
     * @param b The second accumulator to sum
     * @return The resulting summed accumulator
     */
    function add(Accumulator memory a, Accumulator memory b) internal pure returns (Accumulator memory) {
        return Accumulator({maker: a.maker.add(b.maker), taker: a.taker.add(b.taker)});
    }

    /**
     * @notice Subtracts accumulator `b` from `a`
     * @param a The accumulator to subtract from
     * @param b The accumulator to subtract
     * @return The resulting subtracted accumulator
     */
    function sub(Accumulator memory a, Accumulator memory b) internal pure returns (Accumulator memory) {
        return Accumulator({maker: a.maker.sub(b.maker), taker: a.taker.sub(b.taker)});
    }

    /**
     * @notice Multiplies two accumulators together
     * @param a The first accumulator to multiply
     * @param b The second accumulator to multiply
     * @return The resulting multiplied accumulator
     */
    function mul(Accumulator memory a, Accumulator memory b) internal pure returns (Accumulator memory) {
        return Accumulator({maker: a.maker.mul(b.maker), taker: a.taker.mul(b.taker)});
    }

    /**
     * @notice Sums the maker and taker together from a single accumulator
     * @param self The struct to operate on
     * @return The sum of its maker and taker
     */
    function sum(Accumulator memory self) internal pure returns (Fixed18) {
        return self.maker.add(self.taker);
    }
}

File 37 of 43 : PackedAccumulator.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/PackedFixed18.sol";
import "./Accumulator.sol";

/// @dev PackedAccumulator type
struct PackedAccumulator {
    /// @dev maker accumulator per share
    PackedFixed18 maker;
    /// @dev taker accumulator per share
    PackedFixed18 taker;
}
using PackedAccumulatorLib for PackedAccumulator global;

/**
 * @title PackedAccumulatorLib
 * @dev A packed version of the Accumulator which takes up a single storage slot using `PackedFixed18` values.
 * @notice Library for the packed Accumulator type.
 */
library PackedAccumulatorLib {
    /**
     * @notice Creates an accumulator from a packed accumulator
     * @param self packed accumulator
     * @return New accumulator
     */
    function unpack(PackedAccumulator memory self) internal pure returns (Accumulator memory) {
        return Accumulator({maker: self.maker.unpack(), taker: self.taker.unpack()});
    }
}

File 38 of 43 : PackedPosition.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/PackedUFixed18.sol";
import "./Position.sol";

/// @dev PackedPosition type
struct PackedPosition {
    /// @dev Quantity of the maker position
    PackedUFixed18 maker;
    /// @dev Quantity of the taker position
    PackedUFixed18 taker;
}
using PackedPositionLib for PackedPosition global;

/**
 * @title PackedPositionLib
 * @dev A packed version of the Position which takes up a single storage slot using `PackedFixed18` values.
 * @notice Library for the packed Position type.
 */
library PackedPositionLib {
    /**
     * @notice Creates an position from a packed position
     * @param self packed position
     * @return New position
     */
    function unpack(PackedPosition memory self) internal pure returns (Position memory) {
        return Position({maker: self.maker.unpack(), taker: self.taker.unpack()});
    }
}

File 39 of 43 : PayoffDefinition.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "@openzeppelin/contracts/utils/Address.sol";
import "../../interfaces/IContractPayoffProvider.sol";

/// @dev PayoffDefinition tyoe
struct PayoffDefinition {
  PayoffDefinitionLib.PayoffType payoffType;
  PayoffDefinitionLib.PayoffDirection payoffDirection;
  bytes30 data;
}
using PayoffDefinitionLib for PayoffDefinition global;
type PayoffDefinitionStorage is bytes32;
using PayoffDefinitionStorageLib for PayoffDefinitionStorage global;

/**
 * @title PayoffDefinitionLib
 * @dev Library that surfaces logic for PayoffDefinition type functionality
 * @notice Library for the PayoffDefinition type. Performs validity and price transformation
            based on the payoff definition type.
 */
library PayoffDefinitionLib {
  using Address for address;

  error PayoffDefinitionUnsupportedTransform(PayoffType payoffType, PayoffDirection payoffDirection);
  error PayoffDefinitionNotContract(PayoffType payoffType, bytes30 data);

  /// @dev Payoff function type enum
  enum PayoffType { PASSTHROUGH, CONTRACT }
  enum PayoffDirection { LONG, SHORT }

  /**
   * @notice Checks validity of the payoff definition
   * @param self a payoff definition
   * @return Whether the payoff definition is valid for it's given type
   */
  function valid(PayoffDefinition memory self) internal view returns (bool) {
    if (self.payoffType == PayoffType.CONTRACT) return address(_providerContract(self)).isContract();

    // All other payoff types should have no data
    return uint(bytes32(self.data)) == 0;
  }

  /**
   * @notice Transforms a price based on the payoff definition
   * @param self a payoff definition
   * @param price raw oracle price
   * @return Price transformed by the payoff definition function
   */
  function transform(
    PayoffDefinition memory self,
    Fixed18 price
  ) internal view returns (Fixed18) {
    PayoffType payoffType = self.payoffType;
    PayoffDirection payoffDirection = self.payoffDirection;
    Fixed18 transformedPrice;

    // First get the price depending on the type
    if (payoffType == PayoffType.PASSTHROUGH) transformedPrice = price;
    else if (payoffType == PayoffType.CONTRACT) transformedPrice =  _payoffFromContract(self, price);
    else revert PayoffDefinitionUnsupportedTransform(payoffType, payoffDirection);

    // Then transform it depending on the direction flag
    if (self.payoffDirection == PayoffDirection.LONG) return transformedPrice;
    else if (self.payoffDirection == PayoffDirection.SHORT) return transformedPrice.mul(Fixed18Lib.NEG_ONE);
    else revert PayoffDefinitionUnsupportedTransform(payoffType, payoffDirection);
  }

  /**
   * @notice Parses the data field into an address
   * @dev Reverts if payoffType is not CONTRACT
   * @param self a payoff definition
   * @return IContractPayoffProvider address
   */
  function _providerContract(
    PayoffDefinition memory self
  ) private pure returns (IContractPayoffProvider) {
    if (self.payoffType != PayoffType.CONTRACT) revert PayoffDefinitionNotContract(self.payoffType, self.data);
    // Shift to pull the last 20 bytes, then cast to an address
    return IContractPayoffProvider(address(bytes20(self.data << 80)));
  }

  /**
   * @notice Performs a price transformation by calling the underlying payoff contract
   * @param self a payoff definition
   * @param price raw oracle price
   * @return Price transformed by the payoff definition function on the contract
   */
  function _payoffFromContract(
    PayoffDefinition memory self,
    Fixed18 price
  ) private view returns (Fixed18) {
    bytes memory ret = address(_providerContract(self)).functionStaticCall(
      abi.encodeCall(IContractPayoffProvider.payoff, price)
    );
    return Fixed18.wrap(abi.decode(ret, (int256)));
  }
}

/**
 * @title PayoffDefinitionStorageLib
 * @notice Library that surfaces storage read and writes for the PayoffDefinition type
 */
library PayoffDefinitionStorageLib {
    function read(PayoffDefinitionStorage self) internal view returns (PayoffDefinition memory) {
        return _storagePointer(self);
    }

    function store(PayoffDefinitionStorage self, PayoffDefinition memory value) internal {
        PayoffDefinition storage storagePointer = _storagePointer(self);

        storagePointer.payoffType = value.payoffType;
        storagePointer.payoffDirection = value.payoffDirection;
        storagePointer.data = value.data;
    }

    function _storagePointer(
      PayoffDefinitionStorage self
    ) private pure returns (PayoffDefinition storage pointer) {
        assembly { pointer.slot := self } // solhint-disable-line no-inline-assembly
    }
}

File 40 of 43 : PendingFeeUpdates.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.15;

import "@equilibria/root/number/types/UFixed18.sol";

/// @dev PendingFeeUpdates type. Fees can be between 0 and 1 ** 10^18, so uint64 is sufficient
struct PendingFeeUpdates {
    bool makerFeeUpdated;
    uint64 pendingMakerFee;
    bool takerFeeUpdated;
    uint64 pendingTakerFee;
    bool positionFeeUpdated;
    uint64 pendingPositionFee;
}
using PendingFeeUpdatesLib for PendingFeeUpdates global;
type PendingFeeUpdatesStorage is bytes32;
using PendingFeeUpdatesStorageLib for PendingFeeUpdatesStorage global;

/**
 * @title PendingFeeUpdatesLib
 * @dev Library that surfaces convenience functions for the PendingFeeUpdates type
 * @notice Library for the PendingFeeUpdates type. Allows for setting and reading fee updates and clearing state
 */
library PendingFeeUpdatesLib {
    error PendingFeeUpdatesUnsupportedValue(UFixed18 value);

    /**
     * @notice Updates the pending maker fee to `newMakerFee` and sets the `makerFeeUpdated` flag
     * @dev Reverts if `newMakerFee` is invalid
     * @param self PendingFeeUpdates struct
     * @param newMakerFee new maker fee value
     */
    function updateMakerFee(PendingFeeUpdates memory self, UFixed18 newMakerFee) internal pure {
        if (UFixed18.unwrap(newMakerFee) > type(uint64).max) revert PendingFeeUpdatesUnsupportedValue(newMakerFee);
        self.pendingMakerFee = uint64(UFixed18.unwrap(newMakerFee));
        self.makerFeeUpdated = true;
    }

    /// @dev Returns the UFixed18-wrapped pending maker fee
    function makerFee(PendingFeeUpdates memory self) internal pure returns (UFixed18) {
        return UFixed18.wrap(uint256(self.pendingMakerFee));
    }

    /**
     * @notice Updates the pending taker fee to `newTakerFee` and sets the `takerFeeUpdated` flag
     * @dev Reverts if `newTakerFee` is invalid
     * @param self PendingFeeUpdates struct
     * @param newTakerFee new taker fee value
     */
    function updateTakerFee(PendingFeeUpdates memory self, UFixed18 newTakerFee) internal pure {
        if (UFixed18.unwrap(newTakerFee) > type(uint64).max) revert PendingFeeUpdatesUnsupportedValue(newTakerFee);
        self.pendingTakerFee = uint64(UFixed18.unwrap(newTakerFee));
        self.takerFeeUpdated = true;
    }

    /// @dev Returns the UFixed18-wrapped pending taker fee
    function takerFee(PendingFeeUpdates memory self) internal pure returns (UFixed18) {
        return UFixed18.wrap(uint256(self.pendingTakerFee));
    }

    /**
     * @notice Updates the pending position fee to `newPositionFee` and sets the `positionFeeUpdated` flag
     * @dev Reverts if `newPositionFee` is invalid
     * @param self PendingFeeUpdates struct
     * @param newPositionFee new position fee value
     */
    function updatePositionFee(PendingFeeUpdates memory self, UFixed18 newPositionFee) internal pure {
        if (UFixed18.unwrap(newPositionFee) > type(uint64).max) revert PendingFeeUpdatesUnsupportedValue(newPositionFee);
        self.pendingPositionFee = uint64(UFixed18.unwrap(newPositionFee));
        self.positionFeeUpdated = true;
    }

    /// @dev Returns the UFixed18-wrapped pending position fee
    function positionFee(PendingFeeUpdates memory self) internal pure returns (UFixed18) {
        return UFixed18.wrap(uint256(self.pendingPositionFee));
    }

    /// @dev Returns true if any of the updated flags are true
    function hasUpdates(PendingFeeUpdates memory self) internal pure returns (bool) {
        return self.makerFeeUpdated || self.takerFeeUpdated || self.positionFeeUpdated;
    }

    /// @dev Resets all struct values to defaults
    function clear(PendingFeeUpdates memory self) internal pure {
        self.makerFeeUpdated = false;
        self.pendingMakerFee = 0;
        self.takerFeeUpdated = false;
        self.pendingTakerFee = 0;
        self.positionFeeUpdated = false;
        self.pendingPositionFee = 0;
    }
}

/**
 * @title PendingFeeUpdatesStorageLib
 * @notice Library that surfaces storage read and writes for the PendingFeeUpdates type
 */
library PendingFeeUpdatesStorageLib {
    struct PendingFeeUpdatesStoragePointer {
        PendingFeeUpdates value;
    }

    function read(PendingFeeUpdatesStorage self) internal view returns (PendingFeeUpdates memory) {
        return _storagePointer(self).value;
    }

    function store(PendingFeeUpdatesStorage self, PendingFeeUpdates memory value) internal {
        _storagePointer(self).value = value;
    }

    function _storagePointer(
        PendingFeeUpdatesStorage self
    ) private pure returns (PendingFeeUpdatesStoragePointer storage pointer) {
        /// @solidity memory-safe-assembly
        assembly { pointer.slot := self } // solhint-disable-line no-inline-assembly
    }
}

File 41 of 43 : Position.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/math/Math.sol";
import "@equilibria/root/number/types/UFixed18.sol";
import "../IProduct.sol";
import "./Accumulator.sol";
import "./PrePosition.sol";
import "./PackedPosition.sol";

/// @dev Position type
struct Position {
    /// @dev Quantity of the maker position
    UFixed18 maker;
    /// @dev Quantity of the taker position
    UFixed18 taker;
}
using PositionLib for Position global;

/**
 * @title PositionLib
 * @notice Library that surfaces math and settlement computations for the Position type.
 * @dev Positions track the current quantity of the account's maker and taker positions respectively
 *      denominated as a unit of the product's payoff function.
 */
library PositionLib {
    /**
     * @notice Creates a packed position from an position
     * @param self A position
     * @return New packed position
     */
    function pack(Position memory self) internal pure returns (PackedPosition memory) {
        return PackedPosition({maker: self.maker.pack(), taker: self.taker.pack()});
    }

    /**
     * @notice Returns whether the position is fully empty
     * @param self A position
     * @return Whether the position is empty
     */
    function isEmpty(Position memory self) internal pure returns (bool) {
        return self.maker.isZero() && self.taker.isZero();
    }

    /**
     * @notice Adds position `a` and `b` together, returning the result
     * @param a The first position to sum
     * @param b The second position to sum
     * @return Resulting summed position
     */
    function add(Position memory a, Position memory b) internal pure returns (Position memory) {
        return Position({maker: a.maker.add(b.maker), taker: a.taker.add(b.taker)});
    }

    /**
     * @notice Subtracts position `b` from `a`, returning the result
     * @param a The position to subtract from
     * @param b The position to subtract
     * @return Resulting subtracted position
     */
    function sub(Position memory a, Position memory b) internal pure returns (Position memory) {
        return Position({maker: a.maker.sub(b.maker), taker: a.taker.sub(b.taker)});
    }

    /**
     * @notice Multiplies position `self` by accumulator `accumulator` and returns the resulting accumulator
     * @param self The Position to operate on
     * @param accumulator The accumulator to multiply by
     * @return Resulting multiplied accumulator
     */
    function mul(Position memory self, Accumulator memory accumulator) internal pure returns (Accumulator memory) {
        return Accumulator({
            maker: Fixed18Lib.from(self.maker).mul(accumulator.maker),
            taker: Fixed18Lib.from(self.taker).mul(accumulator.taker)
        });
    }

    /**
     * @notice Scales position `self` by fixed-decimal `scale` and returns the resulting position
     * @param self The Position to operate on
     * @param scale The Fixed-decimal to scale by
     * @return Resulting scaled position
     */
    function mul(Position memory self, UFixed18 scale) internal pure returns (Position memory) {
        return Position({maker: self.maker.mul(scale), taker: self.taker.mul(scale)});
    }

    /**
     * @notice Divides position `self` by `b` and returns the resulting accumulator
     * @param self The Position to operate on
     * @param b The number to divide by
     * @return Resulting divided accumulator
     */
    function div(Position memory self, uint256 b) internal pure returns (Accumulator memory) {
        return Accumulator({
            maker: Fixed18Lib.from(self.maker).div(Fixed18Lib.from(UFixed18Lib.from(b))),
            taker: Fixed18Lib.from(self.taker).div(Fixed18Lib.from(UFixed18Lib.from(b)))
        });
    }

    /**
     * @notice Returns the maximum of `self`'s maker and taker values
     * @param self The struct to operate on
     * @return Resulting maximum value
     */
    function max(Position memory self) internal pure returns (UFixed18) {
        return UFixed18Lib.max(self.maker, self.taker);
    }

    /**
     * @notice Sums the maker and taker together from a single position
     * @param self The struct to operate on
     * @return The sum of its maker and taker
     */
    function sum(Position memory self) internal pure returns (UFixed18) {
        return self.maker.add(self.taker);
    }

    /**
     * @notice Computes the next position after the pending-settlement position delta is included
     * @param self The current Position
     * @param pre The pending-settlement position delta
     * @return Next Position
     */
    function next(Position memory self, PrePosition memory pre) internal pure returns (Position memory) {
        return sub(add(self, pre.openPosition), pre.closePosition);
    }

    /**
     * @notice Returns the settled position at oracle version `toOracleVersion`
     * @dev Checks if a new position is ready to be settled based on the provided `toOracleVersion`
     *      and `pre` and returns accordingly
     * @param self The current Position
     * @param pre The pending-settlement position delta
     * @param toOracleVersion The oracle version to settle to
     * @return Settled position at oracle version
     * @return Whether a new position was settled
     */
    function settled(
        Position memory self,
        PrePosition memory pre,
        IOracleProvider.OracleVersion memory toOracleVersion
    ) internal pure returns (Position memory, bool) {
        return pre.canSettle(toOracleVersion) ? (next(self, pre), true) : (self, false);
    }

    /**
     * @notice Returns the socialization factor for the current position
     * @dev Socialization account for the case where `taker` > `maker` temporarily due to a liquidation
     *      on the maker side. This dampens the taker's exposure pro-rata to ensure that the maker side
     *      is never exposed over 1 x short.
     * @param self The Position to operate on
     * @return Socialization factor
     */
    function socializationFactor(Position memory self) internal pure returns (UFixed18) {
        return self.taker.isZero() ? UFixed18Lib.ONE : UFixed18Lib.min(UFixed18Lib.ONE, self.maker.div(self.taker));
    }
}

File 42 of 43 : PrePosition.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/number/types/UFixed18.sol";
import "@equilibria/perennial-oracle/contracts/interfaces/IOracleProvider.sol";
import "./Position.sol";
import "../IProduct.sol";

/// @dev PrePosition type
struct PrePosition {
    /// @dev Oracle version at which the new position delta was recorded
    uint256 oracleVersion;

    /// @dev Size of position to open at oracle version
    Position openPosition;

    /// @dev Size of position to close at oracle version
    Position closePosition;
}
using PrePositionLib for PrePosition global;

/**
 * @title PrePositionLib
 * @notice Library that manages a pre-settlement position delta.
 * @dev PrePositions track the currently awaiting-settlement deltas to a settled Position. These are
 *      Primarily necessary to introduce lag into the settlement system such that oracle lag cannot be
 *      gamed to a user's advantage. When a user opens or closes a new position, it sits as a PrePosition
 *      for one oracle version until it's settle into the Position, making it then effective. PrePositions
 *      are automatically settled at the correct oracle version even if a flywheel call doesn't happen until
 *      several version into the future by using the historical version lookups in the corresponding "Versioned"
 *      global state types.
 */
library PrePositionLib {
    /**
     * @notice Returns whether there is no pending-settlement position delta
     * @param self The struct to operate on
     * @return Whether the pending-settlement position delta is empty
     */
    function isEmpty(PrePosition memory self) internal pure returns (bool) {
        return self.openPosition.isEmpty() && self.closePosition.isEmpty();
    }

    /**
     * @notice Increments the maker side of the open position delta
     * @param self The struct to operate on
     * @param currentVersion The current oracle version index
     * @param amount The position amount to open
     */
    function openMake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal {
        self.openPosition.maker = self.openPosition.maker.add(amount);
        self.oracleVersion = currentVersion;
    }

    /**
     * @notice Increments the maker side of the close position delta
     * @param self The struct to operate on
     * @param currentVersion The current oracle version index
     * @param amount The maker position amount to close
     */
    function closeMake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal {
        self.closePosition.maker = self.closePosition.maker.add(amount);
        self.oracleVersion = currentVersion;
    }

    /**
     * @notice Increments the taker side of the open position delta
     * @param self The struct to operate on
     * @param currentVersion The current oracle version index
     * @param amount The taker position amount to open
     */
    function openTake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal {
        self.openPosition.taker = self.openPosition.taker.add(amount);
        self.oracleVersion = currentVersion;
    }

    /**
     * @notice Increments the taker side of the close position delta
     * @param self The struct to operate on
     * @param currentVersion The current oracle version index
     * @param amount The taker position amount to close
     */
    function closeTake(PrePosition storage self, uint256 currentVersion, UFixed18 amount) internal {
        self.closePosition.taker = self.closePosition.taker.add(amount);
        self.oracleVersion = currentVersion;
    }

    /**
     * @notice Returns whether the the pending position delta can be settled at version `toOracleVersion`
     * @dev Pending-settlement positions deltas can be settled (1) oracle version after they are recorded
     * @param self The struct to operate on
     * @param toOracleVersion The potential oracle version to settle
     * @return Whether the position delta can be settled
     */
    function canSettle(
        PrePosition memory self,
        IOracleProvider.OracleVersion memory toOracleVersion
    ) internal pure returns (bool) {
        return !isEmpty(self) && toOracleVersion.version > self.oracleVersion;
    }

    /**
     * @notice Computes the fee incurred for opening or closing the pending-settlement position
     * @dev Must be called from a valid product to get the proper fee amounts
     * @param self The struct to operate on
     * @param latestOracleVersion The oracle version at which position was modified
     * @return The maker / taker fee incurred
     */
    function computeFee(
        PrePosition memory self,
        IOracleProvider.OracleVersion memory latestOracleVersion
    ) internal view returns (Position memory) {
        Position memory positionDelta = self.openPosition.add(self.closePosition);

        (UFixed18 makerNotional, UFixed18 takerNotional) = (
            Fixed18Lib.from(positionDelta.maker).mul(latestOracleVersion.price).abs(),
            Fixed18Lib.from(positionDelta.taker).mul(latestOracleVersion.price).abs()
        );

        IProduct product = IProduct(address(this));
        return Position(makerNotional.mul(product.makerFee()), takerNotional.mul(product.takerFee()));
    }

    /**
     * @notice Computes the next oracle version to settle
     * @dev - If there is no pending-settlement position delta, returns the current oracle version
     *      - Otherwise returns the oracle version at which the pending-settlement position delta can be first settled
     *
     *      Corresponds to point (b) in the Position settlement flow
     * @param self The struct to operate on
     * @param currentVersion The current oracle version index
     * @return Next oracle version to settle
     */
    function settleVersion(PrePosition storage self, uint256 currentVersion) internal view returns (uint256) {
        uint256 _oracleVersion = self.oracleVersion;
        return _oracleVersion == 0 ? currentVersion : _oracleVersion + 1;
    }
}

File 43 of 43 : ProgramInfo.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;

import "@equilibria/root/token/types/Token18.sol";
import "../IProduct.sol";
import "./Position.sol";
import "./Accumulator.sol";

/// @dev ProgramInfo type
struct ProgramInfo {
    /// @dev Coordinator for this program
    uint256 coordinatorId;

    /// @dev Amount of total maker and taker rewards
    Position amount;

    /// @dev start timestamp of the program
    uint256 start;

    /// @dev duration of the program (in seconds)
    uint256 duration;

    /**
     * @dev Reward ERC20 token contract
     * @notice Perennial does not support non-standard ERC20s as reward tokens for incentive programs, including,
                but not limited to: fee on transfer and rebase tokens. Using such a non-standard token will likely
                result in loss of funds.
     */
    Token18 token;
}
using ProgramInfoLib for ProgramInfo global;

/**
 * @title ProgramInfoLib
 * @notice Library that snapshots the static information for a single program.
 * @dev This information does not change during the operation of a program.
 */
library ProgramInfoLib {
    uint256 private constant MIN_DURATION = 1 days;
    uint256 private constant MAX_DURATION = 2 * 365 days;

    error ProgramInvalidStartError();
    error ProgramInvalidDurationError();

    /**
     * @notice Validates and creates a new Program
     * @dev Reverts for invalid programInfos
     * @param programInfo Un-sanitized static program information
     */
    function validate(ProgramInfo memory programInfo) internal view {
        if (isStarted(programInfo, block.timestamp)) revert ProgramInvalidStartError();
        if (programInfo.duration < MIN_DURATION || programInfo.duration > MAX_DURATION) revert ProgramInvalidDurationError();
    }

    /**
     * @notice Computes a new program info with the fee taken out of the amount
     * @param programInfo Original program info
     * @param incentivizationFee The incentivization fee
     * @return New program info
     * @return Fee amount
     */
    function deductFee(ProgramInfo memory programInfo, UFixed18 incentivizationFee)
    internal pure returns (ProgramInfo memory, UFixed18) {
        Position memory newProgramAmount = programInfo.amount.mul(UFixed18Lib.ONE.sub(incentivizationFee));
        UFixed18 programFeeAmount = programInfo.amount.sub(newProgramAmount).sum();
        programInfo.amount = newProgramAmount;
        return (programInfo, programFeeAmount);
    }

    /**
     * @notice Returns the maker and taker amounts per position share
     * @param self The ProgramInfo to operate on
     * @return programFee Amounts per share
     */
    function amountPerShare(ProgramInfo memory self) internal pure returns (Accumulator memory) {
        return self.amount.div(self.duration);
    }

    /**
     * @notice Returns whether the program has started by timestamp `timestamp`
     * @param self The ProgramInfo to operate on
     * @param timestamp Timestamp to check for
     * @return Whether the program has started
     */
    function isStarted(ProgramInfo memory self, uint256 timestamp) internal pure returns (bool) {
        return timestamp >= self.start;
    }

    /**
     * @notice Returns whether the program is completed by timestamp `timestamp`
     * @param self The ProgramInfo to operate on
     * @param timestamp Timestamp to check for
     * @return Whether the program is completed
     */
    function isComplete(ProgramInfo memory self, uint256 timestamp) internal pure returns (bool) {
        return timestamp >= (self.start + self.duration);
    }
}

Settings
{
  "evmVersion": "london",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs",
    "useLiteralContent": true
  },
  "optimizer": {
    "enabled": false,
    "runs": 1000000
  },
  "remappings": [],
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"Fixed18OverflowError","type":"error"},{"inputs":[],"name":"IncentivizerBatchClaimArgumentMismatchError","type":"error"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"IncentivizerInvalidProgramError","type":"error"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"}],"name":"IncentivizerNotAllowedError","type":"error"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"IncentivizerNotProgramOwnerError","type":"error"},{"inputs":[],"name":"IncentivizerTooManyProgramsError","type":"error"},{"inputs":[],"name":"InvalidControllerError","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"NotAccountOrMultiInvokerError","type":"error"},{"inputs":[],"name":"NotCollateralError","type":"error"},{"inputs":[{"internalType":"uint256","name":"coordinatorId","type":"uint256"}],"name":"NotOwnerError","type":"error"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"}],"name":"NotProductError","type":"error"},{"inputs":[],"name":"PausedError","type":"error"},{"inputs":[],"name":"ProgramInvalidDurationError","type":"error"},{"inputs":[],"name":"ProgramInvalidStartError","type":"error"},{"inputs":[{"internalType":"int256","name":"value","type":"int256"}],"name":"UFixed18UnderflowError","type":"error"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"UInitializableAlreadyInitializedError","type":"error"},{"inputs":[],"name":"UInitializableNotInitializingError","type":"error"},{"inputs":[],"name":"UInitializableZeroVersionError","type":"error"},{"inputs":[],"name":"UReentrancyGuardReentrantCallError","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IProduct","name":"product","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"uint256","name":"programId","type":"uint256"},{"indexed":false,"internalType":"UFixed18","name":"amount","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"Token18","name":"token","type":"address"},{"indexed":false,"internalType":"UFixed18","name":"amount","type":"uint256"}],"name":"FeeClaim","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IProduct","name":"product","type":"address"},{"indexed":true,"internalType":"uint256","name":"programId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ProgramComplete","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IProduct","name":"product","type":"address"},{"indexed":true,"internalType":"uint256","name":"programId","type":"uint256"},{"components":[{"internalType":"uint256","name":"coordinatorId","type":"uint256"},{"components":[{"internalType":"UFixed18","name":"maker","type":"uint256"},{"internalType":"UFixed18","name":"taker","type":"uint256"}],"internalType":"struct Position","name":"amount","type":"tuple"},{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"Token18","name":"token","type":"address"}],"indexed":false,"internalType":"struct ProgramInfo","name":"programInfo","type":"tuple"},{"indexed":false,"internalType":"UFixed18","name":"programFeeAmount","type":"uint256"}],"name":"ProgramCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"contract IProduct","name":"product","type":"address"},{"indexed":true,"internalType":"uint256","name":"programId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ProgramStarted","type":"event"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"}],"name":"active","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"available","outputs":[{"internalType":"UFixed18","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256[]","name":"programIds","type":"uint256[]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IProduct[]","name":"products","type":"address[]"},{"internalType":"uint256[][]","name":"programIds","type":"uint256[][]"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Token18[]","name":"tokens","type":"address[]"}],"name":"claimFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256[]","name":"programIds","type":"uint256[]"}],"name":"claimFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"complete","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"controller","outputs":[{"internalType":"contract IController","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"}],"name":"count","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"components":[{"internalType":"uint256","name":"coordinatorId","type":"uint256"},{"components":[{"internalType":"UFixed18","name":"maker","type":"uint256"},{"internalType":"UFixed18","name":"taker","type":"uint256"}],"internalType":"struct Position","name":"amount","type":"tuple"},{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"Token18","name":"token","type":"address"}],"internalType":"struct ProgramInfo","name":"programInfo","type":"tuple"}],"name":"create","outputs":[{"internalType":"uint256","name":"programId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"Token18","name":"","type":"address"}],"name":"fees","outputs":[{"internalType":"UFixed18","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IController","name":"controller_","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"programInfos","outputs":[{"components":[{"internalType":"uint256","name":"coordinatorId","type":"uint256"},{"components":[{"internalType":"UFixed18","name":"maker","type":"uint256"},{"internalType":"UFixed18","name":"taker","type":"uint256"}],"internalType":"struct Position","name":"amount","type":"tuple"},{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"Token18","name":"token","type":"address"}],"internalType":"struct ProgramInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"version","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"Fixed18","name":"price","type":"int256"}],"internalType":"struct IOracleProvider.OracleVersion","name":"currentOracleVersion","type":"tuple"}],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"components":[{"internalType":"uint256","name":"version","type":"uint256"},{"internalType":"uint256","name":"timestamp","type":"uint256"},{"internalType":"Fixed18","name":"price","type":"int256"}],"internalType":"struct IOracleProvider.OracleVersion","name":"currentOracleVersion","type":"tuple"}],"name":"syncAccount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"treasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"unclaimed","outputs":[{"internalType":"UFixed18","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"versionComplete","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IProduct","name":"product","type":"address"},{"internalType":"uint256","name":"programId","type":"uint256"}],"name":"versionStarted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

608060405234801561001057600080fd5b50615b6180620000216000396000f3fe608060405234801561001057600080fd5b506004361061016b5760003560e01c806370f3f08b116100cd578063d758869111610081578063f77c479111610066578063f77c479114610400578063f8724aba1461041e578063faaebd211461044e5761016b565b8063d7588691146103b4578063e1e5a82e146103e45761016b565b8063a55ff01d116100b2578063a55ff01d14610360578063b774b6011461037c578063c4d66de8146103985761016b565b806370f3f08b1461031457806390672ad8146103445761016b565b80632c3e50e5116101245780634ea71327116101095780634ea713271461028457806366f8162e146102b45780636da0d540146102e45761016b565b80632c3e50e51461023857806345718278146102685761016b565b8063095c824111610155578063095c8241146101bc578063144dcdd1146101ec5780631830349c1461021c5761016b565b80628240531461017057806305d85eda1461018c575b600080fd5b61018a60048036038101906101859190614521565b61047e565b005b6101a660048036038101906101a19190614595565b6106b9565b6040516101b391906145db565b60405180910390f35b6101d660048036038101906101d19190614622565b610707565b6040516101e391906145db565b60405180910390f35b61020660048036038101906102019190614622565b610767565b6040516102139190614671565b60405180910390f35b610236600480360381019061023191906147b7565b610856565b005b610252600480360381019061024d9190614622565b610976565b60405161025f919061492d565b60405180910390f35b610282600480360381019061027d9190614948565b610a8e565b005b61029e60048036038101906102999190614595565b610ba9565b6040516102ab91906145db565b60405180910390f35b6102ce60048036038101906102c991906149a8565b610bf8565b6040516102db9190614a0a565b60405180910390f35b6102fe60048036038101906102f99190614622565b610c55565b60405161030b9190614671565b60405180910390f35b61032e60048036038101906103299190614622565b610d44565b60405161033b91906145db565b60405180910390f35b61035e60048036038101906103599190614ad1565b610da4565b005b61037a60048036038101906103759190614b52565b610f6b565b005b61039660048036038101906103919190614bd5565b6110d1565b005b6103b260048036038101906103ad9190614c60565b611349565b005b6103ce60048036038101906103c99190614cb1565b6114d8565b6040516103db91906145db565b60405180910390f35b6103fe60048036038101906103f99190614622565b611bd5565b005b610408611ec7565b6040516104159190614d12565b60405180910390f35b61043860048036038101906104339190614622565b611ef7565b6040516104459190614a0a565b60405180910390f35b61046860048036038101906104639190614d59565b611f57565b6040516104759190614a0a565b60405180910390f35b60026104a97f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b036104e0576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61051460027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b838073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806105ef5750610552611ec7565b73ffffffffffffffffffffffffffffffffffffffff1663479e58f06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561059c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c09190614dc4565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b6106325780336040517f6dc3a3c3000000000000000000000000000000000000000000000000000000008152600401610629929190614df1565b60405180910390fd5b61067e8585858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611f81565b506106b360017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b50505050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001805490509050919050565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101600083815260200190815260200160002060020154905092915050565b6000610771611ec7565b73ffffffffffffffffffffffffffffffffffffffff16634c5430a06000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000184815481106107df576107de614e1a565b5b9060005260206000209060060201600001546040518263ffffffff1660e01b815260040161080d91906145db565b602060405180830381865afa15801561082a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084e9190614e5e565b905092915050565b61085e611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192336040518263ffffffff1660e01b81526004016108969190614eac565b602060405180830381865afa1580156108b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d79190614eff565b61091857336040517f4d53256500000000000000000000000000000000000000000000000000000000815260040161090f9190614eac565b60405180910390fd5b60003390506109718184846000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206121ab909392919063ffffffff16565b505050565b61097e614365565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000182815481106109d1576109d0614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681525050905092915050565b6002610ab97f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b03610af0576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610b2460027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b610b703384848480806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611f81565b610ba460017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b505050565b6000610bf16000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206124d1565b9050919050565b6000610c4c83836000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206124e69092919063ffffffff16565b90509392505050565b6000610c5f611ec7565b73ffffffffffffffffffffffffffffffffffffffff1663a123c33e6000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018481548110610ccd57610ccc614e1a565b5b9060005260206000209060060201600001546040518263ffffffff1660e01b8152600401610cfb91906145db565b602060405180830381865afa158015610d18573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d3c9190614e5e565b905092915050565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101600083815260200190815260200160002060030154905092915050565b6002610dcf7f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b03610e06576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e3a60027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b818190508484905014610e79576040517f90fb223000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b84849050811015610f3057610f1d33868684818110610e9e57610e9d614e1a565b5b9050602002016020810190610eb39190614595565b858585818110610ec657610ec5614e1a565b5b9050602002810190610ed89190614f3b565b80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611f81565b8080610f2890614fcd565b915050610e7c565b50610f6560017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b50505050565b610f73611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192336040518263ffffffff1660e01b8152600401610fab9190614eac565b602060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614eff565b61102d57336040517f4d5325650000000000000000000000000000000000000000000000000000000081526004016110249190614eac565b60405180910390fd5b6000339050600061108682846000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061255f9092919063ffffffff16565b905060005b81518110156110cb576110b8838383815181106110ab576110aa614e1a565b5b60200260200101516127ba565b80806110c390614fcd565b91505061108b565b50505050565b6110d9611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611123573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111479190614eff565b1561117e576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b828290508110156113445760008383838181106111a1576111a0614e1a565b5b90506020020160208101906111b69190614d59565b90506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506112e161124c611ec7565b73ffffffffffffffffffffffffffffffffffffffff166361d027b36040518163ffffffff1660e01b8152600401602060405180830381865afa158015611296573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ba9190614e5e565b828473ffffffffffffffffffffffffffffffffffffffff166129219092919063ffffffff16565b8173ffffffffffffffffffffffffffffffffffffffff167f10df095d1434aed409b2f33d2a6a8456f8b0824633cc12a1b43032085aadc41d826040516113279190614a0a565b60405180910390a25050808061133c90614fcd565b915050611181565b505050565b600160008103611385576040517fb66146bd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806113af7f5db5abc19987c2b3729df7961b62b6bb0bae886dd47e3ce25bb3a3af34c6d80b611f6f565b106113f157806040517f1e7a9d950000000000000000000000000000000000000000000000000000000081526004016113e891906145db565b60405180910390fd5b611424817f5db5abc19987c2b3729df7961b62b6bb0bae886dd47e3ce25bb3a3af34c6d80b611f7a90919063ffffffff16565b61145860017fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c61295190919063ffffffff16565b61146182612958565b611469612a3f565b61149d60007fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c61295190919063ffffffff16565b7fbe9b076dc5b65990cca9dd9d7366682482e7817a6f6bc7f4faf4dc32af497f32816040516114cc91906145db565b60405180910390a15050565b600060026115057f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b0361153c576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61157060027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b82611579611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192826040518263ffffffff1660e01b81526004016115b19190614eac565b602060405180830381865afa1580156115ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115f29190614eff565b61163357806040517f4d53256500000000000000000000000000000000000000000000000000000000815260040161162a9190614eac565b60405180910390fd5b61163b611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611685573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116a99190614eff565b156116e0576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82600001356116ed611ec7565b73ffffffffffffffffffffffffffffffffffffffff1663a123c33e826040518263ffffffff1660e01b815260040161172591906145db565b602060405180830381865afa158015611742573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117669190614e5e565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146117d557806040517f48f62f3c0000000000000000000000000000000000000000000000000000000081526004016117cc91906145db565b60405180910390fd5b60006117df611ec7565b9050600085600001351415801561187357508073ffffffffffffffffffffffffffffffffffffffff16637d254e66876040518263ffffffff1660e01b815260040161182a9190614eac565b602060405180830381865afa158015611847573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061186b919061502a565b856000013514155b156118b557856040517fe264a5240000000000000000000000000000000000000000000000000000000081526004016118ac9190614eac565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16637d49c1b76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611900573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611924919061502a565b61192d87610ba9565b10611964576040517f1651519200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61197d85803603810190611978919061515f565b612ae7565b600080611a0887803603810190611994919061515f565b8473ffffffffffffffffffffffffffffffffffffffff16633ceda0116040518163ffffffff1660e01b8152600401602060405180830381865afa1580156119df573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a0391906151a1565b612b7e565b91509150611a628160016000856080015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054612bf790919063ffffffff16565b60016000846080015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611af9826000808b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612c0d90919063ffffffff16565b9550611b4533611b1b89602001803603810190611b1691906151ce565b612d11565b846080015173ffffffffffffffffffffffffffffffffffffffff16612d359092919063ffffffff16565b858873ffffffffffffffffffffffffffffffffffffffff167fee8233a38e17998eb73ba822048e13762f2b68729ef94c45133866ee75754dc68484604051611b8e9291906151fb565b60405180910390a35050505050611bcf60017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b92915050565b6002611c007f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b03611c37576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c6b60027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b8181611cbd816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d6790919063ffffffff16565b611d005781816040517fd0635829000000000000000000000000000000000000000000000000000000008152600401611cf7929190615224565b60405180910390fd5b611d08611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611d52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d769190614eff565b15611dad576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8383611db98282610c55565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611e2a5781816040517fa17e3752000000000000000000000000000000000000000000000000000000008152600401611e21929190615224565b60405180910390fd5b6000611e7e87876000808b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d7b9092919063ffffffff16565b9050611e8a87826127ba565b5050505050611ec360017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b5050565b6000611ef27f0e555410d8128dff796eab5d29b97dd593ce9cab44a71b64c08244579ea60533612e5d565b905090565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101600083815260200190815260200160002060010154905092915050565b60016020528060005260406000206000915090505481565b600081549050919050565b8082555050565b81611f8a611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192826040518263ffffffff1660e01b8152600401611fc29190614eac565b602060405180830381865afa158015611fdf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120039190614eff565b61204457806040517f4d53256500000000000000000000000000000000000000000000000000000000815260040161203b9190614eac565b60405180910390fd5b61204c611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612096573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120ba9190614eff565b156120f1576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83838073ffffffffffffffffffffffffffffffffffffffff1663f667f897836040518263ffffffff1660e01b815260040161212c9190614671565b600060405180830381600087803b15801561214657600080fd5b505af115801561215a573d6000803e3d6000fd5b5050505060005b84518110156121a25761218f878787848151811061218257612181614e1a565b5b6020026020010151612e68565b808061219a90614fcd565b915050612161565b50505050505050565b60008460050160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008560000180549050905060008290505b818110156122725761225e818860040160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061300490919063ffffffff16565b50808061226a90614fcd565b915050612203565b50808660050160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060006123048760040160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061301e565b905060005b81518110156124c757600082828151811061232757612326614e1a565b5b602002602001015190506000896001016000838152602001908152602001600020905061242d898b600001848154811061236457612363614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815250508a8a8561303f90949392919063ffffffff16565b612443828b60020161311090919063ffffffff16565b15801561245857508060030154876000015110155b156124b2576124b0828b60040160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061312a90919063ffffffff16565b505b505080806124bf90614fcd565b915050612309565b5050505050505050565b60006124df82600201613144565b9050919050565b60006124f28483612d67565b6124ff5760009050612558565b83600101600083815260200190815260200160002060000160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b9392505050565b6060600061256f8560020161301e565b9050805167ffffffffffffffff81111561258c5761258b6146a2565b5b6040519080825280602002602001820160405280156125c557816020015b6125b26143b0565b8152602001906001900390816125aa5790505b50915060005b81518110156127b15760008282815181106125e9576125e8614e1a565b5b60200260200101519050600087600001828154811061260b5761260a614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815250509050600088600101600084815260200190815260200160002090506000808260020154148015612703575061270288602001518461315990919063ffffffff16565b5b15612716576127138a858a61316b565b90505b60008060008460030154148015612740575061273f8a60200151866131a690919063ffffffff16565b5b15612759576127508c8c886131c7565b80925081935050505b60405180608001604052808781526020018481526020018381526020018281525089888151811061278d5761278c614e1a565b5b602002602001018190525050505050505080806127a990614fcd565b9150506125cb565b50509392505050565b6000816000015190506127d082606001516132e4565b61285c5761285b6127e18483610767565b8360600151612836846000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206132f090919063ffffffff16565b73ffffffffffffffffffffffffffffffffffffffff166129219092919063ffffffff16565b5b60008260200151146128bc57808373ffffffffffffffffffffffffffffffffffffffff167f1239d8c1165187259e281474a9ae5bb87fabb197873589de0a2645ae5e2261da84602001516040516128b391906145db565b60405180910390a35b600082604001511461291c57808373ffffffffffffffffffffffffffffffffffffffff167fc590246f839a70d2279ef40b9e46b50bb20789c016f823abca87b69963f22584846040015160405161291391906145db565b60405180910390a35b505050565b61294c82828573ffffffffffffffffffffffffffffffffffffffff166133429092919063ffffffff16565b505050565b8082555050565b6129606133c8565b15801561299357506129917fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c6133d9565b155b156129ca576040517f689f12a400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6129d3816133e4565b612a09576040517f14878b6900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612a3c817f0e555410d8128dff796eab5d29b97dd593ce9cab44a71b64c08244579ea6053361340790919063ffffffff16565b50565b612a476133c8565b158015612a7a5750612a787fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c6133d9565b155b15612ab1576040517f689f12a400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612ae560017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b565b612af18142613159565b15612b28576040517f0a5af16500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6201518081606001511080612b4457506303c267008160600151115b15612b7b576040517f72cd3d6300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b612b86614365565b600080612bba612ba785670de0b6b3a764000061340e90919063ffffffff16565b866020015161342490919063ffffffff16565b90506000612bdd612bd883886020015161347590919063ffffffff16565b612d11565b905081866020018190525085819350935050509250929050565b60008183612c05919061524d565b905092915050565b600082600001805490509050826000018290806001815401808255809150506001900390600052602060002090600602016000909190919091506000820151816000015560208201518160010160008201518160000155602082015181600101555050604082015181600301556060820151816004015560808201518160050160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050612cf4828460010160008481526020019081526020016000206134ce90919063ffffffff16565b612d0a818460020161300490919063ffffffff16565b5092915050565b6000612d2e82602001518360000151612bf790919063ffffffff16565b9050919050565b612d628230838673ffffffffffffffffffffffffffffffffffffffff166134e7909392919063ffffffff16565b505050565b600082600001805490508210905092915050565b612d836143b0565b600084600101600084815260200190815260200160002090506000816002015403612e2b57612e2185848673ffffffffffffffffffffffffffffffffffffffff16639d888e866040518163ffffffff1660e01b8152600401606060405180830381865afa158015612df8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e1c919061531c565b61316b565b8260200181815250505b6000816003015403612e5557612e428585856131c7565b8360400184606001828152508281525050505b509392505050565b600081549050919050565b8181612eba816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d6790919063ffffffff16565b612efd5781816040517fd0635829000000000000000000000000000000000000000000000000000000008152600401612ef4929190615224565b60405180910390fd5b60008060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090506000612f568786846135709092919063ffffffff16565b9050612f958782612f7088866132f090919063ffffffff16565b73ffffffffffffffffffffffffffffffffffffffff166129219092919063ffffffff16565b848773ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167f865ca08d59f5cb456e85cd2f7ef63664ea4f73327414e9d8152c4158b0e9464584604051612ff39190614a0a565b60405180910390a450505050505050565b6000613016836000018360001b6135a2565b905092915050565b6060600061302e83600001613612565b905060608190508092505050919050565b600061304e868686868661366e565b90506130a4818760000160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054612bf790919063ffffffff16565b8660000160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061310081876001015461340e90919063ffffffff16565b8660010181905550505050505050565b6000613122836000018360001b61393e565b905092915050565b600061313c836000018360001b613961565b905092915050565b600061315282600001613a75565b9050919050565b60008260400151821015905092915050565b60008160000151905061319f8260000151856001016000868152602001908152602001600020613a8690919063ffffffff16565b9392505050565b6000826060015183604001516131bc919061524d565b821015905092915050565b6000806132bd848660000185815481106131e4576131e3614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681525050876001016000878152602001908152602001600020613a939092919063ffffffff16565b80925081935050506132db838660020161312a90919063ffffffff16565b50935093915050565b60008082149050919050565b600082600001828154811061330857613307614e1a565b5b906000526020600020906006020160050160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905092915050565b6133c38363a9059cbb60e01b8484604051602401613361929190615349565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050613c97565b505050565b60006133d3306133e4565b15905090565b600081549050919050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b8082555050565b6000818361341c9190615372565b905092915050565b61342c6143d8565b604051806040016040528061344e848660000151613d5e90919063ffffffff16565b815260200161346a848660200151613d5e90919063ffffffff16565b815250905092915050565b61347d6143d8565b60405180604001604052806134a38460000151866000015161340e90919063ffffffff16565b81526020016134c38460200151866020015161340e90919063ffffffff16565b815250905092915050565b6134db8160200151612d11565b82600101819055505050565b61356a846323b872dd60e01b858585604051602401613508939291906153a6565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050613c97565b50505050565b600061359983856001016000858152602001908152602001600020613d8790919063ffffffff16565b90509392505050565b60006135ae838361393e565b61360757826000018290806001815401808255809150506001900390600052602060002001600090919091909150558260000180549050836001016000848152602001908152602001600020819055506001905061360c565b600090505b92915050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561366257602002820191906000526020600020905b81548152602001906001019080831161364e575b50505050509050919050565b60008060008088600201541461368857876002015461368e565b83600001515b60008960030154146136a45788600301546136c6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b91509150600061375882613753858b73ffffffffffffffffffffffffffffffffffffffff16638e480b208b6040518263ffffffff1660e01b815260040161370d9190614671565b602060405180830381865afa15801561372a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061374e919061502a565b613e1a565b613e34565b9050600061376a838760000151613e34565b905060006138718a73ffffffffffffffffffffffffffffffffffffffff1663476fa96d856040518263ffffffff1660e01b81526004016137aa91906145db565b6040805180830381865afa1580156137c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137ea919061542d565b8b73ffffffffffffffffffffffffffffffffffffffff1663476fa96d856040518263ffffffff1660e01b815260040161382391906145db565b6040805180830381865afa15801561383f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613863919061542d565b613e4d90919063ffffffff16565b90506000613900828c73ffffffffffffffffffffffffffffffffffffffff1663b7648fb98c6040518263ffffffff1660e01b81526004016138b29190614671565b6040805180830381865afa1580156138ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138f291906154aa565b613ea690919063ffffffff16565b905061392d613928613923836139158e613f0f565b613f3990919063ffffffff16565b613f92565b613fb6565b965050505050505095945050505050565b600080836001016000848152602001908152602001600020541415905092915050565b60008083600101600084815260200190815260200160002054905060008114613a695760006001826139939190615372565b90506000600186600001805490506139ab9190615372565b9050818114613a1a5760008660000182815481106139cc576139cb614e1a565b5b90600052602060002001549050808760000184815481106139f0576139ef614e1a565b5b90600052602060002001819055508387600101600083815260200190815260200160002081905550505b85600001805480613a2e57613a2d6154d7565b5b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050613a6f565b60009150505b92915050565b600081600001805490509050919050565b8082600201819055505050565b600080600085600201549050613b17818673ffffffffffffffffffffffffffffffffffffffff1663c07f47d46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613aee573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b12919061502a565b613e1a565b925082866003018190555060008573ffffffffffffffffffffffffffffffffffffffff16637ece075d836040518263ffffffff1660e01b8152600401613b5d91906145db565b606060405180830381865afa158015613b7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b9e919061531c565b905060008673ffffffffffffffffffffffffffffffffffffffff16637ece075d866040518263ffffffff1660e01b8152600401613bdb91906145db565b606060405180830381865afa158015613bf8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c1c919061531c565b9050600082602001518260200151613c349190615372565b8760600151613c439190615372565b9050613c6a818860600151613c5b8a60200151612d11565b61400b9092919063ffffffff16565b9450613c83858a6001015461340e90919063ffffffff16565b896001018190555050505050935093915050565b6000613cf9826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166140219092919063ffffffff16565b9050600081511115613d595780806020019051810190613d199190614eff565b613d58576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401613d4f90615589565b60405180910390fd5b5b505050565b6000670de0b6b3a76400008284613d7591906155a9565b613d7f9190615632565b905092915050565b60008260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008360000160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555092915050565b600081831015613e2a5781613e2c565b825b905092915050565b6000818310613e435781613e45565b825b905092915050565b613e556143f2565b6040518060400160405280613e7b8460000151866000015161403990919063ffffffff16565b8152602001613e9b8460200151866020015161403990919063ffffffff16565b815250905092915050565b613eae6143f2565b6040518060400160405280613edc8460000151613ece876000015161404f565b6140c290919063ffffffff16565b8152602001613f048460200151613ef6876020015161404f565b6140c290919063ffffffff16565b815250905092915050565b613f176143f2565b613f32826060015183602001516140eb90919063ffffffff16565b9050919050565b613f416143f2565b6040518060400160405280613f67846000015186600001516140c290919063ffffffff16565b8152602001613f87846020015186602001516140c290919063ffffffff16565b815250905092915050565b6000613faf8260200151836000015161416c90919063ffffffff16565b9050919050565b600080829050600081121561400257806040517f501f289e000000000000000000000000000000000000000000000000000000008152600401613ff99190615672565b60405180910390fd5b80915050919050565b6000614018848484614182565b90509392505050565b606061403084846000856141a4565b90509392505050565b60008183614047919061568d565b905092915050565b6000808290507f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111156140b957806040517ea07eb50000000000000000000000000000000000000000000000000000000081526004016140b091906145db565b60405180910390fd5b80915050919050565b6000670de0b6b3a764000082846140d99190615721565b6140e39190615838565b905092915050565b6140f36143f2565b604051806040016040528061412d61411261410d866142b8565b61404f565b61411f876000015161404f565b6142d590919063ffffffff16565b8152602001614161614146614141866142b8565b61404f565b614153876020015161404f565b6142d590919063ffffffff16565b815250905092915050565b6000818361417a91906158a2565b905092915050565b600081838561419191906155a9565b61419b9190615632565b90509392505050565b6060824710156141e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016141e0906159a8565b60405180910390fd5b6141f2856133e4565b614231576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161422890615a14565b60405180910390fd5b6000808673ffffffffffffffffffffffffffffffffffffffff16858760405161425a9190615aae565b60006040518083038185875af1925050503d8060008114614297576040519150601f19603f3d011682016040523d82523d6000602084013e61429c565b606091505b50915091506142ac8282866142fe565b92505050949350505050565b6000670de0b6b3a7640000826142ce91906155a9565b9050919050565b600081670de0b6b3a7640000846142ec9190615721565b6142f69190615838565b905092915050565b6060831561430e5782905061435e565b6000835111156143215782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016143559190615b09565b60405180910390fd5b9392505050565b6040518060a001604052806000815260200161437f6143d8565b81526020016000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681525090565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b604051806040016040528060008152602001600081525090565b604051806040016040528060008152602001600081525090565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061444b82614420565b9050919050565b61445b81614440565b811461446657600080fd5b50565b60008135905061447881614452565b92915050565b600061448982614440565b9050919050565b6144998161447e565b81146144a457600080fd5b50565b6000813590506144b681614490565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126144e1576144e06144bc565b5b8235905067ffffffffffffffff8111156144fe576144fd6144c1565b5b60208301915083602082028301111561451a576145196144c6565b5b9250929050565b6000806000806060858703121561453b5761453a614416565b5b600061454987828801614469565b945050602061455a878288016144a7565b935050604085013567ffffffffffffffff81111561457b5761457a61441b565b5b614587878288016144cb565b925092505092959194509250565b6000602082840312156145ab576145aa614416565b5b60006145b9848285016144a7565b91505092915050565b6000819050919050565b6145d5816145c2565b82525050565b60006020820190506145f060008301846145cc565b92915050565b6145ff816145c2565b811461460a57600080fd5b50565b60008135905061461c816145f6565b92915050565b6000806040838503121561463957614638614416565b5b6000614647858286016144a7565b92505060206146588582860161460d565b9150509250929050565b61466b81614440565b82525050565b60006020820190506146866000830184614662565b92915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6146da82614691565b810181811067ffffffffffffffff821117156146f9576146f86146a2565b5b80604052505050565b600061470c61440c565b905061471882826146d1565b919050565b6000819050919050565b6147308161471d565b811461473b57600080fd5b50565b60008135905061474d81614727565b92915050565b6000606082840312156147695761476861468c565b5b6147736060614702565b905060006147838482850161460d565b60008301525060206147978482850161460d565b60208301525060406147ab8482850161473e565b60408301525092915050565b600080608083850312156147ce576147cd614416565b5b60006147dc85828601614469565b92505060206147ed85828601614753565b9150509250929050565b614800816145c2565b82525050565b6000819050919050565b600061482b614826614821846145c2565b614806565b6145c2565b9050919050565b61483b81614810565b82525050565b6040820160008201516148576000850182614832565b50602082015161486a6020850182614832565b50505050565b600061488b61488661488184614420565b614806565b614420565b9050919050565b600061489d82614870565b9050919050565b60006148af82614892565b9050919050565b6148bf816148a4565b82525050565b60c0820160008201516148db60008501826147f7565b5060208201516148ee6020850182614841565b50604082015161490160608501826147f7565b50606082015161491460808501826147f7565b50608082015161492760a08501826148b6565b50505050565b600060c08201905061494260008301846148c5565b92915050565b60008060006040848603121561496157614960614416565b5b600061496f868287016144a7565b935050602084013567ffffffffffffffff8111156149905761498f61441b565b5b61499c868287016144cb565b92509250509250925092565b6000806000606084860312156149c1576149c0614416565b5b60006149cf868287016144a7565b93505060206149e086828701614469565b92505060406149f18682870161460d565b9150509250925092565b614a0481614810565b82525050565b6000602082019050614a1f60008301846149fb565b92915050565b60008083601f840112614a3b57614a3a6144bc565b5b8235905067ffffffffffffffff811115614a5857614a576144c1565b5b602083019150836020820283011115614a7457614a736144c6565b5b9250929050565b60008083601f840112614a9157614a906144bc565b5b8235905067ffffffffffffffff811115614aae57614aad6144c1565b5b602083019150836020820283011115614aca57614ac96144c6565b5b9250929050565b60008060008060408587031215614aeb57614aea614416565b5b600085013567ffffffffffffffff811115614b0957614b0861441b565b5b614b1587828801614a25565b9450945050602085013567ffffffffffffffff811115614b3857614b3761441b565b5b614b4487828801614a7b565b925092505092959194509250565b600060608284031215614b6857614b67614416565b5b6000614b7684828501614753565b91505092915050565b60008083601f840112614b9557614b946144bc565b5b8235905067ffffffffffffffff811115614bb257614bb16144c1565b5b602083019150836020820283011115614bce57614bcd6144c6565b5b9250929050565b60008060208385031215614bec57614beb614416565b5b600083013567ffffffffffffffff811115614c0a57614c0961441b565b5b614c1685828601614b7f565b92509250509250929050565b6000614c2d82614440565b9050919050565b614c3d81614c22565b8114614c4857600080fd5b50565b600081359050614c5a81614c34565b92915050565b600060208284031215614c7657614c75614416565b5b6000614c8484828501614c4b565b91505092915050565b600080fd5b600060c08284031215614ca857614ca7614c8d565b5b81905092915050565b60008060e08385031215614cc857614cc7614416565b5b6000614cd6858286016144a7565b9250506020614ce785828601614c92565b9150509250929050565b6000614cfc82614892565b9050919050565b614d0c81614cf1565b82525050565b6000602082019050614d276000830184614d03565b92915050565b614d3681614440565b8114614d4157600080fd5b50565b600081359050614d5381614d2d565b92915050565b600060208284031215614d6f57614d6e614416565b5b6000614d7d84828501614d44565b91505092915050565b6000614d9182614440565b9050919050565b614da181614d86565b8114614dac57600080fd5b50565b600081519050614dbe81614d98565b92915050565b600060208284031215614dda57614dd9614416565b5b6000614de884828501614daf565b91505092915050565b6000604082019050614e066000830185614662565b614e136020830184614662565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081519050614e5881614452565b92915050565b600060208284031215614e7457614e73614416565b5b6000614e8284828501614e49565b91505092915050565b6000614e9682614892565b9050919050565b614ea681614e8b565b82525050565b6000602082019050614ec16000830184614e9d565b92915050565b60008115159050919050565b614edc81614ec7565b8114614ee757600080fd5b50565b600081519050614ef981614ed3565b92915050565b600060208284031215614f1557614f14614416565b5b6000614f2384828501614eea565b91505092915050565b600080fd5b600080fd5b600080fd5b60008083356001602003843603038112614f5857614f57614f2c565b5b80840192508235915067ffffffffffffffff821115614f7a57614f79614f31565b5b602083019250602082023603831315614f9657614f95614f36565b5b509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000614fd8826145c2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361500a57615009614f9e565b5b600182019050919050565b600081519050615024816145f6565b92915050565b6000602082840312156150405761503f614416565b5b600061504e84828501615015565b91505092915050565b615060816145c2565b811461506b57600080fd5b50565b60008135905061507d81615057565b92915050565b6000604082840312156150995761509861468c565b5b6150a36040614702565b905060006150b38482850161506e565b60008301525060206150c78482850161506e565b60208301525092915050565b600060c082840312156150e9576150e861468c565b5b6150f360a0614702565b905060006151038482850161460d565b600083015250602061511784828501615083565b602083015250606061512b8482850161460d565b604083015250608061513f8482850161460d565b60608301525060a061515384828501614d44565b60808301525092915050565b600060c0828403121561517557615174614416565b5b6000615183848285016150d3565b91505092915050565b60008151905061519b81615057565b92915050565b6000602082840312156151b7576151b6614416565b5b60006151c58482850161518c565b91505092915050565b6000604082840312156151e4576151e3614416565b5b60006151f284828501615083565b91505092915050565b600060e08201905061521060008301856148c5565b61521d60c08301846149fb565b9392505050565b60006040820190506152396000830185614e9d565b61524660208301846145cc565b9392505050565b6000615258826145c2565b9150615263836145c2565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561529857615297614f9e565b5b828201905092915050565b6000815190506152b281614727565b92915050565b6000606082840312156152ce576152cd61468c565b5b6152d86060614702565b905060006152e884828501615015565b60008301525060206152fc84828501615015565b6020830152506040615310848285016152a3565b60408301525092915050565b60006060828403121561533257615331614416565b5b6000615340848285016152b8565b91505092915050565b600060408201905061535e6000830185614662565b61536b60208301846145cc565b9392505050565b600061537d826145c2565b9150615388836145c2565b92508282101561539b5761539a614f9e565b5b828203905092915050565b60006060820190506153bb6000830186614662565b6153c86020830185614662565b6153d560408301846145cc565b949350505050565b6000604082840312156153f3576153f261468c565b5b6153fd6040614702565b9050600061540d848285016152a3565b6000830152506020615421848285016152a3565b60208301525092915050565b60006040828403121561544357615442614416565b5b6000615451848285016153dd565b91505092915050565b6000604082840312156154705761546f61468c565b5b61547a6040614702565b9050600061548a8482850161518c565b600083015250602061549e8482850161518c565b60208301525092915050565b6000604082840312156154c0576154bf614416565b5b60006154ce8482850161545a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600082825260208201905092915050565b7f5361666545524332303a204552433230206f7065726174696f6e20646964206e60008201527f6f74207375636365656400000000000000000000000000000000000000000000602082015250565b6000615573602a83615506565b915061557e82615517565b604082019050919050565b600060208201905081810360008301526155a281615566565b9050919050565b60006155b4826145c2565b91506155bf836145c2565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156155f8576155f7614f9e565b5b828202905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061563d826145c2565b9150615648836145c2565b92508261565857615657615603565b5b828204905092915050565b61566c8161471d565b82525050565b60006020820190506156876000830184615663565b92915050565b60006156988261471d565b91506156a38361471d565b9250827f8000000000000000000000000000000000000000000000000000000000000000018212600084121516156156de576156dd614f9e565b5b827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01821360008412161561571657615715614f9e565b5b828203905092915050565b600061572c8261471d565b91506157378361471d565b9250827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211600084136000841316161561577657615775614f9e565b5b817f800000000000000000000000000000000000000000000000000000000000000005831260008412600084131616156157b3576157b2614f9e565b5b827f800000000000000000000000000000000000000000000000000000000000000005821260008413600084121616156157f0576157ef614f9e565b5b827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff058212600084126000841216161561582d5761582c614f9e565b5b828202905092915050565b60006158438261471d565b915061584e8361471d565b92508261585e5761585d615603565b5b600160000383147f80000000000000000000000000000000000000000000000000000000000000008314161561589757615896614f9e565b5b828205905092915050565b60006158ad8261471d565b91506158b88361471d565b9250817f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038313600083121516156158f3576158f2614f9e565b5b817f800000000000000000000000000000000000000000000000000000000000000003831260008312161561592b5761592a614f9e565b5b828201905092915050565b7f416464726573733a20696e73756666696369656e742062616c616e636520666f60008201527f722063616c6c0000000000000000000000000000000000000000000000000000602082015250565b6000615992602683615506565b915061599d82615936565b604082019050919050565b600060208201905081810360008301526159c181615985565b9050919050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006159fe601d83615506565b9150615a09826159c8565b602082019050919050565b60006020820190508181036000830152615a2d816159f1565b9050919050565b600081519050919050565b600081905092915050565b60005b83811015615a68578082015181840152602081019050615a4d565b83811115615a77576000848401525b50505050565b6000615a8882615a34565b615a928185615a3f565b9350615aa2818560208601615a4a565b80840191505092915050565b6000615aba8284615a7d565b915081905092915050565b600081519050919050565b6000615adb82615ac5565b615ae58185615506565b9350615af5818560208601615a4a565b615afe81614691565b840191505092915050565b60006020820190508181036000830152615b238184615ad0565b90509291505056fea264697066735822122082e9e91072630c6aecaf2986f53f9f659c308576ffcec20b0a3728c7613a1bcd64736f6c634300080f0033

Deployed Bytecode

0x608060405234801561001057600080fd5b506004361061016b5760003560e01c806370f3f08b116100cd578063d758869111610081578063f77c479111610066578063f77c479114610400578063f8724aba1461041e578063faaebd211461044e5761016b565b8063d7588691146103b4578063e1e5a82e146103e45761016b565b8063a55ff01d116100b2578063a55ff01d14610360578063b774b6011461037c578063c4d66de8146103985761016b565b806370f3f08b1461031457806390672ad8146103445761016b565b80632c3e50e5116101245780634ea71327116101095780634ea713271461028457806366f8162e146102b45780636da0d540146102e45761016b565b80632c3e50e51461023857806345718278146102685761016b565b8063095c824111610155578063095c8241146101bc578063144dcdd1146101ec5780631830349c1461021c5761016b565b80628240531461017057806305d85eda1461018c575b600080fd5b61018a60048036038101906101859190614521565b61047e565b005b6101a660048036038101906101a19190614595565b6106b9565b6040516101b391906145db565b60405180910390f35b6101d660048036038101906101d19190614622565b610707565b6040516101e391906145db565b60405180910390f35b61020660048036038101906102019190614622565b610767565b6040516102139190614671565b60405180910390f35b610236600480360381019061023191906147b7565b610856565b005b610252600480360381019061024d9190614622565b610976565b60405161025f919061492d565b60405180910390f35b610282600480360381019061027d9190614948565b610a8e565b005b61029e60048036038101906102999190614595565b610ba9565b6040516102ab91906145db565b60405180910390f35b6102ce60048036038101906102c991906149a8565b610bf8565b6040516102db9190614a0a565b60405180910390f35b6102fe60048036038101906102f99190614622565b610c55565b60405161030b9190614671565b60405180910390f35b61032e60048036038101906103299190614622565b610d44565b60405161033b91906145db565b60405180910390f35b61035e60048036038101906103599190614ad1565b610da4565b005b61037a60048036038101906103759190614b52565b610f6b565b005b61039660048036038101906103919190614bd5565b6110d1565b005b6103b260048036038101906103ad9190614c60565b611349565b005b6103ce60048036038101906103c99190614cb1565b6114d8565b6040516103db91906145db565b60405180910390f35b6103fe60048036038101906103f99190614622565b611bd5565b005b610408611ec7565b6040516104159190614d12565b60405180910390f35b61043860048036038101906104339190614622565b611ef7565b6040516104459190614a0a565b60405180910390f35b61046860048036038101906104639190614d59565b611f57565b6040516104759190614a0a565b60405180910390f35b60026104a97f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b036104e0576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61051460027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b838073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806105ef5750610552611ec7565b73ffffffffffffffffffffffffffffffffffffffff1663479e58f06040518163ffffffff1660e01b8152600401602060405180830381865afa15801561059c573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105c09190614dc4565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b6106325780336040517f6dc3a3c3000000000000000000000000000000000000000000000000000000008152600401610629929190614df1565b60405180910390fd5b61067e8585858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611f81565b506106b360017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b50505050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001805490509050919050565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101600083815260200190815260200160002060020154905092915050565b6000610771611ec7565b73ffffffffffffffffffffffffffffffffffffffff16634c5430a06000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000184815481106107df576107de614e1a565b5b9060005260206000209060060201600001546040518263ffffffff1660e01b815260040161080d91906145db565b602060405180830381865afa15801561082a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084e9190614e5e565b905092915050565b61085e611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192336040518263ffffffff1660e01b81526004016108969190614eac565b602060405180830381865afa1580156108b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108d79190614eff565b61091857336040517f4d53256500000000000000000000000000000000000000000000000000000000815260040161090f9190614eac565b60405180910390fd5b60003390506109718184846000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206121ab909392919063ffffffff16565b505050565b61097e614365565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060000182815481106109d1576109d0614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681525050905092915050565b6002610ab97f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b03610af0576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610b2460027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b610b703384848480806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611f81565b610ba460017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b505050565b6000610bf16000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206124d1565b9050919050565b6000610c4c83836000808873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206124e69092919063ffffffff16565b90509392505050565b6000610c5f611ec7565b73ffffffffffffffffffffffffffffffffffffffff1663a123c33e6000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000018481548110610ccd57610ccc614e1a565b5b9060005260206000209060060201600001546040518263ffffffff1660e01b8152600401610cfb91906145db565b602060405180830381865afa158015610d18573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d3c9190614e5e565b905092915050565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101600083815260200190815260200160002060030154905092915050565b6002610dcf7f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b03610e06576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e3a60027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b818190508484905014610e79576040517f90fb223000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b84849050811015610f3057610f1d33868684818110610e9e57610e9d614e1a565b5b9050602002016020810190610eb39190614595565b858585818110610ec657610ec5614e1a565b5b9050602002810190610ed89190614f3b565b80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611f81565b8080610f2890614fcd565b915050610e7c565b50610f6560017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b50505050565b610f73611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192336040518263ffffffff1660e01b8152600401610fab9190614eac565b602060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614eff565b61102d57336040517f4d5325650000000000000000000000000000000000000000000000000000000081526004016110249190614eac565b60405180910390fd5b6000339050600061108682846000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061255f9092919063ffffffff16565b905060005b81518110156110cb576110b8838383815181106110ab576110aa614e1a565b5b60200260200101516127ba565b80806110c390614fcd565b91505061108b565b50505050565b6110d9611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611123573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111479190614eff565b1561117e576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b828290508110156113445760008383838181106111a1576111a0614e1a565b5b90506020020160208101906111b69190614d59565b90506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490506000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506112e161124c611ec7565b73ffffffffffffffffffffffffffffffffffffffff166361d027b36040518163ffffffff1660e01b8152600401602060405180830381865afa158015611296573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112ba9190614e5e565b828473ffffffffffffffffffffffffffffffffffffffff166129219092919063ffffffff16565b8173ffffffffffffffffffffffffffffffffffffffff167f10df095d1434aed409b2f33d2a6a8456f8b0824633cc12a1b43032085aadc41d826040516113279190614a0a565b60405180910390a25050808061133c90614fcd565b915050611181565b505050565b600160008103611385576040517fb66146bd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806113af7f5db5abc19987c2b3729df7961b62b6bb0bae886dd47e3ce25bb3a3af34c6d80b611f6f565b106113f157806040517f1e7a9d950000000000000000000000000000000000000000000000000000000081526004016113e891906145db565b60405180910390fd5b611424817f5db5abc19987c2b3729df7961b62b6bb0bae886dd47e3ce25bb3a3af34c6d80b611f7a90919063ffffffff16565b61145860017fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c61295190919063ffffffff16565b61146182612958565b611469612a3f565b61149d60007fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c61295190919063ffffffff16565b7fbe9b076dc5b65990cca9dd9d7366682482e7817a6f6bc7f4faf4dc32af497f32816040516114cc91906145db565b60405180910390a15050565b600060026115057f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b0361153c576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61157060027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b82611579611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192826040518263ffffffff1660e01b81526004016115b19190614eac565b602060405180830381865afa1580156115ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115f29190614eff565b61163357806040517f4d53256500000000000000000000000000000000000000000000000000000000815260040161162a9190614eac565b60405180910390fd5b61163b611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611685573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116a99190614eff565b156116e0576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82600001356116ed611ec7565b73ffffffffffffffffffffffffffffffffffffffff1663a123c33e826040518263ffffffff1660e01b815260040161172591906145db565b602060405180830381865afa158015611742573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117669190614e5e565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146117d557806040517f48f62f3c0000000000000000000000000000000000000000000000000000000081526004016117cc91906145db565b60405180910390fd5b60006117df611ec7565b9050600085600001351415801561187357508073ffffffffffffffffffffffffffffffffffffffff16637d254e66876040518263ffffffff1660e01b815260040161182a9190614eac565b602060405180830381865afa158015611847573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061186b919061502a565b856000013514155b156118b557856040517fe264a5240000000000000000000000000000000000000000000000000000000081526004016118ac9190614eac565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16637d49c1b76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611900573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611924919061502a565b61192d87610ba9565b10611964576040517f1651519200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61197d85803603810190611978919061515f565b612ae7565b600080611a0887803603810190611994919061515f565b8473ffffffffffffffffffffffffffffffffffffffff16633ceda0116040518163ffffffff1660e01b8152600401602060405180830381865afa1580156119df573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a0391906151a1565b612b7e565b91509150611a628160016000856080015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054612bf790919063ffffffff16565b60016000846080015173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550611af9826000808b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612c0d90919063ffffffff16565b9550611b4533611b1b89602001803603810190611b1691906151ce565b612d11565b846080015173ffffffffffffffffffffffffffffffffffffffff16612d359092919063ffffffff16565b858873ffffffffffffffffffffffffffffffffffffffff167fee8233a38e17998eb73ba822048e13762f2b68729ef94c45133866ee75754dc68484604051611b8e9291906151fb565b60405180910390a35050505050611bcf60017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b92915050565b6002611c007f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f6f565b03611c37576040517ff320323600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c6b60027f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b8181611cbd816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d6790919063ffffffff16565b611d005781816040517fd0635829000000000000000000000000000000000000000000000000000000008152600401611cf7929190615224565b60405180910390fd5b611d08611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015611d52573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d769190614eff565b15611dad576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8383611db98282610c55565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611e2a5781816040517fa17e3752000000000000000000000000000000000000000000000000000000008152600401611e21929190615224565b60405180910390fd5b6000611e7e87876000808b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d7b9092919063ffffffff16565b9050611e8a87826127ba565b5050505050611ec360017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b5050565b6000611ef27f0e555410d8128dff796eab5d29b97dd593ce9cab44a71b64c08244579ea60533612e5d565b905090565b60008060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101600083815260200190815260200160002060010154905092915050565b60016020528060005260406000206000915090505481565b600081549050919050565b8082555050565b81611f8a611ec7565b73ffffffffffffffffffffffffffffffffffffffff16637fd29192826040518263ffffffff1660e01b8152600401611fc29190614eac565b602060405180830381865afa158015611fdf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120039190614eff565b61204457806040517f4d53256500000000000000000000000000000000000000000000000000000000815260040161203b9190614eac565b60405180910390fd5b61204c611ec7565b73ffffffffffffffffffffffffffffffffffffffff16635c975abb6040518163ffffffff1660e01b8152600401602060405180830381865afa158015612096573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120ba9190614eff565b156120f1576040517feced32bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83838073ffffffffffffffffffffffffffffffffffffffff1663f667f897836040518263ffffffff1660e01b815260040161212c9190614671565b600060405180830381600087803b15801561214657600080fd5b505af115801561215a573d6000803e3d6000fd5b5050505060005b84518110156121a25761218f878787848151811061218257612181614e1a565b5b6020026020010151612e68565b808061219a90614fcd565b915050612161565b50505050505050565b60008460050160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008560000180549050905060008290505b818110156122725761225e818860040160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061300490919063ffffffff16565b50808061226a90614fcd565b915050612203565b50808660050160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555060006123048760040160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061301e565b905060005b81518110156124c757600082828151811061232757612326614e1a565b5b602002602001015190506000896001016000838152602001908152602001600020905061242d898b600001848154811061236457612363614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815250508a8a8561303f90949392919063ffffffff16565b612443828b60020161311090919063ffffffff16565b15801561245857508060030154876000015110155b156124b2576124b0828b60040160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002061312a90919063ffffffff16565b505b505080806124bf90614fcd565b915050612309565b5050505050505050565b60006124df82600201613144565b9050919050565b60006124f28483612d67565b6124ff5760009050612558565b83600101600083815260200190815260200160002060000160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b9392505050565b6060600061256f8560020161301e565b9050805167ffffffffffffffff81111561258c5761258b6146a2565b5b6040519080825280602002602001820160405280156125c557816020015b6125b26143b0565b8152602001906001900390816125aa5790505b50915060005b81518110156127b15760008282815181106125e9576125e8614e1a565b5b60200260200101519050600087600001828154811061260b5761260a614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815250509050600088600101600084815260200190815260200160002090506000808260020154148015612703575061270288602001518461315990919063ffffffff16565b5b15612716576127138a858a61316b565b90505b60008060008460030154148015612740575061273f8a60200151866131a690919063ffffffff16565b5b15612759576127508c8c886131c7565b80925081935050505b60405180608001604052808781526020018481526020018381526020018281525089888151811061278d5761278c614e1a565b5b602002602001018190525050505050505080806127a990614fcd565b9150506125cb565b50509392505050565b6000816000015190506127d082606001516132e4565b61285c5761285b6127e18483610767565b8360600151612836846000808973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206132f090919063ffffffff16565b73ffffffffffffffffffffffffffffffffffffffff166129219092919063ffffffff16565b5b60008260200151146128bc57808373ffffffffffffffffffffffffffffffffffffffff167f1239d8c1165187259e281474a9ae5bb87fabb197873589de0a2645ae5e2261da84602001516040516128b391906145db565b60405180910390a35b600082604001511461291c57808373ffffffffffffffffffffffffffffffffffffffff167fc590246f839a70d2279ef40b9e46b50bb20789c016f823abca87b69963f22584846040015160405161291391906145db565b60405180910390a35b505050565b61294c82828573ffffffffffffffffffffffffffffffffffffffff166133429092919063ffffffff16565b505050565b8082555050565b6129606133c8565b15801561299357506129917fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c6133d9565b155b156129ca576040517f689f12a400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6129d3816133e4565b612a09576040517f14878b6900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612a3c817f0e555410d8128dff796eab5d29b97dd593ce9cab44a71b64c08244579ea6053361340790919063ffffffff16565b50565b612a476133c8565b158015612a7a5750612a787fad57d7911b7e3d6c3c79a68ba909a7f4ba41f9485e5207b12dee0d0c6af5398c6133d9565b155b15612ab1576040517f689f12a400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612ae560017f1b30fe15febd596dffd21d4da0657732eea42b4b5a9789e3e6daab3d570999d7611f7a90919063ffffffff16565b565b612af18142613159565b15612b28576040517f0a5af16500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6201518081606001511080612b4457506303c267008160600151115b15612b7b576040517f72cd3d6300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b612b86614365565b600080612bba612ba785670de0b6b3a764000061340e90919063ffffffff16565b866020015161342490919063ffffffff16565b90506000612bdd612bd883886020015161347590919063ffffffff16565b612d11565b905081866020018190525085819350935050509250929050565b60008183612c05919061524d565b905092915050565b600082600001805490509050826000018290806001815401808255809150506001900390600052602060002090600602016000909190919091506000820151816000015560208201518160010160008201518160000155602082015181600101555050604082015181600301556060820151816004015560808201518160050160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050612cf4828460010160008481526020019081526020016000206134ce90919063ffffffff16565b612d0a818460020161300490919063ffffffff16565b5092915050565b6000612d2e82602001518360000151612bf790919063ffffffff16565b9050919050565b612d628230838673ffffffffffffffffffffffffffffffffffffffff166134e7909392919063ffffffff16565b505050565b600082600001805490508210905092915050565b612d836143b0565b600084600101600084815260200190815260200160002090506000816002015403612e2b57612e2185848673ffffffffffffffffffffffffffffffffffffffff16639d888e866040518163ffffffff1660e01b8152600401606060405180830381865afa158015612df8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e1c919061531c565b61316b565b8260200181815250505b6000816003015403612e5557612e428585856131c7565b8360400184606001828152508281525050505b509392505050565b600081549050919050565b8181612eba816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020612d6790919063ffffffff16565b612efd5781816040517fd0635829000000000000000000000000000000000000000000000000000000008152600401612ef4929190615224565b60405180910390fd5b60008060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090506000612f568786846135709092919063ffffffff16565b9050612f958782612f7088866132f090919063ffffffff16565b73ffffffffffffffffffffffffffffffffffffffff166129219092919063ffffffff16565b848773ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167f865ca08d59f5cb456e85cd2f7ef63664ea4f73327414e9d8152c4158b0e9464584604051612ff39190614a0a565b60405180910390a450505050505050565b6000613016836000018360001b6135a2565b905092915050565b6060600061302e83600001613612565b905060608190508092505050919050565b600061304e868686868661366e565b90506130a4818760000160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054612bf790919063ffffffff16565b8660000160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061310081876001015461340e90919063ffffffff16565b8660010181905550505050505050565b6000613122836000018360001b61393e565b905092915050565b600061313c836000018360001b613961565b905092915050565b600061315282600001613a75565b9050919050565b60008260400151821015905092915050565b60008160000151905061319f8260000151856001016000868152602001908152602001600020613a8690919063ffffffff16565b9392505050565b6000826060015183604001516131bc919061524d565b821015905092915050565b6000806132bd848660000185815481106131e4576131e3614e1a565b5b90600052602060002090600602016040518060a0016040529081600082015481526020016001820160405180604001604052908160008201548152602001600182015481525050815260200160038201548152602001600482015481526020016005820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681525050876001016000878152602001908152602001600020613a939092919063ffffffff16565b80925081935050506132db838660020161312a90919063ffffffff16565b50935093915050565b60008082149050919050565b600082600001828154811061330857613307614e1a565b5b906000526020600020906006020160050160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905092915050565b6133c38363a9059cbb60e01b8484604051602401613361929190615349565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050613c97565b505050565b60006133d3306133e4565b15905090565b600081549050919050565b6000808273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b8082555050565b6000818361341c9190615372565b905092915050565b61342c6143d8565b604051806040016040528061344e848660000151613d5e90919063ffffffff16565b815260200161346a848660200151613d5e90919063ffffffff16565b815250905092915050565b61347d6143d8565b60405180604001604052806134a38460000151866000015161340e90919063ffffffff16565b81526020016134c38460200151866020015161340e90919063ffffffff16565b815250905092915050565b6134db8160200151612d11565b82600101819055505050565b61356a846323b872dd60e01b858585604051602401613508939291906153a6565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050613c97565b50505050565b600061359983856001016000858152602001908152602001600020613d8790919063ffffffff16565b90509392505050565b60006135ae838361393e565b61360757826000018290806001815401808255809150506001900390600052602060002001600090919091909150558260000180549050836001016000848152602001908152602001600020819055506001905061360c565b600090505b92915050565b60608160000180548060200260200160405190810160405280929190818152602001828054801561366257602002820191906000526020600020905b81548152602001906001019080831161364e575b50505050509050919050565b60008060008088600201541461368857876002015461368e565b83600001515b60008960030154146136a45788600301546136c6565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b91509150600061375882613753858b73ffffffffffffffffffffffffffffffffffffffff16638e480b208b6040518263ffffffff1660e01b815260040161370d9190614671565b602060405180830381865afa15801561372a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061374e919061502a565b613e1a565b613e34565b9050600061376a838760000151613e34565b905060006138718a73ffffffffffffffffffffffffffffffffffffffff1663476fa96d856040518263ffffffff1660e01b81526004016137aa91906145db565b6040805180830381865afa1580156137c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906137ea919061542d565b8b73ffffffffffffffffffffffffffffffffffffffff1663476fa96d856040518263ffffffff1660e01b815260040161382391906145db565b6040805180830381865afa15801561383f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613863919061542d565b613e4d90919063ffffffff16565b90506000613900828c73ffffffffffffffffffffffffffffffffffffffff1663b7648fb98c6040518263ffffffff1660e01b81526004016138b29190614671565b6040805180830381865afa1580156138ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138f291906154aa565b613ea690919063ffffffff16565b905061392d613928613923836139158e613f0f565b613f3990919063ffffffff16565b613f92565b613fb6565b965050505050505095945050505050565b600080836001016000848152602001908152602001600020541415905092915050565b60008083600101600084815260200190815260200160002054905060008114613a695760006001826139939190615372565b90506000600186600001805490506139ab9190615372565b9050818114613a1a5760008660000182815481106139cc576139cb614e1a565b5b90600052602060002001549050808760000184815481106139f0576139ef614e1a565b5b90600052602060002001819055508387600101600083815260200190815260200160002081905550505b85600001805480613a2e57613a2d6154d7565b5b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050613a6f565b60009150505b92915050565b600081600001805490509050919050565b8082600201819055505050565b600080600085600201549050613b17818673ffffffffffffffffffffffffffffffffffffffff1663c07f47d46040518163ffffffff1660e01b8152600401602060405180830381865afa158015613aee573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b12919061502a565b613e1a565b925082866003018190555060008573ffffffffffffffffffffffffffffffffffffffff16637ece075d836040518263ffffffff1660e01b8152600401613b5d91906145db565b606060405180830381865afa158015613b7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613b9e919061531c565b905060008673ffffffffffffffffffffffffffffffffffffffff16637ece075d866040518263ffffffff1660e01b8152600401613bdb91906145db565b606060405180830381865afa158015613bf8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c1c919061531c565b9050600082602001518260200151613c349190615372565b8760600151613c439190615372565b9050613c6a818860600151613c5b8a60200151612d11565b61400b9092919063ffffffff16565b9450613c83858a6001015461340e90919063ffffffff16565b896001018190555050505050935093915050565b6000613cf9826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166140219092919063ffffffff16565b9050600081511115613d595780806020019051810190613d199190614eff565b613d58576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401613d4f90615589565b60405180910390fd5b5b505050565b6000670de0b6b3a76400008284613d7591906155a9565b613d7f9190615632565b905092915050565b60008260000160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905060008360000160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555092915050565b600081831015613e2a5781613e2c565b825b905092915050565b6000818310613e435781613e45565b825b905092915050565b613e556143f2565b6040518060400160405280613e7b8460000151866000015161403990919063ffffffff16565b8152602001613e9b8460200151866020015161403990919063ffffffff16565b815250905092915050565b613eae6143f2565b6040518060400160405280613edc8460000151613ece876000015161404f565b6140c290919063ffffffff16565b8152602001613f048460200151613ef6876020015161404f565b6140c290919063ffffffff16565b815250905092915050565b613f176143f2565b613f32826060015183602001516140eb90919063ffffffff16565b9050919050565b613f416143f2565b6040518060400160405280613f67846000015186600001516140c290919063ffffffff16565b8152602001613f87846020015186602001516140c290919063ffffffff16565b815250905092915050565b6000613faf8260200151836000015161416c90919063ffffffff16565b9050919050565b600080829050600081121561400257806040517f501f289e000000000000000000000000000000000000000000000000000000008152600401613ff99190615672565b60405180910390fd5b80915050919050565b6000614018848484614182565b90509392505050565b606061403084846000856141a4565b90509392505050565b60008183614047919061568d565b905092915050565b6000808290507f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8111156140b957806040517ea07eb50000000000000000000000000000000000000000000000000000000081526004016140b091906145db565b60405180910390fd5b80915050919050565b6000670de0b6b3a764000082846140d99190615721565b6140e39190615838565b905092915050565b6140f36143f2565b604051806040016040528061412d61411261410d866142b8565b61404f565b61411f876000015161404f565b6142d590919063ffffffff16565b8152602001614161614146614141866142b8565b61404f565b614153876020015161404f565b6142d590919063ffffffff16565b815250905092915050565b6000818361417a91906158a2565b905092915050565b600081838561419191906155a9565b61419b9190615632565b90509392505050565b6060824710156141e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016141e0906159a8565b60405180910390fd5b6141f2856133e4565b614231576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161422890615a14565b60405180910390fd5b6000808673ffffffffffffffffffffffffffffffffffffffff16858760405161425a9190615aae565b60006040518083038185875af1925050503d8060008114614297576040519150601f19603f3d011682016040523d82523d6000602084013e61429c565b606091505b50915091506142ac8282866142fe565b92505050949350505050565b6000670de0b6b3a7640000826142ce91906155a9565b9050919050565b600081670de0b6b3a7640000846142ec9190615721565b6142f69190615838565b905092915050565b6060831561430e5782905061435e565b6000835111156143215782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016143559190615b09565b60405180910390fd5b9392505050565b6040518060a001604052806000815260200161437f6143d8565b81526020016000815260200160008152602001600073ffffffffffffffffffffffffffffffffffffffff1681525090565b6040518060800160405280600081526020016000815260200160008152602001600081525090565b604051806040016040528060008152602001600081525090565b604051806040016040528060008152602001600081525090565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061444b82614420565b9050919050565b61445b81614440565b811461446657600080fd5b50565b60008135905061447881614452565b92915050565b600061448982614440565b9050919050565b6144998161447e565b81146144a457600080fd5b50565b6000813590506144b681614490565b92915050565b600080fd5b600080fd5b600080fd5b60008083601f8401126144e1576144e06144bc565b5b8235905067ffffffffffffffff8111156144fe576144fd6144c1565b5b60208301915083602082028301111561451a576145196144c6565b5b9250929050565b6000806000806060858703121561453b5761453a614416565b5b600061454987828801614469565b945050602061455a878288016144a7565b935050604085013567ffffffffffffffff81111561457b5761457a61441b565b5b614587878288016144cb565b925092505092959194509250565b6000602082840312156145ab576145aa614416565b5b60006145b9848285016144a7565b91505092915050565b6000819050919050565b6145d5816145c2565b82525050565b60006020820190506145f060008301846145cc565b92915050565b6145ff816145c2565b811461460a57600080fd5b50565b60008135905061461c816145f6565b92915050565b6000806040838503121561463957614638614416565b5b6000614647858286016144a7565b92505060206146588582860161460d565b9150509250929050565b61466b81614440565b82525050565b60006020820190506146866000830184614662565b92915050565b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6146da82614691565b810181811067ffffffffffffffff821117156146f9576146f86146a2565b5b80604052505050565b600061470c61440c565b905061471882826146d1565b919050565b6000819050919050565b6147308161471d565b811461473b57600080fd5b50565b60008135905061474d81614727565b92915050565b6000606082840312156147695761476861468c565b5b6147736060614702565b905060006147838482850161460d565b60008301525060206147978482850161460d565b60208301525060406147ab8482850161473e565b60408301525092915050565b600080608083850312156147ce576147cd614416565b5b60006147dc85828601614469565b92505060206147ed85828601614753565b9150509250929050565b614800816145c2565b82525050565b6000819050919050565b600061482b614826614821846145c2565b614806565b6145c2565b9050919050565b61483b81614810565b82525050565b6040820160008201516148576000850182614832565b50602082015161486a6020850182614832565b50505050565b600061488b61488661488184614420565b614806565b614420565b9050919050565b600061489d82614870565b9050919050565b60006148af82614892565b9050919050565b6148bf816148a4565b82525050565b60c0820160008201516148db60008501826147f7565b5060208201516148ee6020850182614841565b50604082015161490160608501826147f7565b50606082015161491460808501826147f7565b50608082015161492760a08501826148b6565b50505050565b600060c08201905061494260008301846148c5565b92915050565b60008060006040848603121561496157614960614416565b5b600061496f868287016144a7565b935050602084013567ffffffffffffffff8111156149905761498f61441b565b5b61499c868287016144cb565b92509250509250925092565b6000806000606084860312156149c1576149c0614416565b5b60006149cf868287016144a7565b93505060206149e086828701614469565b92505060406149f18682870161460d565b9150509250925092565b614a0481614810565b82525050565b6000602082019050614a1f60008301846149fb565b92915050565b60008083601f840112614a3b57614a3a6144bc565b5b8235905067ffffffffffffffff811115614a5857614a576144c1565b5b602083019150836020820283011115614a7457614a736144c6565b5b9250929050565b60008083601f840112614a9157614a906144bc565b5b8235905067ffffffffffffffff811115614aae57614aad6144c1565b5b602083019150836020820283011115614aca57614ac96144c6565b5b9250929050565b60008060008060408587031215614aeb57614aea614416565b5b600085013567ffffffffffffffff811115614b0957614b0861441b565b5b614b1587828801614a25565b9450945050602085013567ffffffffffffffff811115614b3857614b3761441b565b5b614b4487828801614a7b565b925092505092959194509250565b600060608284031215614b6857614b67614416565b5b6000614b7684828501614753565b91505092915050565b60008083601f840112614b9557614b946144bc565b5b8235905067ffffffffffffffff811115614bb257614bb16144c1565b5b602083019150836020820283011115614bce57614bcd6144c6565b5b9250929050565b60008060208385031215614bec57614beb614416565b5b600083013567ffffffffffffffff811115614c0a57614c0961441b565b5b614c1685828601614b7f565b92509250509250929050565b6000614c2d82614440565b9050919050565b614c3d81614c22565b8114614c4857600080fd5b50565b600081359050614c5a81614c34565b92915050565b600060208284031215614c7657614c75614416565b5b6000614c8484828501614c4b565b91505092915050565b600080fd5b600060c08284031215614ca857614ca7614c8d565b5b81905092915050565b60008060e08385031215614cc857614cc7614416565b5b6000614cd6858286016144a7565b9250506020614ce785828601614c92565b9150509250929050565b6000614cfc82614892565b9050919050565b614d0c81614cf1565b82525050565b6000602082019050614d276000830184614d03565b92915050565b614d3681614440565b8114614d4157600080fd5b50565b600081359050614d5381614d2d565b92915050565b600060208284031215614d6f57614d6e614416565b5b6000614d7d84828501614d44565b91505092915050565b6000614d9182614440565b9050919050565b614da181614d86565b8114614dac57600080fd5b50565b600081519050614dbe81614d98565b92915050565b600060208284031215614dda57614dd9614416565b5b6000614de884828501614daf565b91505092915050565b6000604082019050614e066000830185614662565b614e136020830184614662565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081519050614e5881614452565b92915050565b600060208284031215614e7457614e73614416565b5b6000614e8284828501614e49565b91505092915050565b6000614e9682614892565b9050919050565b614ea681614e8b565b82525050565b6000602082019050614ec16000830184614e9d565b92915050565b60008115159050919050565b614edc81614ec7565b8114614ee757600080fd5b50565b600081519050614ef981614ed3565b92915050565b600060208284031215614f1557614f14614416565b5b6000614f2384828501614eea565b91505092915050565b600080fd5b600080fd5b600080fd5b60008083356001602003843603038112614f5857614f57614f2c565b5b80840192508235915067ffffffffffffffff821115614f7a57614f79614f31565b5b602083019250602082023603831315614f9657614f95614f36565b5b509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000614fd8826145c2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361500a57615009614f9e565b5b600182019050919050565b600081519050615024816145f6565b92915050565b6000602082840312156150405761503f614416565b5b600061504e84828501615015565b91505092915050565b615060816145c2565b811461506b57600080fd5b50565b60008135905061507d81615057565b92915050565b6000604082840312156150995761509861468c565b5b6150a36040614702565b905060006150b38482850161506e565b60008301525060206150c78482850161506e565b60208301525092915050565b600060c082840312156150e9576150e861468c565b5b6150f360a0614702565b905060006151038482850161460d565b600083015250602061511784828501615083565b602083015250606061512b8482850161460d565b604083015250608061513f8482850161460d565b60608301525060a061515384828501614d44565b60808301525092915050565b600060c0828403121561517557615174614416565b5b6000615183848285016150d3565b91505092915050565b60008151905061519b81615057565b92915050565b6000602082840312156151b7576151b6614416565b5b60006151c58482850161518c565b91505092915050565b6000604082840312156151e4576151e3614416565b5b60006151f284828501615083565b91505092915050565b600060e08201905061521060008301856148c5565b61521d60c08301846149fb565b9392505050565b60006040820190506152396000830185614e9d565b61524660208301846145cc565b9392505050565b6000615258826145c2565b9150615263836145c2565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561529857615297614f9e565b5b828201905092915050565b6000815190506152b281614727565b92915050565b6000606082840312156152ce576152cd61468c565b5b6152d86060614702565b905060006152e884828501615015565b60008301525060206152fc84828501615015565b6020830152506040615310848285016152a3565b60408301525092915050565b60006060828403121561533257615331614416565b5b6000615340848285016152b8565b91505092915050565b600060408201905061535e6000830185614662565b61536b60208301846145cc565b9392505050565b600061537d826145c2565b9150615388836145c2565b92508282101561539b5761539a614f9e565b5b828203905092915050565b60006060820190506153bb6000830186614662565b6153c86020830185614662565b6153d560408301846145cc565b949350505050565b6000604082840312156153f3576153f261468c565b5b6153fd6040614702565b9050600061540d848285016152a3565b6000830152506020615421848285016152a3565b60208301525092915050565b60006040828403121561544357615442614416565b5b6000615451848285016153dd565b91505092915050565b6000604082840312156154705761546f61468c565b5b61547a6040614702565b9050600061548a8482850161518c565b600083015250602061549e8482850161518c565b60208301525092915050565b6000604082840312156154c0576154bf614416565b5b60006154ce8482850161545a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fd5b600082825260208201905092915050565b7f5361666545524332303a204552433230206f7065726174696f6e20646964206e60008201527f6f74207375636365656400000000000000000000000000000000000000000000602082015250565b6000615573602a83615506565b915061557e82615517565b604082019050919050565b600060208201905081810360008301526155a281615566565b9050919050565b60006155b4826145c2565b91506155bf836145c2565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156155f8576155f7614f9e565b5b828202905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600061563d826145c2565b9150615648836145c2565b92508261565857615657615603565b5b828204905092915050565b61566c8161471d565b82525050565b60006020820190506156876000830184615663565b92915050565b60006156988261471d565b91506156a38361471d565b9250827f8000000000000000000000000000000000000000000000000000000000000000018212600084121516156156de576156dd614f9e565b5b827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01821360008412161561571657615715614f9e565b5b828203905092915050565b600061572c8261471d565b91506157378361471d565b9250827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211600084136000841316161561577657615775614f9e565b5b817f800000000000000000000000000000000000000000000000000000000000000005831260008412600084131616156157b3576157b2614f9e565b5b827f800000000000000000000000000000000000000000000000000000000000000005821260008413600084121616156157f0576157ef614f9e565b5b827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff058212600084126000841216161561582d5761582c614f9e565b5b828202905092915050565b60006158438261471d565b915061584e8361471d565b92508261585e5761585d615603565b5b600160000383147f80000000000000000000000000000000000000000000000000000000000000008314161561589757615896614f9e565b5b828205905092915050565b60006158ad8261471d565b91506158b88361471d565b9250817f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038313600083121516156158f3576158f2614f9e565b5b817f800000000000000000000000000000000000000000000000000000000000000003831260008312161561592b5761592a614f9e565b5b828201905092915050565b7f416464726573733a20696e73756666696369656e742062616c616e636520666f60008201527f722063616c6c0000000000000000000000000000000000000000000000000000602082015250565b6000615992602683615506565b915061599d82615936565b604082019050919050565b600060208201905081810360008301526159c181615985565b9050919050565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b60006159fe601d83615506565b9150615a09826159c8565b602082019050919050565b60006020820190508181036000830152615a2d816159f1565b9050919050565b600081519050919050565b600081905092915050565b60005b83811015615a68578082015181840152602081019050615a4d565b83811115615a77576000848401525b50505050565b6000615a8882615a34565b615a928185615a3f565b9350615aa2818560208601615a4a565b80840191505092915050565b6000615aba8284615a7d565b915081905092915050565b600081519050919050565b6000615adb82615ac5565b615ae58185615506565b9350615af5818560208601615a4a565b615afe81614691565b840191505092915050565b60006020820190508181036000830152615b238184615ad0565b90509291505056fea264697066735822122082e9e91072630c6aecaf2986f53f9f659c308576ffcec20b0a3728c7613a1bcd64736f6c634300080f0033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
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.