POL Price: $0.647764 (-5.83%)
Gas: 41.3 GWei
 

Overview

POL Balance

Polygon PoS Chain LogoPolygon PoS Chain LogoPolygon PoS Chain Logo0 POL

POL Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

Parent Transaction Hash Block From To
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ERC20BondStakingModuleInfo

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 10000 runs

Other Settings:
default evmVersion
File 1 of 28 : ERC20BondStakingModuleInfo.sol
/*
ERC20BondStakingModuleInfo

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import "../interfaces/IStakingModuleInfo.sol";
import "../interfaces/IStakingModule.sol";
import "../interfaces/IRewardModule.sol";
import "../interfaces/IPool.sol";
import "../interfaces/IConfiguration.sol";
import "../ERC20BondStakingModule.sol";
import "./TokenUtilsInfo.sol";

/**
 * @title ERC20 bond staking module info library
 *
 * @notice this library provides read-only convenience functions to query
 * additional information about the ERC20BondStakingModule contract.
 */
library ERC20BondStakingModuleInfo {
    using Strings for uint256;
    using Strings for address;
    using Address for address;
    using TokenUtilsInfo for IERC20;

    uint256 public constant MAX_BONDS = 128;

    // -- IStakingModuleInfo --------------------------------------------------

    /**
     * @notice convenience function to get all token metadata in a single call
     * @param module address of reward module
     * @return addresses_
     * @return names_
     * @return symbols_
     * @return decimals_
     */
    function tokens(
        address module
    )
        external
        view
        returns (
            address[] memory addresses_,
            string[] memory names_,
            string[] memory symbols_,
            uint8[] memory decimals_
        )
    {
        IStakingModule m = IStakingModule(module);

        addresses_ = m.tokens();
        names_ = new string[](addresses_.length);
        symbols_ = new string[](addresses_.length);
        decimals_ = new uint8[](addresses_.length);

        for (uint256 i; i < addresses_.length; ++i) {
            IERC20Metadata tkn = IERC20Metadata(addresses_[i]);
            names_[i] = tkn.name();
            symbols_[i] = tkn.symbol();
            decimals_[i] = tkn.decimals();
        }
    }

    /**
     * @notice get all staking positions for user
     * @param module address of staking module
     * @param addr user address of interest
     * @param data additional encoded data
     * @return accounts_
     * @return shares_
     */
    function positions(
        address module,
        address addr,
        bytes calldata data
    )
        external
        view
        returns (bytes32[] memory accounts_, uint256[] memory shares_)
    {
        ERC20BondStakingModule m = ERC20BondStakingModule(module);
        uint256 count = m.balanceOf(addr);
        if (count > MAX_BONDS) count = MAX_BONDS;

        accounts_ = new bytes32[](count);
        shares_ = new uint256[](count);

        for (uint256 i; i < count; ++i) {
            uint256 id = m.ownerBonds(addr, i);
            (, , , uint256 debt) = m.bonds(id);
            accounts_[i] = bytes32(id);
            shares_[i] = debt;
        }
    }

    // -- IMetadata -----------------------------------------------------------

    /**
     * @notice provide the metadata URI for a bond position
     * @param module address of bond staking module
     * @param id bond position identifier
     */
    function metadata(
        address module,
        uint256 id,
        bytes calldata
    ) external view returns (string memory) {
        ERC20BondStakingModule m = ERC20BondStakingModule(module);

        // get bond data
        (address market, uint64 timestamp, uint256 principal, uint256 debt) = m
            .bonds(id);
        IERC20Metadata stk = IERC20Metadata(market);
        require(timestamp > 0, "bsmi1");

        // try to get reward data
        address reward;
        if (m.owner().isContract()) {
            try
                IRewardModule(IPool(m.owner()).rewardModule()).tokens()
            returns (address[] memory r) {
                if (r.length == 1) reward = r[0];
            } catch {}
        }

        // svg
        bytes memory svg = abi.encodePacked(
            '<svg width="512"',
            ' height="512"',
            ' fill="',
            "white", //fg,
            '" font-size="24"',
            ' font-family="Monospace"',
            ' xmlns="http://www.w3.org/2000/svg">',
            '<rect x="0" y="0" width="100%" height="100%" style="fill:',
            "#080C42", //bg,
            '"<svg/>'
        );
        svg = abi.encodePacked(
            svg,
            '<text font-size="100%" y="10%" x="5%">',
            reward == address(0) ? "" : IERC20Metadata(reward).symbol(),
            " Bond Position</text>",
            '<text font-size="80%" y="18%" x="5%">Bond ID: ',
            id.toString(),
            "</text>"
        );
        svg = abi.encodePacked(
            svg,
            '<text font-size="60%" y="25%" x="5%">Principal token: ',
            stk.name(),
            "</text>",
            '<text font-size="60%" y="30%" x="5%">Remaining principal: ',
            (principal / 10 ** stk.decimals()).toString(),
            "</text>",
            '<text font-size="60%" y="35%" x="5%">Outstanding debt shares: ',
            (debt / 10 ** stk.decimals()).toString(),
            "</text>"
        );
        if (reward != address(0)) {
            svg = abi.encodePacked(
                svg,
                '<text font-size="60%" y="40%" x="5%">Reward token: ',
                IERC20Metadata(reward).name(),
                "</text>"
            );
        }
        svg = abi.encodePacked(svg, "</svg>");

        // attributes
        bytes memory attrs = abi.encodePacked(
            '{"principal_address":"',
            market.toHexString(),
            '","reward_address":"',
            reward.toHexString(),
            '","timestamp":',
            uint256(timestamp).toString(),
            ',"principal_shares":',
            principal.toString(),
            ',"debt_shares":',
            debt.toString(),
            "}"
        );

        // assemble metadata
        bytes memory data = abi.encodePacked(
            '{"name":"',
            reward == address(0) ? "" : IERC20Metadata(reward).symbol(),
            " Bond Position: ",
            id.toString(),
            '","description":"Bond position that was purchased with ',
            stk.name(),
            " and pays out in ",
            reward == address(0) ? "" : IERC20Metadata(reward).name(),
            '. Powered by GYSR Protocol.","image":"data:image/svg+xml;base64,',
            Base64.encode(svg),
            '","attributes":',
            attrs,
            "}"
        );
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(data)
                )
            );
    }

    // -- ERC20BondStakingModuleInfo ------------------------------------------

    /**
     * @notice quote the debt share values to be issued for an amount of tokens
     * @param module address of bond staking module
     * @param token address of market
     * @param amount number of tokens to be deposited
     * @return debt estimated debt shares issued
     * @return okay if debt amount is within max size and capacity of market (note: this does not check reward module funding)
     */
    function quote(
        address module,
        address token,
        uint256 amount
    ) public view returns (uint256, bool) {
        ERC20BondStakingModule m = ERC20BondStakingModule(module);
        require(amount > 0, "bsmi2");

        // get market
        (, , uint256 mmax, uint256 mcapacity, uint256 mprincipal, , , , ) = m
            .markets(token);
        require(mmax > 0, "bsmi3");

        // principal shares
        uint256 principal = (mprincipal > 0)
            ? IERC20(token).getShares(module, mprincipal, amount)
            : amount * 1e6;

        // get current price
        uint256 price_ = price(module, token);

        // debt pricing
        uint256 debt = (principal * 1e18) / price_;

        return (debt, debt <= mmax && debt <= mcapacity);
    }

    /**
     * @notice quote the debt share values to be issued for an amount of tokens after protocol fees
     * @param module address of bond staking module
     * @param token address of market
     * @param amount number of tokens to be deposited
     * @param config address of configuration contract
     * @return debt estimated debt shares issued
     * @return okay if debt amount is within max size and capacity of market (note: this does not check reward module funding)
     */
    function quote(
        address module,
        address token,
        uint256 amount,
        address config
    ) external view returns (uint256, bool) {
        // get rate
        IConfiguration cfg = IConfiguration(config);
        (, uint256 rate) = cfg.getAddressUint96(
            keccak256("gysr.core.bond.stake.fee")
        );
        // subtract fee and get quote
        amount -= (amount * rate) / 1e18;
        return quote(module, token, amount);
    }

    /**
     * @notice get current price of debt share (reward token) in specified principal token shares
     * @param module address of bond staking module
     * @param token address of market
     * @return price current price of reward debt share in principal token shares
     */
    function price(
        address module,
        address token
    ) public view returns (uint256) {
        ERC20BondStakingModule m = ERC20BondStakingModule(module);

        // get market
        (
            uint256 mprice,
            uint256 mcoeff,
            uint256 mmax,
            uint256 mcapacity,
            uint256 mprincipal,
            ,
            uint256 mdebt,
            uint128 mstart,
            uint128 mupdated
        ) = m.markets(token);
        require(mmax > 0, "bsmi4");

        // estimate debt decay
        uint256 end = mstart + m.period();
        if (block.timestamp < end) {
            mdebt -= (mdebt * (block.timestamp - mupdated)) / (end - mupdated); // approximation, exact value lower bound
        } else {
            mdebt = 0;
        }

        // current price
        return mprice + (mcoeff * mdebt) / 1e24;
    }

    /**
     * @notice preview amount of deposit to be returned for an unstake
     * @param module address of bond staking module
     * @param id bond position identifier
     * @param amount number of tokens to be unstaked (pass 0 for all)
     * @return principal token amount returned
     * @return debt shares to be redeemed (possibly not all vested)
     * @return okay if unstake is valid for bond id and principal amount
     */
    function unstakeable(
        address module,
        uint256 id,
        uint256 amount
    ) external view returns (uint256, uint256, bool) {
        ERC20BondStakingModule m = ERC20BondStakingModule(module);

        // get bond and market
        (
            address btoken,
            uint64 btimestamp,
            uint256 bprincipal,
            uint256 bdebt
        ) = m.bonds(id);
        require(btimestamp > 0, "bsmi5");
        (, , , , uint256 mprincipal, , , , ) = m.markets(btoken);

        if (!m.burndown()) return (0, bdebt, amount == 0);

        uint256 period = m.period();
        uint256 elapsed = block.timestamp - btimestamp;

        // unstake specific nonzero amount
        if (amount > 0) {
            if (elapsed > period) return (0, bdebt, false);

            uint256 shares = IERC20(btoken).getShares(
                module,
                mprincipal,
                amount
            );
            uint256 burned = (shares * period) / (period - elapsed);
            uint256 debt = (bdebt * burned) / bprincipal;
            if (burned < bprincipal) return (amount, debt, true); // valid unstake
            // let invalid unstakes fall through to next block
        }

        // compute max returnable
        uint256 shares = elapsed < period
            ? (bprincipal * (period - elapsed)) / period
            : 0;

        return (
            IERC20(btoken).getAmount(module, mprincipal, shares),
            bdebt,
            amount == 0
        );
    }

    /**
     * @notice get current vested balance of specified principal token
     * @param module address of bond staking module
     * @param token address of market
     * @return withdrawable amount of principal token
     */
    function withdrawable(
        address module,
        address token
    ) public view returns (uint256) {
        ERC20BondStakingModule m = ERC20BondStakingModule(module);

        // get market
        (
            ,
            ,
            ,
            ,
            uint256 mprincipal,
            uint256 mvested,
            ,
            uint128 mstart,
            uint128 mupdated
        ) = m.markets(token);
        require(mstart > 0, "bsmi6");

        if (!m.burndown()) return mvested;

        // estimate principal vesting
        uint256 end = mstart + m.period();
        if (block.timestamp < end) {
            mvested +=
                ((mprincipal - mvested) * (block.timestamp - mupdated)) /
                (end - mupdated); // approximation, exact upper lower bound
        } else {
            mvested = mprincipal;
        }

        return IERC20(token).getAmount(module, mprincipal, mvested);
    }
}

File 2 of 28 : draft-IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

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

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 3 of 28 : 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 4 of 28 : 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 5 of 28 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.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));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @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 6 of 28 : ERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _ownerOf(tokenId);
        require(owner != address(0), "ERC721: invalid token ID");
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner or approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _safeTransfer(from, to, tokenId, data);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _ownerOf(tokenId) != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId, 1);

        // Check that tokenId was not minted by `_beforeTokenTransfer` hook
        require(!_exists(tokenId), "ERC721: token already minted");

        unchecked {
            // Will not overflow unless all 2**256 token ids are minted to the same owner.
            // Given that tokens are minted one by one, it is impossible in practice that
            // this ever happens. Might change if we allow batch minting.
            // The ERC fails to describe this case.
            _balances[to] += 1;
        }

        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId, 1);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId, 1);

        // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
        owner = ERC721.ownerOf(tokenId);

        // Clear approvals
        delete _tokenApprovals[tokenId];

        unchecked {
            // Cannot overflow, as that would require more tokens to be burned/transferred
            // out than the owner initially received through minting and transferring in.
            _balances[owner] -= 1;
        }
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId, 1);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId, 1);

        // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");

        // Clear approvals from the previous owner
        delete _tokenApprovals[tokenId];

        unchecked {
            // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
            // `from`'s balance is the number of token held, which is at least one before the current
            // transfer.
            // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
            // all 2**256 token ids to be minted, which in practice is impossible.
            _balances[from] -= 1;
            _balances[to] += 1;
        }
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId, 1);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits an {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
     * - When `from` is zero, the tokens will be minted for `to`.
     * - When `to` is zero, ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize
    ) internal virtual {}

    /**
     * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
     * - When `from` is zero, the tokens were minted for `to`.
     * - When `to` is zero, ``from``'s tokens were burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 firstTokenId,
        uint256 batchSize
    ) internal virtual {}

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
     * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
     * that `ownerOf(tokenId)` is `a`.
     */
    // solhint-disable-next-line func-name-mixedcase
    function __unsafe_increaseBalance(address account, uint256 amount) internal {
        _balances[account] += amount;
    }
}

File 7 of 28 : IERC721Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

File 8 of 28 : IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

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

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

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

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

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

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

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

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

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

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

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

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

File 9 of 28 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

File 10 of 28 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.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 functionCallWithValue(target, data, 0, "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");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, 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) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, 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) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or 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 {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // 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
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

File 11 of 28 : Base64.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Base64.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides a set of functions to operate with Base64 strings.
 *
 * _Available since v4.5._
 */
library Base64 {
    /**
     * @dev Base64 Encoding/Decoding Table
     */
    string internal constant _TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /**
     * @dev Converts a `bytes` to its Bytes64 `string` representation.
     */
    function encode(bytes memory data) internal pure returns (string memory) {
        /**
         * Inspired by Brecht Devos (Brechtpd) implementation - MIT licence
         * https://github.com/Brechtpd/base64/blob/e78d9fd951e7b0977ddca77d92dc85183770daf4/base64.sol
         */
        if (data.length == 0) return "";

        // Loads the table into memory
        string memory table = _TABLE;

        // Encoding takes 3 bytes chunks of binary data from `bytes` data parameter
        // and split into 4 numbers of 6 bits.
        // The final Base64 length should be `bytes` data length multiplied by 4/3 rounded up
        // - `data.length + 2`  -> Round up
        // - `/ 3`              -> Number of 3-bytes chunks
        // - `4 *`              -> 4 characters for each chunk
        string memory result = new string(4 * ((data.length + 2) / 3));

        /// @solidity memory-safe-assembly
        assembly {
            // Prepare the lookup table (skip the first "length" byte)
            let tablePtr := add(table, 1)

            // Prepare result pointer, jump over length
            let resultPtr := add(result, 32)

            // Run over the input, 3 bytes at a time
            for {
                let dataPtr := data
                let endPtr := add(data, mload(data))
            } lt(dataPtr, endPtr) {

            } {
                // Advance 3 bytes
                dataPtr := add(dataPtr, 3)
                let input := mload(dataPtr)

                // To write each character, shift the 3 bytes (18 bits) chunk
                // 4 times in blocks of 6 bits for each character (18, 12, 6, 0)
                // and apply logical AND with 0x3F which is the number of
                // the previous character in the ASCII table prior to the Base64 Table
                // The result is then added to the table to get the character to write,
                // and finally write it in the result pointer but with a left shift
                // of 256 (1 byte) - 8 (1 ASCII char) = 248 bits

                mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(shr(6, input), 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance

                mstore8(resultPtr, mload(add(tablePtr, and(input, 0x3F))))
                resultPtr := add(resultPtr, 1) // Advance
            }

            // When data `bytes` is not exactly 3 bytes long
            // it is padded with `=` characters at the end
            switch mod(mload(data), 3)
            case 1 {
                mstore8(sub(resultPtr, 1), 0x3d)
                mstore8(sub(resultPtr, 2), 0x3d)
            }
            case 2 {
                mstore8(sub(resultPtr, 1), 0x3d)
            }
        }

        return result;
    }
}

File 12 of 28 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

File 13 of 28 : ERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

File 14 of 28 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

File 15 of 28 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @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 == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10**64) {
                value /= 10**64;
                result += 64;
            }
            if (value >= 10**32) {
                value /= 10**32;
                result += 32;
            }
            if (value >= 10**16) {
                value /= 10**16;
                result += 16;
            }
            if (value >= 10**8) {
                value /= 10**8;
                result += 8;
            }
            if (value >= 10**4) {
                value /= 10**4;
                result += 4;
            }
            if (value >= 10**2) {
                value /= 10**2;
                result += 2;
            }
            if (value >= 10**1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
        }
    }
}

File 16 of 28 : Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}

File 17 of 28 : ERC20BondStakingModule.sol
/*
ERC20BondStakingModule

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

import "./interfaces/IStakingModule.sol";
import "./interfaces/IConfiguration.sol";
import "./interfaces/IMetadata.sol";
import "./OwnerController.sol";
import "./TokenUtils.sol";

/**
 * @title ERC20 bond staking module
 *
 * @notice this staking module allows users to permanently sell an ERC20 token
 * in exchange for bond shares credited to their address. When the user
 * unstakes, these shares will be burned and a reward will be distributed.
 */
contract ERC20BondStakingModule is IStakingModule, OwnerController, ERC721 {
    using SafeERC20 for IERC20;
    using TokenUtils for IERC20;

    // events
    event MarketOpened(
        address indexed token,
        uint256 price,
        uint256 coeff,
        uint256 max,
        uint256 capacity
    );
    event MarketClosed(address indexed token);
    event MarketAdjusted(
        address indexed token,
        uint256 price,
        uint256 coeff,
        uint256 max,
        uint256 capacity
    );
    event MarketBalanceWithdrawn(address indexed token, uint256 amount);

    // bond market
    struct Market {
        uint256 price;
        uint256 coeff; // pricing coefficient
        uint256 max; // max debt for single stake
        uint256 capacity; // remaining debt capacity
        uint256 principal;
        uint256 vested;
        uint256 debt;
        uint128 start; // start of current vesting/decay period
        uint128 updated; // last incremental update to vesting/decay
    }

    // bond position
    struct Bond {
        address market;
        uint64 timestamp;
        uint256 principal; // shares
        uint256 debt; // shares
    }

    // adjustment
    struct Adjustment {
        uint256 price;
        uint256 coeff;
        uint256 timestamp;
    }

    // constant
    uint256 public constant MAX_MARKETS = 16;
    uint256 public constant MAX_BONDS = 128; // only for viewing balances
    uint256 public constant MIN_PERIOD = 3600;

    // members: config
    uint256 public immutable period;
    bool public immutable burndown;
    address private immutable _factory;
    IConfiguration private immutable _config;

    // members: bonds
    mapping(address => Market) public markets;
    address[] private _markets;
    mapping(address => uint256) _marketIndex;
    mapping(uint256 => Bond) public bonds;
    mapping(address => Adjustment) public adjustments;

    // members: indexing
    mapping(address => mapping(uint256 => uint256)) public ownerBonds;
    mapping(uint256 => uint256) public bondIndex;
    uint256 public nonce;

    /**
     * @param period_ bond vesting period
     * @param burndown_ enable burndown period and opt-out for deposited user funds
     * @param config_ address for configuration contract
     * @param factory_ address of module factory
     */
    constructor(
        uint256 period_,
        bool burndown_,
        address config_,
        address factory_
    ) ERC721("GYSR Bond Position", "GYSR-BOND") {
        require(period_ > MIN_PERIOD, "bsm1");
        period = period_;
        burndown = burndown_;
        _config = IConfiguration(config_);
        _factory = factory_;

        nonce = 1;
    }

    // -- IStakingModule -------------------------------------------------

    /**
     * @inheritdoc IStakingModule
     */
    function tokens()
        external
        view
        override
        returns (address[] memory tokens_)
    {
        return _markets;
    }

    /**
     * @inheritdoc IStakingModule
     *
     * @dev user balances will dynamically decrease as bonds vest to reflect
     * the amount that can actually be withdrawn
     */
    function balances(
        address user
    ) external view override returns (uint256[] memory balances_) {
        balances_ = new uint256[](_markets.length);
        if (!burndown) return balances_;
        uint256 count = balanceOf(user);
        if (count > MAX_BONDS) count = MAX_BONDS;
        for (uint256 i; i < count; ++i) {
            Bond storage b = bonds[ownerBonds[user][i]];
            uint256 dt = block.timestamp - b.timestamp;
            if (dt > period) {
                continue;
            }
            uint256 s = (b.principal * (period - dt)) / period;
            uint256 amount = _amount(b.market, s);
            balances_[_marketIndex[b.market]] += amount;
        }
    }

    /**
     * @inheritdoc IStakingModule
     */
    function factory() external view override returns (address) {
        return _factory;
    }

    /**
     * @inheritdoc IStakingModule
     */
    function totals()
        external
        view
        override
        returns (uint256[] memory totals_)
    {
        totals_ = new uint256[](_markets.length);
        for (uint256 i; i < _markets.length; ++i) {
            totals_[i] = IERC20(_markets[i]).balanceOf(address(this));
        }
    }

    /**
     * @inheritdoc IStakingModule
     */
    function stake(
        address sender,
        uint256 amount,
        bytes calldata data
    ) external override onlyOwner returns (bytes32, uint256) {
        // validate
        require(amount > 0, "bsm2");
        require(data.length == 32 || data.length == 64, "bsm3");
        address token;
        assembly {
            token := calldataload(132)
        }
        uint256 minimum;
        if (data.length == 64) {
            assembly {
                minimum := calldataload(164)
            }
        }
        Market storage m = markets[token];
        uint256 capacity = m.capacity;
        require(capacity > 0, "bsm4");

        // update
        _update(token);

        // transfer and process fees
        uint256 minted;
        {
            (address receiver, uint256 rate) = _config.getAddressUint96(
                keccak256("gysr.core.bond.stake.fee")
            );
            minted = IERC20(token).receiveWithFee(
                m.principal,
                sender,
                amount,
                receiver,
                rate
            );
        }

        // pricing
        uint256 debt = (minted * 1e18) / (m.price + (m.coeff * m.debt) / 1e24);
        require(debt <= m.max, "bsm5");
        require(debt <= capacity, "bsm6");
        require(debt > minimum, "bsm7");

        // create new bond
        uint256 id = nonce;
        nonce = id + 1;
        bonds[id] = Bond({
            market: token,
            timestamp: uint64(block.timestamp),
            principal: burndown ? minted : 0, // only need to store if burndown enabled
            debt: debt
        });

        // update bond market
        m.debt += debt;
        m.capacity = capacity - debt;
        m.principal += minted;
        if (!burndown) {
            m.vested += minted;
        }
        m.start = uint128(block.timestamp);

        // mint position
        _safeMint(sender, id);

        // external
        emit Staked(bytes32(id), sender, token, amount, debt);

        return (bytes32(id), debt);
    }

    /**
     * @inheritdoc IStakingModule
     *
     * @dev pass amount zero to unstake all or to unstake fully vested bond
     */
    function unstake(
        address sender,
        uint256 amount,
        bytes calldata data
    ) external override onlyOwner returns (bytes32, address, uint256) {
        // validate
        require(data.length == 32, "bsm8");
        uint256 id;
        assembly {
            id := calldataload(132)
        }
        require(_ownerOf(id) == sender, "bsm9");

        // default unstake with no principal returned
        Bond storage b = bonds[id];
        address token = b.market;
        uint256 shares;
        uint256 debt = b.debt;
        uint256 elapsed = block.timestamp - b.timestamp;
        require(elapsed > 0, "bsm10");

        // update
        _update(token);

        if (amount > 0) {
            // unstake specific amount
            require(burndown, "bsm11");

            // timing
            require(elapsed < period, "bsm12");

            // convert to shares
            shares = IERC20(token).getShares(markets[token].principal, amount); // must have non zero unvested balance
            require(shares > 0, "bsm13");
            uint256 bprincipal = b.principal;
            uint256 bdebt = debt;

            // compute burned principal and debt shares
            uint256 burned = (shares * period) / (period - elapsed);
            require(burned < bprincipal, "bsm14"); // strictly less than total principal
            debt = (bdebt * burned) / bprincipal;

            // decrease bond position
            b.principal = bprincipal - burned;
            b.debt = bdebt - debt;
        } else {
            // unstake all
            if (burndown) {
                if (elapsed < period) {
                    // return any unvested principal
                    shares = (b.principal * (period - elapsed)) / period;
                    amount = IERC20(token).getAmount(
                        markets[token].principal,
                        shares
                    );
                }
            }
            // delete bond position
            delete bonds[id];
            _burn(id);
        }

        // transfer principal back to user
        if (shares > 0) {
            // note: unwinding debt here does introduce a price drop and frontrunning opportunity,
            // but it also prevents manipulation of debt via repeated staking and unstaking
            uint256 udebt = (debt * (period - elapsed)) / period;
            markets[token].debt -= udebt;
            markets[token].capacity += udebt;
            markets[token].principal -= shares;
            IERC20(token).safeTransfer(sender, amount);
        }

        // external
        emit Unstaked(bytes32(id), sender, token, amount, debt);
        return (bytes32(id), sender, debt);
    }

    /**
     * @inheritdoc IStakingModule
     */
    function claim(
        address sender,
        uint256,
        bytes calldata data
    ) external override onlyOwner returns (bytes32, address, uint256) {
        // validate
        require(data.length == 32, "bsm15");
        uint256 id;
        assembly {
            id := calldataload(132)
        }
        require(_ownerOf(id) == sender, "bsm16");

        Bond storage b = bonds[id];
        address token = b.market;
        uint256 debt = b.debt;

        // update
        _update(token);

        // external
        emit Claimed(bytes32(id), sender, token, 0, debt);
        return (bytes32(id), sender, debt);
    }

    /**
     * @inheritdoc IStakingModule
     */
    function update(
        address sender,
        bytes calldata data
    ) external override returns (bytes32) {
        // validate
        requireOwner();
        require(data.length == 32, "bsm17");
        uint256 id;
        assembly {
            id := calldataload(100)
        }
        require(_ownerOf(id) == sender, "bsm18");

        // update
        _update(bonds[id].market);

        return bytes32(id);
    }

    /**
     * @inheritdoc IStakingModule
     */
    function clean(bytes calldata) external override {}

    // -- ERC20BondStakingModule -----------------------------------------

    /**
     * @notice open a new bond market
     * @param token the principal token that will be deposited
     * @param price minimum and starting price of the bond in tokens
     * @param coeff bond pricing coefficient in price increase per debt shares (24 decimals)
     * @param max maximum size for an individual bond in debt shares
     * @param capacity the total debt available for this market in shares
     */
    function open(
        address token,
        uint256 price,
        uint256 coeff,
        uint256 max,
        uint256 capacity
    ) external {
        requireController();
        require(markets[token].max == 0, "bsm19");
        require(_markets.length < MAX_MARKETS, "bsm20");
        require(price > 0, "bsm21");
        require(max > 0, "bsm22");
        require(capacity > 0, "bsm23");

        markets[token] = Market({
            price: price,
            coeff: coeff,
            max: max,
            capacity: capacity,
            principal: 0,
            vested: 0,
            debt: 0,
            start: uint128(block.timestamp),
            updated: uint128(block.timestamp)
        });
        _markets.push(token);
        _marketIndex[token] = _markets.length - 1;

        emit MarketOpened(token, price, coeff, max, capacity);
    }

    /**
     * @notice close an existing bond market
     * @param token the token address of the market to close
     */
    function close(address token) external {
        requireController();
        require(markets[token].capacity > 0, "bsm24");
        markets[token].capacity = 0;
        emit MarketClosed(token);
    }

    /**
     * @notice adjust the configuration of an existing bond market
     * @param token the token address of the market to adjust
     * @param price minimum and starting price of the bond in tokens
     * @param coeff bond pricing coefficient
     * @param max maximum size for an individual bond in debt shares
     * @param capacity the total debt available for this market in shares
     */
    function adjust(
        address token,
        uint256 price,
        uint256 coeff,
        uint256 max,
        uint256 capacity
    ) external {
        requireController();
        require(markets[token].max > 0, "bsm25");
        require(price > 0, "bsm26");
        require(max > 0, "bsm27");
        require(capacity > 0, "bsm28");

        // update
        _update(token);

        // adjust market
        markets[token].max = max;
        markets[token].capacity = capacity;

        // gradual adjustment for price related params
        adjustments[token] = Adjustment({
            price: price,
            coeff: coeff,
            timestamp: block.timestamp
        });

        emit MarketAdjusted(token, price, coeff, max, capacity);
    }

    /**
     * @notice withdraw vested principal token from market
     * @param token the principal token address
     * @param amount number of tokens to withdraw
     */
    function withdraw(address token, uint256 amount) external {
        requireController();
        // validate
        Market storage m = markets[token];
        require(m.max > 0, "bsm29");
        require(amount > 0, "bsm30");

        // update
        _update(token);

        IERC20 tkn = IERC20(token);
        uint256 shares = tkn.getShares(m.principal, amount);
        require(shares > 0);
        require(shares <= m.vested, "bsm31");

        // withdraw
        m.vested -= shares;
        m.principal -= shares;
        tkn.safeTransfer(msg.sender, amount);

        emit MarketBalanceWithdrawn(token, amount);
    }

    // -- ERC721 ---------------------------------------------------------

    /**
     * @inheritdoc IERC721Metadata
     */
    function tokenURI(
        uint256 tokenId
    ) public view override returns (string memory) {
        _requireMinted(tokenId);
        address metadata = _config.getAddress(
            keccak256("gysr.core.bond.metadata")
        );
        return IMetadata(metadata).metadata(address(this), tokenId, "");
    }

    // -- ERC20BondStakingModule internal --------------------------------

    /**
     * @dev internal helper to get token amount from shares
     * @param token address of token
     * @param shares number of shares
     */
    function _amount(
        address token,
        uint256 shares
    ) private view returns (uint256) {
        return IERC20(token).getAmount(markets[token].principal, shares);
    }

    /**
     * @dev internal market update helper for principal vesting, debt decay, and parameter tuning
     * @param token the token address of the market to update
     */
    function _update(address token) private {
        Market storage m = markets[token];

        uint256 updated = m.updated;
        uint256 elapsed = block.timestamp - updated;
        uint256 end = m.start + period;
        if (block.timestamp < end) {
            uint256 remaining = end - updated;

            // vest principal
            if (burndown) {
                uint256 vested = m.vested;
                m.vested =
                    vested +
                    ((m.principal - vested) * elapsed) /
                    remaining; // approximation, exact value upper bound
            }

            // decay debt
            uint256 debt = m.debt;
            m.debt = debt - (debt * elapsed) / remaining; // approximation, exact value lower bound
        } else {
            // vest principal
            if (burndown) m.vested = m.principal;

            // decay debt
            m.debt = 0;
        }

        // adjustments
        uint256 start = adjustments[token].timestamp;
        if (start > 0) {
            if (block.timestamp < start + period) {
                // interpolate
                uint256 target = adjustments[token].price;
                uint256 curr = m.price;
                uint256 remaining = start + period + elapsed - block.timestamp;
                if (target > curr) {
                    m.price = curr + ((target - curr) * elapsed) / remaining;
                } else {
                    m.price = curr - ((curr - target) * elapsed) / remaining;
                }
                target = adjustments[token].coeff;
                curr = m.coeff;
                if (target > curr) {
                    m.coeff = curr + ((target - curr) * elapsed) / remaining;
                } else {
                    m.coeff = curr - ((curr - target) * elapsed) / remaining;
                }
            } else {
                // complete adjustment
                m.price = adjustments[token].price;
                m.coeff = adjustments[token].coeff;
                delete adjustments[token];
            }
        }

        m.updated = uint128(block.timestamp);
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId,
        uint256
    ) internal override {
        if (from != address(0)) _remove(from, tokenId);
        if (to != address(0)) _append(to, tokenId);
    }

    /**
     * @dev internal helper function to add bond position
     */
    function _append(address user, uint256 id) private {
        uint256 len = balanceOf(user);
        ownerBonds[user][len] = id;
        bondIndex[id] = len;
    }

    /**
     * @dev internal helper function to delete and reindex bond position
     */
    function _remove(address user, uint256 id) private {
        uint256 index = bondIndex[id];
        uint256 lastIndex = balanceOf(user) - 1;
        if (index != lastIndex) {
            uint256 lastId = ownerBonds[user][lastIndex];
            ownerBonds[user][index] = lastId;
            bondIndex[lastId] = index;
        }
        delete ownerBonds[user][lastIndex];
        delete bondIndex[id];
    }
}

File 18 of 28 : TokenUtilsInfo.sol
/*
TokenUtilsInfo

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title Token utilities info
 *
 * @notice this library implements utility methods for token handling,
 * dynamic balance accounting, and fee processing.
 *
 * this is a modified version to be used by info libraries.
 */
library TokenUtilsInfo {
    uint256 constant INITIAL_SHARES_PER_TOKEN = 1e6;
    uint256 constant FLOOR_SHARES_PER_TOKEN = 1e3;

    /**
     * @notice get token shares from amount
     * @param token erc20 token interface
     * @param module address of module
     * @param total current total shares
     * @param amount balance of tokens
     */
    function getShares(
        IERC20 token,
        address module,
        uint256 total,
        uint256 amount
    ) internal view returns (uint256) {
        if (total == 0) return 0;
        uint256 balance = token.balanceOf(module);
        if (total < balance * FLOOR_SHARES_PER_TOKEN)
            return amount * FLOOR_SHARES_PER_TOKEN;
        return (total * amount) / balance;
    }

    /**
     * @notice get token amount from shares
     * @param token erc20 token interface
     * @param module address of module
     * @param total current total shares
     * @param shares balance of shares
     */
    function getAmount(
        IERC20 token,
        address module,
        uint256 total,
        uint256 shares
    ) internal view returns (uint256) {
        if (total == 0) return 0;
        uint256 balance = token.balanceOf(module);
        if (total < balance * FLOOR_SHARES_PER_TOKEN)
            return shares / FLOOR_SHARES_PER_TOKEN;
        return (balance * shares) / total;
    }
}

File 19 of 28 : IConfiguration.sol
/*
IConfiguration

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

/**
 * @title Configuration interface
 *
 * @notice this defines the protocol configuration interface
 */
interface IConfiguration {
    // events
    event ParameterUpdated(bytes32 indexed key, address value);
    event ParameterUpdated(bytes32 indexed key, uint256 value);
    event ParameterUpdated(bytes32 indexed key, address value0, uint96 value1);
    event ParameterOverridden(
        address indexed caller,
        bytes32 indexed key,
        address value
    );
    event ParameterOverridden(
        address indexed caller,
        bytes32 indexed key,
        uint256 value
    );
    event ParameterOverridden(
        address indexed caller,
        bytes32 indexed key,
        address value0,
        uint96 value1
    );

    /**
     * @notice set or update uint256 parameter
     * @param key keccak256 hash of parameter key
     * @param value uint256 parameter value
     */
    function setUint256(bytes32 key, uint256 value) external;

    /**
     * @notice set or update address parameter
     * @param key keccak256 hash of parameter key
     * @param value address parameter value
     */
    function setAddress(bytes32 key, address value) external;

    /**
     * @notice set or update packed address + uint96 pair
     * @param key keccak256 hash of parameter key
     * @param value0 address parameter value
     * @param value1 uint96 parameter value
     */
    function setAddressUint96(
        bytes32 key,
        address value0,
        uint96 value1
    ) external;

    /**
     * @notice get uint256 parameter
     * @param key keccak256 hash of parameter key
     * @return uint256 parameter value
     */
    function getUint256(bytes32 key) external view returns (uint256);

    /**
     * @notice get address parameter
     * @param key keccak256 hash of parameter key
     * @return uint256 parameter value
     */
    function getAddress(bytes32 key) external view returns (address);

    /**
     * @notice get packed address + uint96 pair
     * @param key keccak256 hash of parameter key
     * @return address parameter value
     * @return uint96 parameter value
     */
    function getAddressUint96(
        bytes32 key
    ) external view returns (address, uint96);

    /**
     * @notice override uint256 parameter for specific caller
     * @param caller address of caller
     * @param key keccak256 hash of parameter key
     * @param value uint256 parameter value
     */
    function overrideUint256(
        address caller,
        bytes32 key,
        uint256 value
    ) external;

    /**
     * @notice override address parameter for specific caller
     * @param caller address of caller
     * @param key keccak256 hash of parameter key
     * @param value address parameter value
     */
    function overrideAddress(
        address caller,
        bytes32 key,
        address value
    ) external;

    /**
     * @notice override address parameter for specific caller
     * @param caller address of caller
     * @param key keccak256 hash of parameter key
     * @param value0 address parameter value
     * @param value1 uint96 parameter value
     */
    function overrideAddressUint96(
        address caller,
        bytes32 key,
        address value0,
        uint96 value1
    ) external;
}

File 20 of 28 : IEvents.sol
/*
IEvents

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
 */

pragma solidity 0.8.18;

/**
 * @title GYSR event system
 *
 * @notice common interface to define GYSR event system
 */
interface IEvents {
    // staking
    event Staked(
        bytes32 indexed account,
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 shares
    );
    event Unstaked(
        bytes32 indexed account,
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 shares
    );
    event Claimed(
        bytes32 indexed account,
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 shares
    );
    event Updated(bytes32 indexed account, address indexed user);

    // rewards
    event RewardsDistributed(
        address indexed user,
        address indexed token,
        uint256 amount,
        uint256 shares
    );
    event RewardsFunded(
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );
    event RewardsExpired(
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );
    event RewardsWithdrawn(
        address indexed token,
        uint256 amount,
        uint256 shares,
        uint256 timestamp
    );
    event RewardsUpdated(bytes32 indexed account);

    // gysr
    event GysrSpent(address indexed user, uint256 amount);
    event GysrVested(address indexed user, uint256 amount);
    event GysrWithdrawn(uint256 amount);
    event Fee(address indexed receiver, address indexed token, uint256 amount);
}

File 21 of 28 : IMetadata.sol
/*
IMetadata

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

/**
 * @title Metadata interface
 *
 * @notice this defines the metadata library interface for tokenized staking modules
 */
interface IMetadata {
    /**
     * @notice provide the metadata URI for a tokenized staking module position
     * @param module address of staking module
     * @param id position identifier
     * @param data additional encoded data
     */
    function metadata(
        address module,
        uint256 id,
        bytes calldata data
    ) external view returns (string memory);
}

File 22 of 28 : IOwnerController.sol
/*
IOwnerController

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

/**
 * @title Owner controller interface
 *
 * @notice this defines the interface for any contracts that use the
 * owner controller access pattern
 */
interface IOwnerController {
    /**
     * @dev Returns the address of the current owner.
     */
    function owner() external view returns (address);

    /**
     * @dev Returns the address of the current controller.
     */
    function controller() external view returns (address);

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`). This can
     * include renouncing ownership by transferring to the zero address.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) external;

    /**
     * @dev Transfers control of the contract to a new account (`newController`).
     * Can only be called by the owner.
     */
    function transferControl(address newController) external;
}

File 23 of 28 : IPool.sol
/*
IPool

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

/**
 * @title Pool interface
 *
 * @notice this defines the core Pool contract interface
 */
interface IPool {
    /**
     * @return staking tokens for Pool
     */
    function stakingTokens() external view returns (address[] memory);

    /**
     * @return reward tokens for Pool
     */
    function rewardTokens() external view returns (address[] memory);

    /**
     * @return staking balances for user
     */
    function stakingBalances(
        address user
    ) external view returns (uint256[] memory);

    /**
     * @return total staking balances for Pool
     */
    function stakingTotals() external view returns (uint256[] memory);

    /**
     * @return reward balances for Pool
     */
    function rewardBalances() external view returns (uint256[] memory);

    /**
     * @return GYSR usage ratio for Pool
     */
    function usage() external view returns (uint256);

    /**
     * @return address of staking module
     */
    function stakingModule() external view returns (address);

    /**
     * @return address of reward module
     */
    function rewardModule() external view returns (address);

    /**
     * @notice stake asset and begin earning rewards
     * @param amount number of tokens to stake
     * @param stakingdata data passed to staking module
     * @param rewarddata data passed to reward module
     */
    function stake(
        uint256 amount,
        bytes calldata stakingdata,
        bytes calldata rewarddata
    ) external;

    /**
     * @notice unstake asset and claim rewards
     * @param amount number of tokens to unstake
     * @param stakingdata data passed to staking module
     * @param rewarddata data passed to reward module
     */
    function unstake(
        uint256 amount,
        bytes calldata stakingdata,
        bytes calldata rewarddata
    ) external;

    /**
     * @notice claim rewards without unstaking
     * @param amount number of tokens to claim against
     * @param stakingdata data passed to staking module
     * @param rewarddata data passed to reward module
     */
    function claim(
        uint256 amount,
        bytes calldata stakingdata,
        bytes calldata rewarddata
    ) external;

    /**
     * @notice method called ad hoc to update user accounting
     * @param stakingdata data passed to staking module
     * @param rewarddata data passed to reward module
     */
    function update(
        bytes calldata stakingdata,
        bytes calldata rewarddata
    ) external;

    /**
     * @notice method called ad hoc to clean up and perform additional accounting
     * @param stakingdata data passed to staking module
     * @param rewarddata data passed to reward module
     */
    function clean(
        bytes calldata stakingdata,
        bytes calldata rewarddata
    ) external;

    /**
     * @return gysr balance available for withdrawal
     */
    function gysrBalance() external view returns (uint256);

    /**
     * @notice withdraw GYSR tokens applied during unstaking
     * @param amount number of GYSR to withdraw
     */
    function withdraw(uint256 amount) external;

    /**
     * @notice transfer control of the staking module to another account
     * @param newController address of new controller
     */
    function transferControlStakingModule(address newController) external;

    /**
     * @notice transfer control of the reward module to another account
     * @param newController address of new controller
     */
    function transferControlRewardModule(address newController) external;

    /**
     * @notice execute multiple operations in a single call
     * @param data array of encoded function data
     */
    function multicall(
        bytes[] calldata data
    ) external returns (bytes[] memory results);
}

File 24 of 28 : IRewardModule.sol
/*
IRewardModule

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./IEvents.sol";
import "./IOwnerController.sol";

/**
 * @title Reward module interface
 *
 * @notice this contract defines the common interface that any reward module
 * must implement to be compatible with the modular Pool architecture.
 */
interface IRewardModule is IOwnerController, IEvents {
    /**
     * @return array of reward tokens
     */
    function tokens() external view returns (address[] memory);

    /**
     * @return array of reward token balances
     */
    function balances() external view returns (uint256[] memory);

    /**
     * @return GYSR usage ratio for reward module
     */
    function usage() external view returns (uint256);

    /**
     * @return address of module factory
     */
    function factory() external view returns (address);

    /**
     * @notice perform any necessary accounting for new stake
     * @param account bytes32 id of staking account
     * @param sender address of sender
     * @param shares number of new shares minted
     * @param data addtional data
     * @return amount of gysr spent
     * @return amount of gysr vested
     */
    function stake(
        bytes32 account,
        address sender,
        uint256 shares,
        bytes calldata data
    ) external returns (uint256, uint256);

    /**
     * @notice reward user and perform any necessary accounting for unstake
     * @param account bytes32 id of staking account
     * @param sender address of sender
     * @param receiver address of reward receiver
     * @param shares number of shares burned
     * @param data additional data
     * @return amount of gysr spent
     * @return amount of gysr vested
     */
    function unstake(
        bytes32 account,
        address sender,
        address receiver,
        uint256 shares,
        bytes calldata data
    ) external returns (uint256, uint256);

    /**
     * @notice reward user and perform and necessary accounting for existing stake
     * @param account bytes32 id of staking account
     * @param sender address of sender
     * @param receiver address of reward receiver
     * @param shares number of shares being claimed against
     * @param data additional data
     * @return amount of gysr spent
     * @return amount of gysr vested
     */
    function claim(
        bytes32 account,
        address sender,
        address receiver,
        uint256 shares,
        bytes calldata data
    ) external returns (uint256, uint256);

    /**
     * @notice method called by anyone to update accounting
     * @dev will only be called ad hoc and should not contain essential logic
     * @param account bytes32 id of staking account for update
     * @param sender address of sender
     * @param data additional data
     */
    function update(
        bytes32 account,
        address sender,
        bytes calldata data
    ) external;

    /**
     * @notice method called by owner to clean up and perform additional accounting
     * @dev will only be called ad hoc and should not contain any essential logic
     * @param data additional data
     */
    function clean(bytes calldata data) external;
}

File 25 of 28 : IStakingModule.sol
/*
IStakingModule

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./IEvents.sol";
import "./IOwnerController.sol";

/**
 * @title Staking module interface
 *
 * @notice this contract defines the common interface that any staking module
 * must implement to be compatible with the modular Pool architecture.
 */
interface IStakingModule is IOwnerController, IEvents {
    /**
     * @return array of staking tokens
     */
    function tokens() external view returns (address[] memory);

    /**
     * @notice get balance of user
     * @param user address of user
     * @return balances of each staking token
     */
    function balances(address user) external view returns (uint256[] memory);

    /**
     * @return address of module factory
     */
    function factory() external view returns (address);

    /**
     * @notice get total staked amount
     * @return totals for each staking token
     */
    function totals() external view returns (uint256[] memory);

    /**
     * @notice stake an amount of tokens for user
     * @param sender address of sender
     * @param amount number of tokens to stake
     * @param data additional data
     * @return bytes32 id of staking account
     * @return number of shares minted for stake
     */
    function stake(
        address sender,
        uint256 amount,
        bytes calldata data
    ) external returns (bytes32, uint256);

    /**
     * @notice unstake an amount of tokens for user
     * @param sender address of sender
     * @param amount number of tokens to unstake
     * @param data additional data
     * @return bytes32 id of staking account
     * @return address of reward receiver
     * @return number of shares burned for unstake
     */
    function unstake(
        address sender,
        uint256 amount,
        bytes calldata data
    ) external returns (bytes32, address, uint256);

    /**
     * @notice quote the share value for an amount of tokens without unstaking
     * @param sender address of sender
     * @param amount number of tokens to claim with
     * @param data additional data
     * @return bytes32 id of staking account
     * @return address of reward receiver
     * @return number of shares that the claim amount is worth
     */
    function claim(
        address sender,
        uint256 amount,
        bytes calldata data
    ) external returns (bytes32, address, uint256);

    /**
     * @notice method called by anyone to update accounting
     * @dev will only be called ad hoc and should not contain essential logic
     * @param sender address of user for update
     * @param data additional data
     * @return bytes32 id of staking account
     */
    function update(
        address sender,
        bytes calldata data
    ) external returns (bytes32);

    /**
     * @notice method called by owner to clean up and perform additional accounting
     * @dev will only be called ad hoc and should not contain any essential logic
     * @param data additional data
     */
    function clean(bytes calldata data) external;
}

File 26 of 28 : IStakingModuleInfo.sol
/*
IStakingModuleInfo

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

/**
 * @title Staking module info interface
 *
 * @notice this contract defines the common interface that any staking module info
 * must implement to be compatible with the modular Pool architecture.
 */
interface IStakingModuleInfo {
    /**
     * @notice convenience function to get all token metadata in a single call
     * @param module address of staking module
     * @return addresses
     * @return names
     * @return symbols
     * @return decimals
     */
    function tokens(
        address module
    )
        external
        view
        returns (
            address[] memory,
            string[] memory,
            string[] memory,
            uint8[] memory
        );

    /**
     * @notice get all staking positions for user
     * @param module address of staking module
     * @param addr user address of interest
     * @param data additional encoded data
     * @return accounts_
     * @return shares_
     */
    function positions(
        address module,
        address addr,
        bytes calldata data
    ) external view returns (bytes32[] memory, uint256[] memory);
}

File 27 of 28 : OwnerController.sol
/*
OwnerController

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "./interfaces/IOwnerController.sol";

/**
 * @title Owner controller
 *
 * @notice this base contract implements an owner-controller access model.
 *
 * @dev the contract is an adapted version of the OpenZeppelin Ownable contract.
 * It allows the owner to designate an additional account as the controller to
 * perform restricted operations.
 *
 * Other changes include supporting role verification with a require method
 * in addition to the modifier option, and removing some unneeded functionality.
 *
 * Original contract here:
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
 */
contract OwnerController is IOwnerController {
    address private _owner;
    address private _controller;

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

    event ControlTransferred(
        address indexed previousController,
        address indexed newController
    );

    constructor() {
        _owner = msg.sender;
        _controller = msg.sender;
        emit OwnershipTransferred(address(0), _owner);
        emit ControlTransferred(address(0), _owner);
    }

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

    /**
     * @dev Returns the address of the current controller.
     */
    function controller() public view override returns (address) {
        return _controller;
    }

    /**
     * @dev Modifier that throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(_owner == msg.sender, "oc1");
        _;
    }

    /**
     * @dev Modifier that throws if called by any account other than the controller.
     */
    modifier onlyController() {
        require(_controller == msg.sender, "oc2");
        _;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    function requireOwner() internal view {
        require(_owner == msg.sender, "oc1");
    }

    /**
     * @dev Throws if called by any account other than the controller.
     */
    function requireController() internal view {
        require(_controller == msg.sender, "oc2");
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override {
        requireOwner();
        require(newOwner != address(0), "oc3");
        emit OwnershipTransferred(_owner, newOwner);
        _owner = newOwner;
    }

    /**
     * @dev Transfers control of the contract to a new account (`newController`).
     * Can only be called by the owner.
     */
    function transferControl(address newController) public virtual override {
        requireOwner();
        require(newController != address(0), "oc4");
        emit ControlTransferred(_controller, newController);
        _controller = newController;
    }
}

File 28 of 28 : TokenUtils.sol
/*
TokenUtils

https://github.com/gysr-io/core

SPDX-License-Identifier: MIT
*/

pragma solidity 0.8.18;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
 * @title Token utilities
 *
 * @notice this library implements utility methods for token handling,
 * dynamic balance accounting, and fee processing
 */
library TokenUtils {
    using SafeERC20 for IERC20;

    event Fee(address indexed receiver, address indexed token, uint256 amount); // redefinition

    uint256 constant INITIAL_SHARES_PER_TOKEN = 1e6;
    uint256 constant FLOOR_SHARES_PER_TOKEN = 1e3;

    /**
     * @notice get token shares from amount
     * @param token erc20 token interface
     * @param total current total shares
     * @param amount balance of tokens
     */
    function getShares(
        IERC20 token,
        uint256 total,
        uint256 amount
    ) internal view returns (uint256) {
        if (total == 0) return 0;
        uint256 balance = token.balanceOf(address(this));
        if (total < balance * FLOOR_SHARES_PER_TOKEN)
            return amount * FLOOR_SHARES_PER_TOKEN;
        return (total * amount) / balance;
    }

    /**
     * @notice get token amount from shares
     * @param token erc20 token interface
     * @param total current total shares
     * @param shares balance of shares
     */
    function getAmount(
        IERC20 token,
        uint256 total,
        uint256 shares
    ) internal view returns (uint256) {
        if (total == 0) return 0;
        uint256 balance = token.balanceOf(address(this));
        if (total < balance * FLOOR_SHARES_PER_TOKEN)
            return shares / FLOOR_SHARES_PER_TOKEN;
        return (balance * shares) / total;
    }

    /**
     * @notice transfer tokens from sender into contract and convert to shares
     * for internal accounting
     * @param token erc20 token interface
     * @param total current total shares
     * @param sender token sender
     * @param amount number of tokens to be sent
     */
    function receiveAmount(
        IERC20 token,
        uint256 total,
        address sender,
        uint256 amount
    ) internal returns (uint256) {
        // note: we assume amount > 0 has already been validated

        //  transfer
        uint256 balance = token.balanceOf(address(this));
        token.safeTransferFrom(sender, address(this), amount);
        uint256 actual = token.balanceOf(address(this)) - balance;
        require(amount >= actual);

        // mint shares at current rate
        uint256 minted;
        if (total == 0) {
            minted = actual * INITIAL_SHARES_PER_TOKEN;
        } else if (total < balance * FLOOR_SHARES_PER_TOKEN) {
            minted = actual * FLOOR_SHARES_PER_TOKEN;
        } else {
            minted = (total * actual) / balance;
        }
        require(minted > 0);
        return minted;
    }

    /**
     * @notice transfer tokens from sender into contract, process protocol fee,
     * and convert to shares for internal accounting
     * @param token erc20 token interface
     * @param total current total shares
     * @param sender token sender
     * @param amount number of tokens to be sent
     * @param feeReceiver address to receive fee
     * @param feeRate portion of amount to take as fee in 18 decimals
     */
    function receiveWithFee(
        IERC20 token,
        uint256 total,
        address sender,
        uint256 amount,
        address feeReceiver,
        uint256 feeRate
    ) internal returns (uint256) {
        // note: we assume amount > 0 has already been validated

        // check initial token balance
        uint256 balance = token.balanceOf(address(this));

        // process fee
        uint256 fee;
        if (feeReceiver != address(0) && feeRate > 0 && feeRate < 1e18) {
            fee = (amount * feeRate) / 1e18;
            token.safeTransferFrom(sender, feeReceiver, fee);
            emit Fee(feeReceiver, address(token), fee);
        }

        // do transfer
        token.safeTransferFrom(sender, address(this), amount - fee);
        uint256 actual = token.balanceOf(address(this)) - balance;
        require(amount >= actual);

        // mint shares at current rate
        uint256 minted;
        if (total == 0) {
            minted = actual * INITIAL_SHARES_PER_TOKEN;
        } else if (total < balance * FLOOR_SHARES_PER_TOKEN) {
            minted = actual * FLOOR_SHARES_PER_TOKEN;
        } else {
            minted = (total * actual) / balance;
        }
        require(minted > 0);
        return minted;
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 10000
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[],"name":"MAX_BONDS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"metadata","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"address","name":"addr","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"positions","outputs":[{"internalType":"bytes32[]","name":"accounts_","type":"bytes32[]"},{"internalType":"uint256[]","name":"shares_","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"address","name":"token","type":"address"}],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"config","type":"address"}],"name":"quote","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"}],"name":"tokens","outputs":[{"internalType":"address[]","name":"addresses_","type":"address[]"},{"internalType":"string[]","name":"names_","type":"string[]"},{"internalType":"string[]","name":"symbols_","type":"string[]"},{"internalType":"uint8[]","name":"decimals_","type":"uint8[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"unstakeable","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"module","type":"address"},{"internalType":"address","name":"token","type":"address"}],"name":"withdrawable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

61356961003a600b82828239805160001a60731461002d57634e487b7160e01b600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600436106100ad5760003560e01c8063b646638411610080578063c0314d2711610065578063c0314d2714610184578063c8535ee814610197578063e48603391461019f57600080fd5b8063b646638414610149578063ba89214b1461017157600080fd5b80632b00490d146100b25780633e917414146100d85780637a84ba6f146101085780639182f36a14610129575b600080fd5b6100c56100c03660046123dc565b6101c2565b6040519081526020015b60405180910390f35b6100eb6100e6366004612415565b610403565b6040805193845260208401929092521515908201526060016100cf565b61011b610116366004612493565b61079e565b6040516100cf9291906124f8565b61013c610137366004612573565b610a4a565b6040516100cf9190612607565b61015c61015736600461261a565b61140e565b604080519283529015156020830152016100cf565b61015c61017f36600461265b565b611600565b6100c56101923660046123dc565b6116fe565b6100c5608081565b6101b26101ad3660046126ae565b6119a2565b6040516100cf9493929190612720565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301526000918491839182918291829182918291829182918a1690638e8f294b9060240161012060405180830381865afa158015610237573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061025b91906127f6565b9850985098505097509750975097509750600086116102db576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693400000000000000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b6000896001600160a01b031663ef78d4fd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561031b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061033f919061286d565b61035b906fffffffffffffffffffffffffffffffff85166128b5565b9050804210156103c2576103816fffffffffffffffffffffffffffffffff8316826128c8565b61039d6fffffffffffffffffffffffffffffffff8416426128c8565b6103a790866128db565b6103b191906128f2565b6103bb90856128c8565b93506103c7565b600093505b69d3c21bcecceda10000006103dc858a6128db565b6103e691906128f2565b6103f0908a6128b5565b9a50505050505050505050505b92915050565b600080600080869050600080600080846001600160a01b0316635f1c17c08b6040518263ffffffff1660e01b815260040161044091815260200190565b608060405180830381865afa15801561045d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610481919061292d565b935093509350935060008367ffffffffffffffff16116104fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693500000000000000000000000000000000000000000000000000000060448201526064016102d2565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015260009190871690638e8f294b9060240161012060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906127f6565b50505050945050505050856001600160a01b0316630b7210236040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105cd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f19190612982565b61060a5750600097509550508615935061079592505050565b6000866001600160a01b031663ef78d4fd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561064a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061066e919061286d565b9050600061068667ffffffffffffffff8716426128c8565b90508b1561073257818111156106ae5760008460009a509a509a505050505050505050610795565b60006106d08f858f8b6001600160a01b0316611ccb909392919063ffffffff16565b905060006106de83856128c8565b6106e885846128db565b6106f291906128f2565b905060008761070183896128db565b61070b91906128f2565b90508782101561072e578e9d509b5060019a506107959950505050505050505050565b5050505b6000828210610742576000610761565b8261074d83826128c8565b61075790886128db565b61076191906128f2565b90506107838f85838b6001600160a01b0316611dac909392919063ffffffff16565b9b50939950508a159750505050505050505b93509350939050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b038481166004830152606091829187916000918316906370a0823190602401602060405180830381865afa158015610807573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061082b919061286d565b9050608081111561083a575060805b8067ffffffffffffffff811115610853576108536129a4565b60405190808252806020026020018201604052801561087c578160200160208202803683370190505b5093508067ffffffffffffffff811115610898576108986129a4565b6040519080825280602002602001820160405280156108c1578160200160208202803683370190505b50925060005b81811015610a3e576040517f853394cf0000000000000000000000000000000000000000000000000000000081526001600160a01b038981166004830152602482018390526000919085169063853394cf90604401602060405180830381865afa158015610939573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095d919061286d565b6040517f5f1c17c0000000000000000000000000000000000000000000000000000000008152600481018290529091506000906001600160a01b03861690635f1c17c090602401608060405180830381865afa1580156109c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109e5919061292d565b93505050508160001b878481518110610a0057610a006129d3565b60200260200101818152505080868481518110610a1f57610a1f6129d3565b602002602001018181525050505080610a3790612a02565b90506108c7565b50505094509492505050565b60606000859050600080600080846001600160a01b0316635f1c17c08a6040518263ffffffff1660e01b8152600401610a8591815260200190565b608060405180830381865afa158015610aa2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ac6919061292d565b929650909450925090508367ffffffffffffffff8416610b42576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693100000000000000000000000000000000000000000000000000000060448201526064016102d2565b6000610bb8876001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b85573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ba99190612a3a565b6001600160a01b03163b151590565b15610d0c57866001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bfb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c1f9190612a3a565b6001600160a01b031663e70c20d96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c809190612a3a565b6001600160a01b0316639d63848a6040518163ffffffff1660e01b8152600401600060405180830381865afa925050508015610cde57506040513d6000823e601f3d908101601f19168201604052610cdb9190810190612a88565b60015b15610d0c578051600103610d0a5780600081518110610cff57610cff6129d3565b602002602001015191505b505b604080517f3c7376672077696474683d22353132220000000000000000000000000000000060208201527f206865696768743d22353132220000000000000000000000000000000000000060308201527f2066696c6c3d2200000000000000000000000000000000000000000000000000603d8201527f776869746500000000000000000000000000000000000000000000000000000060448201527f2220666f6e742d73697a653d223234220000000000000000000000000000000060498201527f20666f6e742d66616d696c793d224d6f6e6f737061636522000000000000000060598201527f20786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f7360718201527f7667223e0000000000000000000000000000000000000000000000000000000060918201527f3c7265637420783d22302220793d2230222077696474683d223130302522206860958201527f65696768743d223130302522207374796c653d2266696c6c3a0000000000000060b58201527f233038304334320000000000000000000000000000000000000000000000000060ce8201527f223c7376672f3e0000000000000000000000000000000000000000000000000060d5820152815160bc81830301815260dc909101909152806001600160a01b03831615610f6557826001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610f38573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610f609190810190612b3a565b610f76565b604051806020016040528060008152505b610f7f8e611e71565b604051602001610f9193929190612bea565b604051602081830303815290604052905080836001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015610fe1573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526110099190810190612b3a565b611088856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561104a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061106e9190612d1e565b61107990600a612e61565b61108390896128f2565b611e71565b6110c9866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561104a573d6000803e3d6000fd5b6040516020016110dc9493929190612e70565b60408051601f1981840301815291905290506001600160a01b038216156111875780826001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa15801561113c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111649190810190612b3a565b604051602001611175929190613007565b60405160208183030381529060405290505b8060405160200161119891906130b0565b604051602081830303815290604052905060006111bd886001600160a01b0316611f2f565b6111cf846001600160a01b0316611f2f565b6111e28967ffffffffffffffff16611e71565b6111eb89611e71565b6111f489611e71565b6040516020016112089594939291906130f1565b60408051601f19818403018152919052905060006001600160a01b0384161561129657836001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015611269573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526112919190810190612b3a565b6112a7565b604051806020016040528060008152505b6112b08f611e71565b866001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa1580156112ee573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526113169190810190612b3a565b6001600160a01b0387161561139057866001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015611363573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261138b9190810190612b3a565b6113a1565b604051806020016040528060008152505b6113aa87611f45565b866040516020016113c096959493929190613252565b60405160208183030381529060405290506113da81611f45565b6040516020016113ea9190613439565b6040516020818303038152906040529a50505050505050505050505b949350505050565b6000808483611479576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693200000000000000000000000000000000000000000000000000000060448201526064016102d2565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b0386811660048301526000918291829190851690638e8f294b9060240161012060405180830381865afa1580156114e1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061150591906127f6565b5050505094509450945050506000831161157b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693300000000000000000000000000000000000000000000000000000060448201526064016102d2565b60008082116115965761159188620f42406128db565b6115ab565b6115ab6001600160a01b038a168b848b611ccb565b905060006115b98b8b6101c2565b90506000816115d084670de0b6b3a76400006128db565b6115da91906128f2565b9050808682111580156115ed5750858211155b9850985050505050505050935093915050565b6040517f575313cd0000000000000000000000000000000000000000000000000000000081527f857df8276eded18697ff2b948da4c56efcb67270a1e761c2b4431f9afbe7497760048201526000908190839082906001600160a01b0383169063575313cd906024016040805180830381865afa158015611685573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116a9919061347e565b6bffffffffffffffffffffffff169150670de0b6b3a764000090506116ce82886128db565b6116d891906128f2565b6116e290876128c8565b95506116ef88888861140e565b93509350505094509492505050565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015260009184918391829182918291861690638e8f294b9060240161012060405180830381865afa15801561176b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061178f91906127f6565b985098505097509750505050506000826fffffffffffffffffffffffffffffffff1611611818576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693600000000000000000000000000000000000000000000000000000060448201526064016102d2565b846001600160a01b0316630b7210236040518163ffffffff1660e01b8152600401602060405180830381865afa158015611856573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061187a9190612982565b61188b5782955050505050506103fd565b6000856001600160a01b031663ef78d4fd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ef919061286d565b61190b906fffffffffffffffffffffffffffffffff85166128b5565b90508042101561197c576119316fffffffffffffffffffffffffffffffff8316826128c8565b61194d6fffffffffffffffffffffffffffffffff8416426128c8565b61195786886128c8565b61196191906128db565b61196b91906128f2565b61197590856128b5565b9350611980565b8493505b6119956001600160a01b0389168a8787611dac565b9998505050505050505050565b6060806060806000859050806001600160a01b0316639d63848a6040518163ffffffff1660e01b8152600401600060405180830381865afa1580156119eb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611a139190810190612a88565b9450845167ffffffffffffffff811115611a2f57611a2f6129a4565b604051908082528060200260200182016040528015611a6257816020015b6060815260200190600190039081611a4d5790505b509350845167ffffffffffffffff811115611a7f57611a7f6129a4565b604051908082528060200260200182016040528015611ab257816020015b6060815260200190600190039081611a9d5790505b509250845167ffffffffffffffff811115611acf57611acf6129a4565b604051908082528060200260200182016040528015611af8578160200160208202803683370190505b50915060005b8551811015611cc2576000868281518110611b1b57611b1b6129d3565b60200260200101519050806001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015611b63573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611b8b9190810190612b3a565b868381518110611b9d57611b9d6129d3565b6020026020010181905250806001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015611be6573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611c0e9190810190612b3a565b858381518110611c2057611c206129d3565b6020026020010181905250806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611c69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c8d9190612d1e565b848381518110611c9f57611c9f6129d3565b60ff9092166020928302919091019091015250611cbb81612a02565b9050611afe565b50509193509193565b600082600003611cdd57506000611406565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b038581166004830152600091908716906370a0823190602401602060405180830381865afa158015611d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d64919061286d565b9050611d726103e8826128db565b841015611d8d57611d856103e8846128db565b915050611406565b80611d9884866128db565b611da291906128f2565b9695505050505050565b600082600003611dbe57506000611406565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b038581166004830152600091908716906370a0823190602401602060405180830381865afa158015611e21573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e45919061286d565b9050611e536103e8826128db565b841015611e6657611d856103e8846128f2565b83611d9884836128db565b60606000611e7e83612098565b600101905060008167ffffffffffffffff811115611e9e57611e9e6129a4565b6040519080825280601f01601f191660200182016040528015611ec8576020820181803683370190505b5090508181016020015b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff017f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8504945084611ed257509392505050565b60606103fd6001600160a01b038316601461217a565b60608151600003611f6457505060408051602081019091526000815290565b60006040518060600160405280604081526020016134f46040913990506000600384516002611f9391906128b5565b611f9d91906128f2565b611fa89060046128db565b67ffffffffffffffff811115611fc057611fc06129a4565b6040519080825280601f01601f191660200182016040528015611fea576020820181803683370190505b509050600182016020820185865187015b80821015612056576003820191508151603f8160121c168501518453600184019350603f81600c1c168501518453600184019350603f8160061c168501518453600184019350603f8116850151845350600183019250611ffb565b505060038651066001811461207257600281146120855761208d565b603d6001830353603d600283035361208d565b603d60018303535b509195945050505050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083106120e1577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef8100000000831061210d576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061212b57662386f26fc10000830492506010015b6305f5e1008310612143576305f5e100830492506008015b612710831061215757612710830492506004015b60648310612169576064830492506002015b600a83106103fd5760010192915050565b606060006121898360026128db565b6121949060026128b5565b67ffffffffffffffff8111156121ac576121ac6129a4565b6040519080825280601f01601f1916602001820160405280156121d6576020820181803683370190505b5090507f30000000000000000000000000000000000000000000000000000000000000008160008151811061220d5761220d6129d3565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110612270576122706129d3565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060006122ac8460026128db565b6122b79060016128b5565b90505b6001811115612354577f303132333435363738396162636465660000000000000000000000000000000085600f16601081106122f8576122f86129d3565b1a60f81b82828151811061230e5761230e6129d3565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060049490941c9361234d816134be565b90506122ba565b5083156123bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016102d2565b9392505050565b6001600160a01b03811681146123d957600080fd5b50565b600080604083850312156123ef57600080fd5b82356123fa816123c4565b9150602083013561240a816123c4565b809150509250929050565b60008060006060848603121561242a57600080fd5b8335612435816123c4565b95602085013595506040909401359392505050565b60008083601f84011261245c57600080fd5b50813567ffffffffffffffff81111561247457600080fd5b60208301915083602082850101111561248c57600080fd5b9250929050565b600080600080606085870312156124a957600080fd5b84356124b4816123c4565b935060208501356124c4816123c4565b9250604085013567ffffffffffffffff8111156124e057600080fd5b6124ec8782880161244a565b95989497509550505050565b604080825283519082018190526000906020906060840190828701845b8281101561253157815184529284019290840190600101612515565b5050508381038285015284518082528583019183019060005b818110156125665783518352928401929184019160010161254a565b5090979650505050505050565b6000806000806060858703121561258957600080fd5b8435612594816123c4565b935060208501359250604085013567ffffffffffffffff8111156124e057600080fd5b60005b838110156125d25781810151838201526020016125ba565b50506000910152565b600081518084526125f38160208601602086016125b7565b601f01601f19169290920160200192915050565b6020815260006123bd60208301846125db565b60008060006060848603121561262f57600080fd5b833561263a816123c4565b9250602084013561264a816123c4565b929592945050506040919091013590565b6000806000806080858703121561267157600080fd5b843561267c816123c4565b9350602085013561268c816123c4565b92506040850135915060608501356126a3816123c4565b939692955090935050565b6000602082840312156126c057600080fd5b81356123bd816123c4565b600081518084526020808501808196508360051b8101915082860160005b858110156127135782840389526127018483516125db565b988501989350908401906001016126e9565b5091979650505050505050565b6080808252855190820181905260009060209060a0840190828901845b828110156127625781516001600160a01b03168452928401929084019060010161273d565b5050508381038285015261277681886126cb565b9050838103604085015261278a81876126cb565b8481036060860152855180825283870192509083019060005b818110156127c257835160ff16835292840192918401916001016127a3565b50909998505050505050505050565b80516fffffffffffffffffffffffffffffffff811681146127f157600080fd5b919050565b60008060008060008060008060006101208a8c03121561281557600080fd5b8951985060208a0151975060408a0151965060608a0151955060808a0151945060a08a0151935060c08a0151925061284f60e08b016127d1565b915061285e6101008b016127d1565b90509295985092959850929598565b60006020828403121561287f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156103fd576103fd612886565b818103818111156103fd576103fd612886565b80820281158282048414176103fd576103fd612886565b600082612928577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b6000806000806080858703121561294357600080fd5b845161294e816123c4565b602086015190945067ffffffffffffffff8116811461296c57600080fd5b6040860151606090960151949790965092505050565b60006020828403121561299457600080fd5b815180151581146123bd57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612a3357612a33612886565b5060010190565b600060208284031215612a4c57600080fd5b81516123bd816123c4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612a8057612a806129a4565b604052919050565b60006020808385031215612a9b57600080fd5b825167ffffffffffffffff80821115612ab357600080fd5b818501915085601f830112612ac757600080fd5b815181811115612ad957612ad96129a4565b8060051b9150612aea848301612a57565b8181529183018401918481019088841115612b0457600080fd5b938501935b83851015612b2e5784519250612b1e836123c4565b8282529385019390850190612b09565b98975050505050505050565b600060208284031215612b4c57600080fd5b815167ffffffffffffffff80821115612b6457600080fd5b818401915084601f830112612b7857600080fd5b815181811115612b8a57612b8a6129a4565b612b9d6020601f19601f84011601612a57565b9150808252856020828501011115612bb457600080fd5b612bc58160208401602086016125b7565b50949350505050565b60008151612be08185602086016125b7565b9290920192915050565b60008451612bfc8184602089016125b7565b80830190507f3c7465787420666f6e742d73697a653d22313030252220793d2231302522207881527f3d223525223e000000000000000000000000000000000000000000000000000060208201528451612c5d8160268401602089016125b7565b7f20426f6e6420506f736974696f6e3c2f746578743e0000000000000000000000602692909101918201527f3c7465787420666f6e742d73697a653d223830252220793d223138252220783d603b8201527f223525223e426f6e642049443a20000000000000000000000000000000000000605b8201528351612ce78160698401602088016125b7565b7f3c2f746578743e000000000000000000000000000000000000000000000000006069929091019182015260700195945050505050565b600060208284031215612d3057600080fd5b815160ff811681146123bd57600080fd5b600181815b80851115612d9a57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115612d8057612d80612886565b80851615612d8d57918102915b93841c9390800290612d46565b509250929050565b600082612db1575060016103fd565b81612dbe575060006103fd565b8160018114612dd45760028114612dde57612dfa565b60019150506103fd565b60ff841115612def57612def612886565b50506001821b6103fd565b5060208310610133831016604e8410600b8410161715612e1d575081810a6103fd565b612e278383612d41565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115612e5957612e59612886565b029392505050565b60006123bd60ff841683612da2565b60008551612e82818460208a016125b7565b80830190507f3c7465787420666f6e742d73697a653d223630252220793d223235252220783d81527f223525223e5072696e636970616c20746f6b656e3a200000000000000000000060208201528551612ee3816036840160208a016125b7565b8082019150507f3c2f746578743e000000000000000000000000000000000000000000000000008060368301527f3c7465787420666f6e742d73697a653d223630252220793d223330252220783d603d8301527f223525223e52656d61696e696e67207072696e636970616c3a20000000000000605d8301528551612f6f816077850160208a016125b7565b60779201918201527f3c7465787420666f6e742d73697a653d223630252220793d223335252220783d607e8201527f223525223e4f75747374616e64696e672064656274207368617265733a200000609e820152612ffc612fd360bc830186612bce565b7f3c2f746578743e00000000000000000000000000000000000000000000000000815260070190565b979650505050505050565b600083516130198184602088016125b7565b80830190507f3c7465787420666f6e742d73697a653d223630252220793d223430252220783d81527f223525223e52657761726420746f6b656e3a20000000000000000000000000006020820152835161307a8160338401602088016125b7565b7f3c2f746578743e0000000000000000000000000000000000000000000000000060339290910191820152603a01949350505050565b600082516130c28184602087016125b7565b7f3c2f7376673e0000000000000000000000000000000000000000000000000000920191825250600601919050565b7f7b227072696e636970616c5f61646472657373223a2200000000000000000000815260008651613129816016850160208b016125b7565b7f222c227265776172645f61646472657373223a22000000000000000000000000601691840191820152865161316681602a840160208b016125b7565b7f222c2274696d657374616d70223a000000000000000000000000000000000000602a929091019182015285516131a4816038840160208a016125b7565b7f2c227072696e636970616c5f736861726573223a0000000000000000000000006038929091019182015284516131e281604c8401602089016125b7565b7f2c22646562745f736861726573223a0000000000000000000000000000000000604c9290910191820152835161322081605b8401602088016125b7565b611995605b828401017f7d00000000000000000000000000000000000000000000000000000000000000815260010190565b7f7b226e616d65223a22000000000000000000000000000000000000000000000081526000875161328a816009850160208c016125b7565b7f20426f6e6420506f736974696f6e3a200000000000000000000000000000000060099184019182015287516132c7816019840160208c016125b7565b7f222c226465736372697074696f6e223a22426f6e6420706f736974696f6e2074601992909101918201527f68617420776173207075726368617365642077697468200000000000000000006039820152865161332b816050840160208b016125b7565b7f20616e642070617973206f757420696e20000000000000000000000000000000605092909101918201528551613369816061840160208a016125b7565b61342b6134026133fc6133d36133cd6061868801017f2e20506f776572656420627920475953522050726f746f636f6c2e222c22696d81527f616765223a22646174613a696d6167652f7376672b786d6c3b6261736536342c602082015260400190565b8a612bce565b7f222c2261747472696275746573223a00000000000000000000000000000000008152600f0190565b87612bce565b7f7d00000000000000000000000000000000000000000000000000000000000000815260010190565b9a9950505050505050505050565b7f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c00000081526000825161347181601d8501602087016125b7565b91909101601d0192915050565b6000806040838503121561349157600080fd5b825161349c816123c4565b60208401519092506bffffffffffffffffffffffff8116811461240a57600080fd5b6000816134cd576134cd612886565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fe4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2fa26469706673582212200524592495530a830e5427547ad410f3d2be93f04b76c6e4d886d60ac6a28dbb64736f6c63430008120033

Deployed Bytecode

0x738906151f76948cebc1cdcb556c38cc7d3bbbe61d30146080604052600436106100ad5760003560e01c8063b646638411610080578063c0314d2711610065578063c0314d2714610184578063c8535ee814610197578063e48603391461019f57600080fd5b8063b646638414610149578063ba89214b1461017157600080fd5b80632b00490d146100b25780633e917414146100d85780637a84ba6f146101085780639182f36a14610129575b600080fd5b6100c56100c03660046123dc565b6101c2565b6040519081526020015b60405180910390f35b6100eb6100e6366004612415565b610403565b6040805193845260208401929092521515908201526060016100cf565b61011b610116366004612493565b61079e565b6040516100cf9291906124f8565b61013c610137366004612573565b610a4a565b6040516100cf9190612607565b61015c61015736600461261a565b61140e565b604080519283529015156020830152016100cf565b61015c61017f36600461265b565b611600565b6100c56101923660046123dc565b6116fe565b6100c5608081565b6101b26101ad3660046126ae565b6119a2565b6040516100cf9493929190612720565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b0382811660048301526000918491839182918291829182918291829182918a1690638e8f294b9060240161012060405180830381865afa158015610237573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061025b91906127f6565b9850985098505097509750975097509750600086116102db576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693400000000000000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b6000896001600160a01b031663ef78d4fd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561031b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061033f919061286d565b61035b906fffffffffffffffffffffffffffffffff85166128b5565b9050804210156103c2576103816fffffffffffffffffffffffffffffffff8316826128c8565b61039d6fffffffffffffffffffffffffffffffff8416426128c8565b6103a790866128db565b6103b191906128f2565b6103bb90856128c8565b93506103c7565b600093505b69d3c21bcecceda10000006103dc858a6128db565b6103e691906128f2565b6103f0908a6128b5565b9a50505050505050505050505b92915050565b600080600080869050600080600080846001600160a01b0316635f1c17c08b6040518263ffffffff1660e01b815260040161044091815260200190565b608060405180830381865afa15801561045d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610481919061292d565b935093509350935060008367ffffffffffffffff16116104fd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693500000000000000000000000000000000000000000000000000000060448201526064016102d2565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015260009190871690638e8f294b9060240161012060405180830381865afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906127f6565b50505050945050505050856001600160a01b0316630b7210236040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105cd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105f19190612982565b61060a5750600097509550508615935061079592505050565b6000866001600160a01b031663ef78d4fd6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561064a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061066e919061286d565b9050600061068667ffffffffffffffff8716426128c8565b90508b1561073257818111156106ae5760008460009a509a509a505050505050505050610795565b60006106d08f858f8b6001600160a01b0316611ccb909392919063ffffffff16565b905060006106de83856128c8565b6106e885846128db565b6106f291906128f2565b905060008761070183896128db565b61070b91906128f2565b90508782101561072e578e9d509b5060019a506107959950505050505050505050565b5050505b6000828210610742576000610761565b8261074d83826128c8565b61075790886128db565b61076191906128f2565b90506107838f85838b6001600160a01b0316611dac909392919063ffffffff16565b9b50939950508a159750505050505050505b93509350939050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b038481166004830152606091829187916000918316906370a0823190602401602060405180830381865afa158015610807573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061082b919061286d565b9050608081111561083a575060805b8067ffffffffffffffff811115610853576108536129a4565b60405190808252806020026020018201604052801561087c578160200160208202803683370190505b5093508067ffffffffffffffff811115610898576108986129a4565b6040519080825280602002602001820160405280156108c1578160200160208202803683370190505b50925060005b81811015610a3e576040517f853394cf0000000000000000000000000000000000000000000000000000000081526001600160a01b038981166004830152602482018390526000919085169063853394cf90604401602060405180830381865afa158015610939573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061095d919061286d565b6040517f5f1c17c0000000000000000000000000000000000000000000000000000000008152600481018290529091506000906001600160a01b03861690635f1c17c090602401608060405180830381865afa1580156109c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109e5919061292d565b93505050508160001b878481518110610a0057610a006129d3565b60200260200101818152505080868481518110610a1f57610a1f6129d3565b602002602001018181525050505080610a3790612a02565b90506108c7565b50505094509492505050565b60606000859050600080600080846001600160a01b0316635f1c17c08a6040518263ffffffff1660e01b8152600401610a8591815260200190565b608060405180830381865afa158015610aa2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ac6919061292d565b929650909450925090508367ffffffffffffffff8416610b42576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693100000000000000000000000000000000000000000000000000000060448201526064016102d2565b6000610bb8876001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610b85573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ba99190612a3a565b6001600160a01b03163b151590565b15610d0c57866001600160a01b0316638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610bfb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c1f9190612a3a565b6001600160a01b031663e70c20d96040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c809190612a3a565b6001600160a01b0316639d63848a6040518163ffffffff1660e01b8152600401600060405180830381865afa925050508015610cde57506040513d6000823e601f3d908101601f19168201604052610cdb9190810190612a88565b60015b15610d0c578051600103610d0a5780600081518110610cff57610cff6129d3565b602002602001015191505b505b604080517f3c7376672077696474683d22353132220000000000000000000000000000000060208201527f206865696768743d22353132220000000000000000000000000000000000000060308201527f2066696c6c3d2200000000000000000000000000000000000000000000000000603d8201527f776869746500000000000000000000000000000000000000000000000000000060448201527f2220666f6e742d73697a653d223234220000000000000000000000000000000060498201527f20666f6e742d66616d696c793d224d6f6e6f737061636522000000000000000060598201527f20786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323030302f7360718201527f7667223e0000000000000000000000000000000000000000000000000000000060918201527f3c7265637420783d22302220793d2230222077696474683d223130302522206860958201527f65696768743d223130302522207374796c653d2266696c6c3a0000000000000060b58201527f233038304334320000000000000000000000000000000000000000000000000060ce8201527f223c7376672f3e0000000000000000000000000000000000000000000000000060d5820152815160bc81830301815260dc909101909152806001600160a01b03831615610f6557826001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015610f38573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610f609190810190612b3a565b610f76565b604051806020016040528060008152505b610f7f8e611e71565b604051602001610f9193929190612bea565b604051602081830303815290604052905080836001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015610fe1573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526110099190810190612b3a565b611088856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561104a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061106e9190612d1e565b61107990600a612e61565b61108390896128f2565b611e71565b6110c9866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801561104a573d6000803e3d6000fd5b6040516020016110dc9493929190612e70565b60408051601f1981840301815291905290506001600160a01b038216156111875780826001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa15801561113c573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111649190810190612b3a565b604051602001611175929190613007565b60405160208183030381529060405290505b8060405160200161119891906130b0565b604051602081830303815290604052905060006111bd886001600160a01b0316611f2f565b6111cf846001600160a01b0316611f2f565b6111e28967ffffffffffffffff16611e71565b6111eb89611e71565b6111f489611e71565b6040516020016112089594939291906130f1565b60408051601f19818403018152919052905060006001600160a01b0384161561129657836001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015611269573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526112919190810190612b3a565b6112a7565b604051806020016040528060008152505b6112b08f611e71565b866001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa1580156112ee573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526113169190810190612b3a565b6001600160a01b0387161561139057866001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015611363573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261138b9190810190612b3a565b6113a1565b604051806020016040528060008152505b6113aa87611f45565b866040516020016113c096959493929190613252565b60405160208183030381529060405290506113da81611f45565b6040516020016113ea9190613439565b6040516020818303038152906040529a50505050505050505050505b949350505050565b6000808483611479576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693200000000000000000000000000000000000000000000000000000060448201526064016102d2565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b0386811660048301526000918291829190851690638e8f294b9060240161012060405180830381865afa1580156114e1573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061150591906127f6565b5050505094509450945050506000831161157b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693300000000000000000000000000000000000000000000000000000060448201526064016102d2565b60008082116115965761159188620f42406128db565b6115ab565b6115ab6001600160a01b038a168b848b611ccb565b905060006115b98b8b6101c2565b90506000816115d084670de0b6b3a76400006128db565b6115da91906128f2565b9050808682111580156115ed5750858211155b9850985050505050505050935093915050565b6040517f575313cd0000000000000000000000000000000000000000000000000000000081527f857df8276eded18697ff2b948da4c56efcb67270a1e761c2b4431f9afbe7497760048201526000908190839082906001600160a01b0383169063575313cd906024016040805180830381865afa158015611685573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116a9919061347e565b6bffffffffffffffffffffffff169150670de0b6b3a764000090506116ce82886128db565b6116d891906128f2565b6116e290876128c8565b95506116ef88888861140e565b93509350505094509492505050565b6040517f8e8f294b0000000000000000000000000000000000000000000000000000000081526001600160a01b03828116600483015260009184918391829182918291861690638e8f294b9060240161012060405180830381865afa15801561176b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061178f91906127f6565b985098505097509750505050506000826fffffffffffffffffffffffffffffffff1611611818576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600560248201527f62736d693600000000000000000000000000000000000000000000000000000060448201526064016102d2565b846001600160a01b0316630b7210236040518163ffffffff1660e01b8152600401602060405180830381865afa158015611856573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061187a9190612982565b61188b5782955050505050506103fd565b6000856001600160a01b031663ef78d4fd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118cb573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ef919061286d565b61190b906fffffffffffffffffffffffffffffffff85166128b5565b90508042101561197c576119316fffffffffffffffffffffffffffffffff8316826128c8565b61194d6fffffffffffffffffffffffffffffffff8416426128c8565b61195786886128c8565b61196191906128db565b61196b91906128f2565b61197590856128b5565b9350611980565b8493505b6119956001600160a01b0389168a8787611dac565b9998505050505050505050565b6060806060806000859050806001600160a01b0316639d63848a6040518163ffffffff1660e01b8152600401600060405180830381865afa1580156119eb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611a139190810190612a88565b9450845167ffffffffffffffff811115611a2f57611a2f6129a4565b604051908082528060200260200182016040528015611a6257816020015b6060815260200190600190039081611a4d5790505b509350845167ffffffffffffffff811115611a7f57611a7f6129a4565b604051908082528060200260200182016040528015611ab257816020015b6060815260200190600190039081611a9d5790505b509250845167ffffffffffffffff811115611acf57611acf6129a4565b604051908082528060200260200182016040528015611af8578160200160208202803683370190505b50915060005b8551811015611cc2576000868281518110611b1b57611b1b6129d3565b60200260200101519050806001600160a01b03166306fdde036040518163ffffffff1660e01b8152600401600060405180830381865afa158015611b63573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611b8b9190810190612b3a565b868381518110611b9d57611b9d6129d3565b6020026020010181905250806001600160a01b03166395d89b416040518163ffffffff1660e01b8152600401600060405180830381865afa158015611be6573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052611c0e9190810190612b3a565b858381518110611c2057611c206129d3565b6020026020010181905250806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611c69573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c8d9190612d1e565b848381518110611c9f57611c9f6129d3565b60ff9092166020928302919091019091015250611cbb81612a02565b9050611afe565b50509193509193565b600082600003611cdd57506000611406565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b038581166004830152600091908716906370a0823190602401602060405180830381865afa158015611d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d64919061286d565b9050611d726103e8826128db565b841015611d8d57611d856103e8846128db565b915050611406565b80611d9884866128db565b611da291906128f2565b9695505050505050565b600082600003611dbe57506000611406565b6040517f70a082310000000000000000000000000000000000000000000000000000000081526001600160a01b038581166004830152600091908716906370a0823190602401602060405180830381865afa158015611e21573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e45919061286d565b9050611e536103e8826128db565b841015611e6657611d856103e8846128f2565b83611d9884836128db565b60606000611e7e83612098565b600101905060008167ffffffffffffffff811115611e9e57611e9e6129a4565b6040519080825280601f01601f191660200182016040528015611ec8576020820181803683370190505b5090508181016020015b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff017f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8504945084611ed257509392505050565b60606103fd6001600160a01b038316601461217a565b60608151600003611f6457505060408051602081019091526000815290565b60006040518060600160405280604081526020016134f46040913990506000600384516002611f9391906128b5565b611f9d91906128f2565b611fa89060046128db565b67ffffffffffffffff811115611fc057611fc06129a4565b6040519080825280601f01601f191660200182016040528015611fea576020820181803683370190505b509050600182016020820185865187015b80821015612056576003820191508151603f8160121c168501518453600184019350603f81600c1c168501518453600184019350603f8160061c168501518453600184019350603f8116850151845350600183019250611ffb565b505060038651066001811461207257600281146120855761208d565b603d6001830353603d600283035361208d565b603d60018303535b509195945050505050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083106120e1577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef8100000000831061210d576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc10000831061212b57662386f26fc10000830492506010015b6305f5e1008310612143576305f5e100830492506008015b612710831061215757612710830492506004015b60648310612169576064830492506002015b600a83106103fd5760010192915050565b606060006121898360026128db565b6121949060026128b5565b67ffffffffffffffff8111156121ac576121ac6129a4565b6040519080825280601f01601f1916602001820160405280156121d6576020820181803683370190505b5090507f30000000000000000000000000000000000000000000000000000000000000008160008151811061220d5761220d6129d3565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110612270576122706129d3565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060006122ac8460026128db565b6122b79060016128b5565b90505b6001811115612354577f303132333435363738396162636465660000000000000000000000000000000085600f16601081106122f8576122f86129d3565b1a60f81b82828151811061230e5761230e6129d3565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060049490941c9361234d816134be565b90506122ba565b5083156123bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e7460448201526064016102d2565b9392505050565b6001600160a01b03811681146123d957600080fd5b50565b600080604083850312156123ef57600080fd5b82356123fa816123c4565b9150602083013561240a816123c4565b809150509250929050565b60008060006060848603121561242a57600080fd5b8335612435816123c4565b95602085013595506040909401359392505050565b60008083601f84011261245c57600080fd5b50813567ffffffffffffffff81111561247457600080fd5b60208301915083602082850101111561248c57600080fd5b9250929050565b600080600080606085870312156124a957600080fd5b84356124b4816123c4565b935060208501356124c4816123c4565b9250604085013567ffffffffffffffff8111156124e057600080fd5b6124ec8782880161244a565b95989497509550505050565b604080825283519082018190526000906020906060840190828701845b8281101561253157815184529284019290840190600101612515565b5050508381038285015284518082528583019183019060005b818110156125665783518352928401929184019160010161254a565b5090979650505050505050565b6000806000806060858703121561258957600080fd5b8435612594816123c4565b935060208501359250604085013567ffffffffffffffff8111156124e057600080fd5b60005b838110156125d25781810151838201526020016125ba565b50506000910152565b600081518084526125f38160208601602086016125b7565b601f01601f19169290920160200192915050565b6020815260006123bd60208301846125db565b60008060006060848603121561262f57600080fd5b833561263a816123c4565b9250602084013561264a816123c4565b929592945050506040919091013590565b6000806000806080858703121561267157600080fd5b843561267c816123c4565b9350602085013561268c816123c4565b92506040850135915060608501356126a3816123c4565b939692955090935050565b6000602082840312156126c057600080fd5b81356123bd816123c4565b600081518084526020808501808196508360051b8101915082860160005b858110156127135782840389526127018483516125db565b988501989350908401906001016126e9565b5091979650505050505050565b6080808252855190820181905260009060209060a0840190828901845b828110156127625781516001600160a01b03168452928401929084019060010161273d565b5050508381038285015261277681886126cb565b9050838103604085015261278a81876126cb565b8481036060860152855180825283870192509083019060005b818110156127c257835160ff16835292840192918401916001016127a3565b50909998505050505050505050565b80516fffffffffffffffffffffffffffffffff811681146127f157600080fd5b919050565b60008060008060008060008060006101208a8c03121561281557600080fd5b8951985060208a0151975060408a0151965060608a0151955060808a0151945060a08a0151935060c08a0151925061284f60e08b016127d1565b915061285e6101008b016127d1565b90509295985092959850929598565b60006020828403121561287f57600080fd5b5051919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b808201808211156103fd576103fd612886565b818103818111156103fd576103fd612886565b80820281158282048414176103fd576103fd612886565b600082612928577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b6000806000806080858703121561294357600080fd5b845161294e816123c4565b602086015190945067ffffffffffffffff8116811461296c57600080fd5b6040860151606090960151949790965092505050565b60006020828403121561299457600080fd5b815180151581146123bd57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203612a3357612a33612886565b5060010190565b600060208284031215612a4c57600080fd5b81516123bd816123c4565b604051601f8201601f1916810167ffffffffffffffff81118282101715612a8057612a806129a4565b604052919050565b60006020808385031215612a9b57600080fd5b825167ffffffffffffffff80821115612ab357600080fd5b818501915085601f830112612ac757600080fd5b815181811115612ad957612ad96129a4565b8060051b9150612aea848301612a57565b8181529183018401918481019088841115612b0457600080fd5b938501935b83851015612b2e5784519250612b1e836123c4565b8282529385019390850190612b09565b98975050505050505050565b600060208284031215612b4c57600080fd5b815167ffffffffffffffff80821115612b6457600080fd5b818401915084601f830112612b7857600080fd5b815181811115612b8a57612b8a6129a4565b612b9d6020601f19601f84011601612a57565b9150808252856020828501011115612bb457600080fd5b612bc58160208401602086016125b7565b50949350505050565b60008151612be08185602086016125b7565b9290920192915050565b60008451612bfc8184602089016125b7565b80830190507f3c7465787420666f6e742d73697a653d22313030252220793d2231302522207881527f3d223525223e000000000000000000000000000000000000000000000000000060208201528451612c5d8160268401602089016125b7565b7f20426f6e6420506f736974696f6e3c2f746578743e0000000000000000000000602692909101918201527f3c7465787420666f6e742d73697a653d223830252220793d223138252220783d603b8201527f223525223e426f6e642049443a20000000000000000000000000000000000000605b8201528351612ce78160698401602088016125b7565b7f3c2f746578743e000000000000000000000000000000000000000000000000006069929091019182015260700195945050505050565b600060208284031215612d3057600080fd5b815160ff811681146123bd57600080fd5b600181815b80851115612d9a57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115612d8057612d80612886565b80851615612d8d57918102915b93841c9390800290612d46565b509250929050565b600082612db1575060016103fd565b81612dbe575060006103fd565b8160018114612dd45760028114612dde57612dfa565b60019150506103fd565b60ff841115612def57612def612886565b50506001821b6103fd565b5060208310610133831016604e8410600b8410161715612e1d575081810a6103fd565b612e278383612d41565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115612e5957612e59612886565b029392505050565b60006123bd60ff841683612da2565b60008551612e82818460208a016125b7565b80830190507f3c7465787420666f6e742d73697a653d223630252220793d223235252220783d81527f223525223e5072696e636970616c20746f6b656e3a200000000000000000000060208201528551612ee3816036840160208a016125b7565b8082019150507f3c2f746578743e000000000000000000000000000000000000000000000000008060368301527f3c7465787420666f6e742d73697a653d223630252220793d223330252220783d603d8301527f223525223e52656d61696e696e67207072696e636970616c3a20000000000000605d8301528551612f6f816077850160208a016125b7565b60779201918201527f3c7465787420666f6e742d73697a653d223630252220793d223335252220783d607e8201527f223525223e4f75747374616e64696e672064656274207368617265733a200000609e820152612ffc612fd360bc830186612bce565b7f3c2f746578743e00000000000000000000000000000000000000000000000000815260070190565b979650505050505050565b600083516130198184602088016125b7565b80830190507f3c7465787420666f6e742d73697a653d223630252220793d223430252220783d81527f223525223e52657761726420746f6b656e3a20000000000000000000000000006020820152835161307a8160338401602088016125b7565b7f3c2f746578743e0000000000000000000000000000000000000000000000000060339290910191820152603a01949350505050565b600082516130c28184602087016125b7565b7f3c2f7376673e0000000000000000000000000000000000000000000000000000920191825250600601919050565b7f7b227072696e636970616c5f61646472657373223a2200000000000000000000815260008651613129816016850160208b016125b7565b7f222c227265776172645f61646472657373223a22000000000000000000000000601691840191820152865161316681602a840160208b016125b7565b7f222c2274696d657374616d70223a000000000000000000000000000000000000602a929091019182015285516131a4816038840160208a016125b7565b7f2c227072696e636970616c5f736861726573223a0000000000000000000000006038929091019182015284516131e281604c8401602089016125b7565b7f2c22646562745f736861726573223a0000000000000000000000000000000000604c9290910191820152835161322081605b8401602088016125b7565b611995605b828401017f7d00000000000000000000000000000000000000000000000000000000000000815260010190565b7f7b226e616d65223a22000000000000000000000000000000000000000000000081526000875161328a816009850160208c016125b7565b7f20426f6e6420506f736974696f6e3a200000000000000000000000000000000060099184019182015287516132c7816019840160208c016125b7565b7f222c226465736372697074696f6e223a22426f6e6420706f736974696f6e2074601992909101918201527f68617420776173207075726368617365642077697468200000000000000000006039820152865161332b816050840160208b016125b7565b7f20616e642070617973206f757420696e20000000000000000000000000000000605092909101918201528551613369816061840160208a016125b7565b61342b6134026133fc6133d36133cd6061868801017f2e20506f776572656420627920475953522050726f746f636f6c2e222c22696d81527f616765223a22646174613a696d6167652f7376672b786d6c3b6261736536342c602082015260400190565b8a612bce565b7f222c2261747472696275746573223a00000000000000000000000000000000008152600f0190565b87612bce565b7f7d00000000000000000000000000000000000000000000000000000000000000815260010190565b9a9950505050505050505050565b7f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c00000081526000825161347181601d8501602087016125b7565b91909101601d0192915050565b6000806040838503121561349157600080fd5b825161349c816123c4565b60208401519092506bffffffffffffffffffffffff8116811461240a57600080fd5b6000816134cd576134cd612886565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff019056fe4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2fa26469706673582212200524592495530a830e5427547ad410f3d2be93f04b76c6e4d886d60ac6a28dbb64736f6c63430008120033

Block Transaction 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

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.