ETH Price: $2,884.89 (-5.96%)
Gas: 1 Gwei

Contract

0x690E382baD9016F688Cfc18AF306e1fDD6CB28E8
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Value
0x60806040147097632022-05-04 8:05:07795 days ago1651651507IN
 Create: SavingsContract_imusd_mainnet_22
0 ETH0.1567009345

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
SavingsContract_imusd_mainnet_22

Compiler Version
v0.5.16+commit.9c3226ce

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion
File 1 of 1 : imusd-mainnet-22.sol
pragma solidity 0.5.16;

/* is IERC20 */
interface IERC4626Vault {
    /// @notice The address of the underlying token used for the Vault uses for accounting, depositing, and withdrawing
    function asset() external view returns (address assetTokenAddress);

    /// @notice Total amount of the underlying asset that is “managed” by Vault
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @notice The amount of shares that the Vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.
     * @param assets The amount of underlying assets to be convert to vault shares.
     * @return shares The amount of vault shares converted from the underlying assets.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @notice The amount of assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.
     * @param shares The amount of vault shares to be converted to the underlying assets.
     * @return assets The amount of underlying assets converted from the vault shares.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @notice The maximum number of underlying assets that caller can deposit.
     * @param caller Account that the assets will be transferred from.
     * @return maxAssets The maximum amount of underlying assets the caller can deposit.
     */
    function maxDeposit(address caller) external view returns (uint256 maxAssets);

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.
     * @param assets The amount of underlying assets to be transferred.
     * @return shares The amount of vault shares that will be minted.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /**
     * @notice Mint vault shares to receiver by transferring exact amount of underlying asset tokens from the caller.
     * @param assets The amount of underlying assets to be transferred to the vault.
     * @param receiver The account that the vault shares will be minted to.
     * @return shares The amount of vault shares that were minted.
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /**
     * @notice The maximum number of vault shares that caller can mint.
     * @param caller Account that the underlying assets will be transferred from.
     * @return maxShares The maximum amount of vault shares the caller can mint.
     */
    function maxMint(address caller) external view returns (uint256 maxShares);

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.
     * @param shares The amount of vault shares to be minted.
     * @return assets The amount of underlying assests that will be transferred from the caller.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /**
     * @notice Mint exact amount of vault shares to the receiver by transferring enough underlying asset tokens from the caller.
     * @param shares The amount of vault shares to be minted.
     * @param receiver The account the vault shares will be minted to.
     * @return assets The amount of underlying assets that were transferred from the caller.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /**
     * @notice The maximum number of underlying assets that owner can withdraw.
     * @param owner Account that owns the vault shares.
     * @return maxAssets The maximum amount of underlying assets the owner can withdraw.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.
     * @param assets The amount of underlying assets to be withdrawn.
     * @return shares The amount of vault shares that will be burnt.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /**
     * @notice Burns enough vault shares from owner and transfers the exact amount of underlying asset tokens to the receiver.
     * @param assets The amount of underlying assets to be withdrawn from the vault.
     * @param receiver The account that the underlying assets will be transferred to.
     * @param owner Account that owns the vault shares to be burnt.
     * @return shares The amount of vault shares that were burnt.
     */
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) external returns (uint256 shares);

    /**
     * @notice The maximum number of shares an owner can redeem for underlying assets.
     * @param owner Account that owns the vault shares.
     * @return maxShares The maximum amount of shares the owner can redeem.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions.
     * @param shares The amount of vault shares to be burnt.
     * @return assets The amount of underlying assests that will transferred to the receiver.
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /**
     * @notice Burns exact amount of vault shares from owner and transfers the underlying asset tokens to the receiver.
     * @param shares The amount of vault shares to be burnt.
     * @param receiver The account the underlying assets will be transferred to.
     * @param owner The account that owns the vault shares to be burnt.
     * @return assets The amount of underlying assets that were transferred to the receiver.
     */
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) external returns (uint256 assets);

    /*///////////////////////////////////////////////////////////////
                                Events
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Emitted when caller has exchanged assets for shares, and transferred those shares to owner.
     *
     * Note It must be emitted when tokens are deposited into the Vault in ERC4626.mint or ERC4626.deposit methods.
     *
     */
    event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
    /**
     * @dev Emitted when sender has exchanged shares for assets, and transferred those assets to receiver.
     *
     * Note It must be emitted when shares are withdrawn from the Vault in ERC4626.redeem or ERC4626.withdraw methods.
     *
     */
    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );
}

interface IUnwrapper {
    // @dev Get bAssetOut status
    function getIsBassetOut(
        address _masset,
        bool _inputIsCredit,
        address _output
    ) external view returns (bool isBassetOut);

    /// @dev Estimate output
    function getUnwrapOutput(
        bool _isBassetOut,
        address _router,
        address _input,
        bool _inputIsCredit,
        address _output,
        uint256 _amount
    ) external view returns (uint256 output);

    /// @dev Unwrap and send
    function unwrapAndSend(
        bool _isBassetOut,
        address _router,
        address _input,
        address _output,
        uint256 _amount,
        uint256 _minAmountOut,
        address _beneficiary
    ) external returns (uint256 outputQuantity);
}

interface ISavingsManager {
    /** @dev Admin privs */
    function distributeUnallocatedInterest(address _mAsset) external;

    /** @dev Liquidator */
    function depositLiquidation(address _mAsset, uint256 _liquidation) external;

    /** @dev Liquidator */
    function collectAndStreamInterest(address _mAsset) external;

    /** @dev Public privs */
    function collectAndDistributeInterest(address _mAsset) external;
}

interface ISavingsContractV1 {
    function depositInterest(uint256 _amount) external;

    function depositSavings(uint256 _amount) external returns (uint256 creditsIssued);

    function redeem(uint256 _amount) external returns (uint256 massetReturned);

    function exchangeRate() external view returns (uint256);

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

interface ISavingsContractV4 {
    // DEPRECATED but still backwards compatible
    function redeem(uint256 _amount) external returns (uint256 massetReturned);

    function creditBalances(address) external view returns (uint256); // V1 & V2 (use balanceOf)

    // --------------------------------------------

    function depositInterest(uint256 _amount) external; // V1 & V2

    function depositSavings(uint256 _amount) external returns (uint256 creditsIssued); // V1 & V2

    function depositSavings(uint256 _amount, address _beneficiary)
        external
        returns (uint256 creditsIssued); // V2

    function redeemCredits(uint256 _amount) external returns (uint256 underlyingReturned); // V2

    function redeemUnderlying(uint256 _amount) external returns (uint256 creditsBurned); // V2

    function exchangeRate() external view returns (uint256); // V1 & V2

    function balanceOfUnderlying(address _user) external view returns (uint256 balance); // V2

    function underlyingToCredits(uint256 _credits) external view returns (uint256 underlying); // V2

    function creditsToUnderlying(uint256 _underlying) external view returns (uint256 credits); // V2

    // --------------------------------------------

    function redeemAndUnwrap(
        uint256 _amount,
        bool _isCreditAmt,
        uint256 _minAmountOut,
        address _output,
        address _beneficiary,
        address _router,
        bool _isBassetOut
    )
        external
        returns (
            uint256 creditsBurned,
            uint256 massetRedeemed,
            uint256 outputQuantity
        );

    function depositSavings(
        uint256 _underlying,
        address _beneficiary,
        address _referrer
    ) external returns (uint256 creditsIssued);

    // -------------------------------------------- V4
    function deposit(
        uint256 assets,
        address receiver,
        address referrer
    ) external returns (uint256 shares);

    function mint(
        uint256 shares,
        address receiver,
        address referrer
    ) external returns (uint256 assets);
}

/*
 * @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 GSN 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.
 */
contract Context {
    // Empty internal constructor, to prevent people from mistakenly deploying
    // an instance of this contract, which should be used via inheritance.
    constructor() internal {}

    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

    /**
     * @dev 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 Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

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

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

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

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()].sub(
                amount,
                "ERC20: transfer amount exceeds allowance"
            )
        );
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].sub(
                subtractedValue,
                "ERC20: decreased allowance below zero"
            )
        );
        return true;
    }

    /**
     * @dev Moves tokens `amount` from `sender` to `recipient`.
     *
     * This is internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `sender` cannot be the zero address.
     * - `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     */
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements
     *
     * - `to` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See {_burn} and {_approve}.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(
            account,
            _msgSender(),
            _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance")
        );
    }
}

contract InitializableERC20Detailed is IERC20 {
    string private _name;
    string private _symbol;
    uint8 private _decimals;

    /**
     * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
     * these values are immutable: they can only be set once during
     * construction.
     * @notice To avoid variable shadowing appended `Arg` after arguments name.
     */
    function _initialize(
        string memory nameArg,
        string memory symbolArg,
        uint8 decimalsArg
    ) internal {
        _name = nameArg;
        _symbol = symbolArg;
        _decimals = decimalsArg;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5,05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view returns (uint8) {
        return _decimals;
    }
}

contract InitializableToken is ERC20, InitializableERC20Detailed {
    /**
     * @dev Initialization function for implementing contract
     * @notice To avoid variable shadowing appended `Arg` after arguments name.
     */
    function _initialize(string memory _nameArg, string memory _symbolArg) internal {
        InitializableERC20Detailed._initialize(_nameArg, _symbolArg, 18);
    }
}

contract ModuleKeys {
    // Governance
    // ===========
    // keccak256("Governance");
    bytes32 internal constant KEY_GOVERNANCE =
        0x9409903de1e6fd852dfc61c9dacb48196c48535b60e25abf92acc92dd689078d;
    //keccak256("Staking");
    bytes32 internal constant KEY_STAKING =
        0x1df41cd916959d1163dc8f0671a666ea8a3e434c13e40faef527133b5d167034;
    //keccak256("ProxyAdmin");
    bytes32 internal constant KEY_PROXY_ADMIN =
        0x96ed0203eb7e975a4cbcaa23951943fa35c5d8288117d50c12b3d48b0fab48d1;

    // mStable
    // =======
    // keccak256("OracleHub");
    bytes32 internal constant KEY_ORACLE_HUB =
        0x8ae3a082c61a7379e2280f3356a5131507d9829d222d853bfa7c9fe1200dd040;
    // keccak256("Manager");
    bytes32 internal constant KEY_MANAGER =
        0x6d439300980e333f0256d64be2c9f67e86f4493ce25f82498d6db7f4be3d9e6f;
    //keccak256("Recollateraliser");
    bytes32 internal constant KEY_RECOLLATERALISER =
        0x39e3ed1fc335ce346a8cbe3e64dd525cf22b37f1e2104a755e761c3c1eb4734f;
    //keccak256("MetaToken");
    bytes32 internal constant KEY_META_TOKEN =
        0xea7469b14936af748ee93c53b2fe510b9928edbdccac3963321efca7eb1a57a2;
    // keccak256("SavingsManager");
    bytes32 internal constant KEY_SAVINGS_MANAGER =
        0x12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf1;
    // keccak256("Liquidator");
    bytes32 internal constant KEY_LIQUIDATOR =
        0x1e9cb14d7560734a61fa5ff9273953e971ff3cd9283c03d8346e3264617933d4;
}

interface INexus {
    function governor() external view returns (address);

    function getModule(bytes32 key) external view returns (address);

    function proposeModule(bytes32 _key, address _addr) external;

    function cancelProposedModule(bytes32 _key) external;

    function acceptProposedModule(bytes32 _key) external;

    function acceptProposedModules(bytes32[] calldata _keys) external;

    function requestLockModule(bytes32 _key) external;

    function cancelLockModule(bytes32 _key) external;

    function lockModule(bytes32 _key) external;
}

contract InitializableModule2 is ModuleKeys {
    INexus public constant nexus = INexus(0xAFcE80b19A8cE13DEc0739a1aaB7A028d6845Eb3);

    /**
     * @dev Modifier to allow function calls only from the Governor.
     */
    modifier onlyGovernor() {
        require(msg.sender == _governor(), "Only governor can execute");
        _;
    }

    /**
     * @dev Modifier to allow function calls only from the Governance.
     *      Governance is either Governor address or Governance address.
     */
    modifier onlyGovernance() {
        require(
            msg.sender == _governor() || msg.sender == _governance(),
            "Only governance can execute"
        );
        _;
    }

    /**
     * @dev Modifier to allow function calls only from the ProxyAdmin.
     */
    modifier onlyProxyAdmin() {
        require(msg.sender == _proxyAdmin(), "Only ProxyAdmin can execute");
        _;
    }

    /**
     * @dev Modifier to allow function calls only from the Manager.
     */
    modifier onlyManager() {
        require(msg.sender == _manager(), "Only manager can execute");
        _;
    }

    /**
     * @dev Returns Governor address from the Nexus
     * @return Address of Governor Contract
     */
    function _governor() internal view returns (address) {
        return nexus.governor();
    }

    /**
     * @dev Returns Governance Module address from the Nexus
     * @return Address of the Governance (Phase 2)
     */
    function _governance() internal view returns (address) {
        return nexus.getModule(KEY_GOVERNANCE);
    }

    /**
     * @dev Return Staking Module address from the Nexus
     * @return Address of the Staking Module contract
     */
    function _staking() internal view returns (address) {
        return nexus.getModule(KEY_STAKING);
    }

    /**
     * @dev Return ProxyAdmin Module address from the Nexus
     * @return Address of the ProxyAdmin Module contract
     */
    function _proxyAdmin() internal view returns (address) {
        return nexus.getModule(KEY_PROXY_ADMIN);
    }

    /**
     * @dev Return MetaToken Module address from the Nexus
     * @return Address of the MetaToken Module contract
     */
    function _metaToken() internal view returns (address) {
        return nexus.getModule(KEY_META_TOKEN);
    }

    /**
     * @dev Return OracleHub Module address from the Nexus
     * @return Address of the OracleHub Module contract
     */
    function _oracleHub() internal view returns (address) {
        return nexus.getModule(KEY_ORACLE_HUB);
    }

    /**
     * @dev Return Manager Module address from the Nexus
     * @return Address of the Manager Module contract
     */
    function _manager() internal view returns (address) {
        return nexus.getModule(KEY_MANAGER);
    }

    /**
     * @dev Return SavingsManager Module address from the Nexus
     * @return Address of the SavingsManager Module contract
     */
    function _savingsManager() internal view returns (address) {
        return nexus.getModule(KEY_SAVINGS_MANAGER);
    }

    /**
     * @dev Return Recollateraliser Module address from the Nexus
     * @return  Address of the Recollateraliser Module contract (Phase 2)
     */
    function _recollateraliser() internal view returns (address) {
        return nexus.getModule(KEY_RECOLLATERALISER);
    }
}

interface IConnector {
    /**
     * @notice Deposits the mAsset into the connector
     * @param _amount Units of mAsset to receive and deposit
     */
    function deposit(uint256 _amount) external;

    /**
     * @notice Withdraws a specific amount of mAsset from the connector
     * @param _amount Units of mAsset to withdraw
     */
    function withdraw(uint256 _amount) external;

    /**
     * @notice Withdraws all mAsset from the connector
     */
    function withdrawAll() external;

    /**
     * @notice Returns the available balance in the connector. In connections
     * where there is likely to be an initial dip in value due to conservative
     * exchange rates (e.g. with Curves `get_virtual_price`), it should return
     * max(deposited, balance) to avoid temporary negative yield. Any negative yield
     * should be corrected during a withdrawal or over time.
     * @return Balance of mAsset in the connector
     */
    function checkBalance() external view returns (uint256);
}

contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private initializing;

    /**
     * @dev Modifier to use in the initializer function of a contract.
     */
    modifier initializer() {
        require(
            initializing || isConstructor() || !initialized,
            "Contract instance has already been initialized"
        );

        bool isTopLevelCall = !initializing;
        if (isTopLevelCall) {
            initializing = true;
            initialized = true;
        }

        _;

        if (isTopLevelCall) {
            initializing = false;
        }
    }

    /// @dev Returns true if and only if the function is running in the constructor
    function isConstructor() private view returns (bool) {
        // extcodesize checks the size of the code stored in an address, and
        // address returns the current address. Since the code is still not
        // deployed when running a constructor, any checks on its code size will
        // yield zero, making it an effective way to detect if a contract is
        // under construction or not.
        address self = address(this);
        uint256 cs;
        assembly {
            cs := extcodesize(self)
        }
        return cs == 0;
    }

    // Reserved storage space to allow for layout changes in the future.
    uint256[50] private ______gap;
}

library StableMath {
    using SafeMath for uint256;

    /**
     * @dev Scaling unit for use in specific calculations,
     * where 1 * 10**18, or 1e18 represents a unit '1'
     */
    uint256 private constant FULL_SCALE = 1e18;

    /**
     * @notice Token Ratios are used when converting between units of bAsset, mAsset and MTA
     * Reasoning: Takes into account token decimals, and difference in base unit (i.e. grams to Troy oz for gold)
     * @dev bAsset ratio unit for use in exact calculations,
     * where (1 bAsset unit * bAsset.ratio) / ratioScale == x mAsset unit
     */
    uint256 private constant RATIO_SCALE = 1e8;

    /**
     * @dev Provides an interface to the scaling unit
     * @return Scaling unit (1e18 or 1 * 10**18)
     */
    function getFullScale() internal pure returns (uint256) {
        return FULL_SCALE;
    }

    /**
     * @dev Provides an interface to the ratio unit
     * @return Ratio scale unit (1e8 or 1 * 10**8)
     */
    function getRatioScale() internal pure returns (uint256) {
        return RATIO_SCALE;
    }

    /**
     * @dev Scales a given integer to the power of the full scale.
     * @param x   Simple uint256 to scale
     * @return    Scaled value a to an exact number
     */
    function scaleInteger(uint256 x) internal pure returns (uint256) {
        return x.mul(FULL_SCALE);
    }

    /***************************************
              PRECISE ARITHMETIC
    ****************************************/

    /**
     * @dev Multiplies two precise units, and then truncates by the full scale
     * @param x     Left hand input to multiplication
     * @param y     Right hand input to multiplication
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              scale unit
     */
    function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulTruncateScale(x, y, FULL_SCALE);
    }

    /**
     * @dev Multiplies two precise units, and then truncates by the given scale. For example,
     * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
     * @param x     Left hand input to multiplication
     * @param y     Right hand input to multiplication
     * @param scale Scale unit
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              scale unit
     */
    function mulTruncateScale(
        uint256 x,
        uint256 y,
        uint256 scale
    ) internal pure returns (uint256) {
        // e.g. assume scale = fullScale
        // z = 10e18 * 9e17 = 9e36
        uint256 z = x.mul(y);
        // return 9e38 / 1e18 = 9e18
        return z.div(scale);
    }

    /**
     * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
     * @param x     Left hand input to multiplication
     * @param y     Right hand input to multiplication
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              scale unit, rounded up to the closest base unit.
     */
    function mulTruncateCeil(uint256 x, uint256 y) internal pure returns (uint256) {
        // e.g. 8e17 * 17268172638 = 138145381104e17
        uint256 scaled = x.mul(y);
        // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
        uint256 ceil = scaled.add(FULL_SCALE.sub(1));
        // e.g. 13814538111.399...e18 / 1e18 = 13814538111
        return ceil.div(FULL_SCALE);
    }

    /**
     * @dev Precisely divides two units, by first scaling the left hand operand. Useful
     *      for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
     * @param x     Left hand input to division
     * @param y     Right hand input to division
     * @return      Result after multiplying the left operand by the scale, and
     *              executing the division on the right hand input.
     */
    function divPrecisely(uint256 x, uint256 y) internal pure returns (uint256) {
        // e.g. 8e18 * 1e18 = 8e36
        uint256 z = x.mul(FULL_SCALE);
        // e.g. 8e36 / 10e18 = 8e17
        return z.div(y);
    }

    /***************************************
                  RATIO FUNCS
    ****************************************/

    /**
     * @dev Multiplies and truncates a token ratio, essentially flooring the result
     *      i.e. How much mAsset is this bAsset worth?
     * @param x     Left hand operand to multiplication (i.e Exact quantity)
     * @param ratio bAsset ratio
     * @return      Result after multiplying the two inputs and then dividing by the ratio scale
     */
    function mulRatioTruncate(uint256 x, uint256 ratio) internal pure returns (uint256 c) {
        return mulTruncateScale(x, ratio, RATIO_SCALE);
    }

    /**
     * @dev Multiplies and truncates a token ratio, rounding up the result
     *      i.e. How much mAsset is this bAsset worth?
     * @param x     Left hand input to multiplication (i.e Exact quantity)
     * @param ratio bAsset ratio
     * @return      Result after multiplying the two inputs and then dividing by the shared
     *              ratio scale, rounded up to the closest base unit.
     */
    function mulRatioTruncateCeil(uint256 x, uint256 ratio) internal pure returns (uint256) {
        // e.g. How much mAsset should I burn for this bAsset (x)?
        // 1e18 * 1e8 = 1e26
        uint256 scaled = x.mul(ratio);
        // 1e26 + 9.99e7 = 100..00.999e8
        uint256 ceil = scaled.add(RATIO_SCALE.sub(1));
        // return 100..00.999e8 / 1e8 = 1e18
        return ceil.div(RATIO_SCALE);
    }

    /**
     * @dev Precisely divides two ratioed units, by first scaling the left hand operand
     *      i.e. How much bAsset is this mAsset worth?
     * @param x     Left hand operand in division
     * @param ratio bAsset ratio
     * @return      Result after multiplying the left operand by the scale, and
     *              executing the division on the right hand input.
     */
    function divRatioPrecisely(uint256 x, uint256 ratio) internal pure returns (uint256 c) {
        // e.g. 1e14 * 1e8 = 1e22
        uint256 y = x.mul(RATIO_SCALE);
        // return 1e22 / 1e12 = 1e10
        return y.div(ratio);
    }

    /***************************************
                    HELPERS
    ****************************************/

    /**
     * @dev Calculates minimum of two numbers
     * @param x     Left hand input
     * @param y     Right hand input
     * @return      Minimum of the two inputs
     */
    function min(uint256 x, uint256 y) internal pure returns (uint256) {
        return x > y ? y : x;
    }

    /**
     * @dev Calculated maximum of two numbers
     * @param x     Left hand input
     * @param y     Right hand input
     * @return      Maximum of the two inputs
     */
    function max(uint256 x, uint256 y) internal pure returns (uint256) {
        return x > y ? x : y;
    }

    /**
     * @dev Clamps a value to an upper bound
     * @param x           Left hand input
     * @param upperBound  Maximum possible value to return
     * @return            Input x clamped to a maximum value, upperBound
     */
    function clamp(uint256 x, uint256 upperBound) internal pure returns (uint256) {
        return x > upperBound ? upperBound : x;
    }
}

/**
 * @title   SavingsContract
 * @author  Stability Labs Pty. Ltd.
 * @notice  Savings contract uses the ever increasing "exchangeRate" to increase
 *          the value of the Savers "credits" (ERC20) relative to the amount of additional
 *          underlying collateral that has been deposited into this contract ("interest")
 * @dev     VERSION: 2.1
 *          DATE:    2021-11-25
 */
contract SavingsContract_imusd_mainnet_22 is
    ISavingsContractV1,
    ISavingsContractV4,
    IERC4626Vault,
    Initializable,
    InitializableToken,
    InitializableModule2
{
    using SafeMath for uint256;
    using StableMath for uint256;

    // Core events for depositing and withdrawing
    event ExchangeRateUpdated(uint256 newExchangeRate, uint256 interestCollected);
    event SavingsDeposited(address indexed saver, uint256 savingsDeposited, uint256 creditsIssued);
    event CreditsRedeemed(
        address indexed redeemer,
        uint256 creditsRedeemed,
        uint256 savingsCredited
    );

    event AutomaticInterestCollectionSwitched(bool automationEnabled);

    // Connector poking
    event PokerUpdated(address poker);

    event FractionUpdated(uint256 fraction);
    event ConnectorUpdated(address connector);
    event EmergencyUpdate();

    event Poked(uint256 oldBalance, uint256 newBalance, uint256 interestDetected);
    event PokedRaw();

    // Tracking events
    event Referral(address indexed referrer, address beneficiary, uint256 amount);

    // Rate between 'savings credits' and underlying
    // e.g. 1 credit (1e17) mulTruncate(exchangeRate) = underlying, starts at 10:1
    // exchangeRate increases over time
    uint256 private constant startingRate = 1e17;
    uint256 public exchangeRate;

    // Underlying asset is underlying
    IERC20 public constant underlying = IERC20(0xe2f2a5C287993345a840Db3B0845fbC70f5935a5);
    bool private automateInterestCollection;

    // Yield
    // Poker is responsible for depositing/withdrawing from connector
    address public poker;
    // Last time a poke was made
    uint256 public lastPoke;
    // Last known balance of the connector
    uint256 public lastBalance;
    // Fraction of capital assigned to the connector (100% = 1e18)
    uint256 public fraction;
    // Address of the current connector (all IConnectors are mStable validated)
    IConnector public connector;
    // How often do we allow pokes
    uint256 private constant POKE_CADENCE = 4 hours;
    // Max APY generated on the capital in the connector
    uint256 private constant MAX_APY = 4e18;
    uint256 private constant SECONDS_IN_YEAR = 365 days;
    // Proxy contract for easy redemption
    address public constant unwrapper = 0xc1443Cb9ce81915fB914C270d74B0D57D1c87be0;
    uint256 private constant MAX_INT256 = 2**256 - 1;

    // Add these constants to bytecode at deploytime
    function initialize(
        address _poker,
        string calldata _nameArg,
        string calldata _symbolArg
    ) external initializer {
        InitializableToken._initialize(_nameArg, _symbolArg);

        require(_poker != address(0), "Invalid poker address");
        poker = _poker;

        fraction = 2e17;
        automateInterestCollection = true;
        exchangeRate = startingRate;
    }

    /** @dev Only the savings managaer (pulled from Nexus) can execute this */
    modifier onlySavingsManager() {
        require(msg.sender == _savingsManager(), "Only savings manager can execute");
        _;
    }

    /***************************************
                    VIEW - E
    ****************************************/

    /**
     * @dev Returns the underlying balance of a given user
     * @param _user     Address of the user to check
     * @return balance  Units of underlying owned by the user
     */
    function balanceOfUnderlying(address _user) external view returns (uint256 balance) {
        (balance, ) = _creditsToUnderlying(balanceOf(_user));
    }

    /**
     * @dev Converts a given underlying amount into credits
     * @param _underlying  Units of underlying
     * @return credits     Credit units (a.k.a imUSD)
     */
    function underlyingToCredits(uint256 _underlying) external view returns (uint256 credits) {
        (credits, ) = _underlyingToCredits(_underlying);
    }

    /**
     * @dev Converts a given credit amount into underlying
     * @param _credits  Units of credits
     * @return amount   Corresponding underlying amount
     */
    function creditsToUnderlying(uint256 _credits) external view returns (uint256 amount) {
        (amount, ) = _creditsToUnderlying(_credits);
    }

    // Deprecated in favour of `balanceOf(address)`
    // Maintained for backwards compatibility
    // Returns the credit balance of a given user
    function creditBalances(address _user) external view returns (uint256) {
        return balanceOf(_user);
    }

    /***************************************
                    INTEREST
    ****************************************/

    /**
     * @dev Deposit interest (add to savings) and update exchange rate of contract.
     *      Exchange rate is calculated as the ratio between new savings q and credits:
     *                    exchange rate = savings / credits
     *
     * @param _amount   Units of underlying to add to the savings vault
     */
    function depositInterest(uint256 _amount) external onlySavingsManager {
        require(_amount > 0, "Must deposit something");

        // Transfer the interest from sender to here
        require(underlying.transferFrom(msg.sender, address(this), _amount), "Must receive tokens");

        // Calc new exchange rate, protect against initialisation case
        uint256 totalCredits = totalSupply();
        if (totalCredits > 0) {
            // new exchange rate is relationship between _totalCredits & totalSavings
            // _totalCredits * exchangeRate = totalSavings
            // exchangeRate = totalSavings/_totalCredits
            (uint256 totalCollat, ) = _creditsToUnderlying(totalCredits);
            uint256 newExchangeRate = _calcExchangeRate(totalCollat.add(_amount), totalCredits);
            exchangeRate = newExchangeRate;

            emit ExchangeRateUpdated(newExchangeRate, _amount);
        }
    }

    /** @dev Enable or disable the automation of fee collection during deposit process */
    function automateInterestCollectionFlag(bool _enabled) external onlyGovernor {
        automateInterestCollection = _enabled;
        emit AutomaticInterestCollectionSwitched(_enabled);
    }

    /***************************************
                    DEPOSIT
    ****************************************/

    /**
     * @dev During a migration period, allow savers to deposit underlying here before the interest has been redirected
     * @param _underlying      Units of underlying to deposit into savings vault
     * @param _beneficiary     Immediately transfer the imUSD token to this beneficiary address
     * @return creditsIssued   Units of credits (imUSD) issued
     */
    function preDeposit(uint256 _underlying, address _beneficiary)
        external
        returns (uint256 creditsIssued)
    {
        require(exchangeRate == startingRate, "Can only use this method before streaming begins");
        return _deposit(_underlying, _beneficiary, false);
    }

    /**
     * @dev Deposit the senders savings to the vault, and credit them internally with "credits".
     *      Credit amount is calculated as a ratio of deposit amount and exchange rate:
     *                    credits = underlying / exchangeRate
     *      We will first update the internal exchange rate by collecting any interest generated on the underlying.
     * @param _underlying      Units of underlying to deposit into savings vault
     * @return creditsIssued   Units of credits (imUSD) issued
     */
    function depositSavings(uint256 _underlying) external returns (uint256 creditsIssued) {
        return _deposit(_underlying, msg.sender, true);
    }

    /**
     * @dev Deposit the senders savings to the vault, and credit them internally with "credits".
     *      Credit amount is calculated as a ratio of deposit amount and exchange rate:
     *                    credits = underlying / exchangeRate
     *      We will first update the internal exchange rate by collecting any interest generated on the underlying.
     * @param _underlying      Units of underlying to deposit into savings vault
     * @param _beneficiary     Immediately transfer the imUSD token to this beneficiary address
     * @return creditsIssued   Units of credits (imUSD) issued
     */
    function depositSavings(uint256 _underlying, address _beneficiary)
        external
        returns (uint256 creditsIssued)
    {
        return _deposit(_underlying, _beneficiary, true);
    }

    /**
     * @dev Overloaded `depositSavings` method with an optional referrer address.
     * @param _underlying      Units of underlying to deposit into savings vault
     * @param _beneficiary     Immediately transfer the imUSD token to this beneficiary address
     * @param _referrer        Referrer address for this deposit
     * @return creditsIssued   Units of credits (imUSD) issued
     */
    function depositSavings(
        uint256 _underlying,
        address _beneficiary,
        address _referrer
    ) external returns (uint256 creditsIssued) {
        emit Referral(_referrer, _beneficiary, _underlying);
        return _deposit(_underlying, _beneficiary, true);
    }

    /**
     * @dev Internally deposit the _underlying from the sender and credit the beneficiary with new imUSD
     */
    function _deposit(
        uint256 _underlying,
        address _beneficiary,
        bool _collectInterest
    ) internal returns (uint256 creditsIssued) {
        creditsIssued = _transferAndMint(_underlying, _beneficiary, _collectInterest);
    }

    /***************************************
                    REDEEM
    ****************************************/

    // Deprecated in favour of redeemCredits
    // Maintaining backwards compatibility, this fn minimics the old redeem fn, in which
    // credits are redeemed but the interest from the underlying is not collected.
    function redeem(uint256 _credits) external returns (uint256 massetReturned) {
        require(_credits > 0, "Must withdraw something");

        (, uint256 payout) = _redeem(_credits, true, true);

        // Collect recent interest generated by basket and update exchange rate
        if (automateInterestCollection) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(underlying));
        }

        return payout;
    }

    /**
     * @dev Redeem specific number of the senders "credits" in exchange for underlying.
     *      Payout amount is calculated as a ratio of credits and exchange rate:
     *                    payout = credits * exchangeRate
     * @param _credits         Amount of credits to redeem
     * @return massetReturned  Units of underlying mAsset paid out
     */
    function redeemCredits(uint256 _credits) external returns (uint256 massetReturned) {
        require(_credits > 0, "Must withdraw something");

        // Collect recent interest generated by basket and update exchange rate
        if (automateInterestCollection) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(underlying));
        }

        (, uint256 payout) = _redeem(_credits, true, true);

        return payout;
    }

    /**
     * @dev Redeem credits into a specific amount of underlying.
     *      Credits needed to burn is calculated using:
     *                    credits = underlying / exchangeRate
     * @param _underlying     Amount of underlying to redeem
     * @return creditsBurned  Units of credits burned from sender
     */
    function redeemUnderlying(uint256 _underlying) external returns (uint256 creditsBurned) {
        require(_underlying > 0, "Must withdraw something");

        // Collect recent interest generated by basket and update exchange rate
        if (automateInterestCollection) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(underlying));
        }

        // Ensure that the payout was sufficient
        (uint256 credits, uint256 massetReturned) = _redeem(_underlying, false, true);
        require(massetReturned == _underlying, "Invalid output");

        return credits;
    }

    /**
     * @notice Redeem credits into a specific amount of underlying, unwrap
     *      into a selected output asset, and send to a beneficiary
     *      Credits needed to burn is calculated using:
     *                    credits = underlying / exchangeRate
     * @param _amount         Units to redeem (either underlying or credit amount).
     * @param _isCreditAmt    `true` if `amount` is in credits. eg imUSD. `false` if `amount` is in underlying. eg mUSD.
     * @param _minAmountOut   Minimum amount of `output` tokens to unwrap for. This is to the same decimal places as the `output` token.
     * @param _output         Asset to receive in exchange for the redeemed mAssets. This can be a bAsset or a fAsset. For example:
        - bAssets (USDC, DAI, sUSD or USDT) or fAssets (GUSD, BUSD, alUSD, FEI or RAI) for mainnet imUSD Vault.
        - bAssets (USDC, DAI or USDT) or fAsset FRAX for Polygon imUSD Vault.
        - bAssets (WBTC, sBTC or renBTC) or fAssets (HBTC or TBTCV2) for mainnet imBTC Vault.
     * @param _beneficiary    Address to send `output` tokens to.
     * @param _router         mAsset address if the output is a bAsset. Feeder Pool address if the output is a fAsset.
     * @param _isBassetOut    `true` if `output` is a bAsset. `false` if `output` is a fAsset.
     * @return creditsBurned  Units of credits burned from sender. eg imUSD or imBTC.
     * @return massetReturned Units of the underlying mAssets that were redeemed or swapped for the output tokens. eg mUSD or mBTC.
     * @return outputQuantity Units of `output` tokens sent to the beneficiary.
     */
    function redeemAndUnwrap(
        uint256 _amount,
        bool _isCreditAmt,
        uint256 _minAmountOut,
        address _output,
        address _beneficiary,
        address _router,
        bool _isBassetOut
    )
        external
        returns (
            uint256 creditsBurned,
            uint256 massetReturned,
            uint256 outputQuantity
        )
    {
        require(_amount > 0, "Must withdraw something");
        require(_output != address(0), "Output address is zero");
        require(_beneficiary != address(0), "Beneficiary address is zero");
        require(_router != address(0), "Router address is zero");

        // Collect recent interest generated by basket and update exchange rate
        if (automateInterestCollection) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(underlying));
        }

        // Ensure that the payout was sufficient
        (creditsBurned, massetReturned) = _redeem(_amount, _isCreditAmt, false);
        require(
            _isCreditAmt ? creditsBurned == _amount : massetReturned == _amount,
            "Invalid output"
        );

        // Approve wrapper to spend contract's underlying; just for this tx
        underlying.approve(unwrapper, massetReturned);

        // Unwrap the underlying into `output` and transfer to `beneficiary`
        outputQuantity = IUnwrapper(unwrapper).unwrapAndSend(
            _isBassetOut,
            _router,
            address(underlying),
            _output,
            massetReturned,
            _minAmountOut,
            _beneficiary
        );
    }

    /**
     * @dev Internally burn the credits and send the underlying to msg.sender
     */
    function _redeem(
        uint256 _amt,
        bool _isCreditAmt,
        bool _transferUnderlying
    ) internal returns (uint256 creditsBurned, uint256 massetReturned) {
        // Centralise credit <> underlying calcs and minimise SLOAD count
        uint256 credits_;
        uint256 underlying_;
        uint256 exchangeRate_;
        // If the input is a credit amt, then calculate underlying payout and cache the exchangeRate
        if (_isCreditAmt) {
            credits_ = _amt;
            (underlying_, exchangeRate_) = _creditsToUnderlying(_amt);
        }
        // If the input is in underlying, then calculate credits needed to burn
        else {
            underlying_ = _amt;
            (credits_, exchangeRate_) = _underlyingToCredits(_amt);
        }

        _burnTransfer(
            underlying_,
            credits_,
            msg.sender,
            msg.sender,
            exchangeRate_,
            _transferUnderlying
        );

        emit CreditsRedeemed(msg.sender, credits_, underlying_);

        return (credits_, underlying_);
    }

    struct ConnectorStatus {
        // Limit is the max amount of units allowed in the connector
        uint256 limit;
        // Derived balance of the connector
        uint256 inConnector;
    }

    /**
     * @dev Derives the units of collateral held in the connector
     * @param _data         Struct containing data on balances
     * @param _exchangeRate Current system exchange rate
     * @return status       Contains max amount of assets allowed in connector
     */
    function _getConnectorStatus(CachedData memory _data, uint256 _exchangeRate)
        internal
        pure
        returns (ConnectorStatus memory)
    {
        // Total units of underlying collateralised
        uint256 totalCollat = _data.totalCredits.mulTruncate(_exchangeRate);
        // Max amount of underlying that can be held in the connector
        uint256 limit = totalCollat.mulTruncate(_data.fraction.add(2e17));
        // Derives amount of underlying present in the connector
        uint256 inConnector = _data.rawBalance >= totalCollat
            ? 0
            : totalCollat.sub(_data.rawBalance);

        return ConnectorStatus(limit, inConnector);
    }

    /***************************************
                    YIELD - E
    ****************************************/

    /** @dev Modifier allowing only the designated poker to execute the fn */
    modifier onlyPoker() {
        require(msg.sender == poker, "Only poker can execute");
        _;
    }

    /**
     * @dev External poke function allows for the redistribution of collateral between here and the
     * current connector, setting the ratio back to the defined optimal.
     */
    function poke() external onlyPoker {
        CachedData memory cachedData = _cacheData();
        _poke(cachedData, false);
    }

    /**
     * @dev Governance action to set the address of a new poker
     * @param _newPoker     Address of the new poker
     */
    function setPoker(address _newPoker) external onlyGovernor {
        require(_newPoker != address(0) && _newPoker != poker, "Invalid poker");

        poker = _newPoker;

        emit PokerUpdated(_newPoker);
    }

    /**
     * @dev Governance action to set the percentage of assets that should be held
     * in the connector.
     * @param _fraction     Percentage of assets that should be held there (where 20% == 2e17)
     */
    function setFraction(uint256 _fraction) external onlyGovernor {
        require(_fraction <= 5e17, "Fraction must be <= 50%");

        fraction = _fraction;

        CachedData memory cachedData = _cacheData();
        _poke(cachedData, true);

        emit FractionUpdated(_fraction);
    }

    /**
     * @dev Governance action to set the address of a new connector, and move funds (if any) across.
     * @param _newConnector     Address of the new connector
     */
    function setConnector(address _newConnector) external onlyGovernor {
        // Withdraw all from previous by setting target = 0
        CachedData memory cachedData = _cacheData();
        cachedData.fraction = 0;
        _poke(cachedData, true);

        // Set new connector
        CachedData memory cachedDataNew = _cacheData();
        connector = IConnector(_newConnector);
        _poke(cachedDataNew, true);

        emit ConnectorUpdated(_newConnector);
    }

    /**
     * @dev Governance action to perform an emergency withdraw of the assets in the connector,
     * should it be the case that some or all of the liquidity is trapped in. This causes the total
     * collateral in the system to go down, causing a hard refresh.
     */
    function emergencyWithdraw(uint256 _withdrawAmount) external onlyGovernor {
        // withdraw _withdrawAmount from connection
        connector.withdraw(_withdrawAmount);

        // reset the connector
        connector = IConnector(address(0));
        emit ConnectorUpdated(address(0));

        // set fraction to 0
        fraction = 0;
        emit FractionUpdated(0);

        // check total collateralisation of credits
        CachedData memory data = _cacheData();
        // use rawBalance as the remaining liquidity in the connector is now written off
        _refreshExchangeRate(data.rawBalance, data.totalCredits, true);

        emit EmergencyUpdate();
    }

    /***************************************
                    YIELD - I
    ****************************************/

    /** @dev Internal poke function to keep the balance between connector and raw balance healthy */
    function _poke(CachedData memory _data, bool _ignoreCadence) internal {
        require(_data.totalCredits > 0, "Must have something to poke");

        // 1. Verify that poke cadence is valid, unless this is a manual action by governance
        uint256 currentTime = uint256(now);
        uint256 timeSinceLastPoke = currentTime.sub(lastPoke);
        require(_ignoreCadence || timeSinceLastPoke > POKE_CADENCE, "Not enough time elapsed");
        lastPoke = currentTime;

        // If there is a connector, check the balance and settle to the specified fraction %
        IConnector connector_ = connector;
        if (address(connector_) != address(0)) {
            // 2. Check and verify new connector balance
            uint256 lastBalance_ = lastBalance;
            uint256 connectorBalance = connector_.checkBalance();
            //      Always expect the collateral in the connector to increase in value
            require(connectorBalance >= lastBalance_, "Invalid yield");
            if (connectorBalance > 0) {
                //  Validate the collection by ensuring that the APY is not ridiculous
                _validateCollection(
                    connectorBalance,
                    connectorBalance.sub(lastBalance_),
                    timeSinceLastPoke
                );
            }

            // 3. Level the assets to Fraction (connector) & 100-fraction (raw)
            uint256 sum = _data.rawBalance.add(connectorBalance);
            uint256 ideal = sum.mulTruncate(_data.fraction);
            //     If there is not enough mAsset in the connector, then deposit
            if (ideal > connectorBalance) {
                uint256 deposit = ideal.sub(connectorBalance);
                underlying.approve(address(connector_), deposit);
                connector_.deposit(deposit);
            }
            //     Else withdraw, if there is too much mAsset in the connector
            else if (connectorBalance > ideal) {
                // If fraction == 0, then withdraw everything
                if (ideal == 0) {
                    connector_.withdrawAll();
                    sum = IERC20(underlying).balanceOf(address(this));
                } else {
                    connector_.withdraw(connectorBalance.sub(ideal));
                }
            }
            //     Else ideal == connectorBalance (e.g. 0), do nothing
            require(connector_.checkBalance() >= ideal, "Enforce system invariant");

            // 4i. Refresh exchange rate and emit event
            lastBalance = ideal;
            _refreshExchangeRate(sum, _data.totalCredits, false);
            emit Poked(lastBalance_, ideal, connectorBalance.sub(lastBalance_));
        } else {
            // 4ii. Refresh exchange rate and emit event
            lastBalance = 0;
            _refreshExchangeRate(_data.rawBalance, _data.totalCredits, false);
            emit PokedRaw();
        }
    }

    /**
     * @dev Internal fn to refresh the exchange rate, based on the sum of collateral and the number of credits
     * @param _realSum          Sum of collateral held by the contract
     * @param _totalCredits     Total number of credits in the system
     * @param _ignoreValidation This is for use in the emergency situation, and ignores a decreasing exchangeRate
     */
    function _refreshExchangeRate(
        uint256 _realSum,
        uint256 _totalCredits,
        bool _ignoreValidation
    ) internal {
        // Based on the current exchange rate, how much underlying is collateralised?
        (uint256 totalCredited, ) = _creditsToUnderlying(_totalCredits);

        // Require the amount of capital held to be greater than the previously credited units
        require(_ignoreValidation || _realSum >= totalCredited, "ExchangeRate must increase");
        // Work out the new exchange rate based on the current capital
        uint256 newExchangeRate = _calcExchangeRate(_realSum, _totalCredits);
        exchangeRate = newExchangeRate;

        emit ExchangeRateUpdated(
            newExchangeRate,
            _realSum > totalCredited ? _realSum.sub(totalCredited) : 0
        );
    }

    /**
     * FORKED DIRECTLY FROM SAVINGSMANAGER.sol
     * ---------------------------------------
     * @dev Validates that an interest collection does not exceed a maximum APY. If last collection
     * was under 30 mins ago, simply check it does not exceed 10bps
     * @param _newBalance              New balance of the underlying
     * @param _interest                Increase in total supply since last collection
     * @param _timeSinceLastCollection Seconds since last collection
     */
    function _validateCollection(
        uint256 _newBalance,
        uint256 _interest,
        uint256 _timeSinceLastCollection
    ) internal pure returns (uint256 extrapolatedAPY) {
        // Protect against division by 0
        uint256 protectedTime = StableMath.max(1, _timeSinceLastCollection);

        uint256 oldSupply = _newBalance.sub(_interest);
        uint256 percentageIncrease = _interest.divPrecisely(oldSupply);

        uint256 yearsSinceLastCollection = protectedTime.divPrecisely(SECONDS_IN_YEAR);

        extrapolatedAPY = percentageIncrease.divPrecisely(yearsSinceLastCollection);

        if (protectedTime > 30 minutes) {
            require(extrapolatedAPY < MAX_APY, "Interest protected from inflating past maxAPY");
        } else {
            require(percentageIncrease < 1e15, "Interest protected from inflating past 10 Bps");
        }
    }

    /***************************************
                    VIEW - I
    ****************************************/

    struct CachedData {
        // SLOAD from 'fraction'
        uint256 fraction;
        // ERC20 balance of underlying, held by this contract
        // underlying.balanceOf(address(this))
        uint256 rawBalance;
        // totalSupply()
        uint256 totalCredits;
    }

    /**
     * @dev Retrieves generic data to avoid duplicate SLOADs
     */
    function _cacheData() internal view returns (CachedData memory) {
        uint256 balance = underlying.balanceOf(address(this));
        return CachedData(fraction, balance, totalSupply());
    }

    /**
     * @dev Converts masset amount into credits based on exchange rate
     *               c = (masset / exchangeRate) + 1
     */
    function _underlyingToCredits(uint256 _underlying)
        internal
        view
        returns (uint256 credits, uint256 exchangeRate_)
    {
        // e.g. (1e20 * 1e18) / 1e18 = 1e20
        // e.g. (1e20 * 1e18) / 14e17 = 7.1429e19
        // e.g. 1 * 1e18 / 1e17 + 1 = 11 => 11 * 1e17 / 1e18 = 1.1e18 / 1e18 = 1
        exchangeRate_ = exchangeRate;
        credits = _underlying.divPrecisely(exchangeRate_).add(1);
    }

    /**
     * @dev Works out a new exchange rate, given an amount of collateral and total credits
     *               e = underlying / (credits-1)
     */
    function _calcExchangeRate(uint256 _totalCollateral, uint256 _totalCredits)
        internal
        pure
        returns (uint256 _exchangeRate)
    {
        _exchangeRate = _totalCollateral.divPrecisely(_totalCredits.sub(1));
    }

    /**
     * @dev Converts credit amount into masset based on exchange rate
     *               m = credits * exchangeRate
     */
    function _creditsToUnderlying(uint256 _credits)
        internal
        view
        returns (uint256 underlyingAmount, uint256 exchangeRate_)
    {
        // e.g. (1e20 * 1e18) / 1e18 = 1e20
        // e.g. (1e20 * 14e17) / 1e18 = 1.4e20
        exchangeRate_ = exchangeRate;
        underlyingAmount = _credits.mulTruncate(exchangeRate_);
    }

    /*///////////////////////////////////////////////////////////////
                                IERC4626Vault
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice it must be an ERC-20 token contract. Must not revert.
     *
     * @return assetTokenAddress the address of the underlying asset token. eg mUSD or mBTC
     */
    function asset() external view returns (address assetTokenAddress) {
        return address(underlying);
    }

    /**
     * @return totalManagedAssets the total amount of the underlying asset tokens that is “managed” by Vault.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets) {
        return underlying.balanceOf(address(this));
    }

    /**
     * @notice The amount of shares that the Vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.
     * @param assets The amount of underlying assets to be convert to vault shares.
     * @return shares The amount of vault shares converted from the underlying assets.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares) {
        (shares, ) = _underlyingToCredits(assets);
    }

    /**
     * @notice The amount of assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.
     * @param shares The amount of vault shares to be converted to the underlying assets.
     * @return assets The amount of underlying assets converted from the vault shares.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets) {
        (assets, ) = _creditsToUnderlying(shares);
    }

    /**
     * @notice The maximum number of underlying assets that caller can deposit.
     * caller Account that the assets will be transferred from.
     * @return maxAssets The maximum amount of underlying assets the caller can deposit.
     */
    function maxDeposit(
        address /** caller **/
    ) external view returns (uint256 maxAssets) {
        maxAssets = MAX_INT256;
    }

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.
     * @param assets The amount of underlying assets to be transferred.
     * @return shares The amount of vault shares that will be minted.
     */
    function previewDeposit(uint256 assets) external view returns (uint256 shares) {
        require(assets > 0, "Must deposit something");
        (shares, ) = _underlyingToCredits(assets);
    }

    /**
     * @notice Mint vault shares to receiver by transferring exact amount of underlying asset tokens from the caller.
     *      Credit amount is calculated as a ratio of deposit amount and exchange rate:
     *                    credits = underlying / exchangeRate
     *      We will first update the internal exchange rate by collecting any interest generated on the underlying.
     * Emits a {Deposit} event.
     * @param assets      Units of underlying to deposit into savings vault. eg mUSD or mBTC
     * @param receiver    The address to receive the Vault shares.
     * @return shares     Units of credits issued. eg imUSD or imBTC
     */
    function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
        shares = _transferAndMint(assets, receiver, true);
    }

    /**
     *
     * @notice Overloaded `deposit` method with an optional referrer address.
     * @param assets    Units of underlying to deposit into savings vault. eg mUSD or mBTC
     * @param receiver  Address to the new credits will be issued to.
     * @param referrer  Referrer address for this deposit.
     * @return shares   Units of credits issued. eg imUSD or imBTC
     */
    function deposit(
        uint256 assets,
        address receiver,
        address referrer
    ) external returns (uint256 shares) {
        shares = _transferAndMint(assets, receiver, true);
        emit Referral(referrer, receiver, assets);
    }

    /**
     * @notice The maximum number of vault shares that caller can mint.
     * caller Account that the underlying assets will be transferred from.
     * @return maxShares The maximum amount of vault shares the caller can mint.
     */
    function maxMint(
        address /* caller */
    ) external view returns (uint256 maxShares) {
        maxShares = MAX_INT256;
    }

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.
     * @param shares The amount of vault shares to be minted.
     * @return assets The amount of underlying assests that will be transferred from the caller.
     */
    function previewMint(uint256 shares) external view returns (uint256 assets) {
        (assets, ) = _creditsToUnderlying(shares);
        return assets;
    }

    /**
     * @notice Mint exact amount of vault shares to the receiver by transferring enough underlying asset tokens from the caller.
     * Emits a {Deposit} event.
     *
     * @param shares The amount of vault shares to be minted.
     * @param receiver The account the vault shares will be minted to.
     * @return assets The amount of underlying assets that were transferred from the caller.
     */
    function mint(uint256 shares, address receiver) external returns (uint256 assets) {
        (assets, ) = _creditsToUnderlying(shares);
        _transferAndMint(assets, receiver, true);
    }

    /**
     * @notice Mint exact amount of vault shares to the receiver by transferring enough underlying asset tokens from the caller.
     * @param shares The amount of vault shares to be minted.
     * @param receiver The account the vault shares will be minted to.
     * @param referrer  Referrer address for this deposit.
     * @return assets The amount of underlying assets that were transferred from the caller.
     * Emits a {Deposit}, {Referral} events
     */
    function mint(
        uint256 shares,
        address receiver,
        address referrer
    ) external returns (uint256 assets) {
        (assets, ) = _creditsToUnderlying(shares);
        _transferAndMint(assets, receiver, true);
        emit Referral(referrer, receiver, assets);
    }

    /**
     * @notice The maximum number of underlying assets that owner can withdraw.
     * @param owner Address that owns the underlying assets.
     * @return maxAssets The maximum amount of underlying assets the owner can withdraw.
     */
    function maxWithdraw(address owner) external view returns (uint256 maxAssets) {
        (maxAssets, ) = _creditsToUnderlying(balanceOf(owner));
    }

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.
     * @param assets The amount of underlying assets to be withdrawn.
     * @return shares The amount of vault shares that will be burnt.
     */
    function previewWithdraw(uint256 assets) external view returns (uint256 shares) {
        (shares, ) = _underlyingToCredits(assets);
    }

    /**
     * @notice Burns enough vault shares from owner and transfers the exact amount of underlying asset tokens to the receiver.
     * Emits a {Withdraw} event.
     *
     * @param assets The amount of underlying assets to be withdrawn from the vault.
     * @param receiver The account that the underlying assets will be transferred to.
     * @param owner Account that owns the vault shares to be burnt.
     * @return shares The amount of vault shares that were burnt.
     */
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) external returns (uint256 shares) {
        require(assets > 0, "Must withdraw something");
        uint256 _exchangeRate;
        if (automateInterestCollection) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(underlying));
        }
        (shares, _exchangeRate) = _underlyingToCredits(assets);

        _burnTransfer(assets, shares, receiver, owner, _exchangeRate, true);
    }

    /**
     * @notice it must return a limited value if owner is subject to some withdrawal limit or timelock. must return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. MAY be used in the previewRedeem or redeem methods for shares input parameter. must NOT revert.
     *
     * @param owner Address that owns the shares.
     * @return maxShares Total number of shares that owner can redeem.
     */
    function maxRedeem(address owner) external view returns (uint256 maxShares) {
        maxShares = balanceOf(owner);
    }

    /**
     * @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions.
     *
     * @return assets the exact amount of underlying assets that would be withdrawn by the caller if redeeming a given exact amount of Vault shares using the redeem method
     */
    function previewRedeem(uint256 shares) external view returns (uint256 assets) {
        (assets, ) = _creditsToUnderlying(shares);
        return assets;
    }

    /**
     * @notice Burns exact amount of vault shares from owner and transfers the underlying asset tokens to the receiver.
     * Emits a {Withdraw} event.
     *
     * @param shares The amount of vault shares to be burnt.
     * @param receiver The account the underlying assets will be transferred to.
     * @param owner The account that owns the vault shares to be burnt.
     * @return assets The amount of underlying assets that were transferred to the receiver.
     */
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) external returns (uint256 assets) {
        require(shares > 0, "Must withdraw something");
        uint256 _exchangeRate;
        if (automateInterestCollection) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(underlying));
        }
        (assets, _exchangeRate) = _creditsToUnderlying(shares);

        _burnTransfer(assets, shares, receiver, owner, _exchangeRate, true); //transferAssets=true
    }

    /*///////////////////////////////////////////////////////////////
                        INTERNAL DEPOSIT/MINT
    //////////////////////////////////////////////////////////////*/
    function _transferAndMint(
        uint256 assets,
        address receiver,
        bool _collectInterest
    ) internal returns (uint256 shares) {
        require(assets > 0, "Must deposit something");
        require(receiver != address(0), "Invalid beneficiary address");

        // Collect recent interest generated by basket and update exchange rate
        IERC20 mAsset = underlying;
        if (_collectInterest) {
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(mAsset));
        }

        // Transfer tokens from sender to here
        require(mAsset.transferFrom(msg.sender, address(this), assets), "Must receive tokens");

        // Calc how many credits they receive based on currentRatio
        (shares, ) = _underlyingToCredits(assets);

        // add credits to ERC20 balances
        _mint(receiver, shares);
        emit Deposit(msg.sender, receiver, assets, shares);
        emit SavingsDeposited(receiver, assets, shares);
    }

    /*///////////////////////////////////////////////////////////////
                        INTERNAL WITHDRAW/REDEEM
    //////////////////////////////////////////////////////////////*/

    function _burnTransfer(
        uint256 assets,
        uint256 shares,
        address receiver,
        address owner,
        uint256 _exchangeRate,
        bool transferAssets
    ) internal {
        require(receiver != address(0), "Invalid beneficiary address");

        // If caller is not the owner of the shares
        uint256 allowed = allowance(owner, msg.sender);
        if (msg.sender != owner && allowed != MAX_INT256) {
            require(shares <= allowed, "Amount exceeds allowance");
            _approve(owner, msg.sender, allowed - shares);
        }

        // Burn required shares from the owner FIRST
        _burn(owner, shares);

        // Optionally, transfer tokens from here to receiver
        if (transferAssets) {
            require(underlying.transfer(receiver, assets), "Must send tokens");
            emit Withdraw(msg.sender, receiver, owner, assets, shares);
        }
        // If this withdrawal pushes the portion of stored collateral in the `connector` over a certain
        // threshold (fraction + 20%), then this should trigger a _poke on the connector. This is to avoid
        // a situation in which there is a rush on withdrawals for some reason, causing the connector
        // balance to go up and thus having too large an exposure.
        CachedData memory cachedData = _cacheData();
        ConnectorStatus memory status = _getConnectorStatus(cachedData, _exchangeRate);
        if (status.inConnector > status.limit) {
            _poke(cachedData, false);
        }
    }
}

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

Contract Security Audit

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"automationEnabled","type":"bool"}],"name":"AutomaticInterestCollectionSwitched","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"connector","type":"address"}],"name":"ConnectorUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"redeemer","type":"address"},{"indexed":false,"internalType":"uint256","name":"creditsRedeemed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"savingsCredited","type":"uint256"}],"name":"CreditsRedeemed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[],"name":"EmergencyUpdate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newExchangeRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"interestCollected","type":"uint256"}],"name":"ExchangeRateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"fraction","type":"uint256"}],"name":"FractionUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"interestDetected","type":"uint256"}],"name":"Poked","type":"event"},{"anonymous":false,"inputs":[],"name":"PokedRaw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"poker","type":"address"}],"name":"PokerUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"referrer","type":"address"},{"indexed":false,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Referral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"saver","type":"address"},{"indexed":false,"internalType":"uint256","name":"savingsDeposited","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"creditsIssued","type":"uint256"}],"name":"SavingsDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"assets","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"shares","type":"uint256"}],"name":"Withdraw","type":"event"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"asset","outputs":[{"internalType":"address","name":"assetTokenAddress","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bool","name":"_enabled","type":"bool"}],"name":"automateInterestCollectionFlag","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"balanceOfUnderlying","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"connector","outputs":[{"internalType":"contract IConnector","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"convertToShares","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"creditBalances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"_credits","type":"uint256"}],"name":"creditsToUnderlying","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"referrer","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"depositInterest","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_underlying","type":"uint256"},{"internalType":"address","name":"_beneficiary","type":"address"},{"internalType":"address","name":"_referrer","type":"address"}],"name":"depositSavings","outputs":[{"internalType":"uint256","name":"creditsIssued","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_underlying","type":"uint256"},{"internalType":"address","name":"_beneficiary","type":"address"}],"name":"depositSavings","outputs":[{"internalType":"uint256","name":"creditsIssued","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_underlying","type":"uint256"}],"name":"depositSavings","outputs":[{"internalType":"uint256","name":"creditsIssued","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_withdrawAmount","type":"uint256"}],"name":"emergencyWithdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"exchangeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"fraction","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_poker","type":"address"},{"internalType":"string","name":"_nameArg","type":"string"},{"internalType":"string","name":"_symbolArg","type":"string"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"lastBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"lastPoke","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxDeposit","outputs":[{"internalType":"uint256","name":"maxAssets","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"maxMint","outputs":[{"internalType":"uint256","name":"maxShares","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxRedeem","outputs":[{"internalType":"uint256","name":"maxShares","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"maxWithdraw","outputs":[{"internalType":"uint256","name":"maxAssets","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"referrer","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"nexus","outputs":[{"internalType":"contract INexus","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"poke","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"poker","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_underlying","type":"uint256"},{"internalType":"address","name":"_beneficiary","type":"address"}],"name":"preDeposit","outputs":[{"internalType":"uint256","name":"creditsIssued","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewMint","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"name":"previewRedeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"assets","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_credits","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"massetReturned","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bool","name":"_isCreditAmt","type":"bool"},{"internalType":"uint256","name":"_minAmountOut","type":"uint256"},{"internalType":"address","name":"_output","type":"address"},{"internalType":"address","name":"_beneficiary","type":"address"},{"internalType":"address","name":"_router","type":"address"},{"internalType":"bool","name":"_isBassetOut","type":"bool"}],"name":"redeemAndUnwrap","outputs":[{"internalType":"uint256","name":"creditsBurned","type":"uint256"},{"internalType":"uint256","name":"massetReturned","type":"uint256"},{"internalType":"uint256","name":"outputQuantity","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_credits","type":"uint256"}],"name":"redeemCredits","outputs":[{"internalType":"uint256","name":"massetReturned","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_underlying","type":"uint256"}],"name":"redeemUnderlying","outputs":[{"internalType":"uint256","name":"creditsBurned","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_newConnector","type":"address"}],"name":"setConnector","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_fraction","type":"uint256"}],"name":"setFraction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_newPoker","type":"address"}],"name":"setPoker","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"totalManagedAssets","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"underlying","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"uint256","name":"_underlying","type":"uint256"}],"name":"underlyingToCredits","outputs":[{"internalType":"uint256","name":"credits","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"unwrapper","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"assets","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"shares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]

6080604052613e0d806100136000396000f3fe608060405234801561001057600080fd5b506004361061038d5760003560e01c806370a08231116101de578063ba0876521161010f578063d8894bb5116100ad578063dd62ed3e1161007c578063dd62ed3e14610a2f578063ef8b30f714610a5d578063f25816b514610a7a578063f4430dd814610aea5761038d565b8063d8894bb5146109d6578063d905777e146106fa578063da39b3e7146109de578063db006a7514610a125761038d565b8063c6e6f592116100e9578063c6e6f592146104b2578063c9f18ebd1461099a578063cbe54173146109b7578063ce96cb77146106555761038d565b8063ba0876521461095e578063c4ec22ad14610992578063c63d75b6146106835761038d565b806395d89b411161017c578063a457c2d711610156578063a457c2d7146108d2578063a9059cbb146108fe578063b3d7f6b914610455578063b460af941461092a5761038d565b806395d89b41146108a5578063a3f5c1d2146108ad578063a453b6cf146108b55761038d565b8063852a12e3116101b8578063852a12e3146107825780638f1c56bd1461079f57806390657147146107a757806394bf804d146108795761038d565b806370a08231146107545780637781b05a1461045557806383f3084f1461077a5761038d565b80632e2d2984116102c357806343e38da811610261578063590745c511610230578063590745c5146106ce5780636009a2d8146106fa5780636e553f65146107205780636f307dc31461074c5761038d565b806343e38da8146104b25780634b6bdf1d146106a95780634cdad506146104555780635312ea8e146106b15761038d565b8063395093511161029d57806339509351146106295780633af9e669146106555780633ba0b9a91461067b578063402d267d146106835761038d565b80632e2d2984146105cf578063313ce5671461060357806338d52e0f146106215761038d565b806318160ddd116103305780631dcfcb461161030a5780631dcfcb46146105245780631fcd9cc914610558578063220c5bbb1461057c57806323b872dd146105995761038d565b806318160ddd146104f757806318178358146104ff578063182815b0146105075761038d565b806307a2d13a1161036c57806307a2d13a14610455578063095ea7b3146104725780630a28a477146104b257806310188aef146104cf5761038d565b80625745991461039257806301e1d114146103d057806306fdde03146103d8575b600080fd5b6103be600480360360408110156103a857600080fd5b50803590602001356001600160a01b0316610b10565b60408051918252519081900360200190f35b6103be610b6f565b6103e0610bf0565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561041a578181015183820152602001610402565b50505050905090810190601f1680156104475780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103be6004803603602081101561046b57600080fd5b5035610c86565b61049e6004803603604081101561048857600080fd5b506001600160a01b038135169060200135610c98565b604080519115158252519081900360200190f35b6103be600480360360208110156104c857600080fd5b5035610cb5565b6104f5600480360360208110156104e557600080fd5b50356001600160a01b0316610cc0565b005b6103be610db5565b6104f5610dbb565b6104f56004803603602081101561051d57600080fd5b5035610e38565b6103be6004803603606081101561053a57600080fd5b508035906001600160a01b0360208201358116916040013516610f49565b610560610fa7565b604080516001600160a01b039092168252519081900360200190f35b6104f56004803603602081101561059257600080fd5b5035610fbf565b61049e600480360360608110156105af57600080fd5b506001600160a01b038135811691602081013590911690604001356111cc565b6103be600480360360608110156105e557600080fd5b508035906001600160a01b0360208201358116916040013516611259565b61060b6112b9565b6040805160ff9092168252519081900360200190f35b6105606112c2565b61049e6004803603604081101561063f57600080fd5b506001600160a01b0381351690602001356112d4565b6103be6004803603602081101561066b57600080fd5b50356001600160a01b0316611328565b6103be61133b565b6103be6004803603602081101561069957600080fd5b50356001600160a01b0316611341565b610560611348565b6104f5600480360360208110156106c757600080fd5b503561135c565b6103be600480360360408110156106e457600080fd5b50803590602001356001600160a01b03166114ec565b6103be6004803603602081101561071057600080fd5b50356001600160a01b03166114fa565b6103be6004803603604081101561073657600080fd5b50803590602001356001600160a01b0316611505565b610560611513565b6103be6004803603602081101561076a57600080fd5b50356001600160a01b0316611525565b610560611540565b6103be6004803603602081101561079857600080fd5b503561154f565b6103be61167d565b6104f5600480360360608110156107bd57600080fd5b6001600160a01b0382351691908101906040810160208201356401000000008111156107e857600080fd5b8201836020820111156107fa57600080fd5b8035906020019184600183028401116401000000008311171561081c57600080fd5b91939092909160208101903564010000000081111561083a57600080fd5b82018360208201111561084c57600080fd5b8035906020019184600183028401116401000000008311171561086e57600080fd5b509092509050611683565b6103be6004803603604081101561088f57600080fd5b50803590602001356001600160a01b0316611831565b6103e061184b565b6105606118ac565b6103be600480360360208110156108cb57600080fd5b50356118c4565b61049e600480360360408110156108e857600080fd5b506001600160a01b0381351690602001356118d2565b61049e6004803603604081101561091457600080fd5b506001600160a01b038135169060200135611940565b6103be6004803603606081101561094057600080fd5b508035906001600160a01b0360208201358116916040013516611954565b6103be6004803603606081101561097457600080fd5b508035906001600160a01b0360208201358116916040013516611a51565b6103be611b46565b6103be600480360360208110156109b057600080fd5b5035611b4c565b6104f5600480360360208110156109cd57600080fd5b50351515611c2f565b6103be611cd1565b6103be600480360360608110156109f457600080fd5b508035906001600160a01b0360208201358116916040013516611cd7565b6103be60048036036020811015610a2857600080fd5b5035611d3f565b6103be60048036036040811015610a4557600080fd5b506001600160a01b0381358116916020013516611e2c565b6103be60048036036020811015610a7357600080fd5b5035611e57565b610acc600480360360e0811015610a9057600080fd5b5080359060208101351515906040810135906001600160a01b036060820135811691608081013582169160a0820135169060c001351515611eaf565b60408051938452602084019290925282820152519081900360600190f35b6104f560048036036020811015610b0057600080fd5b50356001600160a01b0316612255565b600067016345785d8a000060395414610b5a5760405162461bcd60e51b8152600401808060200182810382526030815260200180613b7f6030913960400191505060405180910390fd5b610b6683836000612378565b90505b92915050565b604080516370a0823160e01b81523060048201529051600091600080516020613c66833981519152916370a0823191602480820192602092909190829003018186803b158015610bbe57600080fd5b505afa158015610bd2573d6000803e3d6000fd5b505050506040513d6020811015610be857600080fd5b505190505b90565b60368054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610c7c5780601f10610c5157610100808354040283529160200191610c7c565b820191906000526020600020905b815481529060010190602001808311610c5f57829003601f168201915b5050505050905090565b6000610c9182612385565b5092915050565b6000610cac610ca56123a2565b84846123a6565b50600192915050565b6000610c9182612492565b610cc86124b9565b6001600160a01b0316336001600160a01b031614610d1b576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b610d23613a88565b610d2b612508565b600081529050610d3c8160016125b6565b610d44613a88565b610d4c612508565b603e80546001600160a01b0319166001600160a01b0386161790559050610d748160016125b6565b604080516001600160a01b038516815290517fa69fcd183e7a93ec06820fdd33d015efbd84f4363ecd4c0ca0405c1220888a129181900360200190a1505050565b60355490565b603a5461010090046001600160a01b03163314610e18576040805162461bcd60e51b81526020600482015260166024820152754f6e6c7920706f6b65722063616e206578656375746560501b604482015290519081900360640190fd5b610e20613a88565b610e28612508565b9050610e358160006125b6565b50565b610e406124b9565b6001600160a01b0316336001600160a01b031614610e93576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b6706f05b59d3b20000811115610ef0576040805162461bcd60e51b815260206004820152601760248201527f4672616374696f6e206d757374206265203c3d20353025000000000000000000604482015290519081900360640190fd5b603d819055610efd613a88565b610f05612508565b9050610f128160016125b6565b6040805183815290517f9e73ab667f43467c2a324114c461b2bd6fcf80f8e92918085a459d67438d62f39181900360200190a15050565b604080516001600160a01b038481168252602082018690528251600093918516927faeecfcda1271d292db728294b8ae465871ec039d51404caf49a7eb0ade51770a92908290030190a2610f9f84846001612378565b949350505050565b73c1443cb9ce81915fb914c270d74b0d57d1c87be081565b610fc7612b76565b6001600160a01b0316336001600160a01b03161461102c576040805162461bcd60e51b815260206004820181905260248201527f4f6e6c7920736176696e6773206d616e616765722063616e2065786563757465604482015290519081900360640190fd5b6000811161107a576040805162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b604482015290519081900360640190fd5b604080516323b872dd60e01b8152336004820152306024820152604481018390529051600080516020613c66833981519152916323b872dd9160648083019260209291908290030181600087803b1580156110d457600080fd5b505af11580156110e8573d6000803e3d6000fd5b505050506040513d60208110156110fe57600080fd5b5051611147576040805162461bcd60e51b81526020600482015260136024820152724d757374207265636569766520746f6b656e7360681b604482015290519081900360640190fd5b6000611151610db5565b905080156111c857600061116482612385565b509050600061118261117c838663ffffffff612beb16565b84612c45565b6039819055604080518281526020810187905281519293507fc8d1043f24843c0a1c9251fdc30017d84e87498fbcf232af9f86816b5e182bde929081900390910190a150505b5050565b60006111d9848484612c68565b61124f846111e56123a2565b61124a85604051806060016040528060288152602001613cc7602891396001600160a01b038a166000908152603460205260408120906112236123a2565b6001600160a01b03168152602081019190915260400160002054919063ffffffff612dc616565b6123a6565b5060019392505050565b600061126784846001612e5d565b604080516001600160a01b0386811682526020820188905282519394508516927faeecfcda1271d292db728294b8ae465871ec039d51404caf49a7eb0ade51770a929181900390910190a29392505050565b60385460ff1690565b600080516020613c6683398151915290565b6000610cac6112e16123a2565b8461124a85603460006112f26123a2565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549063ffffffff612beb16565b6000610c9161133683611525565b612385565b60395481565b5060001990565b603a5461010090046001600160a01b031681565b6113646124b9565b6001600160a01b0316336001600160a01b0316146113b7576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b603e5460408051632e1a7d4d60e01b81526004810184905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b15801561140457600080fd5b505af1158015611418573d6000803e3d6000fd5b5050603e80546001600160a01b03191690555050604080516000815290517fa69fcd183e7a93ec06820fdd33d015efbd84f4363ecd4c0ca0405c1220888a129181900360200190a16000603d81905560408051918252517f9e73ab667f43467c2a324114c461b2bd6fcf80f8e92918085a459d67438d62f39181900360200190a16114a1613a88565b6114a9612508565b90506114bf816020015182604001516001613105565b6040517f834278d829ecf99d9ab0d75d6bdda3ed701c4ed9675f560e8f03de69240be76490600090a15050565b6000610b6683836001612378565b6000610b6982611525565b6000610b6683836001612e5d565b600080516020613c6683398151915281565b6001600160a01b031660009081526033602052604090205490565b603e546001600160a01b031681565b6000808211611593576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460ff1615611624576115a6612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b15801561160b57600080fd5b505af115801561161f573d6000803e3d6000fd5b505050505b60008061163484600060016131e2565b91509150838114610c91576040805162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a59081bdd5d1c1d5d60921b604482015290519081900360640190fd5b603c5481565b600054610100900460ff168061169c575061169c61326d565b806116aa575060005460ff16155b6116e55760405162461bcd60e51b815260040180806020018281038252602e815260200180613cef602e913960400191505060405180910390fd5b600054610100900460ff16158015611710576000805460ff1961ff0019909116610100171660011790555b61178385858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525061327392505050565b6001600160a01b0386166117d6576040805162461bcd60e51b8152602060048201526015602482015274496e76616c696420706f6b6572206164647265737360581b604482015290519081900360640190fd5b603a80546702c68af0bb140000603d556001610100600160a81b03199091166101006001600160a01b038a16021760ff191617905567016345785d8a00006039558015611829576000805461ff00191690555b505050505050565b600061183c83612385565b509050610c9181836001612e5d565b60378054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610c7c5780601f10610c5157610100808354040283529160200191610c7c565b73afce80b19a8ce13dec0739a1aab7a028d6845eb381565b6000610b6982336001612378565b6000610cac6118df6123a2565b8461124a85604051806060016040528060258152602001613db460259139603460006119096123a2565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919063ffffffff612dc616565b6000610cac61194d6123a2565b8484612c68565b6000808411611998576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460009060ff1615611a2c576119ae612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611a1357600080fd5b505af1158015611a27573d6000803e3d6000fd5b505050505b611a3585612492565b9092509050611a498583868685600161327f565b509392505050565b6000808411611a95576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460009060ff1615611b2957611aab612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611b1057600080fd5b505af1158015611b24573d6000803e3d6000fd5b505050505b611b3285612385565b9092509050611a498286868685600161327f565b603b5481565b6000808211611b90576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460ff1615611c2157611ba3612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611c0857600080fd5b505af1158015611c1c573d6000803e3d6000fd5b505050505b6000610f9f836001806131e2565b611c376124b9565b6001600160a01b0316336001600160a01b031614611c8a576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b603a805482151560ff19909116811790915560408051918252517fb9fdedbd8818c7929701eadc08c748a262fb39d9237b27537ae1809061e305f69181900360200190a150565b603d5481565b6000611ce284612385565b509050611cf181846001612e5d565b50604080516001600160a01b038581168252602082018490528251908516927faeecfcda1271d292db728294b8ae465871ec039d51404caf49a7eb0ade51770a928290030190a29392505050565b6000808211611d83576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b6000611d91836001806131e2565b603a5490925060ff16159050610b6957611da9612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611e0e57600080fd5b505af1158015611e22573d6000803e3d6000fd5b5050505092915050565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b6000808211611ea6576040805162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b604482015290519081900360640190fd5b610c9182612492565b6000806000808a11611ef6576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b6001600160a01b038716611f4a576040805162461bcd60e51b81526020600482015260166024820152754f75747075742061646472657373206973207a65726f60501b604482015290519081900360640190fd5b6001600160a01b038616611fa5576040805162461bcd60e51b815260206004820152601b60248201527f42656e65666963696172792061646472657373206973207a65726f0000000000604482015290519081900360640190fd5b6001600160a01b038516611ff9576040805162461bcd60e51b8152602060048201526016602482015275526f757465722061646472657373206973207a65726f60501b604482015290519081900360640190fd5b603a5460ff161561208a5761200c612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b15801561207157600080fd5b505af1158015612085573d6000803e3d6000fd5b505050505b6120968a8a60006131e2565b9093509150886120a8578982146120ac565b8983145b6120ee576040805162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a59081bdd5d1c1d5d60921b604482015290519081900360640190fd5b6040805163095ea7b360e01b815273c1443cb9ce81915fb914c270d74b0d57d1c87be06004820152602481018490529051600080516020613c668339815191529163095ea7b39160448083019260209291908290030181600087803b15801561215657600080fd5b505af115801561216a573d6000803e3d6000fd5b505050506040513d602081101561218057600080fd5b50506040805163c280cbc160e01b815285151560048201526001600160a01b038781166024830152600080516020613c66833981519152604483015289811660648301526084820185905260a482018b9052881660c4820152905173c1443cb9ce81915fb914c270d74b0d57d1c87be09163c280cbc19160e48083019260209291908290030181600087803b15801561221857600080fd5b505af115801561222c573d6000803e3d6000fd5b505050506040513d602081101561224257600080fd5b5051929a91995091975095505050505050565b61225d6124b9565b6001600160a01b0316336001600160a01b0316146122b0576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b6001600160a01b038116158015906122db5750603a546001600160a01b038281166101009092041614155b61231c576040805162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103837b5b2b960991b604482015290519081900360640190fd5b603a80546001600160a01b0383166101008102610100600160a81b03199092169190911790915560408051918252517f4324a90fd94048b85fa5a72fb447d1313da0a602723a297a1a550e910004aef49181900360200190a150565b6000610f9f848484612e5d565b60395460009061239b838263ffffffff6134f116565b9150915091565b3390565b6001600160a01b0383166123eb5760405162461bcd60e51b8152600401808060200182810382526024815260200180613d906024913960400191505060405180910390fd5b6001600160a01b0382166124305760405162461bcd60e51b8152600401808060200182810382526022815260200180613bd16022913960400191505060405180910390fd5b6001600160a01b03808416600081815260346020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b60395460009061239b60016124ad858463ffffffff61350616565b9063ffffffff612beb16565b600073afce80b19a8ce13dec0739a1aab7a028d6845eb36001600160a01b0316630c340a246040518163ffffffff1660e01b815260040160206040518083038186803b158015610bbe57600080fd5b612510613a88565b604080516370a0823160e01b81523060048201529051600091600080516020613c66833981519152916370a0823191602480820192602092909190829003018186803b15801561255f57600080fd5b505afa158015612573573d6000803e3d6000fd5b505050506040513d602081101561258957600080fd5b505160408051606081018252603d5481526020810183905291925081016125ae610db5565b905291505090565b600082604001511161260f576040805162461bcd60e51b815260206004820152601b60248201527f4d757374206861766520736f6d657468696e6720746f20706f6b650000000000604482015290519081900360640190fd5b603b54429060009061262890839063ffffffff61353316565b90508280612637575061384081115b612688576040805162461bcd60e51b815260206004820152601760248201527f4e6f7420656e6f7567682074696d6520656c6170736564000000000000000000604482015290519081900360640190fd5b603b829055603e546001600160a01b03168015612b2b576000603c5490506000826001600160a01b031663c71daccb6040518163ffffffff1660e01b815260040160206040518083038186803b1580156126e157600080fd5b505afa1580156126f5573d6000803e3d6000fd5b505050506040513d602081101561270b57600080fd5b5051905081811015612754576040805162461bcd60e51b815260206004820152600d60248201526c125b9d985b1a59081e5a595b19609a1b604482015290519081900360640190fd5b8015612776576127748161276e818563ffffffff61353316565b86613575565b505b602087015160009061278e908363ffffffff612beb16565b88519091506000906127a790839063ffffffff6134f116565b9050828111156128b65760006127c3828563ffffffff61353316565b6040805163095ea7b360e01b81526001600160a01b0389166004820152602481018390529051919250600080516020613c668339815191529163095ea7b3916044808201926020929091908290030181600087803b15801561282457600080fd5b505af1158015612838573d6000803e3d6000fd5b505050506040513d602081101561284e57600080fd5b50506040805163b6b55f2560e01b81526004810183905290516001600160a01b0388169163b6b55f2591602480830192600092919082900301818387803b15801561289857600080fd5b505af11580156128ac573d6000803e3d6000fd5b5050505050612a02565b80831115612a02578061299457846001600160a01b031663853828b66040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156128fe57600080fd5b505af1158015612912573d6000803e3d6000fd5b5050604080516370a0823160e01b81523060048201529051600080516020613c6683398151915293506370a0823192506024808301926020929190829003018186803b15801561296157600080fd5b505afa158015612975573d6000803e3d6000fd5b505050506040513d602081101561298b57600080fd5b50519150612a02565b6001600160a01b038516632e1a7d4d6129b3858463ffffffff61353316565b6040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156129e957600080fd5b505af11580156129fd573d6000803e3d6000fd5b505050505b80856001600160a01b031663c71daccb6040518163ffffffff1660e01b815260040160206040518083038186803b158015612a3c57600080fd5b505afa158015612a50573d6000803e3d6000fd5b505050506040513d6020811015612a6657600080fd5b50511015612abb576040805162461bcd60e51b815260206004820152601860248201527f456e666f7263652073797374656d20696e76617269616e740000000000000000604482015290519081900360640190fd5b80603c81905550612ad2828a604001516000613105565b7fd68aa1501d765a03d8e4d6897e6ad6719d0c4aef69497be7f0b3471d1fe8fcb68482612b05868363ffffffff61353316565b60408051938452602084019290925282820152519081900360600190a150505050612b6f565b6000603c81905560208601516040870151612b4592613105565b6040517f8c27968f65a3dbc27abc3cab9653365e9789a455e5ff479fa8aad2d63881cdae90600090a15b5050505050565b604080516385acd64160e01b81527f12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf16004820152905160009173afce80b19a8ce13dec0739a1aab7a028d6845eb3916385acd64191602480820192602092909190829003018186803b158015610bbe57600080fd5b600082820183811015610b66576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000610b66612c5b83600163ffffffff61353316565b849063ffffffff61350616565b6001600160a01b038316612cad5760405162461bcd60e51b8152600401808060200182810382526025815260200180613d6b6025913960400191505060405180910390fd5b6001600160a01b038216612cf25760405162461bcd60e51b8152600401808060200182810382526023815260200180613b5c6023913960400191505060405180910390fd5b612d3581604051806060016040528060268152602001613c13602691396001600160a01b038616600090815260336020526040902054919063ffffffff612dc616565b6001600160a01b038085166000908152603360205260408082209390935590841681522054612d6a908263ffffffff612beb16565b6001600160a01b0380841660008181526033602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b60008184841115612e555760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612e1a578181015183820152602001612e02565b50505050905090810190601f168015612e475780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6000808411612eac576040805162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b604482015290519081900360640190fd5b6001600160a01b038316612f07576040805162461bcd60e51b815260206004820152601b60248201527f496e76616c69642062656e656669636961727920616464726573730000000000604482015290519081900360640190fd5b600080516020613c668339815191528215612f9457612f24612b76565b6001600160a01b031663ca2bdfe6826040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015612f7b57600080fd5b505af1158015612f8f573d6000803e3d6000fd5b505050505b604080516323b872dd60e01b81523360048201523060248201526044810187905290516001600160a01b038316916323b872dd9160648083019260209291908290030181600087803b158015612fe957600080fd5b505af1158015612ffd573d6000803e3d6000fd5b505050506040513d602081101561301357600080fd5b505161305c576040805162461bcd60e51b81526020600482015260136024820152724d757374207265636569766520746f6b656e7360681b604482015290519081900360640190fd5b61306585612492565b509150613072848361367c565b604080518681526020810184905281516001600160a01b0387169233927fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7929081900390910190a3604080518681526020810184905281516001600160a01b038716927f1a96ef469e332471886959f90a71ae924792a05ad7eea4e43a1c29becdf238ed928290030190a2509392505050565b600061311083612385565b509050818061311f5750808410155b613170576040805162461bcd60e51b815260206004820152601a60248201527f45786368616e676552617465206d75737420696e637265617365000000000000604482015290519081900360640190fd5b600061317c8585612c45565b603981905590507fc8d1043f24843c0a1c9251fdc30017d84e87498fbcf232af9f86816b5e182bde818387116131b35760006131c3565b6131c3878563ffffffff61353316565b6040805192835260208301919091528051918290030190a15050505050565b60008060008060008615613206578792506131fc88612385565b9092509050613218565b87915061321288612492565b90935090505b61322682843333858b61327f565b6040805184815260208101849052815133927fa4feb388d266c39bec6f79f16c3a7dba32e12d9688ddbb14493d46ce4c1ce657928290030190a25090969095509350505050565b303b1590565b6111c88282601261376e565b6001600160a01b0384166132da576040805162461bcd60e51b815260206004820152601b60248201527f496e76616c69642062656e656669636961727920616464726573730000000000604482015290519081900360640190fd5b60006132e68433611e2c565b9050336001600160a01b0385161480159061330357506000198114155b1561336a578086111561335d576040805162461bcd60e51b815260206004820152601860248201527f416d6f756e74206578636565647320616c6c6f77616e63650000000000000000604482015290519081900360640190fd5b61336a84338884036123a6565b61337484876137ae565b81156134a5576040805163a9059cbb60e01b81526001600160a01b0387166004820152602481018990529051600080516020613c668339815191529163a9059cbb9160448083019260209291908290030181600087803b1580156133d757600080fd5b505af11580156133eb573d6000803e3d6000fd5b505050506040513d602081101561340157600080fd5b5051613447576040805162461bcd60e51b815260206004820152601060248201526f4d7573742073656e6420746f6b656e7360801b604482015290519081900360640190fd5b836001600160a01b0316856001600160a01b0316336001600160a01b03167ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db8a8a604051808381526020018281526020019250505060405180910390a45b6134ad613a88565b6134b5612508565b90506134bf613aa9565b6134c982866138aa565b90508060000151816020015111156134e6576134e68260006125b6565b505050505050505050565b6000610b668383670de0b6b3a7640000613944565b60008061352184670de0b6b3a764000063ffffffff61397216565b9050610f9f818463ffffffff6139cb16565b6000610b6683836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612dc6565b600080613583600184613a0d565b90506000613597868663ffffffff61353316565b905060006135ab868363ffffffff61350616565b905060006135c3846301e1338063ffffffff61350616565b90506135d5828263ffffffff61350616565b945061070884111561362c57673782dace9d90000085106136275760405162461bcd60e51b815260040180806020018281038252602d815260200180613d1d602d913960400191505060405180910390fd5b613671565b66038d7ea4c6800082106136715760405162461bcd60e51b815260040180806020018281038252602d815260200180613c39602d913960400191505060405180910390fd5b505050509392505050565b6001600160a01b0382166136d7576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6035546136ea908263ffffffff612beb16565b6035556001600160a01b038216600090815260336020526040902054613716908263ffffffff612beb16565b6001600160a01b03831660008181526033602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b8251613781906036906020860190613ac3565b508151613795906037906020850190613ac3565b506038805460ff191660ff929092169190911790555050565b6001600160a01b0382166137f35760405162461bcd60e51b8152600401808060200182810382526021815260200180613d4a6021913960400191505060405180910390fd5b61383681604051806060016040528060228152602001613baf602291396001600160a01b038516600090815260336020526040902054919063ffffffff612dc616565b6001600160a01b038316600090815260336020526040902055603554613862908263ffffffff61353316565b6035556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b6138b2613aa9565b60408301516000906138ca908463ffffffff6134f116565b84519091506000906138fb906138ee906702c68af0bb14000063ffffffff612beb16565b839063ffffffff6134f116565b90506000828660200151101561392657602086015161392190849063ffffffff61353316565b613929565b60005b60408051808201909152928352602083015250949350505050565b600080613957858563ffffffff61397216565b9050613969818463ffffffff6139cb16565b95945050505050565b60008261398157506000610b69565b8282028284828161398e57fe5b0414610b665760405162461bcd60e51b8152600401808060200182810382526021815260200180613ca66021913960400191505060405180910390fd5b6000610b6683836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613a23565b6000818311613a1c5781610b66565b5090919050565b60008183613a725760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315612e1a578181015183820152602001612e02565b506000838581613a7e57fe5b0495945050505050565b60405180606001604052806000815260200160008152602001600081525090565b604051806040016040528060008152602001600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613b0457805160ff1916838001178555613b31565b82800160010185558215613b31579182015b82811115613b31578251825591602001919060010190613b16565b50613b3d929150613b41565b5090565b610bed91905b80821115613b3d5760008155600101613b4756fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737343616e206f6e6c79207573652074686973206d6574686f64206265666f72652073747265616d696e6720626567696e7345524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a20617070726f766520746f20746865207a65726f20616464726573734d75737420776974686472617720736f6d657468696e6700000000000000000045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e6365496e7465726573742070726f7465637465642066726f6d20696e666c6174696e67207061737420313020427073000000000000000000000000e2f2a5c287993345a840db3b0845fbc70f5935a54f6e6c7920676f7665726e6f722063616e206578656375746500000000000000536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7745524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365436f6e747261637420696e7374616e63652068617320616c7265616479206265656e20696e697469616c697a6564496e7465726573742070726f7465637465642066726f6d20696e666c6174696e672070617374206d617841505945524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa265627a7a723158204c107b238d495fe40e114db902cb653b9e067a89e67f9d29bb49b841d974858564736f6c63430005100032

Deployed Bytecode

0x608060405234801561001057600080fd5b506004361061038d5760003560e01c806370a08231116101de578063ba0876521161010f578063d8894bb5116100ad578063dd62ed3e1161007c578063dd62ed3e14610a2f578063ef8b30f714610a5d578063f25816b514610a7a578063f4430dd814610aea5761038d565b8063d8894bb5146109d6578063d905777e146106fa578063da39b3e7146109de578063db006a7514610a125761038d565b8063c6e6f592116100e9578063c6e6f592146104b2578063c9f18ebd1461099a578063cbe54173146109b7578063ce96cb77146106555761038d565b8063ba0876521461095e578063c4ec22ad14610992578063c63d75b6146106835761038d565b806395d89b411161017c578063a457c2d711610156578063a457c2d7146108d2578063a9059cbb146108fe578063b3d7f6b914610455578063b460af941461092a5761038d565b806395d89b41146108a5578063a3f5c1d2146108ad578063a453b6cf146108b55761038d565b8063852a12e3116101b8578063852a12e3146107825780638f1c56bd1461079f57806390657147146107a757806394bf804d146108795761038d565b806370a08231146107545780637781b05a1461045557806383f3084f1461077a5761038d565b80632e2d2984116102c357806343e38da811610261578063590745c511610230578063590745c5146106ce5780636009a2d8146106fa5780636e553f65146107205780636f307dc31461074c5761038d565b806343e38da8146104b25780634b6bdf1d146106a95780634cdad506146104555780635312ea8e146106b15761038d565b8063395093511161029d57806339509351146106295780633af9e669146106555780633ba0b9a91461067b578063402d267d146106835761038d565b80632e2d2984146105cf578063313ce5671461060357806338d52e0f146106215761038d565b806318160ddd116103305780631dcfcb461161030a5780631dcfcb46146105245780631fcd9cc914610558578063220c5bbb1461057c57806323b872dd146105995761038d565b806318160ddd146104f757806318178358146104ff578063182815b0146105075761038d565b806307a2d13a1161036c57806307a2d13a14610455578063095ea7b3146104725780630a28a477146104b257806310188aef146104cf5761038d565b80625745991461039257806301e1d114146103d057806306fdde03146103d8575b600080fd5b6103be600480360360408110156103a857600080fd5b50803590602001356001600160a01b0316610b10565b60408051918252519081900360200190f35b6103be610b6f565b6103e0610bf0565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561041a578181015183820152602001610402565b50505050905090810190601f1680156104475780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103be6004803603602081101561046b57600080fd5b5035610c86565b61049e6004803603604081101561048857600080fd5b506001600160a01b038135169060200135610c98565b604080519115158252519081900360200190f35b6103be600480360360208110156104c857600080fd5b5035610cb5565b6104f5600480360360208110156104e557600080fd5b50356001600160a01b0316610cc0565b005b6103be610db5565b6104f5610dbb565b6104f56004803603602081101561051d57600080fd5b5035610e38565b6103be6004803603606081101561053a57600080fd5b508035906001600160a01b0360208201358116916040013516610f49565b610560610fa7565b604080516001600160a01b039092168252519081900360200190f35b6104f56004803603602081101561059257600080fd5b5035610fbf565b61049e600480360360608110156105af57600080fd5b506001600160a01b038135811691602081013590911690604001356111cc565b6103be600480360360608110156105e557600080fd5b508035906001600160a01b0360208201358116916040013516611259565b61060b6112b9565b6040805160ff9092168252519081900360200190f35b6105606112c2565b61049e6004803603604081101561063f57600080fd5b506001600160a01b0381351690602001356112d4565b6103be6004803603602081101561066b57600080fd5b50356001600160a01b0316611328565b6103be61133b565b6103be6004803603602081101561069957600080fd5b50356001600160a01b0316611341565b610560611348565b6104f5600480360360208110156106c757600080fd5b503561135c565b6103be600480360360408110156106e457600080fd5b50803590602001356001600160a01b03166114ec565b6103be6004803603602081101561071057600080fd5b50356001600160a01b03166114fa565b6103be6004803603604081101561073657600080fd5b50803590602001356001600160a01b0316611505565b610560611513565b6103be6004803603602081101561076a57600080fd5b50356001600160a01b0316611525565b610560611540565b6103be6004803603602081101561079857600080fd5b503561154f565b6103be61167d565b6104f5600480360360608110156107bd57600080fd5b6001600160a01b0382351691908101906040810160208201356401000000008111156107e857600080fd5b8201836020820111156107fa57600080fd5b8035906020019184600183028401116401000000008311171561081c57600080fd5b91939092909160208101903564010000000081111561083a57600080fd5b82018360208201111561084c57600080fd5b8035906020019184600183028401116401000000008311171561086e57600080fd5b509092509050611683565b6103be6004803603604081101561088f57600080fd5b50803590602001356001600160a01b0316611831565b6103e061184b565b6105606118ac565b6103be600480360360208110156108cb57600080fd5b50356118c4565b61049e600480360360408110156108e857600080fd5b506001600160a01b0381351690602001356118d2565b61049e6004803603604081101561091457600080fd5b506001600160a01b038135169060200135611940565b6103be6004803603606081101561094057600080fd5b508035906001600160a01b0360208201358116916040013516611954565b6103be6004803603606081101561097457600080fd5b508035906001600160a01b0360208201358116916040013516611a51565b6103be611b46565b6103be600480360360208110156109b057600080fd5b5035611b4c565b6104f5600480360360208110156109cd57600080fd5b50351515611c2f565b6103be611cd1565b6103be600480360360608110156109f457600080fd5b508035906001600160a01b0360208201358116916040013516611cd7565b6103be60048036036020811015610a2857600080fd5b5035611d3f565b6103be60048036036040811015610a4557600080fd5b506001600160a01b0381358116916020013516611e2c565b6103be60048036036020811015610a7357600080fd5b5035611e57565b610acc600480360360e0811015610a9057600080fd5b5080359060208101351515906040810135906001600160a01b036060820135811691608081013582169160a0820135169060c001351515611eaf565b60408051938452602084019290925282820152519081900360600190f35b6104f560048036036020811015610b0057600080fd5b50356001600160a01b0316612255565b600067016345785d8a000060395414610b5a5760405162461bcd60e51b8152600401808060200182810382526030815260200180613b7f6030913960400191505060405180910390fd5b610b6683836000612378565b90505b92915050565b604080516370a0823160e01b81523060048201529051600091600080516020613c66833981519152916370a0823191602480820192602092909190829003018186803b158015610bbe57600080fd5b505afa158015610bd2573d6000803e3d6000fd5b505050506040513d6020811015610be857600080fd5b505190505b90565b60368054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610c7c5780601f10610c5157610100808354040283529160200191610c7c565b820191906000526020600020905b815481529060010190602001808311610c5f57829003601f168201915b5050505050905090565b6000610c9182612385565b5092915050565b6000610cac610ca56123a2565b84846123a6565b50600192915050565b6000610c9182612492565b610cc86124b9565b6001600160a01b0316336001600160a01b031614610d1b576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b610d23613a88565b610d2b612508565b600081529050610d3c8160016125b6565b610d44613a88565b610d4c612508565b603e80546001600160a01b0319166001600160a01b0386161790559050610d748160016125b6565b604080516001600160a01b038516815290517fa69fcd183e7a93ec06820fdd33d015efbd84f4363ecd4c0ca0405c1220888a129181900360200190a1505050565b60355490565b603a5461010090046001600160a01b03163314610e18576040805162461bcd60e51b81526020600482015260166024820152754f6e6c7920706f6b65722063616e206578656375746560501b604482015290519081900360640190fd5b610e20613a88565b610e28612508565b9050610e358160006125b6565b50565b610e406124b9565b6001600160a01b0316336001600160a01b031614610e93576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b6706f05b59d3b20000811115610ef0576040805162461bcd60e51b815260206004820152601760248201527f4672616374696f6e206d757374206265203c3d20353025000000000000000000604482015290519081900360640190fd5b603d819055610efd613a88565b610f05612508565b9050610f128160016125b6565b6040805183815290517f9e73ab667f43467c2a324114c461b2bd6fcf80f8e92918085a459d67438d62f39181900360200190a15050565b604080516001600160a01b038481168252602082018690528251600093918516927faeecfcda1271d292db728294b8ae465871ec039d51404caf49a7eb0ade51770a92908290030190a2610f9f84846001612378565b949350505050565b73c1443cb9ce81915fb914c270d74b0d57d1c87be081565b610fc7612b76565b6001600160a01b0316336001600160a01b03161461102c576040805162461bcd60e51b815260206004820181905260248201527f4f6e6c7920736176696e6773206d616e616765722063616e2065786563757465604482015290519081900360640190fd5b6000811161107a576040805162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b604482015290519081900360640190fd5b604080516323b872dd60e01b8152336004820152306024820152604481018390529051600080516020613c66833981519152916323b872dd9160648083019260209291908290030181600087803b1580156110d457600080fd5b505af11580156110e8573d6000803e3d6000fd5b505050506040513d60208110156110fe57600080fd5b5051611147576040805162461bcd60e51b81526020600482015260136024820152724d757374207265636569766520746f6b656e7360681b604482015290519081900360640190fd5b6000611151610db5565b905080156111c857600061116482612385565b509050600061118261117c838663ffffffff612beb16565b84612c45565b6039819055604080518281526020810187905281519293507fc8d1043f24843c0a1c9251fdc30017d84e87498fbcf232af9f86816b5e182bde929081900390910190a150505b5050565b60006111d9848484612c68565b61124f846111e56123a2565b61124a85604051806060016040528060288152602001613cc7602891396001600160a01b038a166000908152603460205260408120906112236123a2565b6001600160a01b03168152602081019190915260400160002054919063ffffffff612dc616565b6123a6565b5060019392505050565b600061126784846001612e5d565b604080516001600160a01b0386811682526020820188905282519394508516927faeecfcda1271d292db728294b8ae465871ec039d51404caf49a7eb0ade51770a929181900390910190a29392505050565b60385460ff1690565b600080516020613c6683398151915290565b6000610cac6112e16123a2565b8461124a85603460006112f26123a2565b6001600160a01b03908116825260208083019390935260409182016000908120918c16815292529020549063ffffffff612beb16565b6000610c9161133683611525565b612385565b60395481565b5060001990565b603a5461010090046001600160a01b031681565b6113646124b9565b6001600160a01b0316336001600160a01b0316146113b7576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b603e5460408051632e1a7d4d60e01b81526004810184905290516001600160a01b0390921691632e1a7d4d9160248082019260009290919082900301818387803b15801561140457600080fd5b505af1158015611418573d6000803e3d6000fd5b5050603e80546001600160a01b03191690555050604080516000815290517fa69fcd183e7a93ec06820fdd33d015efbd84f4363ecd4c0ca0405c1220888a129181900360200190a16000603d81905560408051918252517f9e73ab667f43467c2a324114c461b2bd6fcf80f8e92918085a459d67438d62f39181900360200190a16114a1613a88565b6114a9612508565b90506114bf816020015182604001516001613105565b6040517f834278d829ecf99d9ab0d75d6bdda3ed701c4ed9675f560e8f03de69240be76490600090a15050565b6000610b6683836001612378565b6000610b6982611525565b6000610b6683836001612e5d565b600080516020613c6683398151915281565b6001600160a01b031660009081526033602052604090205490565b603e546001600160a01b031681565b6000808211611593576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460ff1615611624576115a6612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b15801561160b57600080fd5b505af115801561161f573d6000803e3d6000fd5b505050505b60008061163484600060016131e2565b91509150838114610c91576040805162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a59081bdd5d1c1d5d60921b604482015290519081900360640190fd5b603c5481565b600054610100900460ff168061169c575061169c61326d565b806116aa575060005460ff16155b6116e55760405162461bcd60e51b815260040180806020018281038252602e815260200180613cef602e913960400191505060405180910390fd5b600054610100900460ff16158015611710576000805460ff1961ff0019909116610100171660011790555b61178385858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050604080516020601f8901819004810282018101909252878152925087915086908190840183828082843760009201919091525061327392505050565b6001600160a01b0386166117d6576040805162461bcd60e51b8152602060048201526015602482015274496e76616c696420706f6b6572206164647265737360581b604482015290519081900360640190fd5b603a80546702c68af0bb140000603d556001610100600160a81b03199091166101006001600160a01b038a16021760ff191617905567016345785d8a00006039558015611829576000805461ff00191690555b505050505050565b600061183c83612385565b509050610c9181836001612e5d565b60378054604080516020601f6002600019610100600188161502019095169490940493840181900481028201810190925282815260609390929091830182828015610c7c5780601f10610c5157610100808354040283529160200191610c7c565b73afce80b19a8ce13dec0739a1aab7a028d6845eb381565b6000610b6982336001612378565b6000610cac6118df6123a2565b8461124a85604051806060016040528060258152602001613db460259139603460006119096123a2565b6001600160a01b03908116825260208083019390935260409182016000908120918d1681529252902054919063ffffffff612dc616565b6000610cac61194d6123a2565b8484612c68565b6000808411611998576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460009060ff1615611a2c576119ae612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611a1357600080fd5b505af1158015611a27573d6000803e3d6000fd5b505050505b611a3585612492565b9092509050611a498583868685600161327f565b509392505050565b6000808411611a95576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460009060ff1615611b2957611aab612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611b1057600080fd5b505af1158015611b24573d6000803e3d6000fd5b505050505b611b3285612385565b9092509050611a498286868685600161327f565b603b5481565b6000808211611b90576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b603a5460ff1615611c2157611ba3612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611c0857600080fd5b505af1158015611c1c573d6000803e3d6000fd5b505050505b6000610f9f836001806131e2565b611c376124b9565b6001600160a01b0316336001600160a01b031614611c8a576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b603a805482151560ff19909116811790915560408051918252517fb9fdedbd8818c7929701eadc08c748a262fb39d9237b27537ae1809061e305f69181900360200190a150565b603d5481565b6000611ce284612385565b509050611cf181846001612e5d565b50604080516001600160a01b038581168252602082018490528251908516927faeecfcda1271d292db728294b8ae465871ec039d51404caf49a7eb0ade51770a928290030190a29392505050565b6000808211611d83576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b6000611d91836001806131e2565b603a5490925060ff16159050610b6957611da9612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015611e0e57600080fd5b505af1158015611e22573d6000803e3d6000fd5b5050505092915050565b6001600160a01b03918216600090815260346020908152604080832093909416825291909152205490565b6000808211611ea6576040805162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b604482015290519081900360640190fd5b610c9182612492565b6000806000808a11611ef6576040805162461bcd60e51b81526020600482015260176024820152600080516020613bf3833981519152604482015290519081900360640190fd5b6001600160a01b038716611f4a576040805162461bcd60e51b81526020600482015260166024820152754f75747075742061646472657373206973207a65726f60501b604482015290519081900360640190fd5b6001600160a01b038616611fa5576040805162461bcd60e51b815260206004820152601b60248201527f42656e65666963696172792061646472657373206973207a65726f0000000000604482015290519081900360640190fd5b6001600160a01b038516611ff9576040805162461bcd60e51b8152602060048201526016602482015275526f757465722061646472657373206973207a65726f60501b604482015290519081900360640190fd5b603a5460ff161561208a5761200c612b76565b6001600160a01b031663ca2bdfe6600080516020613c668339815191526040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b15801561207157600080fd5b505af1158015612085573d6000803e3d6000fd5b505050505b6120968a8a60006131e2565b9093509150886120a8578982146120ac565b8983145b6120ee576040805162461bcd60e51b815260206004820152600e60248201526d125b9d985b1a59081bdd5d1c1d5d60921b604482015290519081900360640190fd5b6040805163095ea7b360e01b815273c1443cb9ce81915fb914c270d74b0d57d1c87be06004820152602481018490529051600080516020613c668339815191529163095ea7b39160448083019260209291908290030181600087803b15801561215657600080fd5b505af115801561216a573d6000803e3d6000fd5b505050506040513d602081101561218057600080fd5b50506040805163c280cbc160e01b815285151560048201526001600160a01b038781166024830152600080516020613c66833981519152604483015289811660648301526084820185905260a482018b9052881660c4820152905173c1443cb9ce81915fb914c270d74b0d57d1c87be09163c280cbc19160e48083019260209291908290030181600087803b15801561221857600080fd5b505af115801561222c573d6000803e3d6000fd5b505050506040513d602081101561224257600080fd5b5051929a91995091975095505050505050565b61225d6124b9565b6001600160a01b0316336001600160a01b0316146122b0576040805162461bcd60e51b81526020600482015260196024820152600080516020613c86833981519152604482015290519081900360640190fd5b6001600160a01b038116158015906122db5750603a546001600160a01b038281166101009092041614155b61231c576040805162461bcd60e51b815260206004820152600d60248201526c24b73b30b634b2103837b5b2b960991b604482015290519081900360640190fd5b603a80546001600160a01b0383166101008102610100600160a81b03199092169190911790915560408051918252517f4324a90fd94048b85fa5a72fb447d1313da0a602723a297a1a550e910004aef49181900360200190a150565b6000610f9f848484612e5d565b60395460009061239b838263ffffffff6134f116565b9150915091565b3390565b6001600160a01b0383166123eb5760405162461bcd60e51b8152600401808060200182810382526024815260200180613d906024913960400191505060405180910390fd5b6001600160a01b0382166124305760405162461bcd60e51b8152600401808060200182810382526022815260200180613bd16022913960400191505060405180910390fd5b6001600160a01b03808416600081815260346020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b60395460009061239b60016124ad858463ffffffff61350616565b9063ffffffff612beb16565b600073afce80b19a8ce13dec0739a1aab7a028d6845eb36001600160a01b0316630c340a246040518163ffffffff1660e01b815260040160206040518083038186803b158015610bbe57600080fd5b612510613a88565b604080516370a0823160e01b81523060048201529051600091600080516020613c66833981519152916370a0823191602480820192602092909190829003018186803b15801561255f57600080fd5b505afa158015612573573d6000803e3d6000fd5b505050506040513d602081101561258957600080fd5b505160408051606081018252603d5481526020810183905291925081016125ae610db5565b905291505090565b600082604001511161260f576040805162461bcd60e51b815260206004820152601b60248201527f4d757374206861766520736f6d657468696e6720746f20706f6b650000000000604482015290519081900360640190fd5b603b54429060009061262890839063ffffffff61353316565b90508280612637575061384081115b612688576040805162461bcd60e51b815260206004820152601760248201527f4e6f7420656e6f7567682074696d6520656c6170736564000000000000000000604482015290519081900360640190fd5b603b829055603e546001600160a01b03168015612b2b576000603c5490506000826001600160a01b031663c71daccb6040518163ffffffff1660e01b815260040160206040518083038186803b1580156126e157600080fd5b505afa1580156126f5573d6000803e3d6000fd5b505050506040513d602081101561270b57600080fd5b5051905081811015612754576040805162461bcd60e51b815260206004820152600d60248201526c125b9d985b1a59081e5a595b19609a1b604482015290519081900360640190fd5b8015612776576127748161276e818563ffffffff61353316565b86613575565b505b602087015160009061278e908363ffffffff612beb16565b88519091506000906127a790839063ffffffff6134f116565b9050828111156128b65760006127c3828563ffffffff61353316565b6040805163095ea7b360e01b81526001600160a01b0389166004820152602481018390529051919250600080516020613c668339815191529163095ea7b3916044808201926020929091908290030181600087803b15801561282457600080fd5b505af1158015612838573d6000803e3d6000fd5b505050506040513d602081101561284e57600080fd5b50506040805163b6b55f2560e01b81526004810183905290516001600160a01b0388169163b6b55f2591602480830192600092919082900301818387803b15801561289857600080fd5b505af11580156128ac573d6000803e3d6000fd5b5050505050612a02565b80831115612a02578061299457846001600160a01b031663853828b66040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156128fe57600080fd5b505af1158015612912573d6000803e3d6000fd5b5050604080516370a0823160e01b81523060048201529051600080516020613c6683398151915293506370a0823192506024808301926020929190829003018186803b15801561296157600080fd5b505afa158015612975573d6000803e3d6000fd5b505050506040513d602081101561298b57600080fd5b50519150612a02565b6001600160a01b038516632e1a7d4d6129b3858463ffffffff61353316565b6040518263ffffffff1660e01b815260040180828152602001915050600060405180830381600087803b1580156129e957600080fd5b505af11580156129fd573d6000803e3d6000fd5b505050505b80856001600160a01b031663c71daccb6040518163ffffffff1660e01b815260040160206040518083038186803b158015612a3c57600080fd5b505afa158015612a50573d6000803e3d6000fd5b505050506040513d6020811015612a6657600080fd5b50511015612abb576040805162461bcd60e51b815260206004820152601860248201527f456e666f7263652073797374656d20696e76617269616e740000000000000000604482015290519081900360640190fd5b80603c81905550612ad2828a604001516000613105565b7fd68aa1501d765a03d8e4d6897e6ad6719d0c4aef69497be7f0b3471d1fe8fcb68482612b05868363ffffffff61353316565b60408051938452602084019290925282820152519081900360600190a150505050612b6f565b6000603c81905560208601516040870151612b4592613105565b6040517f8c27968f65a3dbc27abc3cab9653365e9789a455e5ff479fa8aad2d63881cdae90600090a15b5050505050565b604080516385acd64160e01b81527f12fe936c77a1e196473c4314f3bed8eeac1d757b319abb85bdda70df35511bf16004820152905160009173afce80b19a8ce13dec0739a1aab7a028d6845eb3916385acd64191602480820192602092909190829003018186803b158015610bbe57600080fd5b600082820183811015610b66576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6000610b66612c5b83600163ffffffff61353316565b849063ffffffff61350616565b6001600160a01b038316612cad5760405162461bcd60e51b8152600401808060200182810382526025815260200180613d6b6025913960400191505060405180910390fd5b6001600160a01b038216612cf25760405162461bcd60e51b8152600401808060200182810382526023815260200180613b5c6023913960400191505060405180910390fd5b612d3581604051806060016040528060268152602001613c13602691396001600160a01b038616600090815260336020526040902054919063ffffffff612dc616565b6001600160a01b038085166000908152603360205260408082209390935590841681522054612d6a908263ffffffff612beb16565b6001600160a01b0380841660008181526033602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b60008184841115612e555760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612e1a578181015183820152602001612e02565b50505050905090810190601f168015612e475780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6000808411612eac576040805162461bcd60e51b81526020600482015260166024820152754d757374206465706f73697420736f6d657468696e6760501b604482015290519081900360640190fd5b6001600160a01b038316612f07576040805162461bcd60e51b815260206004820152601b60248201527f496e76616c69642062656e656669636961727920616464726573730000000000604482015290519081900360640190fd5b600080516020613c668339815191528215612f9457612f24612b76565b6001600160a01b031663ca2bdfe6826040518263ffffffff1660e01b815260040180826001600160a01b03166001600160a01b03168152602001915050600060405180830381600087803b158015612f7b57600080fd5b505af1158015612f8f573d6000803e3d6000fd5b505050505b604080516323b872dd60e01b81523360048201523060248201526044810187905290516001600160a01b038316916323b872dd9160648083019260209291908290030181600087803b158015612fe957600080fd5b505af1158015612ffd573d6000803e3d6000fd5b505050506040513d602081101561301357600080fd5b505161305c576040805162461bcd60e51b81526020600482015260136024820152724d757374207265636569766520746f6b656e7360681b604482015290519081900360640190fd5b61306585612492565b509150613072848361367c565b604080518681526020810184905281516001600160a01b0387169233927fdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7929081900390910190a3604080518681526020810184905281516001600160a01b038716927f1a96ef469e332471886959f90a71ae924792a05ad7eea4e43a1c29becdf238ed928290030190a2509392505050565b600061311083612385565b509050818061311f5750808410155b613170576040805162461bcd60e51b815260206004820152601a60248201527f45786368616e676552617465206d75737420696e637265617365000000000000604482015290519081900360640190fd5b600061317c8585612c45565b603981905590507fc8d1043f24843c0a1c9251fdc30017d84e87498fbcf232af9f86816b5e182bde818387116131b35760006131c3565b6131c3878563ffffffff61353316565b6040805192835260208301919091528051918290030190a15050505050565b60008060008060008615613206578792506131fc88612385565b9092509050613218565b87915061321288612492565b90935090505b61322682843333858b61327f565b6040805184815260208101849052815133927fa4feb388d266c39bec6f79f16c3a7dba32e12d9688ddbb14493d46ce4c1ce657928290030190a25090969095509350505050565b303b1590565b6111c88282601261376e565b6001600160a01b0384166132da576040805162461bcd60e51b815260206004820152601b60248201527f496e76616c69642062656e656669636961727920616464726573730000000000604482015290519081900360640190fd5b60006132e68433611e2c565b9050336001600160a01b0385161480159061330357506000198114155b1561336a578086111561335d576040805162461bcd60e51b815260206004820152601860248201527f416d6f756e74206578636565647320616c6c6f77616e63650000000000000000604482015290519081900360640190fd5b61336a84338884036123a6565b61337484876137ae565b81156134a5576040805163a9059cbb60e01b81526001600160a01b0387166004820152602481018990529051600080516020613c668339815191529163a9059cbb9160448083019260209291908290030181600087803b1580156133d757600080fd5b505af11580156133eb573d6000803e3d6000fd5b505050506040513d602081101561340157600080fd5b5051613447576040805162461bcd60e51b815260206004820152601060248201526f4d7573742073656e6420746f6b656e7360801b604482015290519081900360640190fd5b836001600160a01b0316856001600160a01b0316336001600160a01b03167ffbde797d201c681b91056529119e0b02407c7bb96a4a2c75c01fc9667232c8db8a8a604051808381526020018281526020019250505060405180910390a45b6134ad613a88565b6134b5612508565b90506134bf613aa9565b6134c982866138aa565b90508060000151816020015111156134e6576134e68260006125b6565b505050505050505050565b6000610b668383670de0b6b3a7640000613944565b60008061352184670de0b6b3a764000063ffffffff61397216565b9050610f9f818463ffffffff6139cb16565b6000610b6683836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612dc6565b600080613583600184613a0d565b90506000613597868663ffffffff61353316565b905060006135ab868363ffffffff61350616565b905060006135c3846301e1338063ffffffff61350616565b90506135d5828263ffffffff61350616565b945061070884111561362c57673782dace9d90000085106136275760405162461bcd60e51b815260040180806020018281038252602d815260200180613d1d602d913960400191505060405180910390fd5b613671565b66038d7ea4c6800082106136715760405162461bcd60e51b815260040180806020018281038252602d815260200180613c39602d913960400191505060405180910390fd5b505050509392505050565b6001600160a01b0382166136d7576040805162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015290519081900360640190fd5b6035546136ea908263ffffffff612beb16565b6035556001600160a01b038216600090815260336020526040902054613716908263ffffffff612beb16565b6001600160a01b03831660008181526033602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b8251613781906036906020860190613ac3565b508151613795906037906020850190613ac3565b506038805460ff191660ff929092169190911790555050565b6001600160a01b0382166137f35760405162461bcd60e51b8152600401808060200182810382526021815260200180613d4a6021913960400191505060405180910390fd5b61383681604051806060016040528060228152602001613baf602291396001600160a01b038516600090815260336020526040902054919063ffffffff612dc616565b6001600160a01b038316600090815260336020526040902055603554613862908263ffffffff61353316565b6035556040805182815290516000916001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b6138b2613aa9565b60408301516000906138ca908463ffffffff6134f116565b84519091506000906138fb906138ee906702c68af0bb14000063ffffffff612beb16565b839063ffffffff6134f116565b90506000828660200151101561392657602086015161392190849063ffffffff61353316565b613929565b60005b60408051808201909152928352602083015250949350505050565b600080613957858563ffffffff61397216565b9050613969818463ffffffff6139cb16565b95945050505050565b60008261398157506000610b69565b8282028284828161398e57fe5b0414610b665760405162461bcd60e51b8152600401808060200182810382526021815260200180613ca66021913960400191505060405180910390fd5b6000610b6683836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250613a23565b6000818311613a1c5781610b66565b5090919050565b60008183613a725760405162461bcd60e51b8152602060048201818152835160248401528351909283926044909101919085019080838360008315612e1a578181015183820152602001612e02565b506000838581613a7e57fe5b0495945050505050565b60405180606001604052806000815260200160008152602001600081525090565b604051806040016040528060008152602001600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10613b0457805160ff1916838001178555613b31565b82800160010185558215613b31579182015b82811115613b31578251825591602001919060010190613b16565b50613b3d929150613b41565b5090565b610bed91905b80821115613b3d5760008155600101613b4756fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737343616e206f6e6c79207573652074686973206d6574686f64206265666f72652073747265616d696e6720626567696e7345524332303a206275726e20616d6f756e7420657863656564732062616c616e636545524332303a20617070726f766520746f20746865207a65726f20616464726573734d75737420776974686472617720736f6d657468696e6700000000000000000045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e6365496e7465726573742070726f7465637465642066726f6d20696e666c6174696e67207061737420313020427073000000000000000000000000e2f2a5c287993345a840db3b0845fbc70f5935a54f6e6c7920676f7665726e6f722063616e206578656375746500000000000000536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7745524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6365436f6e747261637420696e7374616e63652068617320616c7265616479206265656e20696e697469616c697a6564496e7465726573742070726f7465637465642066726f6d20696e666c6174696e672070617374206d617841505945524332303a206275726e2066726f6d20746865207a65726f206164647265737345524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa265627a7a723158204c107b238d495fe40e114db902cb653b9e067a89e67f9d29bb49b841d974858564736f6c63430005100032

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

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