ETH Price: $2,076.43 (-2.41%)

Contract

0x2dFdEb833c199ba5D166C90A3B25B0E72288076B
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

Please try again later

View more zero value Internal Transactions in Advanced View mode

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

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

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

Contract Source Code Verified (Exact Match)

Contract Name:
Portal

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 10000 runs

Other Settings:
paris EvmVersion
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;

import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./interfaces/IReceiveApproval.sol";
import "./interfaces/IReceiptToken.sol";
import "./interfaces/IERC20WithPermit.sol";
import "./interfaces/IERC20WithDecimals.sol";

contract Portal is Ownable2StepUpgradeable, IReceiveApproval {
    using SafeERC20 for IERC20;

    /// @notice Supported token ability defines what can be done with
    ///         a given token. Some tokens can be deposited but can not be locked.
    enum TokenAbility {
        None,
        Deposit,
        DepositAndLock
    }

    /// @notice Represents the to-tBTC migration state of a deposit. Note that
    ///         to-tBTC migration is only available for selected assets, as set
    ///         by the governance.
    enum TbtcMigrationState {
        NotRequested,
        Requested,
        InProgress,
        Completed
    }

    /// @notice Supported token struct to pair a token address with its ability
    /// @dev This is an auxiliary struct used to initialize the contract with
    ///      the supported tokens and for governance to add new supported tokens.
    ///      This struct is not used in the storage.
    struct SupportedToken {
        address token;
        TokenAbility tokenAbility;
    }

    /// @notice Groups depositor address to their deposit ID to pass in an array
    ///         to trigger and complete the to-tBTC migration by the tBTC
    ///         migration treasury.
    /// @dev This is an auxiliary struct used as a parameter in the
    ///      `withdrawForTbtcMigration` and `finalizeTbtcMigration` functions.
    ///      This struct is not used in the storage.
    struct DepositToMigrate {
        address depositor;
        uint256 depositId;
    }

    /// @notice DepositInfo keeps track of the deposit balance and unlock time.
    ///         Each deposit is tracked separately and associated with a specific
    ///         token. Some tokens can be deposited but can not be locked - in
    ///         that case the unlockAt is the block timestamp of when the deposit
    ///         was created. The same is true for tokens that can be locked but
    ///         the depositor decided not to lock them. Some deposits can mint
    ///         a receipt tokens against them: receiptMinted is the amount of
    ///         receipt tokens minted against a deposit, while feeOwed is the
    ///         fee owed by the deposit to Portal, and the lastFeeIntegral is
    ///         the last updated value of the fee integral.
    struct DepositInfo {
        uint96 balance;
        uint32 unlockAt;
        // The amount of the receipt token minted for the deposit. Zero if no
        // receipt token was minted.
        uint96 receiptMinted;
        // The fee owed if the receipt token was minted and the fee is non-zero.
        // Fee owed is calculated based on the receipt tokens minted and is stored
        // using receipt token decimals. The fee is collected when the deposit is
        // withdrawn, adjusted to the deposit token decimals.
        uint96 feeOwed;
        // The last value of the receipt token fee integral. Zero if no receipt
        // token was minted, if the fee is zero, or if the integral was not yet
        // calculated.
        uint88 lastFeeIntegral;
        // The state of tBTC migration. If the to-tBTC migration is not allowed
        // for the asset or if it was not requested by the depositor, the state
        // is NotRequested.
        TbtcMigrationState tbtcMigrationState;
    }

    /// @notice Keeps information about to-tBTC migration status for the asset:
    ///         whether the migration is allowed and what is the total amount
    ///         of the asset being currently in-progress for the migration.
    struct TbtcMigrationInfo {
        // Indicates whether the migration to tBTC was enabled by the governance
        // for the asset.
        bool isAllowed;
        // The total amount that is currently migrated to tBTC. The value uses
        // the same number of decimals as the asset being migrated.
        uint96 totalMigrating;
    }

    /// @notice FeeInfo keeps track of the receipt token minting situation per
    ///         the supported deposit token.
    ///
    ///         The current fee rate for minting against deposits is
    ///         calculated separately for each token, and tracked as integral.
    ///
    ///         The fee rate has a single component: a governable base annual
    ///         fee, capped at 100%.
    ///
    ///         The annual fee is converted to a non-compounding fee per second,
    ///         R. This rate is then multiplied by time in seconds to get the
    ///         accumulated fee integral. The difference between the integral
    ///         in time T_1 and T_2 tracks how much fee is accrued per minted
    ///         token unit during that time. The minted amount A is multiplied
    ///         by R, and divided by a scalar constant to get the amount of fee
    ///         in the same units as A.
    struct FeeInfo {
        // XXX: not tracking total deposited - use token balance
        // someone could send token to the contract outside locked deposits

        // The amount of receipt tokens minted across all deposits for the given
        // deposit token.
        uint96 totalMinted;
        // The timestamp of the block of the last fee update.
        uint32 lastFeeUpdateAt;
        // The integral tracks how much fee is accrued over time.
        uint88 feeIntegral;
        // Fee per annum in percentage.
        uint8 annualFee;
        // Receipt token minting cap in percentage, per deposit.
        uint8 mintCap;
        // A token supporting the IReceiptToken interface and used as a receipt
        // token. Zero address if the given deposit token does not allow minting
        // receipt tokens.
        address receiptToken;
        // Counts fees collected across all withdrawn deposits for the given
        // deposit token. Fees are collected based on the receipt tokens earlier
        // minted and the annual fee.
        uint96 feeCollected;
    }

    /// @notice Mapping of the details of all the deposited funds:
    ///         depositor address -> token address -> deposit id -> DepositInfo
    mapping(address => mapping(address => mapping(uint256 => DepositInfo)))
        public deposits;

    /// @notice The number of deposits created. Includes the deposits that
    ///         were fully withdrawn. This is also the identifier of the most
    ///         recently created deposit.
    uint256 public depositCount;

    /// @notice List of tokens enabled for deposit or for deposit and lock.
    mapping(address => TokenAbility) public tokenAbility;

    /// @notice Minimum time a deposit can be locked.
    uint32 public minLockPeriod;

    /// @notice Maximum time a deposit can be locked.
    uint32 public maxLockPeriod;

    /// @notice Address of the liquidity treasury multisig.
    address public liquidityTreasury;

    /// @notice Mapping to store whether an asset is managed by the liquidity
    ///         treasury multisig.
    mapping(address => bool) public liquidityTreasuryManaged;

    /// @notice Mapping of depositable tokens to their receipt token fee
    ///         parameters.
    mapping(address => FeeInfo) public feeInfo;

    /// @notice Address of the tBTC token. Used for the to-tBTC migration of
    ///         deposited assets for which the migration was enabled by the
    ///         governance.
    address public tbtcToken;

    /// @notice Address of the tBTC migration treasury multisig. The tBTC
    ///         migration treasury is responsible for conducting the
    ///         to-tBTC migration of deposited assets for which the migration
    ///         was enabled by the governance.
    address public tbtcMigrationTreasury;

    /// @notice Mapping indicating whether the given asset can be migrated to
    ///         tBTC by the tBTC migration treasury and if so, what is the
    ///         global status of migrations. The key to the mapping is the
    ///         address of the asset being migrated.
    mapping(address => TbtcMigrationInfo) public tbtcMigrations;

    event Deposited(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId,
        uint256 amount
    );

    event Withdrawn(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId,
        uint256 amount
    );

    event Locked(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId,
        uint32 unlockAt,
        uint32 lockPeriod
    );

    event SupportedTokenAdded(address indexed token, TokenAbility tokenAbility);

    event MaxLockPeriodUpdated(uint32 maxLockPeriod);

    event MinLockPeriodUpdated(uint32 minLockPeriod);

    /// @notice Event emitted when the liquidity treasury address is updated.
    event LiquidityTreasuryUpdated(
        address indexed previousLiquidityTreasury,
        address indexed newLiquidityTreasury
    );

    /// @notice Event emitted when an asset is withdrawn by the liquidity
    ///         treasury multisig.
    event WithdrawnByLiquidityTreasury(address indexed token, uint256 amount);

    /// @notice Event emitted when an asset is set/unset as a liquidity
    ///         treasury multisig managed asset.
    event LiquidityTreasuryManagedAssetUpdated(
        address indexed asset,
        bool isManaged
    );

    /// @notice Event emitted when the receipt parameters for a deposit token
    ///         are updated.
    event ReceiptParamsUpdated(
        address indexed token,
        uint8 annualFee,
        uint8 mintCap,
        address receiptToken
    );

    /// @notice Event emitted when a receipt token is minted for a deposit.
    event ReceiptMinted(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId,
        uint256 amount
    );

    /// @notice Event emitted when a receipt token is repaid for a deposit.
    event ReceiptRepaid(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId,
        uint256 amount
    );

    /// @notice Event emitted when a fee is collected for a deposit.
    event FeeCollected(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId,
        uint256 fee
    );

    /// @notice Event emitted when the tBTC token address used for the
    ///         to-tBTC migration was set by the governance.
    /// @dev This event can be emitted only one time.
    event TbtcTokenAddressSet(address tbtc);

    /// @notice Event emitted when the to-tBTC migration treasury address was
    ///         updated.
    event TbtcMigrationTreasuryUpdated(
        address indexed previousMigrationTreasury,
        address indexed newMigrationTreasury
    );

    /// @notice Event emitted when the eligibility for the to-tBTC migration
    ///         for the asset was updated.
    event TbtcMigrationAllowedUpdated(address indexed token, bool isAllowed);

    /// @notice Event emitted when the to-tBTC migration was requested for the
    ///         deposit.
    event TbtcMigrationRequested(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId
    );

    /// @notice Event emitted when the to-tBTC migration was started for the
    ///         deposit.
    event TbtcMigrationStarted(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId
    );

    /// @notice Event emitted when the tBTC migration treasury started to-tBTC
    ///         migration for a set of deposits withdrawing the given amount of
    ///         the token from the Portal contract.
    event WithdrawnForTbtcMigration(address indexed token, uint256 amount);

    /// @notice Event emitted when the to-tBTC migration was completed for the
    ///         deposit.
    event TbtcMigrationCompleted(
        address indexed depositor,
        address indexed token,
        uint256 indexed depositId
    );

    /// @notice Event emitted when the to-tBTC migration was completed for one
    ///         or multiple deposits and the Portal contract was funded with
    ///         tBTC coming from the migrated assets.
    /// @dev The `amount` in the event is the amount in tBTC, using tBTC token's
    ///      precision.
    event FundedFromTbtcMigration(uint256 amount);

    /// @notice Event emitted in similar circumstances as `Withdrawn` except
    ///         that it represents a to-tBTC migrated deposit where the
    ///         withdrawal is performed in tBTC and not in the original deposit
    ///         token.
    event WithdrawnTbtcMigrated(
        address indexed depositor,
        address indexed token,
        address tbtcToken,
        uint256 indexed depositId,
        uint256 amountInTbtc
    );

    /// @notice Event emitted in similar circumstances as `FeeCollected`
    ///         except that it represents a to-tBTC migrated deposit where the
    ///         fee is collected in tBTC and not in the original deposit token.
    event FeeCollectedTbtcMigrated(
        address indexed depositor,
        address indexed token,
        address tbtcToken,
        uint256 indexed depositId,
        uint256 feeInTbtc
    );

    error IncorrectTokenAddress(address token);

    error IncorrectTokenAbility(TokenAbility ability);

    error IncorrectDepositor(address depositor);

    error IncorrectAmount(uint256 amount);

    error TokenNotSupported(address token);

    error TokenAlreadySupported(address token, TokenAbility tokenAbility);

    error InsufficientTokenAbility(address token, TokenAbility tokenAbility);

    error LockPeriodOutOfRange(uint32 lockPeriod);

    error IncorrectLockPeriod(uint256 lockPeriod);

    error LockPeriodTooShort(
        uint32 lockPeriod,
        uint32 newUnlockAt,
        uint32 existingUnlockAt
    );

    error DepositLocked(uint32 unlockAt);

    error DepositNotFound();

    /// @notice Error when the partial withdrawal amount is too high. Either it
    ///         equals the deposit amount (not a partial withdrawal) or it is
    ///         higher than the deposit amount.
    error PartialWithdrawalAmountTooHigh(uint256 depositAmount);

    /// @notice Error when the sender is not the liquidity treasury.
    error SenderNotLiquidityTreasury(address sender);

    /// @notice Error when the asset is not managed by the liquidity treasury.
    error AssetNotManagedByLiquidityTreasury(address asset);

    /// @notice Error when trying to withdraw without repaying the minted
    ///         receipt tokens back to Portal.
    error ReceiptNotRepaid(uint256 receiptMinted);

    /// @notice Error when the user tries to partially withdraw but the fee for
    ///         minting the receipt token is non-zero. In this case only a full
    ///         deposit withdrawal is possible.
    error ReceiptFeeOwed(uint256 feeOwed);

    /// @notice Error when the user tries to mint more than the mint limit
    ///         allows.
    error ReceiptMintLimitExceeded(
        uint256 mintLimit,
        uint96 currentlyMinted,
        uint256 feeOwed,
        uint256 amount
    );

    /// @notice Error when the user tries to repay more than the minted debt.
    error RepayAmountExceededDebt(uint96 mintedDebt, uint256 amount);

    /// @notice Error when the annual fee proposed exceeds 100%.
    error MaxAnnualFeeExceeded(uint8 annualFee);

    /// @notice Error when the mint cap proposed exceeds 100%.
    error MaxReceiptMintCapExceeded(uint8 mintCap);

    /// @notice Error when there is no receipt token set for the asset.
    error ReceiptMintingDisabled();

    /// @notice Error when the receipt token is already initialized.
    error ReceiptTokenAlreadyInitialized();

    /// @notice Error when the receipt token decimals are not 18.
    error IncorrectReceiptTokenDecimals(address receiptToken);

    /// @notice Error when token being accepted as deposit token does not
    ///         support decimals() function or the function is malfunctioning.
    error UnknownTokenDecimals(address token);

    /// @notice The given asset can be marked as eligible for tBTC migration if
    ///         it is not managed by the liquidity treasury. The given asset
    ///         can be managed by the liquidity treasury if it is not marked as
    ///         eligible for tBTC migration. The transaction reverts with this
    ///         error when the governance changes would conflict with this
    ///         limitation.
    error TbtcMigrationAndLiquidityManagementConflict();

    /// @notice Error when the governance tries to add tBTC as an asset eligible
    ///         for to-tBTC migration.
    error TbtcCanNotBeMigrated();

    /// @notice Error when the tBTC token address used for to-tBTC migrations
    ///         was already set by the governance.
    error TbtcTokenAddressAlreadySet();

    /// @notice Error when the tBTC token address used for to-tBTC migrations
    ///         was not set by the governance but there is an attempt to enable
    ///         to-tBTC migration for some asset.
    error TbtcTokenAddressNotSet();

    /// @notice Error when someone else but the tBTC migration treasury tried
    ///         to withdraw tokens for tBTC migration or to complete the
    ///         migration process.
    error SenderNotTbtcMigrationTreasury();

    /// @notice Error when the given to-tBTC transition state change is
    ///         requested from an unexpected state. For example, it is not
    ///         allowed to complete the migration of a deposit for which the
    ///         migration was not requested.
    error UnexpectedTbtcMigrationState(
        uint256 depositId,
        TbtcMigrationState currentState,
        TbtcMigrationState expectedState
    );

    /// @notice Error when the to-tBTC migration for the asset was not allowed
    ///         by the governance but the depositor tried to request a migration.
    error TbtcMigrationNotAllowed();

    /// @notice Error when the to-tBTC migration has been requested but not yet
    ///         completed and the depositor tries to withdraw the deposit.
    error TbtcMigrationNotCompleted();

    /// @notice Error when the to-tBTC migration was requested and the depositor
    ///         tries to partially withdraw the deposit.
    /// @dev The `Err` suffix is to distinguish this error from the
    ///      `TbtcMigrationRequested` event.
    error TbtcMigrationRequestedErr();

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /// @notice Initialize the contract with the supported tokens and their
    ///         abilities.
    /// @dev We are using OpenZeppelin's initializer pattern to initialize
    ///      the contract. This function is called only once during the contract
    ///      deployment.
    /// @param supportedTokens List of supported tokens and their abilities
    function initialize(
        SupportedToken[] memory supportedTokens
    ) external initializer {
        __Ownable_init(msg.sender);

        minLockPeriod = 4 weeks;
        maxLockPeriod = 39 weeks;
        depositCount = 0;

        for (uint256 i = 0; i < supportedTokens.length; i++) {
            address token = supportedTokens[i].token;
            TokenAbility ability = supportedTokens[i].tokenAbility;

            if (token == address(0)) {
                revert IncorrectTokenAddress(token);
            }

            tokenAbility[token] = ability;
        }
    }

    /// @notice Add a new supported token and define its ability.
    /// @dev Only the owner can add a new supported token. Supported token must
    ///      implement the decimals() method so Portal is able to perform
    ///      correct calculations.
    /// @param supportedToken Supported token and its ability
    function addSupportedToken(
        SupportedToken calldata supportedToken
    ) external onlyOwner {
        address token = supportedToken.token;
        TokenAbility ability = supportedToken.tokenAbility;

        if (token == address(0)) {
            revert IncorrectTokenAddress(token);
        }

        if (ability == TokenAbility.None) {
            revert IncorrectTokenAbility(ability);
        }

        if (tokenAbility[token] != TokenAbility.None) {
            revert TokenAlreadySupported(token, tokenAbility[token]);
        }

        // Attempt to call decimals() on the token to verify it exists
        // slither-disable-next-line unused-return
        try IERC20WithDecimals(token).decimals() {} catch {
            revert UnknownTokenDecimals(token);
        }

        tokenAbility[token] = ability;

        emit SupportedTokenAdded(token, ability);
    }

    /// @notice Set the liquidity treasury multisig address.
    /// @dev Only the owner can set the liquidity treasury multisig address. It
    ///      is possible to set the liquidity treasury multisig address to zero
    ///      in case the liquidity mining should be disabled.
    /// @param  _liquidityTreasury address of the liquidity treasury multisig
    function setLiquidityTreasury(
        address _liquidityTreasury
    ) external onlyOwner {
        emit LiquidityTreasuryUpdated(liquidityTreasury, _liquidityTreasury);

        // slither-disable-next-line missing-zero-check
        liquidityTreasury = _liquidityTreasury;
    }

    /// @notice Set whether an asset is managed by the liquidity treasury multisig.
    /// @dev Only the owner can set whether an asset is managed by the liquidity
    ///      treasury multisig. Only assets which can be locked in the Portal can
    ///      be managed by the liquidity treasury multisig.
    /// @param asset address of the asset to be managed by the liquidity treasury
    /// @param isManaged boolean value to set whether the asset is managed by the
    ///        liquidity treasury multisig
    function setAssetAsLiquidityTreasuryManaged(
        address asset,
        bool isManaged
    ) external onlyOwner {
        TokenAbility ability = tokenAbility[asset];

        if (ability == TokenAbility.None) {
            revert TokenNotSupported(asset);
        }

        if (ability != TokenAbility.DepositAndLock) {
            revert InsufficientTokenAbility(asset, tokenAbility[asset]);
        }

        if (tbtcMigrations[asset].isAllowed) {
            revert TbtcMigrationAndLiquidityManagementConflict();
        }

        liquidityTreasuryManaged[asset] = isManaged;

        emit LiquidityTreasuryManagedAssetUpdated(asset, isManaged);
    }

    /// @notice Set the tBTC token address. Setting the tBTC token address
    ///         allows the governance to enable to-tBTC migrations of selected
    ///         deposited assets.
    /// @dev Can only be executed one time. Can only be executed by the
    ///      governance.
    /// @param _tbtcToken address of the tBTC token
    function setTbtcTokenAddress(address _tbtcToken) external onlyOwner {
        if (_tbtcToken == address(0)) {
            revert IncorrectTokenAddress(_tbtcToken);
        }
        if (tbtcToken != address(0)) {
            revert TbtcTokenAddressAlreadySet();
        }

        tbtcToken = _tbtcToken;

        emit TbtcTokenAddressSet(_tbtcToken);
    }

    /// @notice Set the tBTC migration treasury multisig address.
    /// @dev Only the owner can set the tBTC migraton treasury multisig address.
    ///      It is possible to set the migration treasury address to zero in
    ///      case the migration should be disabled.
    /// @param  _tbtcMigrationTreasury the new address of the tBTC migration
    ///         treasury multisig
    function setTbtcMigrationTreasury(
        address _tbtcMigrationTreasury
    ) external onlyOwner {
        emit TbtcMigrationTreasuryUpdated(
            tbtcMigrationTreasury,
            _tbtcMigrationTreasury
        );

        // slither-disable-next-line missing-zero-check
        tbtcMigrationTreasury = _tbtcMigrationTreasury;
    }

    /// @notice Set whether an asset can be migrated to tBTC by the migration
    ///         treasury multisig. Only assets that can be deposited in the
    ///         Portal can be marked as eligible for migration. Only assets that
    ///         represent Bitcoin and can be mapped 1:1 to tBTC can be marked
    ///         as eligible for migration.
    /// @param asset address of the asset to upgrade the to-tBTC migration
    ///        settings for
    /// @param isAllowed boolean value indicating whether to-tBTC migration
    ///        is allowed
    function setAssetTbtcMigrationAllowed(
        address asset,
        bool isAllowed
    ) external onlyOwner {
        if (tbtcToken == address(0)) {
            revert TbtcTokenAddressNotSet();
        }
        if (asset == tbtcToken) {
            revert TbtcCanNotBeMigrated();
        }

        TokenAbility ability = tokenAbility[asset];

        if (ability == TokenAbility.None) {
            revert TokenNotSupported(asset);
        }

        if (liquidityTreasuryManaged[asset]) {
            revert TbtcMigrationAndLiquidityManagementConflict();
        }

        tbtcMigrations[asset].isAllowed = isAllowed;

        emit TbtcMigrationAllowedUpdated(asset, isAllowed);
    }

    /// @notice Set the minimum lock period for deposits.
    /// @dev Only the owner can update the minimum lock period. The new value
    ///      must be normalized to weeks, non-zero, and not higher than the
    ///      maximum lock period.
    /// @param _minLockPeriod new minimum lock period
    function setMinLockPeriod(uint32 _minLockPeriod) external onlyOwner {
        uint32 normalizedLockPeriod = _normalizeLockPeriod(_minLockPeriod);

        if (
            _minLockPeriod != normalizedLockPeriod ||
            normalizedLockPeriod == 0 ||
            _minLockPeriod > maxLockPeriod
        ) {
            revert IncorrectLockPeriod(_minLockPeriod);
        }

        minLockPeriod = _minLockPeriod;
        emit MinLockPeriodUpdated(_minLockPeriod);
    }

    /// @notice Set the maximum lock period for deposits. Maximum lock
    ///      period is used as a limit to prevent users from accidentally
    ///      locking their deposits for too long.
    /// @dev Only the owner can update the maximum lock period. The new value
    ///      must be normalized to weeks and not lower than the minimum lock
    ///      period.
    /// @param _maxLockPeriod new maximum lock period
    function setMaxLockPeriod(uint32 _maxLockPeriod) external onlyOwner {
        if (
            _maxLockPeriod != _normalizeLockPeriod(_maxLockPeriod) ||
            _maxLockPeriod < minLockPeriod
        ) {
            revert IncorrectLockPeriod(_maxLockPeriod);
        }

        maxLockPeriod = _maxLockPeriod;
        emit MaxLockPeriodUpdated(_maxLockPeriod);
    }

    /// @notice Set the receipt parameters for a supported deposit token. The
    ///         receipt token address can be set only one time. All following
    ///         calls to this function for the same deposit token needs to pass
    ///         the same address of the receipt token as set initially.
    /// @dev Only the owner can set the receipt parameters. Receipt token must
    ///      implement the decimals() method so Portal is able to perform correct
    ///      calculations for minting receipt tokens.
    /// @param token deposit token address to set the parameters
    /// @param annualFee annual fee in percentage for minting the receipt token
    /// @param mintCap mint cap in percentage for minting the receipt token
    /// @param receiptToken receipt token address
    function setReceiptParams(
        address token,
        uint8 annualFee,
        uint8 mintCap,
        address receiptToken
    ) external onlyOwner {
        if (tokenAbility[token] == TokenAbility.None) {
            revert TokenNotSupported(token);
        }

        if (annualFee > 100) {
            revert MaxAnnualFeeExceeded(annualFee);
        }
        if (mintCap > 100) {
            revert MaxReceiptMintCapExceeded(mintCap);
        }
        if (receiptToken == address(0)) {
            revert IncorrectTokenAddress(receiptToken);
        }

        FeeInfo storage i = feeInfo[token];

        if (i.receiptToken != address(0) && i.receiptToken != receiptToken) {
            revert ReceiptTokenAlreadyInitialized();
        }

        uint8 decimals = IERC20WithDecimals(receiptToken).decimals();

        if (decimals != 18) {
            revert IncorrectReceiptTokenDecimals(receiptToken);
        }

        _updateFeeIntegral(token);

        i.annualFee = annualFee;
        i.mintCap = mintCap;
        i.receiptToken = receiptToken;
        // solhint-disable-next-line not-rely-on-time
        i.lastFeeUpdateAt = uint32(block.timestamp);

        emit ReceiptParamsUpdated(token, annualFee, mintCap, receiptToken);
    }

    /// @notice Withdraws all deposited tokens.
    ///
    ///         Deposited lockable tokens can be withdrawn at any time if
    ///         there is no lock set on the deposit or the lock period has passed.
    ///         There is no way to withdraw locked deposit. Tokens that are not
    ///         lockable can be withdrawn at any time.
    ///
    ///         Deposits for which receipt tokens were minted and not fully
    ///         repaid can not be withdrawn even if the lock expired. Repaying
    ///         all receipt tokens is a must to withdraw the deposit. Upon
    ///         withdrawing a deposit for which the receipt tokens were minted,
    ///         the fee is collected based on the annual fee and the amount
    ///         of minted receipt tokens.
    ///
    ///         Deposits for which to-tBTC migration was requested but not yet
    ///         completed yet can not be withdrawn. The deposits for which the
    ///         to-tBTC migration was completed are withdrawn in tBTC.
    ///
    ///         This function withdraws all deposited tokens. For partial
    ///         withdrawals, use `withdrawPartially`.
    /// @param token deposited token address
    /// @param depositId id of the deposit
    function withdraw(address token, uint256 depositId) external {
        TokenAbility ability = tokenAbility[token];

        if (ability == TokenAbility.None) {
            revert TokenNotSupported(token);
        }

        DepositInfo storage selectedDeposit = deposits[msg.sender][token][
            depositId
        ];

        if (
            selectedDeposit.tbtcMigrationState ==
            TbtcMigrationState.Requested ||
            selectedDeposit.tbtcMigrationState == TbtcMigrationState.InProgress
        ) {
            revert TbtcMigrationNotCompleted();
        }

        if (
            ability == TokenAbility.DepositAndLock &&
            // solhint-disable-next-line not-rely-on-time
            block.timestamp < selectedDeposit.unlockAt
        ) {
            revert DepositLocked(selectedDeposit.unlockAt);
        }

        if (selectedDeposit.receiptMinted > 0) {
            revert ReceiptNotRepaid(selectedDeposit.receiptMinted);
        }

        uint96 depositedAmount = selectedDeposit.balance;

        if (depositedAmount == 0) {
            revert DepositNotFound();
        }

        if (
            selectedDeposit.tbtcMigrationState == TbtcMigrationState.Completed
        ) {
            uint96 feeInTbtc = 0;

            // The fee is collected based on the pre-migration rules, so based
            // on the annual fee as set by the governance for the original token.
            // The fee is collected in tBTC as the entire deposit was migrated
            // to tBTC.
            if (selectedDeposit.feeOwed > 0) {
                FeeInfo storage tokenFeeInfo = feeInfo[token];

                feeInTbtc = _adjustTokenDecimals(
                    tokenFeeInfo.receiptToken,
                    tbtcToken,
                    selectedDeposit.feeOwed
                );
            }

            uint96 depositedAmountInTbtc = _adjustTokenDecimals(
                token,
                tbtcToken,
                depositedAmount
            );

            uint96 withdrawableInTbtc = depositedAmountInTbtc - feeInTbtc;

            emit WithdrawnTbtcMigrated(
                msg.sender,
                token,
                tbtcToken,
                depositId,
                withdrawableInTbtc
            );
            emit FeeCollectedTbtcMigrated(
                msg.sender,
                token,
                tbtcToken,
                depositId,
                feeInTbtc
            );

            feeInfo[tbtcToken].feeCollected += feeInTbtc;

            delete deposits[msg.sender][token][depositId];
            IERC20(tbtcToken).safeTransfer(msg.sender, withdrawableInTbtc);
        } else {
            uint96 fee = 0;

            if (selectedDeposit.feeOwed > 0) {
                FeeInfo storage tokenFeeInfo = feeInfo[token];

                fee = _adjustTokenDecimals(
                    tokenFeeInfo.receiptToken,
                    token,
                    selectedDeposit.feeOwed
                );
            }

            uint96 withdrawable = depositedAmount - fee;

            emit Withdrawn(msg.sender, token, depositId, withdrawable);
            emit FeeCollected(msg.sender, token, depositId, fee);

            feeInfo[token].feeCollected += fee;

            delete deposits[msg.sender][token][depositId];
            IERC20(token).safeTransfer(msg.sender, withdrawable);
        }
    }

    /// @notice Withdraws part of the deposited tokens.
    ///
    ///         Deposited lockable tokens can be withdrawn at any time if
    ///         there is no lock set on the deposit or the lock period has passed.
    ///         There is no way to withdraw locked deposit. Tokens that are not
    ///         lockable can be withdrawn at any time.
    ///
    ///         Deposits for which receipt tokens were minted and fully repaid
    ///         can not be partially withdrawn even if the lock expired.
    ///         Repaying all receipt tokens is a must to partially withdraw the
    ///         deposit. If the fee for receipt tokens minted is non-zero, the
    ///         deposit can not be partially withdrawn and only a full
    ///         withdrawal is possible.
    ///
    ///         Deposits for which the to-tBTC migration was requested can not
    ///         be withdrawn partially.
    ///
    ///         This function allows only for partial withdrawals. For full
    ///         withdrawals, use `withdraw`.
    /// @param token deposited token address
    /// @param depositId id of the deposit
    /// @param amount the amount to be withdrawn
    function withdrawPartially(
        address token,
        uint256 depositId,
        uint96 amount
    ) external {
        TokenAbility ability = tokenAbility[token];

        if (ability == TokenAbility.None) {
            revert TokenNotSupported(token);
        }

        if (amount == 0) {
            revert IncorrectAmount(amount);
        }

        DepositInfo storage selectedDeposit = deposits[msg.sender][token][
            depositId
        ];

        if (selectedDeposit.balance == 0) {
            revert DepositNotFound();
        }

        if (amount >= selectedDeposit.balance) {
            revert PartialWithdrawalAmountTooHigh(selectedDeposit.balance);
        }

        if (
            ability == TokenAbility.DepositAndLock &&
            // solhint-disable-next-line not-rely-on-time
            block.timestamp < selectedDeposit.unlockAt
        ) {
            revert DepositLocked(selectedDeposit.unlockAt);
        }

        if (selectedDeposit.receiptMinted > 0) {
            revert ReceiptNotRepaid(selectedDeposit.receiptMinted);
        }

        if (selectedDeposit.feeOwed > 0) {
            revert ReceiptFeeOwed(selectedDeposit.feeOwed);
        }

        if (
            selectedDeposit.tbtcMigrationState !=
            TbtcMigrationState.NotRequested
        ) {
            revert TbtcMigrationRequestedErr();
        }

        emit Withdrawn(msg.sender, token, depositId, amount);
        selectedDeposit.balance -= amount;
        IERC20(token).safeTransfer(msg.sender, amount);
    }

    /// @notice Receive approval from a token contract and deposit the tokens in
    ///         one transaction. If the the token is lockable and lock period is
    ///         greater than 0, the deposit will be locked for the given period.
    /// @dev This function is called by the token following the `approveAndCall`
    ///      pattern.
    //       Encoded lock period is counted in seconds, will be normalized to
    ///      weeks. If non-zero, it must not be shorter than the minimum lock
    ///      period and must not be longer than the maximum lock period. To not
    ///      lock the deposit, the lock period must be 0.
    /// @param from address which approved and sent the tokens
    /// @param amount amount of tokens sent
    /// @param token address of the token contract
    /// @param data encoded lock period
    function receiveApproval(
        address from,
        uint256 amount,
        address token,
        bytes calldata data
    ) external override {
        if (token != msg.sender) {
            revert IncorrectTokenAddress(token);
        }
        if (amount > type(uint96).max) {
            revert IncorrectAmount(amount);
        }

        uint32 lockPeriod = abi.decode(data, (uint32));

        _depositFor(from, from, token, uint96(amount), lockPeriod);
    }

    /// @notice Deposit and optionally lock tokens for the given period.
    /// @dev Lock period will be normalized to weeks. If non-zero, it must not
    ///      be shorter than the minimum lock period and must not be longer than
    ///      the maximum lock period.
    /// @param token token address to deposit
    /// @param amount amount of tokens to deposit
    /// @param lockPeriod lock period in seconds, 0 to not lock the deposit
    function deposit(address token, uint96 amount, uint32 lockPeriod) external {
        _depositFor(msg.sender, msg.sender, token, amount, lockPeriod);
    }

    /// @notice Deposit and optionally lock tokens to the contract on behalf of
    ///         someone else.
    /// @dev Lock period will be normalized to weeks. If non-zero, it must not
    ///      be shorter than the minimum lock period and must not be longer than
    ///      the maximum lock period.
    /// @param depositOwner address that will be able to withdraw the deposit
    /// @param token token address to deposit
    /// @param amount amount of tokens to deposit
    /// @param lockPeriod lock period in seconds, 0 to not lock the deposit
    function depositFor(
        address depositOwner,
        address token,
        uint96 amount,
        uint32 lockPeriod
    ) external {
        _depositFor(depositOwner, msg.sender, token, amount, lockPeriod);
    }

    /// @notice Lock existing deposit for a given period. This function can be
    ///         used to lock a deposit that was not locked when it was created or
    ///         to extend the lock period of an already locked deposit.
    /// @param token address of the deposited token
    /// @param depositId id of the deposit
    /// @param lockPeriod new lock period in seconds
    function lock(
        address token,
        uint256 depositId,
        uint32 lockPeriod
    ) external {
        DepositInfo storage depositInfo = deposits[msg.sender][token][
            depositId
        ];

        if (depositInfo.balance == 0) {
            revert DepositNotFound();
        }

        TokenAbility ability = tokenAbility[token];
        if (ability != TokenAbility.DepositAndLock) {
            revert InsufficientTokenAbility(token, ability);
        }

        uint32 existingUnlockAt = depositInfo.unlockAt;
        uint32 newUnlockAt = _calculateUnlockTime(
            msg.sender,
            token,
            depositId,
            lockPeriod
        );
        if (newUnlockAt <= existingUnlockAt) {
            revert LockPeriodTooShort(
                lockPeriod,
                newUnlockAt,
                existingUnlockAt
            );
        }

        depositInfo.unlockAt = newUnlockAt;
    }

    /// @notice External withdraw function to enable the liquidity treasury to
    ///         withdraw tokens from the contract. Only the liquidity treasury
    ///         can withdraw tokens from the contract using this function.
    /// @param token token address to withdraw
    /// @param amount amount of respective token to withdraw
    function withdrawAsLiquidityTreasury(
        address token,
        uint256 amount
    ) external {
        if (msg.sender != liquidityTreasury) {
            revert SenderNotLiquidityTreasury(msg.sender);
        }

        if (amount == 0) {
            revert IncorrectAmount(amount);
        }

        if (!liquidityTreasuryManaged[token]) {
            revert AssetNotManagedByLiquidityTreasury(token);
        }

        emit WithdrawnByLiquidityTreasury(token, amount);

        IERC20(token).safeTransfer(msg.sender, amount);
    }

    /// @notice Mint a deposit receipt token against an existing deposit.
    /// @param token the token address related with the deposit
    /// @param depositId the ID of the deposit
    /// @param amount amount of the receipt token to mint
    function mintReceipt(
        address token,
        uint256 depositId,
        uint256 amount
    ) external {
        FeeInfo storage fee = feeInfo[token];

        if (fee.receiptToken == address(0)) {
            revert ReceiptMintingDisabled();
        }

        if (amount == 0) {
            revert IncorrectAmount(amount);
        }

        DepositInfo storage depositInfo = deposits[msg.sender][token][
            depositId
        ];

        if (depositInfo.balance == 0) {
            revert DepositNotFound();
        }

        _updateFee(depositInfo, token);

        // Normalize deposit balance to match receipt token decimals
        uint96 normalizedBalance = _adjustTokenDecimals(
            token,
            fee.receiptToken,
            depositInfo.balance
        );

        uint96 mintLimit = (normalizedBalance * fee.mintCap) / 100;

        if (
            amount + depositInfo.receiptMinted + depositInfo.feeOwed > mintLimit
        ) {
            revert ReceiptMintLimitExceeded(
                mintLimit,
                depositInfo.receiptMinted,
                depositInfo.feeOwed,
                amount
            );
        }

        depositInfo.receiptMinted += uint96(amount);
        fee.totalMinted += uint96(amount);

        emit ReceiptMinted(msg.sender, token, depositId, amount);

        IReceiptToken(fee.receiptToken).mintReceipt(msg.sender, amount);
    }

    /// @notice Repay the deposit receipt token of a particular deposit.
    /// @param token the token address related with the deposit
    /// @param depositId the ID of the deposit
    /// @param amount how much of the token to repay, up to outstanding balance
    function repayReceipt(
        address token,
        uint256 depositId,
        uint256 amount
    ) public {
        FeeInfo storage fee = feeInfo[token];

        if (fee.receiptToken == address(0)) {
            revert ReceiptMintingDisabled();
        }

        if (amount == 0) {
            revert IncorrectAmount(amount);
        }

        DepositInfo storage depositInfo = deposits[msg.sender][token][
            depositId
        ];

        if (depositInfo.balance == 0) {
            revert DepositNotFound();
        }

        if (depositInfo.receiptMinted < amount) {
            revert RepayAmountExceededDebt(depositInfo.receiptMinted, amount);
        }

        _updateFee(depositInfo, token);

        depositInfo.receiptMinted -= uint96(amount);
        fee.totalMinted -= uint96(amount);

        emit ReceiptRepaid(msg.sender, token, depositId, amount);

        // transfer the repaid receipt tokens and burn them
        IERC20(fee.receiptToken).safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );

        IReceiptToken(fee.receiptToken).burnReceipt(amount);
    }

    /// @notice Request the to-tBTC migration for the given deposit. The
    ///         migration happens asynchronously and is managed by the tBTC
    ///         migration treasury. The contract gives no guarantees when the
    ///         migration will be completed.
    /// @param token deposited token address
    /// @param depositId id of the deposit
    function requestTbtcMigration(address token, uint256 depositId) external {
        DepositInfo storage depositInfo = deposits[msg.sender][token][
            depositId
        ];

        TbtcMigrationInfo storage migrationInfo = tbtcMigrations[token];
        if (!migrationInfo.isAllowed) {
            revert TbtcMigrationNotAllowed();
        }

        if (depositInfo.tbtcMigrationState != TbtcMigrationState.NotRequested) {
            revert UnexpectedTbtcMigrationState(
                depositId,
                depositInfo.tbtcMigrationState,
                TbtcMigrationState.NotRequested
            );
        }

        depositInfo.tbtcMigrationState = TbtcMigrationState.Requested;

        emit TbtcMigrationRequested(msg.sender, token, depositId);
    }

    /// @notice Used by the tBTC migration treasury to withdraw tokens from the
    ///         deposits marked by their owners as requested for to-tBTC
    ///         migration. The caller specifies which deposits are to begin the
    ///         migration. Calling this function for the deposit marks it
    ///         as to-tBTC migration started but gives no promise as when the
    ///         to-tBTC migration will be completed. For all deposits for which
    ///         the to-tBTC migration was started, the tBTC migration multisig
    ///         must ensure to call the `finalizeTbtcMigration` function once
    ///         the migration is completed.
    /// @param token deposited token address
    /// @param depositsToMigrate an array of deposits for which the to-tBTC
    ///        migration was requested and from which the assets should be
    ///        withdrawn for the to-tBTC migration
    function withdrawForTbtcMigration(
        address token,
        DepositToMigrate[] memory depositsToMigrate
    ) external {
        if (msg.sender != tbtcMigrationTreasury) {
            revert SenderNotTbtcMigrationTreasury();
        }

        uint96 totalToMigrate = 0;

        for (uint256 i = 0; i < depositsToMigrate.length; i++) {
            address depositOwner = depositsToMigrate[i].depositor;
            uint256 depositId = depositsToMigrate[i].depositId;

            DepositInfo storage depositInfo = deposits[depositOwner][token][
                depositId
            ];
            if (depositInfo.balance == 0) {
                revert DepositNotFound();
            }

            if (
                depositInfo.tbtcMigrationState != TbtcMigrationState.Requested
            ) {
                revert UnexpectedTbtcMigrationState(
                    depositId,
                    depositInfo.tbtcMigrationState,
                    TbtcMigrationState.Requested
                );
            }

            depositInfo.tbtcMigrationState = TbtcMigrationState.InProgress;
            totalToMigrate += depositInfo.balance;

            emit TbtcMigrationStarted(depositOwner, token, depositId);
        }

        TbtcMigrationInfo storage migrationInfo = tbtcMigrations[token];
        migrationInfo.totalMigrating += totalToMigrate;

        emit WithdrawnForTbtcMigration(token, totalToMigrate);

        IERC20(token).safeTransfer(tbtcMigrationTreasury, totalToMigrate);
    }

    /// @notice Used by the tBTC migration treasury to complete the to-tBTC
    ///         migration for the selected set of deposits. For all of those
    ///         deposits the migration should be started by calling
    ///         `withdrawForTbtcMigration` function before. The treasury has to
    ///         approve the amount of tBTC to the Portal contract equal to the
    ///         total migrated amount of the migrated deposits, adjusted to tBTC
    ///         token decimals.
    /// @param token pre-migration deposited token address
    /// @param migratedDeposits an array of deposits for which the to-tBTC
    ///        migration was completed
    function completeTbtcMigration(
        address token,
        DepositToMigrate[] memory migratedDeposits
    ) external {
        if (msg.sender != tbtcMigrationTreasury) {
            revert SenderNotTbtcMigrationTreasury();
        }

        uint96 totalMigrated = 0;

        for (uint256 i = 0; i < migratedDeposits.length; i++) {
            address depositOwner = migratedDeposits[i].depositor;
            uint256 depositId = migratedDeposits[i].depositId;

            DepositInfo storage depositInfo = deposits[depositOwner][token][
                depositId
            ];

            if (
                depositInfo.tbtcMigrationState != TbtcMigrationState.InProgress
            ) {
                revert UnexpectedTbtcMigrationState(
                    depositId,
                    depositInfo.tbtcMigrationState,
                    TbtcMigrationState.InProgress
                );
            }

            totalMigrated += depositInfo.balance;

            depositInfo.tbtcMigrationState = TbtcMigrationState.Completed;

            emit TbtcMigrationCompleted(depositOwner, token, depositId);
        }

        TbtcMigrationInfo storage migrationInfo = tbtcMigrations[token];
        migrationInfo.totalMigrating -= totalMigrated;

        uint96 totalMigratedInTbtc = _adjustTokenDecimals(
            token,
            tbtcToken,
            totalMigrated
        );

        emit FundedFromTbtcMigration(totalMigratedInTbtc);

        IERC20(tbtcToken).safeTransferFrom(
            msg.sender,
            address(this),
            totalMigratedInTbtc
        );
    }

    /// @notice Get the balance and unlock time of a given deposit. Note that
    ///         the deposit could be migrated to tBTC so even though the `token`
    ///         key under which it is available could still point to the
    ///         pre-migration version, the witdrawal - when requested - will
    ///         happen in tBTC.
    /// @param depositor depositor address
    /// @param token token address to get the balance
    /// @param depositId id of the deposit
    function getDeposit(
        address depositor,
        address token,
        uint256 depositId
    ) external view returns (DepositInfo memory) {
        return deposits[depositor][token][depositId];
    }

    /// @notice Internal deposit function to deposit tokens on behalf of a given
    ///         address - sender or someone else. If the lock period passed as
    ///         a parameter is non-zero, the function will lock the deposit.
    /// @param depositOwner address that will be able to withdraw the deposit
    /// @param token token address to deposit
    /// @param amount amount of tokens to deposit
    /// @param lockPeriod lock period in seconds, 0 to not lock the deposit
    function _depositFor(
        address depositOwner,
        address depositFunder,
        address token,
        uint96 amount,
        uint32 lockPeriod
    ) internal {
        TokenAbility ability = tokenAbility[token];
        if (ability == TokenAbility.None) {
            revert TokenNotSupported(token);
        }
        if (lockPeriod > 0 && ability == TokenAbility.Deposit) {
            revert InsufficientTokenAbility(token, ability);
        }

        if (depositOwner == address(0)) {
            revert IncorrectDepositor(depositOwner);
        }

        if (amount == 0) {
            revert IncorrectAmount(amount);
        }

        depositCount++;

        uint256 depositId = depositCount;

        emit Deposited(depositOwner, token, depositId, amount);

        deposits[depositOwner][token][depositId] = DepositInfo({
            balance: amount,
            // solhint-disable-next-line not-rely-on-time
            unlockAt: _calculateUnlockTime(
                depositOwner,
                token,
                depositId,
                lockPeriod
            ),
            receiptMinted: 0,
            feeOwed: 0,
            lastFeeIntegral: 0,
            tbtcMigrationState: TbtcMigrationState.NotRequested
        });

        IERC20(token).safeTransferFrom(depositFunder, address(this), amount);
    }

    /// @notice Calculates the unlock time normalizing the lock time value and
    ///         making sure it is in the allowed range. Returns the timestamp at
    ///         which the deposit can be unlocked.
    /// @dev This function DOES NOT check if the deposit exists and if the
    ///      existing deposit already has a lock time. Please validate it before
    ///      calling this function.
    /// @param depositor depositor address
    /// @param token token of the deposit that will be locked
    /// @param depositId id of the deposit that will be locked
    /// @param lockPeriod lock period in seconds
    function _calculateUnlockTime(
        address depositor,
        address token,
        uint depositId,
        uint32 lockPeriod
    ) internal returns (uint32) {
        // Short-circuit if there is no intention to lock. There is no need to
        // check the minimum/maximum lock time or normalize the lock period.
        if (lockPeriod == 0) {
            // solhint-disable-next-line not-rely-on-time
            return uint32(block.timestamp);
        }

        uint32 normalizedLockPeriod = _normalizeLockPeriod(lockPeriod);

        if (
            normalizedLockPeriod == 0 ||
            normalizedLockPeriod < minLockPeriod ||
            normalizedLockPeriod > maxLockPeriod
        ) {
            revert LockPeriodOutOfRange(lockPeriod);
        }

        // solhint-disable-next-line not-rely-on-time
        uint32 unlockAt = uint32(block.timestamp) + normalizedLockPeriod;

        // It is gas-cheaper to pass the required parameters and emit the event
        // here instead of checking the result and emit the event in the calling
        // function. Keep in mind internal functions are inlined.
        emit Locked(
            depositor,
            token,
            depositId,
            unlockAt,
            normalizedLockPeriod
        );

        return unlockAt;
    }

    /// @notice Internal function to update the fee information of a given
    ///         deposit.
    /// @param depositInfo Storage reference to the deposit info to be updated.
    /// @param token Address of the token related to the deposit.
    function _updateFee(
        DepositInfo storage depositInfo,
        address token
    ) internal {
        _updateFeeIntegral(token);

        FeeInfo memory fee = feeInfo[token];

        depositInfo.feeOwed += _calculateFeeAccrued(
            fee.feeIntegral - depositInfo.lastFeeIntegral,
            depositInfo.receiptMinted
        );
        depositInfo.lastFeeIntegral = fee.feeIntegral;
    }

    /// @notice Internal function to update the fee integral for a given token.
    /// @param token Address of the token to update the fee integral.
    function _updateFeeIntegral(address token) internal {
        FeeInfo storage i = feeInfo[token];
        uint96 feePerSecond = _calculateFeeRate(token);

        // solhint-disable-next-line not-rely-on-time
        uint32 timeInterval = uint32(block.timestamp) - i.lastFeeUpdateAt;
        uint96 accumulated = timeInterval * feePerSecond;
        i.feeIntegral += uint88(accumulated);
        // solhint-disable-next-line not-rely-on-time
        i.lastFeeUpdateAt = uint32(block.timestamp);
    }

    /// @notice Internal function to calculate the fee rate per second for a
    ///         given token.
    /// @param token Address of the token.
    function _calculateFeeRate(address token) internal view returns (uint96) {
        FeeInfo memory i = feeInfo[token];
        // The i.annualFee is expressed in % so we divide by 100 to get the
        // fee rate in token units: 10^18 / 10^2 = 10^16
        return (uint96(i.annualFee) * (10 ** 16)) / (365 days);
    }

    /// @notice Calculates the fee accrued based on the given integral
    ///         difference and minted amount.
    /// @dev Multiplies the integral difference by the minted amount and scales
    ///      it down to fit the desired precision.
    /// @param integralDiff The difference in integral values.
    /// @param mintedAmount The amount of tokens minted.
    function _calculateFeeAccrued(
        uint88 integralDiff,
        uint96 mintedAmount
    ) internal pure returns (uint96) {
        return
            uint96((uint256(integralDiff) * uint256(mintedAmount)) / 10 ** 18);
    }

    /// @notice Normalizes the lock period to weeks. Will round down if not
    ///         normalized.
    /// @param lockPeriod the lock period to be normalized
    function _normalizeLockPeriod(
        uint32 lockPeriod
    ) internal pure returns (uint32) {
        // slither-disable-next-line divide-before-multiply
        return (lockPeriod / 1 weeks) * 1 weeks;
    }

    /// @notice Adjusts the decimals amount to desired precision for the two
    ///         token addresses and amount expressed in the precision used by
    ///         one of them.
    /// @param sourceToken the token address of the source amount
    /// @param targetToken the token address of the target amount
    /// @param amount the source amount to be adjusted
    function _adjustTokenDecimals(
        address sourceToken,
        address targetToken,
        uint96 amount
    ) internal view returns (uint96) {
        uint8 sourceDecimals = IERC20WithDecimals(sourceToken).decimals();
        uint8 targetDecimals = IERC20WithDecimals(targetToken).decimals();

        if (sourceDecimals < targetDecimals) {
            return uint96(amount * (10 ** (targetDecimals - sourceDecimals)));
        } else if (sourceDecimals > targetDecimals) {
            return uint96(amount / (10 ** (sourceDecimals - targetDecimals)));
        }

        return amount;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
    struct Ownable2StepStorage {
        address _pendingOwner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;

    function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
        assembly {
            $.slot := Ownable2StepStorageLocation
        }
    }

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

    function __Ownable2Step_init() internal onlyInitializing {
    }

    function __Ownable2Step_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        return $._pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        $._pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        delete $._pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable
    struct OwnableStorage {
        address _owner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;

    function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
        assembly {
            $.slot := OwnableStorageLocation
        }
    }

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    function __Ownable_init(address initialOwner) internal onlyInitializing {
        __Ownable_init_unchained(initialOwner);
    }

    function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        OwnableStorage storage $ = _getOwnableStorage();
        return $._owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        OwnableStorage storage $ = _getOwnableStorage();
        address oldOwner = $._owner;
        $._owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

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

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

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

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

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

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

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

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

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

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

File 10 of 13 : IERC20WithDecimals.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;

/// @notice An interface to be optionally implemented by ERC20 tokens to retrieve
///         the decimals places of the token. We need this interface because to
///         properly calculate amounts when 2 tokens are involved we need to know
///         their decimals places.
///         As decimals() is optional in the ERC20 standard, additional interface
///         is needed to retrieve the decimals places of the token.
///         Portal contract assumes that only tokens supporting this interface
///         are accepted as supported tokens or receipt tokens.
interface IERC20WithDecimals {
    /// @notice Returns the decimals places of the token
    function decimals() external view returns (uint8);
}

File 11 of 13 : IERC20WithPermit.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;

/// @notice An interface to be optionally implemented by ERC20 tokens to enable
///         gasless approvals.
interface IERC20WithPermit {
    /// @notice EIP2612 approval made with secp256k1 signature.
    ///         Users can authorize a transfer of their tokens with a signature
    ///         conforming EIP712 standard, rather than an on-chain transaction
    ///         from their address. Anyone can submit this signature on the
    ///         user's behalf by calling the permit function, paying gas fees,
    ///         and possibly performing other actions in the same transaction.
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;

/// @notice IReceiptToken is an interface that should be implemented by ERC20
///         receipt tokens used in the Portal contract.
interface IReceiptToken {
    /// @notice Mints `amount` receipt tokens and transfers them to the `to`
    ///         address.
    function mintReceipt(address to, uint256 amount) external;

    /// @notice Burns `amount` of receipt tokens owned by `msg.sender`.
    function burnReceipt(uint256 amount) external;
}

File 13 of 13 : IReceiveApproval.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.24;

/// @notice An interface that should be implemented by contracts supporting
///         `approveAndCall`/`receiveApproval` pattern.
interface IReceiveApproval {
    /// @notice Receives approval to spend tokens. Called as a result of
    ///         `approveAndCall` call on the token.
    function receiveApproval(
        address from,
        uint256 amount,
        address token,
        bytes calldata extraData
    ) external;
}

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

Contract Security Audit

Contract ABI

API
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"AssetNotManagedByLiquidityTreasury","type":"error"},{"inputs":[{"internalType":"uint32","name":"unlockAt","type":"uint32"}],"name":"DepositLocked","type":"error"},{"inputs":[],"name":"DepositNotFound","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"IncorrectAmount","type":"error"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"}],"name":"IncorrectDepositor","type":"error"},{"inputs":[{"internalType":"uint256","name":"lockPeriod","type":"uint256"}],"name":"IncorrectLockPeriod","type":"error"},{"inputs":[{"internalType":"address","name":"receiptToken","type":"address"}],"name":"IncorrectReceiptTokenDecimals","type":"error"},{"inputs":[{"internalType":"enum Portal.TokenAbility","name":"ability","type":"uint8"}],"name":"IncorrectTokenAbility","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"IncorrectTokenAddress","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum Portal.TokenAbility","name":"tokenAbility","type":"uint8"}],"name":"InsufficientTokenAbility","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[{"internalType":"uint32","name":"lockPeriod","type":"uint32"}],"name":"LockPeriodOutOfRange","type":"error"},{"inputs":[{"internalType":"uint32","name":"lockPeriod","type":"uint32"},{"internalType":"uint32","name":"newUnlockAt","type":"uint32"},{"internalType":"uint32","name":"existingUnlockAt","type":"uint32"}],"name":"LockPeriodTooShort","type":"error"},{"inputs":[{"internalType":"uint8","name":"annualFee","type":"uint8"}],"name":"MaxAnnualFeeExceeded","type":"error"},{"inputs":[{"internalType":"uint8","name":"mintCap","type":"uint8"}],"name":"MaxReceiptMintCapExceeded","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositAmount","type":"uint256"}],"name":"PartialWithdrawalAmountTooHigh","type":"error"},{"inputs":[{"internalType":"uint256","name":"feeOwed","type":"uint256"}],"name":"ReceiptFeeOwed","type":"error"},{"inputs":[{"internalType":"uint256","name":"mintLimit","type":"uint256"},{"internalType":"uint96","name":"currentlyMinted","type":"uint96"},{"internalType":"uint256","name":"feeOwed","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReceiptMintLimitExceeded","type":"error"},{"inputs":[],"name":"ReceiptMintingDisabled","type":"error"},{"inputs":[{"internalType":"uint256","name":"receiptMinted","type":"uint256"}],"name":"ReceiptNotRepaid","type":"error"},{"inputs":[],"name":"ReceiptTokenAlreadyInitialized","type":"error"},{"inputs":[{"internalType":"uint96","name":"mintedDebt","type":"uint96"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RepayAmountExceededDebt","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"SenderNotLiquidityTreasury","type":"error"},{"inputs":[],"name":"SenderNotTbtcMigrationTreasury","type":"error"},{"inputs":[],"name":"TbtcCanNotBeMigrated","type":"error"},{"inputs":[],"name":"TbtcMigrationAndLiquidityManagementConflict","type":"error"},{"inputs":[],"name":"TbtcMigrationNotAllowed","type":"error"},{"inputs":[],"name":"TbtcMigrationNotCompleted","type":"error"},{"inputs":[],"name":"TbtcMigrationRequestedErr","type":"error"},{"inputs":[],"name":"TbtcTokenAddressAlreadySet","type":"error"},{"inputs":[],"name":"TbtcTokenAddressNotSet","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum Portal.TokenAbility","name":"tokenAbility","type":"uint8"}],"name":"TokenAlreadySupported","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"TokenNotSupported","type":"error"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"},{"internalType":"enum Portal.TbtcMigrationState","name":"currentState","type":"uint8"},{"internalType":"enum Portal.TbtcMigrationState","name":"expectedState","type":"uint8"}],"name":"UnexpectedTbtcMigrationState","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"UnknownTokenDecimals","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fee","type":"uint256"}],"name":"FeeCollected","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"tbtcToken","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeInTbtc","type":"uint256"}],"name":"FeeCollectedTbtcMigrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FundedFromTbtcMigration","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"bool","name":"isManaged","type":"bool"}],"name":"LiquidityTreasuryManagedAssetUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousLiquidityTreasury","type":"address"},{"indexed":true,"internalType":"address","name":"newLiquidityTreasury","type":"address"}],"name":"LiquidityTreasuryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"unlockAt","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"lockPeriod","type":"uint32"}],"name":"Locked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"maxLockPeriod","type":"uint32"}],"name":"MaxLockPeriodUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"minLockPeriod","type":"uint32"}],"name":"MinLockPeriodUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReceiptMinted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint8","name":"annualFee","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"mintCap","type":"uint8"},{"indexed":false,"internalType":"address","name":"receiptToken","type":"address"}],"name":"ReceiptParamsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReceiptRepaid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"enum Portal.TokenAbility","name":"tokenAbility","type":"uint8"}],"name":"SupportedTokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"bool","name":"isAllowed","type":"bool"}],"name":"TbtcMigrationAllowedUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"TbtcMigrationCompleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"TbtcMigrationRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"TbtcMigrationStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousMigrationTreasury","type":"address"},{"indexed":true,"internalType":"address","name":"newMigrationTreasury","type":"address"}],"name":"TbtcMigrationTreasuryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"tbtc","type":"address"}],"name":"TbtcTokenAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawnByLiquidityTreasury","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawnForTbtcMigration","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"depositor","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"tbtcToken","type":"address"},{"indexed":true,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountInTbtc","type":"uint256"}],"name":"WithdrawnTbtcMigrated","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum Portal.TokenAbility","name":"tokenAbility","type":"uint8"}],"internalType":"struct Portal.SupportedToken","name":"supportedToken","type":"tuple"}],"name":"addSupportedToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"address","name":"depositor","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"internalType":"struct Portal.DepositToMigrate[]","name":"migratedDeposits","type":"tuple[]"}],"name":"completeTbtcMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"uint32","name":"lockPeriod","type":"uint32"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"depositOwner","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint96","name":"amount","type":"uint96"},{"internalType":"uint32","name":"lockPeriod","type":"uint32"}],"name":"depositFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"deposits","outputs":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"unlockAt","type":"uint32"},{"internalType":"uint96","name":"receiptMinted","type":"uint96"},{"internalType":"uint96","name":"feeOwed","type":"uint96"},{"internalType":"uint88","name":"lastFeeIntegral","type":"uint88"},{"internalType":"enum Portal.TbtcMigrationState","name":"tbtcMigrationState","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"feeInfo","outputs":[{"internalType":"uint96","name":"totalMinted","type":"uint96"},{"internalType":"uint32","name":"lastFeeUpdateAt","type":"uint32"},{"internalType":"uint88","name":"feeIntegral","type":"uint88"},{"internalType":"uint8","name":"annualFee","type":"uint8"},{"internalType":"uint8","name":"mintCap","type":"uint8"},{"internalType":"address","name":"receiptToken","type":"address"},{"internalType":"uint96","name":"feeCollected","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"depositor","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"getDeposit","outputs":[{"components":[{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint32","name":"unlockAt","type":"uint32"},{"internalType":"uint96","name":"receiptMinted","type":"uint96"},{"internalType":"uint96","name":"feeOwed","type":"uint96"},{"internalType":"uint88","name":"lastFeeIntegral","type":"uint88"},{"internalType":"enum Portal.TbtcMigrationState","name":"tbtcMigrationState","type":"uint8"}],"internalType":"struct Portal.DepositInfo","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum Portal.TokenAbility","name":"tokenAbility","type":"uint8"}],"internalType":"struct Portal.SupportedToken[]","name":"supportedTokens","type":"tuple[]"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liquidityTreasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"liquidityTreasuryManaged","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"},{"internalType":"uint32","name":"lockPeriod","type":"uint32"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxLockPeriod","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minLockPeriod","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintReceipt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"receiveApproval","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"repayReceipt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"requestTbtcMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"bool","name":"isManaged","type":"bool"}],"name":"setAssetAsLiquidityTreasuryManaged","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"},{"internalType":"bool","name":"isAllowed","type":"bool"}],"name":"setAssetTbtcMigrationAllowed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_liquidityTreasury","type":"address"}],"name":"setLiquidityTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_maxLockPeriod","type":"uint32"}],"name":"setMaxLockPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"_minLockPeriod","type":"uint32"}],"name":"setMinLockPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint8","name":"annualFee","type":"uint8"},{"internalType":"uint8","name":"mintCap","type":"uint8"},{"internalType":"address","name":"receiptToken","type":"address"}],"name":"setReceiptParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_tbtcMigrationTreasury","type":"address"}],"name":"setTbtcMigrationTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_tbtcToken","type":"address"}],"name":"setTbtcTokenAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tbtcMigrationTreasury","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tbtcMigrations","outputs":[{"internalType":"bool","name":"isAllowed","type":"bool"},{"internalType":"uint96","name":"totalMigrating","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tbtcToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tokenAbility","outputs":[{"internalType":"enum Portal.TokenAbility","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawAsLiquidityTreasury","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"components":[{"internalType":"address","name":"depositor","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"}],"internalType":"struct Portal.DepositToMigrate[]","name":"depositsToMigrate","type":"tuple[]"}],"name":"withdrawForTbtcMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"depositId","type":"uint256"},{"internalType":"uint96","name":"amount","type":"uint96"}],"name":"withdrawPartially","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60806040523480156200001157600080fd5b506200001c62000022565b620000d6565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff1615620000735760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b0390811614620000d35780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b6153fc80620000e66000396000f3fe608060405234801561001057600080fd5b50600436106102ad5760003560e01c806373ae54b51161017b578063c74b552e116100d8578063f3fef3a31161008c578063f64a6c9011610071578063f64a6c90146107e6578063fa4e00c0146107f9578063fe10b9fb1461080c57600080fd5b8063f3fef3a3146107c0578063f5e8d327146107d357600080fd5b8063e30c3978116100bd578063e30c397814610792578063e5d3d7141461079a578063f2fde38b146107ad57600080fd5b8063c74b552e1461076c578063dfb6c2d21461077f57600080fd5b806392673f551161012f578063b918e5f911610114578063b918e5f9146105f5578063bc06b96514610614578063bfcfa66b1461062757600080fd5b806392673f55146105cf578063a2b7e2dd146105e257600080fd5b80637b498fc1116101605780637b498fc1146105895780638da5cb5b1461059c5780638f4ffcb1146105bc57600080fd5b806373ae54b51461057157806379ba50971461058157600080fd5b80634b1d29b4116102295780635fc7f7c5116101dd5780636572c5dd116101c25780636572c5dd146104dd5780636889d5d01461050d578063715018a61461056957600080fd5b80635fc7f7c5146104b7578063639fcbc2146104ca57600080fd5b8063563170e31161020e578063563170e3146103c8578063584c970b146103e85780635d93a3fc146103fb57600080fd5b80634b1d29b414610388578063554e6e21146103b557600080fd5b806325e102a9116102805780632dfdf0b5116102655780632dfdf0b51461034b57806331645d4e146103625780633c2b87451461037557600080fd5b806325e102a9146103255780632c4b24ae1461033857600080fd5b8063012f180e146102b25780630908c7dc146102c757806319808171146102da5780631ae41f8414610312575b600080fd5b6102c56102c0366004614881565b61081f565b005b6102c56102d53660046148d9565b610bf2565b6102fd6102e83660046148f1565b60046020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6102c5610320366004614928565b610e9b565b6102c56103333660046148f1565b6112e1565b6102c5610346366004614a3e565b611375565b61035460015481565b604051908152602001610309565b6102c5610370366004614b1c565b61163b565b6102c56103833660046148f1565b61164d565b6003546103a090640100000000900463ffffffff1681565b60405163ffffffff9091168152602001610309565b6102c56103c3366004614b56565b6116c9565b6103db6103d6366004614b80565b611810565b6040516103099190614bff565b6102c56103f6366004614c81565b611942565b6104a5610409366004614b80565b60006020818152938152604080822085529281528281209093528252902080546001909101546bffffffffffffffffffffffff808316926c0100000000000000000000000080820463ffffffff16937001000000000000000000000000000000009092048316928216919081046affffffffffffffffffffff169077010000000000000000000000000000000000000000000000900460ff1686565b60405161030996959493929190614cb8565b6102c56104c5366004614d09565b611af1565b6102c56104d8366004614c81565b611ec9565b6105006104eb3660046148f1565b60026020526000908152604090205460ff1681565b6040516103099190614d4c565b61054561051b3660046148f1565b60086020526000908152604090205460ff81169061010090046bffffffffffffffffffffffff1682565b6040805192151583526bffffffffffffffffffffffff909116602083015201610309565b6102c5612095565b6003546103a09063ffffffff1681565b6102c56120a9565b6102c5610597366004614b56565b61210a565b6105a4612282565b6040516001600160a01b039091168152602001610309565b6102c56105ca366004614d5a565b6122b7565b6102c56105dd366004614df5565b61236a565b6007546105a4906001600160a01b031681565b6003546105a4906801000000000000000090046001600160a01b031681565b6102c56106223660046148f1565b612465565b6107036106353660046148f1565b600560205260009081526040902080546001909101546bffffffffffffffffffffffff8083169263ffffffff6c01000000000000000000000000820416926affffffffffffffffffffff7001000000000000000000000000000000008304169260ff7b0100000000000000000000000000000000000000000000000000000084048116937c0100000000000000000000000000000000000000000000000000000000900416916001600160a01b03811691740100000000000000000000000000000000000000009091041687565b604080516bffffffffffffffffffffffff988916815263ffffffff90971660208801526affffffffffffffffffffff9095169486019490945260ff9283166060860152911660808401526001600160a01b031660a08301529190911660c082015260e001610309565b6102c561077a366004614e10565b612568565b6102c561078d366004614edf565b612853565b6105a4612866565b6006546105a4906001600160a01b031681565b6102c56107bb3660046148f1565b61288f565b6102c56107ce366004614b56565b61292c565b6102c56107e1366004614f28565b61306d565b6102c56107f4366004614df5565b61321c565b6102c5610807366004614d09565b6132fe565b6102c561081a366004614e10565b613640565b610827613979565b6001600160a01b038416600090815260026020819052604082205460ff169081111561085557610855614bbc565b0361089c576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024015b60405180910390fd5b60648360ff1611156108df576040517f68704cc400000000000000000000000000000000000000000000000000000000815260ff84166004820152602401610893565b60648260ff161115610922576040517fc51c004900000000000000000000000000000000000000000000000000000000815260ff83166004820152602401610893565b6001600160a01b03811661096d576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610893565b6001600160a01b0380851660009081526005602052604090206001810154909116158015906109ac575060018101546001600160a01b03838116911614155b156109e3576040517fd3d9f5c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000826001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a479190614f5b565b90508060ff16601214610a91576040517fcd5cf2c50000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b610a9a866139c4565b81546001830180547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b038681169182179092557fffffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffff9092167b0100000000000000000000000000000000000000000000000000000060ff8981169182027fffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffff16929092177c0100000000000000000000000000000000000000000000000000000000928916928302177fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff166c010000000000000000000000004263ffffffff1602178655604080519182526020820192909252908101929092528716907fcc06fae89af7176b522e80e0f792b25901e66006b16c4ccac33cc75324a16dd39060600160405180910390a2505050505050565b610bfa613979565b6000610c0960208301836148f1565b90506000610c1d6040840160208501614f78565b90506001600160a01b038216610c6a576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b6000816002811115610c7e57610c7e614bbc565b03610cb757806040517fd6a8a3da0000000000000000000000000000000000000000000000000000000081526004016108939190614d4c565b6001600160a01b038216600090815260026020819052604082205460ff1690811115610ce557610ce5614bbc565b14610d3e576001600160a01b038216600090815260026020526040908190205490517f71265fe200000000000000000000000000000000000000000000000000000000815261089391849160ff90911690600401614f93565b816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610db6575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252610db391810190614f5b565b60015b610df7576040517fbcf463bf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b506001600160a01b0382166000908152600260208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909116906001908490811115610e5057610e50614bbc565b0217905550816001600160a01b03167f29dd5553eda23e846a442697aeea6662a9699d1a79bb82afba4ba8994898b92c82604051610e8e9190614d4c565b60405180910390a2505050565b6001600160a01b03831660009081526002602052604081205460ff1690816002811115610eca57610eca614bbc565b03610f0c576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610893565b816bffffffffffffffffffffffff16600003610f64576040517f88967d2f0000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff83166004820152602401610893565b336000908152602081815260408083206001600160a01b038816845282528083208684529091528120805490916bffffffffffffffffffffffff9091169003610fd9576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80546bffffffffffffffffffffffff908116908416106110385780546040517f9dd2b6810000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff9091166004820152602401610893565b600282600281111561104c5761104c614bbc565b14801561106f575080546c01000000000000000000000000900463ffffffff1642105b156110c05780546040517fced227db0000000000000000000000000000000000000000000000000000000081526c0100000000000000000000000090910463ffffffff166004820152602401610893565b805470010000000000000000000000000000000090046bffffffffffffffffffffffff16156111415780546040517f226503810000000000000000000000000000000000000000000000000000000081527001000000000000000000000000000000009091046bffffffffffffffffffffffff166004820152602401610893565b60018101546bffffffffffffffffffffffff16156111a15760018101546040517f01e9e74a0000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff9091166004820152602401610893565b6000600182015477010000000000000000000000000000000000000000000000900460ff1660038111156111d7576111d7614bbc565b1461120e576040517faf343ffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040516bffffffffffffffffffffffff8416815284906001600160a01b0387169033907f91fb9d98b786c57d74c099ccd2beca1739e9f6a81fb49001ca465c4b7591bbe29060200160405180910390a48054839082906000906112809084906bffffffffffffffffffffffff16614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506112da33846bffffffffffffffffffffffff16876001600160a01b0316613ac69092919063ffffffff16565b5050505050565b6112e9613979565b6003546040516001600160a01b038084169268010000000000000000900416907f087168495b2024a05f1e51c26b5abadc7eaa5984c24a419d3563f092693ca1d590600090a3600380546001600160a01b0390921668010000000000000000027fffffffff0000000000000000000000000000000000000000ffffffffffffffff909216919091179055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff166000811580156113c05750825b905060008267ffffffffffffffff1660011480156113dd5750303b155b9050811580156113eb575080155b15611422576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016600117855583156114835784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b61148c33613b3a565b600380547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016670167e9800024ea00179055600060018190555b86518110156115d15760008782815181106114e3576114e3615004565b6020026020010151600001519050600088838151811061150557611505615004565b602002602001015160200151905060006001600160a01b0316826001600160a01b03160361156a576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b6001600160a01b0382166000908152600260208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009091169060019084908111156115c2576115c2614bbc565b021790555050506001016114c6565b5083156116335784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6116483333858585613b4b565b505050565b611655613979565b6007546040516001600160a01b038084169216907f2393a3a0213901ea187a0528e61d30bfd31577cb6efa698270cc0757e82cc28e90600090a3600780547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b6003546801000000000000000090046001600160a01b0316331461171b576040517fd4bcb487000000000000000000000000000000000000000000000000000000008152336004820152602401610893565b80600003611758576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101829052602401610893565b6001600160a01b03821660009081526004602052604090205460ff166117b5576040517f1cc7e5cf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b816001600160a01b03167f9ab9b817afca6d91dd7d523c53a3d2af8939f0a0805d85d0f67b07585fed524b826040516117f091815260200190565b60405180910390a261180c6001600160a01b0383163383613ac6565b5050565b6118466040805160c08101825260008082526020820181905291810182905260608101829052608081018290529060a082015290565b6001600160a01b038481166000908152602081815260408083209387168352928152828220858352815290829020825160c08101845281546bffffffffffffffffffffffff80821683526c0100000000000000000000000080830463ffffffff1695840195909552700100000000000000000000000000000000909104811694820194909452600182015493841660608201529183046affffffffffffffffffffff166080830152909160a083019077010000000000000000000000000000000000000000000000900460ff16600381111561192457611924614bbc565b600381111561193557611935614bbc565b90525090505b9392505050565b61194a613979565b6001600160a01b03821660009081526002602052604081205460ff169081600281111561197957611979614bbc565b036119bb576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b60028160028111156119cf576119cf614bbc565b14611a28576001600160a01b038316600090815260026020526040908190205490517ff8d3a93600000000000000000000000000000000000000000000000000000000815261089391859160ff90911690600401614f93565b6001600160a01b03831660009081526008602052604090205460ff1615611a7b576040517f0d232cb900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03831660008181526004602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f37757c6e0f561c1754a2bc68c5299e01bc49b31193e7928f6a6809920e6811e09101610e8e565b6001600160a01b0380841660009081526005602052604090206001810154909116611b48576040517fce16ea8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600003611b85576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101839052602401610893565b336000908152602081815260408083206001600160a01b038816845282528083208684529091528120805490916bffffffffffffffffffffffff9091169003611bfa576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c048186613f1c565b60018201548154600091611c319188916001600160a01b0316906bffffffffffffffffffffffff166140f1565b8354909150600090606490611c69907c0100000000000000000000000000000000000000000000000000000000900460ff1684615033565b611c739190615092565b600184015484549192506bffffffffffffffffffffffff8084169291811691611cb29170010000000000000000000000000000000090910416886150bd565b611cbc91906150bd565b1115611d3657825460018401546040517fffdacd140000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff8085166004830152700100000000000000000000000000000000909304831660248201529116604482015260648101869052608401610893565b825485908490601090611d6c90849070010000000000000000000000000000000090046bffffffffffffffffffffffff166150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550848460000160008282829054906101000a90046bffffffffffffffffffffffff16611dc591906150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555085876001600160a01b0316336001600160a01b03167f862f9b789dcac5ebaaeece5aa03067d5588c6d3f84c140d527894495a028b17388604051611e3991815260200190565b60405180910390a460018401546040517f2389128a000000000000000000000000000000000000000000000000000000008152336004820152602481018790526001600160a01b0390911690632389128a90604401600060405180830381600087803b158015611ea857600080fd5b505af1158015611ebc573d6000803e3d6000fd5b5050505050505050505050565b611ed1613979565b6006546001600160a01b0316611f13576040517f8142dcf600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006546001600160a01b0390811690831603611f5b576040517fa10c8e1f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03821660009081526002602052604081205460ff1690816002811115611f8a57611f8a614bbc565b03611fcc576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b6001600160a01b03831660009081526004602052604090205460ff161561201f576040517f0d232cb900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03831660008181526008602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f58282641aa313d24bee632c3ec1cdcbc8b924460dbda396d88cfc2a579446ecf9101610e8e565b61209d613979565b6120a76000614247565b565b33806120b3612866565b6001600160a01b0316146120fe576040517f118cdaa70000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610893565b61210781614247565b50565b336000908152602081815260408083206001600160a01b03861680855290835281842085855283528184209084526008909252909120805460ff1661217b576040517fa487737000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600183015477010000000000000000000000000000000000000000000000900460ff1660038111156121b1576121b1614bbc565b1461220057828260010160179054906101000a900460ff1660006040517f0c278ce1000000000000000000000000000000000000000000000000000000008152600401610893939291906150f5565b6001820180547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff167701000000000000000000000000000000000000000000000017905560405183906001600160a01b0386169033907f4713d6a3ccd421deeb6fb632d8c97878f2e4ae58ac48de1e520b362040b4abf990600090a450505050565b6000807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b6001600160a01b0383163314612304576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b6bffffffffffffffffffffffff84111561234d576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101859052602401610893565b600061235b82840184614df5565b90506116338687868885613b4b565b612372613979565b600061237d82614297565b90508063ffffffff168263ffffffff1614158061239e575063ffffffff8116155b806123bc575060035463ffffffff6401000000009091048116908316115b156123fb576040517f74af145000000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610893565b600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff84169081179091556040519081527f4c35d0e4acd88f9d47ba71b6a74a890a34499d0af9d7536e5b46c2b190ea18be9060200160405180910390a15050565b61246d613979565b6001600160a01b0381166124b8576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610893565b6006546001600160a01b0316156124fb576040517f4bc00c4c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600680547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0383169081179091556040519081527f3d8b27d0955baa4924ce9638e61ff44b8fca3c80475d3dfc8fd6582c5df016cf906020015b60405180910390a150565b6007546001600160a01b031633146125ac576040517fc670608600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b82518110156127595760008382815181106125cd576125cd615004565b602002602001015160000151905060008483815181106125ef576125ef615004565b6020908102919091018101518101516001600160a01b0380851660009081528084526040808220928b1682529184528181208382529093529091209091506002600182015477010000000000000000000000000000000000000000000000900460ff16600381111561266357612663614bbc565b146126b257818160010160179054906101000a900460ff1660026040517f0c278ce1000000000000000000000000000000000000000000000000000000008152600401610893939291906150f5565b80546126cc906bffffffffffffffffffffffff16866150d0565b6001820180547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff167703000000000000000000000000000000000000000000000017905560405190955082906001600160a01b0389811691908616907f68d9ffd354ad98f5572d5a19eb60d1be4e8cb57a2d8337d10a3ecfca40b1ebe990600090a45050506001016125b0565b506001600160a01b038316600090815260086020526040902080548290829060019061279990849061010090046bffffffffffffffffffffffff16614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555060006127e985600660009054906101000a90046001600160a01b0316856140f1565b6040516bffffffffffffffffffffffff821681529091507f4c0f021c587c95b1c98d00bd52fef4dc732158bc51f121461f3dc4e41990c5639060200160405180910390a16006546112da906001600160a01b031633306bffffffffffffffffffffffff85166142b9565b6128608433858585613b4b565b50505050565b6000807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c006122a7565b612897613979565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b03831690811782556128f3612282565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b6001600160a01b03821660009081526002602052604081205460ff169081600281111561295b5761295b614bbc565b0361299d576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b336000908152602081815260408083206001600160a01b03871684528252808320858452909152902060018082015477010000000000000000000000000000000000000000000000900460ff1660038111156129fb576129fb614bbc565b1480612a3a57506002600182015477010000000000000000000000000000000000000000000000900460ff166003811115612a3857612a38614bbc565b145b15612a71576040517fe966904900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826002811115612a8557612a85614bbc565b148015612aa8575080546c01000000000000000000000000900463ffffffff1642105b15612af95780546040517fced227db0000000000000000000000000000000000000000000000000000000081526c0100000000000000000000000090910463ffffffff166004820152602401610893565b805470010000000000000000000000000000000090046bffffffffffffffffffffffff1615612b7a5780546040517f226503810000000000000000000000000000000000000000000000000000000081527001000000000000000000000000000000009091046bffffffffffffffffffffffff166004820152602401610893565b80546bffffffffffffffffffffffff166000819003612bc5576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003600183015477010000000000000000000000000000000000000000000000900460ff166003811115612bfb57612bfb614bbc565b03612e545760018201546000906bffffffffffffffffffffffff1615612c64576001600160a01b038087166000908152600560205260409020600180820154600654918701549293612c60939181169216906bffffffffffffffffffffffff166140f1565b9150505b600654600090612c7f9088906001600160a01b0316856140f1565b90506000612c8d8383614fdf565b600654604080516001600160a01b0392831681526bffffffffffffffffffffffff841660208201529293508992918b169133917faabf355ccacfa8b7366b9f6a14af62036d7dd401797d7591faae42a5bbbc3db9910160405180910390a4600654604080516001600160a01b0392831681526bffffffffffffffffffffffff8616602082015289928b169133917f5f82682eb95ce785b4c40b5c57de2b7ae2ca818ac5f1e7ab89300e6142215d8f910160405180910390a46006546001600160a01b031660009081526005602052604090206001018054849190601490612d9b9084907401000000000000000000000000000000000000000090046bffffffffffffffffffffffff166150d0565b82546101009290920a6bffffffffffffffffffffffff818102199093169183160217909155336000818152602081815260408083206001600160a01b038f811685529083528184208e855290925290912080547fffffffff0000000000000000000000000000000000000000000000000000000016815560010180547fffffffffffffffff000000000000000000000000000000000000000000000000169055600654612e4c945016918416613ac6565b5050506112da565b60018201546000906bffffffffffffffffffffffff1615612eb4576001600160a01b038087166000908152600560205260409020600180820154908601549192612eb09291169089906bffffffffffffffffffffffff166140f1565b9150505b6000612ec08284614fdf565b6040516bffffffffffffffffffffffff8216815290915086906001600160a01b0389169033907f91fb9d98b786c57d74c099ccd2beca1739e9f6a81fb49001ca465c4b7591bbe29060200160405180910390a46040516bffffffffffffffffffffffff8316815286906001600160a01b0389169033907f205442d60b70af1203d43cab62352c3b69b94f091be32fe683198057282b5c929060200160405180910390a46001600160a01b03871660009081526005602052604090206001018054839190601490612fb79084907401000000000000000000000000000000000000000090046bffffffffffffffffffffffff166150d0565b82546101009290920a6bffffffffffffffffffffffff818102199093169183160217909155336000818152602081815260408083206001600160a01b038e168085529083528184208d855290925290912080547fffffffff0000000000000000000000000000000000000000000000000000000016815560010180547fffffffffffffffff0000000000000000000000000000000000000000000000001690556130649350918416613ac6565b50505050505050565b336000908152602081815260408083206001600160a01b038716845282528083208584529091528120805490916bffffffffffffffffffffffff90911690036130e2576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03841660009081526002602081905260409091205460ff169081600281111561311457613114614bbc565b1461314f5784816040517ff8d3a936000000000000000000000000000000000000000000000000000000008152600401610893929190614f93565b81546c01000000000000000000000000900463ffffffff166000613175338888886142f2565b90508163ffffffff168163ffffffff16116131d4576040517f9e03653a00000000000000000000000000000000000000000000000000000000815263ffffffff8087166004830152808316602483015283166044820152606401610893565b835463ffffffff9091166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff909116179092555050505050565b613224613979565b61322d81614297565b63ffffffff168163ffffffff16141580613252575060035463ffffffff908116908216105b15613291576040517f74af145000000000000000000000000000000000000000000000000000000000815263ffffffff82166004820152602401610893565b600380547fffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffff1664010000000063ffffffff8416908102919091179091556040519081527fe02644567ab9266166c374f84f05396b070729fc139339e70d0237bb37e59dc59060200161255d565b6001600160a01b0380841660009081526005602052604090206001810154909116613355576040517fce16ea8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600003613392576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101839052602401610893565b336000908152602081815260408083206001600160a01b038816845282528083208684529091528120805490916bffffffffffffffffffffffff9091169003613407576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805470010000000000000000000000000000000090046bffffffffffffffffffffffff168311156134915780546040517f5f6faf860000000000000000000000000000000000000000000000000000000081527001000000000000000000000000000000009091046bffffffffffffffffffffffff16600482015260248101849052604401610893565b61349b8186613f1c565b8054839082906010906134d190849070010000000000000000000000000000000090046bffffffffffffffffffffffff16614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550828260000160008282829054906101000a90046bffffffffffffffffffffffff1661352a9190614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555083856001600160a01b0316336001600160a01b03167f6043289a72dfdddcba5a5eebd82a24572023a2344a1292dfcf3b56c1a142f6068660405161359e91815260200190565b60405180910390a460018201546135c0906001600160a01b03163330866142b9565b60018201546040517f942c5875000000000000000000000000000000000000000000000000000000008152600481018590526001600160a01b039091169063942c587590602401600060405180830381600087803b15801561362157600080fd5b505af1158015613635573d6000803e3d6000fd5b505050505050505050565b6007546001600160a01b03163314613684576040517fc670608600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b825181101561388e5760008382815181106136a5576136a5615004565b602002602001015160000151905060008483815181106136c7576136c7615004565b6020908102919091018101518101516001600160a01b0380851660009081528084526040808220928b16825291845281812083825290935282208054919350916bffffffffffffffffffffffff909116900361374f576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018082015477010000000000000000000000000000000000000000000000900460ff16600381111561378457613784614bbc565b146137e2576001818101546040517f0c278ce100000000000000000000000000000000000000000000000000000000815261089392859277010000000000000000000000000000000000000000000000900460ff16916004016150f5565b6001810180547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16770200000000000000000000000000000000000000000000001790558054613840906bffffffffffffffffffffffff16866150d0565b945081876001600160a01b0316846001600160a01b03167f41f6c6b107a872f7e7a62127f1104669af1b4b25a8eba2a4207a8266bd2b2c6460405160405180910390a4505050600101613688565b506001600160a01b03831660009081526008602052604090208054829082906001906138ce90849061010090046bffffffffffffffffffffffff166150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550836001600160a01b03167fd9953834583f8ccc107d531dd2133b07f00bf5c8cebe8f594486930986996c988360405161394991906bffffffffffffffffffffffff91909116815260200190565b60405180910390a2600754612860906001600160a01b0386811691166bffffffffffffffffffffffff8516613ac6565b33613982612282565b6001600160a01b0316146120a7576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610893565b6001600160a01b0381166000908152600560205260408120906139e68361440b565b8254909150600090613a0e906c01000000000000000000000000900463ffffffff1642615116565b90506000613a228363ffffffff8416615033565b845490915081908590601090613a5a90849070010000000000000000000000000000000090046affffffffffffffffffffff16615133565b82546101009290920a6affffffffffffffffffffff818102199093169190921691909102179055505082547fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff166c010000000000000000000000004263ffffffff160217909255505050565b6040516001600160a01b0383811660248301526044820183905261164891859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061452c565b613b426145a8565b6121078161460f565b6001600160a01b03831660009081526002602052604081205460ff1690816002811115613b7a57613b7a614bbc565b03613bbc576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610893565b60008263ffffffff16118015613be357506001816002811115613be157613be1614bbc565b145b15613c1e5783816040517ff8d3a936000000000000000000000000000000000000000000000000000000008152600401610893929190614f93565b6001600160a01b038616613c69576040517f9c64f6a10000000000000000000000000000000000000000000000000000000081526001600160a01b0387166004820152602401610893565b826bffffffffffffffffffffffff16600003613cc1576040517f88967d2f0000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff84166004820152602401610893565b60018054906000613cd183615157565b90915550506001546040516bffffffffffffffffffffffff8516815281906001600160a01b0380881691908a16907ff5681f9d0db1b911ac18ee83d515a1cf1051853a9eae418316a2fdf7dea427c59060200160405180910390a46040518060c00160405280856bffffffffffffffffffffffff168152602001613d57898885886142f2565b63ffffffff16815260006020820181905260408201819052606082018190526080909101526001600160a01b03808916600090815260208181526040808320938a1683529281528282208583528152908290208351815492850151938501516bffffffffffffffffffffffff908116700100000000000000000000000000000000027fffffffff000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9096166c010000000000000000000000009081027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909616938316939093179490941794909416929092178155606084015160018201805460808701516affffffffffffffffffffff169094027fffffffffffffffffff000000000000000000000000000000000000000000000090941691909416179190911780835560a08401519192907fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff1677010000000000000000000000000000000000000000000000836003811115613ef157613ef1614bbc565b02179055506130649150506001600160a01b03861687306bffffffffffffffffffffffff88166142b9565b613f25816139c4565b6001600160a01b03818116600090815260056020908152604091829020825160e08101845281546bffffffffffffffffffffffff80821683526c0100000000000000000000000080830463ffffffff169584019590955270010000000000000000000000000000000082046affffffffffffffffffffff9081169684018790527b01000000000000000000000000000000000000000000000000000000830460ff90811660608601527c0100000000000000000000000000000000000000000000000000000000909304909216608084015260019384015496871660a08401527401000000000000000000000000000000000000000090960490951660c08201529086015490936140679361403e93909204169061518f565b845470010000000000000000000000000000000090046bffffffffffffffffffffffff1661465a565b6001840180546000906140899084906bffffffffffffffffffffffff166150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550806040015183600101600c6101000a8154816affffffffffffffffffffff02191690836affffffffffffffffffffff160217905550505050565b600080846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614132573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141569190614f5b565b90506000846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614198573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141bc9190614f5b565b90508060ff168260ff161015614202576141d682826151b3565b6141e190600a6152ec565b6141f9906bffffffffffffffffffffffff86166152fb565b9250505061193b565b8060ff168260ff16111561423d5761421a81836151b3565b61422590600a6152ec565b6141f9906bffffffffffffffffffffffff8616615312565b5091949350505050565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080547fffffffffffffffffffffffff000000000000000000000000000000000000000016815561180c82614694565b60006142a662093a8083615326565b6142b39062093a8061533d565b92915050565b6040516001600160a01b0384811660248301528381166044830152606482018390526128609186918216906323b872dd90608401613af3565b60008163ffffffff16600003614309575042614403565b600061431483614297565b905063ffffffff81161580614334575060035463ffffffff908116908216105b80614352575060035463ffffffff6401000000009091048116908216115b15614391576040517f8dbf963f00000000000000000000000000000000000000000000000000000000815263ffffffff84166004820152602401610893565b600061439d824261535d565b905084866001600160a01b0316886001600160a01b03167f8b65b80ac62fde507cb8196bad6c93c114c2babc6ac846aae39ed6943ad36c4984866040516143f792919063ffffffff92831681529116602082015260400190565b60405180910390a49150505b949350505050565b6001600160a01b038082166000908152600560209081526040808320815160e08101835281546bffffffffffffffffffffffff808216835263ffffffff6c01000000000000000000000000830416958301959095526affffffffffffffffffffff7001000000000000000000000000000000008204169382019390935260ff7b0100000000000000000000000000000000000000000000000000000084048116606083018190527c010000000000000000000000000000000000000000000000000000000090940416608082015260019091015494851660a08201527401000000000000000000000000000000000000000090940490911660c08401529091906301e133809061452290662386f26fc10000615033565b61193b9190615092565b60006145416001600160a01b0384168361471d565b90508051600014158015614566575080806020019051810190614564919061537a565b155b15611648576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff166120a7576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6146176145a8565b6001600160a01b0381166120fe576040517f1e4fbdf700000000000000000000000000000000000000000000000000000000815260006004820152602401610893565b6000670de0b6b3a764000061468a6bffffffffffffffffffffffff84166affffffffffffffffffffff86166152fb565b61193b9190615312565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080547fffffffffffffffffffffffff000000000000000000000000000000000000000081166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b606061193b8383600084600080856001600160a01b031684866040516147439190615397565b60006040518083038185875af1925050503d8060008114614780576040519150601f19603f3d011682016040523d82523d6000602084013e614785565b606091505b509150915061479586838361479f565b9695505050505050565b6060826147b4576147af82614814565b61193b565b81511580156147cb57506001600160a01b0384163b155b1561480d576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610893565b508061193b565b8051156148245780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461486d57600080fd5b919050565b60ff8116811461210757600080fd5b6000806000806080858703121561489757600080fd5b6148a085614856565b935060208501356148b081614872565b925060408501356148c081614872565b91506148ce60608601614856565b905092959194509250565b6000604082840312156148eb57600080fd5b50919050565b60006020828403121561490357600080fd5b61193b82614856565b80356bffffffffffffffffffffffff8116811461486d57600080fd5b60008060006060848603121561493d57600080fd5b61494684614856565b92506020840135915061495b6040850161490c565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156149b6576149b6614964565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614a0357614a03614964565b604052919050565b600067ffffffffffffffff821115614a2557614a25614964565b5060051b60200190565b80356003811061486d57600080fd5b60006020808385031215614a5157600080fd5b823567ffffffffffffffff811115614a6857600080fd5b8301601f81018513614a7957600080fd5b8035614a8c614a8782614a0b565b6149bc565b81815260069190911b82018301908381019087831115614aab57600080fd5b928401925b82841015614afd5760408489031215614ac95760008081fd5b614ad1614993565b614ada85614856565b8152614ae7868601614a2f565b8187015282526040939093019290840190614ab0565b979650505050505050565b803563ffffffff8116811461486d57600080fd5b600080600060608486031215614b3157600080fd5b614b3a84614856565b9250614b486020850161490c565b915061495b60408501614b08565b60008060408385031215614b6957600080fd5b614b7283614856565b946020939093013593505050565b600080600060608486031215614b9557600080fd5b614b9e84614856565b9250614bac60208501614856565b9150604084013590509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60048110614bfb57614bfb614bbc565b9052565b600060c0820190506bffffffffffffffffffffffff80845116835263ffffffff6020850151166020840152806040850151166040840152806060850151166060840152506affffffffffffffffffffff608084015116608083015260a0830151614c6c60a0840182614beb565b5092915050565b801515811461210757600080fd5b60008060408385031215614c9457600080fd5b614c9d83614856565b91506020830135614cad81614c73565b809150509250929050565b6bffffffffffffffffffffffff878116825263ffffffff871660208301528581166040830152841660608201526affffffffffffffffffffff8316608082015260c08101614afd60a0830184614beb565b600080600060608486031215614d1e57600080fd5b614d2784614856565b95602085013595506040909401359392505050565b60038110614bfb57614bfb614bbc565b602081016142b38284614d3c565b600080600080600060808688031215614d7257600080fd5b614d7b86614856565b945060208601359350614d9060408701614856565b9250606086013567ffffffffffffffff80821115614dad57600080fd5b818801915088601f830112614dc157600080fd5b813581811115614dd057600080fd5b896020828501011115614de257600080fd5b9699959850939650602001949392505050565b600060208284031215614e0757600080fd5b61193b82614b08565b6000806040808486031215614e2457600080fd5b614e2d84614856565b925060208085013567ffffffffffffffff811115614e4a57600080fd5b8501601f81018713614e5b57600080fd5b8035614e69614a8782614a0b565b81815260069190911b82018301908381019089831115614e8857600080fd5b928401925b82841015614ecf5785848b031215614ea55760008081fd5b614ead614993565b614eb685614856565b8152848601358682015282529285019290840190614e8d565b8096505050505050509250929050565b60008060008060808587031215614ef557600080fd5b614efe85614856565b9350614f0c60208601614856565b9250614f1a6040860161490c565b91506148ce60608601614b08565b600080600060608486031215614f3d57600080fd5b614f4684614856565b92506020840135915061495b60408501614b08565b600060208284031215614f6d57600080fd5b815161193b81614872565b600060208284031215614f8a57600080fd5b61193b82614a2f565b6001600160a01b03831681526040810161193b6020830184614d3c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff828116828216039080821115614c6c57614c6c614fb0565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6bffffffffffffffffffffffff81811683821602808216919082811461505b5761505b614fb0565b505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006bffffffffffffffffffffffff808416806150b1576150b1615063565b92169190910492915050565b808201808211156142b3576142b3614fb0565b6bffffffffffffffffffffffff818116838216019080821115614c6c57614c6c614fb0565b838152606081016151096020830185614beb565b6144036040830184614beb565b63ffffffff828116828216039080821115614c6c57614c6c614fb0565b6affffffffffffffffffffff818116838216019080821115614c6c57614c6c614fb0565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361518857615188614fb0565b5060010190565b6affffffffffffffffffffff828116828216039080821115614c6c57614c6c614fb0565b60ff82811682821603908111156142b3576142b3614fb0565b600181815b8085111561522557817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561520b5761520b614fb0565b8085161561521857918102915b93841c93908002906151d1565b509250929050565b60008261523c575060016142b3565b81615249575060006142b3565b816001811461525f576002811461526957615285565b60019150506142b3565b60ff84111561527a5761527a614fb0565b50506001821b6142b3565b5060208310610133831016604e8410600b84101617156152a8575081810a6142b3565b6152b283836151cc565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156152e4576152e4614fb0565b029392505050565b600061193b60ff84168361522d565b80820281158282048414176142b3576142b3614fb0565b60008261532157615321615063565b500490565b600063ffffffff808416806150b1576150b1615063565b63ffffffff81811683821602808216919082811461505b5761505b614fb0565b63ffffffff818116838216019080821115614c6c57614c6c614fb0565b60006020828403121561538c57600080fd5b815161193b81614c73565b6000825160005b818110156153b8576020818601810151858301520161539e565b50600092019182525091905056fea26469706673582212204f4c8738f189afb9d4789abd80b92b01657882f015929fba454de37d775515b164736f6c63430008180033

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106102ad5760003560e01c806373ae54b51161017b578063c74b552e116100d8578063f3fef3a31161008c578063f64a6c9011610071578063f64a6c90146107e6578063fa4e00c0146107f9578063fe10b9fb1461080c57600080fd5b8063f3fef3a3146107c0578063f5e8d327146107d357600080fd5b8063e30c3978116100bd578063e30c397814610792578063e5d3d7141461079a578063f2fde38b146107ad57600080fd5b8063c74b552e1461076c578063dfb6c2d21461077f57600080fd5b806392673f551161012f578063b918e5f911610114578063b918e5f9146105f5578063bc06b96514610614578063bfcfa66b1461062757600080fd5b806392673f55146105cf578063a2b7e2dd146105e257600080fd5b80637b498fc1116101605780637b498fc1146105895780638da5cb5b1461059c5780638f4ffcb1146105bc57600080fd5b806373ae54b51461057157806379ba50971461058157600080fd5b80634b1d29b4116102295780635fc7f7c5116101dd5780636572c5dd116101c25780636572c5dd146104dd5780636889d5d01461050d578063715018a61461056957600080fd5b80635fc7f7c5146104b7578063639fcbc2146104ca57600080fd5b8063563170e31161020e578063563170e3146103c8578063584c970b146103e85780635d93a3fc146103fb57600080fd5b80634b1d29b414610388578063554e6e21146103b557600080fd5b806325e102a9116102805780632dfdf0b5116102655780632dfdf0b51461034b57806331645d4e146103625780633c2b87451461037557600080fd5b806325e102a9146103255780632c4b24ae1461033857600080fd5b8063012f180e146102b25780630908c7dc146102c757806319808171146102da5780631ae41f8414610312575b600080fd5b6102c56102c0366004614881565b61081f565b005b6102c56102d53660046148d9565b610bf2565b6102fd6102e83660046148f1565b60046020526000908152604090205460ff1681565b60405190151581526020015b60405180910390f35b6102c5610320366004614928565b610e9b565b6102c56103333660046148f1565b6112e1565b6102c5610346366004614a3e565b611375565b61035460015481565b604051908152602001610309565b6102c5610370366004614b1c565b61163b565b6102c56103833660046148f1565b61164d565b6003546103a090640100000000900463ffffffff1681565b60405163ffffffff9091168152602001610309565b6102c56103c3366004614b56565b6116c9565b6103db6103d6366004614b80565b611810565b6040516103099190614bff565b6102c56103f6366004614c81565b611942565b6104a5610409366004614b80565b60006020818152938152604080822085529281528281209093528252902080546001909101546bffffffffffffffffffffffff808316926c0100000000000000000000000080820463ffffffff16937001000000000000000000000000000000009092048316928216919081046affffffffffffffffffffff169077010000000000000000000000000000000000000000000000900460ff1686565b60405161030996959493929190614cb8565b6102c56104c5366004614d09565b611af1565b6102c56104d8366004614c81565b611ec9565b6105006104eb3660046148f1565b60026020526000908152604090205460ff1681565b6040516103099190614d4c565b61054561051b3660046148f1565b60086020526000908152604090205460ff81169061010090046bffffffffffffffffffffffff1682565b6040805192151583526bffffffffffffffffffffffff909116602083015201610309565b6102c5612095565b6003546103a09063ffffffff1681565b6102c56120a9565b6102c5610597366004614b56565b61210a565b6105a4612282565b6040516001600160a01b039091168152602001610309565b6102c56105ca366004614d5a565b6122b7565b6102c56105dd366004614df5565b61236a565b6007546105a4906001600160a01b031681565b6003546105a4906801000000000000000090046001600160a01b031681565b6102c56106223660046148f1565b612465565b6107036106353660046148f1565b600560205260009081526040902080546001909101546bffffffffffffffffffffffff8083169263ffffffff6c01000000000000000000000000820416926affffffffffffffffffffff7001000000000000000000000000000000008304169260ff7b0100000000000000000000000000000000000000000000000000000084048116937c0100000000000000000000000000000000000000000000000000000000900416916001600160a01b03811691740100000000000000000000000000000000000000009091041687565b604080516bffffffffffffffffffffffff988916815263ffffffff90971660208801526affffffffffffffffffffff9095169486019490945260ff9283166060860152911660808401526001600160a01b031660a08301529190911660c082015260e001610309565b6102c561077a366004614e10565b612568565b6102c561078d366004614edf565b612853565b6105a4612866565b6006546105a4906001600160a01b031681565b6102c56107bb3660046148f1565b61288f565b6102c56107ce366004614b56565b61292c565b6102c56107e1366004614f28565b61306d565b6102c56107f4366004614df5565b61321c565b6102c5610807366004614d09565b6132fe565b6102c561081a366004614e10565b613640565b610827613979565b6001600160a01b038416600090815260026020819052604082205460ff169081111561085557610855614bbc565b0361089c576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024015b60405180910390fd5b60648360ff1611156108df576040517f68704cc400000000000000000000000000000000000000000000000000000000815260ff84166004820152602401610893565b60648260ff161115610922576040517fc51c004900000000000000000000000000000000000000000000000000000000815260ff83166004820152602401610893565b6001600160a01b03811661096d576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610893565b6001600160a01b0380851660009081526005602052604090206001810154909116158015906109ac575060018101546001600160a01b03838116911614155b156109e3576040517fd3d9f5c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000826001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a479190614f5b565b90508060ff16601214610a91576040517fcd5cf2c50000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b610a9a866139c4565b81546001830180547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b038681169182179092557fffffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffff9092167b0100000000000000000000000000000000000000000000000000000060ff8981169182027fffffff00ffffffffffffffffffffffffffffffffffffffffffffffffffffffff16929092177c0100000000000000000000000000000000000000000000000000000000928916928302177fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff166c010000000000000000000000004263ffffffff1602178655604080519182526020820192909252908101929092528716907fcc06fae89af7176b522e80e0f792b25901e66006b16c4ccac33cc75324a16dd39060600160405180910390a2505050505050565b610bfa613979565b6000610c0960208301836148f1565b90506000610c1d6040840160208501614f78565b90506001600160a01b038216610c6a576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b6000816002811115610c7e57610c7e614bbc565b03610cb757806040517fd6a8a3da0000000000000000000000000000000000000000000000000000000081526004016108939190614d4c565b6001600160a01b038216600090815260026020819052604082205460ff1690811115610ce557610ce5614bbc565b14610d3e576001600160a01b038216600090815260026020526040908190205490517f71265fe200000000000000000000000000000000000000000000000000000000815261089391849160ff90911690600401614f93565b816001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015610db6575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252610db391810190614f5b565b60015b610df7576040517fbcf463bf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b506001600160a01b0382166000908152600260208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00909116906001908490811115610e5057610e50614bbc565b0217905550816001600160a01b03167f29dd5553eda23e846a442697aeea6662a9699d1a79bb82afba4ba8994898b92c82604051610e8e9190614d4c565b60405180910390a2505050565b6001600160a01b03831660009081526002602052604081205460ff1690816002811115610eca57610eca614bbc565b03610f0c576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610893565b816bffffffffffffffffffffffff16600003610f64576040517f88967d2f0000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff83166004820152602401610893565b336000908152602081815260408083206001600160a01b038816845282528083208684529091528120805490916bffffffffffffffffffffffff9091169003610fd9576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80546bffffffffffffffffffffffff908116908416106110385780546040517f9dd2b6810000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff9091166004820152602401610893565b600282600281111561104c5761104c614bbc565b14801561106f575080546c01000000000000000000000000900463ffffffff1642105b156110c05780546040517fced227db0000000000000000000000000000000000000000000000000000000081526c0100000000000000000000000090910463ffffffff166004820152602401610893565b805470010000000000000000000000000000000090046bffffffffffffffffffffffff16156111415780546040517f226503810000000000000000000000000000000000000000000000000000000081527001000000000000000000000000000000009091046bffffffffffffffffffffffff166004820152602401610893565b60018101546bffffffffffffffffffffffff16156111a15760018101546040517f01e9e74a0000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff9091166004820152602401610893565b6000600182015477010000000000000000000000000000000000000000000000900460ff1660038111156111d7576111d7614bbc565b1461120e576040517faf343ffd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040516bffffffffffffffffffffffff8416815284906001600160a01b0387169033907f91fb9d98b786c57d74c099ccd2beca1739e9f6a81fb49001ca465c4b7591bbe29060200160405180910390a48054839082906000906112809084906bffffffffffffffffffffffff16614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055506112da33846bffffffffffffffffffffffff16876001600160a01b0316613ac69092919063ffffffff16565b5050505050565b6112e9613979565b6003546040516001600160a01b038084169268010000000000000000900416907f087168495b2024a05f1e51c26b5abadc7eaa5984c24a419d3563f092693ca1d590600090a3600380546001600160a01b0390921668010000000000000000027fffffffff0000000000000000000000000000000000000000ffffffffffffffff909216919091179055565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff166000811580156113c05750825b905060008267ffffffffffffffff1660011480156113dd5750303b155b9050811580156113eb575080155b15611422576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b84547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016600117855583156114835784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff16680100000000000000001785555b61148c33613b3a565b600380547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016670167e9800024ea00179055600060018190555b86518110156115d15760008782815181106114e3576114e3615004565b6020026020010151600001519050600088838151811061150557611505615004565b602002602001015160200151905060006001600160a01b0316826001600160a01b03160361156a576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b6001600160a01b0382166000908152600260208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009091169060019084908111156115c2576115c2614bbc565b021790555050506001016114c6565b5083156116335784547fffffffffffffffffffffffffffffffffffffffffffffff00ffffffffffffffff168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6116483333858585613b4b565b505050565b611655613979565b6007546040516001600160a01b038084169216907f2393a3a0213901ea187a0528e61d30bfd31577cb6efa698270cc0757e82cc28e90600090a3600780547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b6003546801000000000000000090046001600160a01b0316331461171b576040517fd4bcb487000000000000000000000000000000000000000000000000000000008152336004820152602401610893565b80600003611758576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101829052602401610893565b6001600160a01b03821660009081526004602052604090205460ff166117b5576040517f1cc7e5cf0000000000000000000000000000000000000000000000000000000081526001600160a01b0383166004820152602401610893565b816001600160a01b03167f9ab9b817afca6d91dd7d523c53a3d2af8939f0a0805d85d0f67b07585fed524b826040516117f091815260200190565b60405180910390a261180c6001600160a01b0383163383613ac6565b5050565b6118466040805160c08101825260008082526020820181905291810182905260608101829052608081018290529060a082015290565b6001600160a01b038481166000908152602081815260408083209387168352928152828220858352815290829020825160c08101845281546bffffffffffffffffffffffff80821683526c0100000000000000000000000080830463ffffffff1695840195909552700100000000000000000000000000000000909104811694820194909452600182015493841660608201529183046affffffffffffffffffffff166080830152909160a083019077010000000000000000000000000000000000000000000000900460ff16600381111561192457611924614bbc565b600381111561193557611935614bbc565b90525090505b9392505050565b61194a613979565b6001600160a01b03821660009081526002602052604081205460ff169081600281111561197957611979614bbc565b036119bb576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b60028160028111156119cf576119cf614bbc565b14611a28576001600160a01b038316600090815260026020526040908190205490517ff8d3a93600000000000000000000000000000000000000000000000000000000815261089391859160ff90911690600401614f93565b6001600160a01b03831660009081526008602052604090205460ff1615611a7b576040517f0d232cb900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03831660008181526004602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f37757c6e0f561c1754a2bc68c5299e01bc49b31193e7928f6a6809920e6811e09101610e8e565b6001600160a01b0380841660009081526005602052604090206001810154909116611b48576040517fce16ea8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600003611b85576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101839052602401610893565b336000908152602081815260408083206001600160a01b038816845282528083208684529091528120805490916bffffffffffffffffffffffff9091169003611bfa576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611c048186613f1c565b60018201548154600091611c319188916001600160a01b0316906bffffffffffffffffffffffff166140f1565b8354909150600090606490611c69907c0100000000000000000000000000000000000000000000000000000000900460ff1684615033565b611c739190615092565b600184015484549192506bffffffffffffffffffffffff8084169291811691611cb29170010000000000000000000000000000000090910416886150bd565b611cbc91906150bd565b1115611d3657825460018401546040517fffdacd140000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff8085166004830152700100000000000000000000000000000000909304831660248201529116604482015260648101869052608401610893565b825485908490601090611d6c90849070010000000000000000000000000000000090046bffffffffffffffffffffffff166150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550848460000160008282829054906101000a90046bffffffffffffffffffffffff16611dc591906150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555085876001600160a01b0316336001600160a01b03167f862f9b789dcac5ebaaeece5aa03067d5588c6d3f84c140d527894495a028b17388604051611e3991815260200190565b60405180910390a460018401546040517f2389128a000000000000000000000000000000000000000000000000000000008152336004820152602481018790526001600160a01b0390911690632389128a90604401600060405180830381600087803b158015611ea857600080fd5b505af1158015611ebc573d6000803e3d6000fd5b5050505050505050505050565b611ed1613979565b6006546001600160a01b0316611f13576040517f8142dcf600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6006546001600160a01b0390811690831603611f5b576040517fa10c8e1f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03821660009081526002602052604081205460ff1690816002811115611f8a57611f8a614bbc565b03611fcc576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b6001600160a01b03831660009081526004602052604090205460ff161561201f576040517f0d232cb900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03831660008181526008602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f58282641aa313d24bee632c3ec1cdcbc8b924460dbda396d88cfc2a579446ecf9101610e8e565b61209d613979565b6120a76000614247565b565b33806120b3612866565b6001600160a01b0316146120fe576040517f118cdaa70000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610893565b61210781614247565b50565b336000908152602081815260408083206001600160a01b03861680855290835281842085855283528184209084526008909252909120805460ff1661217b576040517fa487737000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600183015477010000000000000000000000000000000000000000000000900460ff1660038111156121b1576121b1614bbc565b1461220057828260010160179054906101000a900460ff1660006040517f0c278ce1000000000000000000000000000000000000000000000000000000008152600401610893939291906150f5565b6001820180547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff167701000000000000000000000000000000000000000000000017905560405183906001600160a01b0386169033907f4713d6a3ccd421deeb6fb632d8c97878f2e4ae58ac48de1e520b362040b4abf990600090a450505050565b6000807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b6001600160a01b0383163314612304576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b6bffffffffffffffffffffffff84111561234d576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101859052602401610893565b600061235b82840184614df5565b90506116338687868885613b4b565b612372613979565b600061237d82614297565b90508063ffffffff168263ffffffff1614158061239e575063ffffffff8116155b806123bc575060035463ffffffff6401000000009091048116908316115b156123fb576040517f74af145000000000000000000000000000000000000000000000000000000000815263ffffffff83166004820152602401610893565b600380547fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000001663ffffffff84169081179091556040519081527f4c35d0e4acd88f9d47ba71b6a74a890a34499d0af9d7536e5b46c2b190ea18be9060200160405180910390a15050565b61246d613979565b6001600160a01b0381166124b8576040517f6a2e6fbf0000000000000000000000000000000000000000000000000000000081526001600160a01b0382166004820152602401610893565b6006546001600160a01b0316156124fb576040517f4bc00c4c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600680547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0383169081179091556040519081527f3d8b27d0955baa4924ce9638e61ff44b8fca3c80475d3dfc8fd6582c5df016cf906020015b60405180910390a150565b6007546001600160a01b031633146125ac576040517fc670608600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b82518110156127595760008382815181106125cd576125cd615004565b602002602001015160000151905060008483815181106125ef576125ef615004565b6020908102919091018101518101516001600160a01b0380851660009081528084526040808220928b1682529184528181208382529093529091209091506002600182015477010000000000000000000000000000000000000000000000900460ff16600381111561266357612663614bbc565b146126b257818160010160179054906101000a900460ff1660026040517f0c278ce1000000000000000000000000000000000000000000000000000000008152600401610893939291906150f5565b80546126cc906bffffffffffffffffffffffff16866150d0565b6001820180547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff167703000000000000000000000000000000000000000000000017905560405190955082906001600160a01b0389811691908616907f68d9ffd354ad98f5572d5a19eb60d1be4e8cb57a2d8337d10a3ecfca40b1ebe990600090a45050506001016125b0565b506001600160a01b038316600090815260086020526040902080548290829060019061279990849061010090046bffffffffffffffffffffffff16614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555060006127e985600660009054906101000a90046001600160a01b0316856140f1565b6040516bffffffffffffffffffffffff821681529091507f4c0f021c587c95b1c98d00bd52fef4dc732158bc51f121461f3dc4e41990c5639060200160405180910390a16006546112da906001600160a01b031633306bffffffffffffffffffffffff85166142b9565b6128608433858585613b4b565b50505050565b6000807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c006122a7565b612897613979565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b03831690811782556128f3612282565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b6001600160a01b03821660009081526002602052604081205460ff169081600281111561295b5761295b614bbc565b0361299d576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b336000908152602081815260408083206001600160a01b03871684528252808320858452909152902060018082015477010000000000000000000000000000000000000000000000900460ff1660038111156129fb576129fb614bbc565b1480612a3a57506002600182015477010000000000000000000000000000000000000000000000900460ff166003811115612a3857612a38614bbc565b145b15612a71576040517fe966904900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002826002811115612a8557612a85614bbc565b148015612aa8575080546c01000000000000000000000000900463ffffffff1642105b15612af95780546040517fced227db0000000000000000000000000000000000000000000000000000000081526c0100000000000000000000000090910463ffffffff166004820152602401610893565b805470010000000000000000000000000000000090046bffffffffffffffffffffffff1615612b7a5780546040517f226503810000000000000000000000000000000000000000000000000000000081527001000000000000000000000000000000009091046bffffffffffffffffffffffff166004820152602401610893565b80546bffffffffffffffffffffffff166000819003612bc5576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6003600183015477010000000000000000000000000000000000000000000000900460ff166003811115612bfb57612bfb614bbc565b03612e545760018201546000906bffffffffffffffffffffffff1615612c64576001600160a01b038087166000908152600560205260409020600180820154600654918701549293612c60939181169216906bffffffffffffffffffffffff166140f1565b9150505b600654600090612c7f9088906001600160a01b0316856140f1565b90506000612c8d8383614fdf565b600654604080516001600160a01b0392831681526bffffffffffffffffffffffff841660208201529293508992918b169133917faabf355ccacfa8b7366b9f6a14af62036d7dd401797d7591faae42a5bbbc3db9910160405180910390a4600654604080516001600160a01b0392831681526bffffffffffffffffffffffff8616602082015289928b169133917f5f82682eb95ce785b4c40b5c57de2b7ae2ca818ac5f1e7ab89300e6142215d8f910160405180910390a46006546001600160a01b031660009081526005602052604090206001018054849190601490612d9b9084907401000000000000000000000000000000000000000090046bffffffffffffffffffffffff166150d0565b82546101009290920a6bffffffffffffffffffffffff818102199093169183160217909155336000818152602081815260408083206001600160a01b038f811685529083528184208e855290925290912080547fffffffff0000000000000000000000000000000000000000000000000000000016815560010180547fffffffffffffffff000000000000000000000000000000000000000000000000169055600654612e4c945016918416613ac6565b5050506112da565b60018201546000906bffffffffffffffffffffffff1615612eb4576001600160a01b038087166000908152600560205260409020600180820154908601549192612eb09291169089906bffffffffffffffffffffffff166140f1565b9150505b6000612ec08284614fdf565b6040516bffffffffffffffffffffffff8216815290915086906001600160a01b0389169033907f91fb9d98b786c57d74c099ccd2beca1739e9f6a81fb49001ca465c4b7591bbe29060200160405180910390a46040516bffffffffffffffffffffffff8316815286906001600160a01b0389169033907f205442d60b70af1203d43cab62352c3b69b94f091be32fe683198057282b5c929060200160405180910390a46001600160a01b03871660009081526005602052604090206001018054839190601490612fb79084907401000000000000000000000000000000000000000090046bffffffffffffffffffffffff166150d0565b82546101009290920a6bffffffffffffffffffffffff818102199093169183160217909155336000818152602081815260408083206001600160a01b038e168085529083528184208d855290925290912080547fffffffff0000000000000000000000000000000000000000000000000000000016815560010180547fffffffffffffffff0000000000000000000000000000000000000000000000001690556130649350918416613ac6565b50505050505050565b336000908152602081815260408083206001600160a01b038716845282528083208584529091528120805490916bffffffffffffffffffffffff90911690036130e2576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b03841660009081526002602081905260409091205460ff169081600281111561311457613114614bbc565b1461314f5784816040517ff8d3a936000000000000000000000000000000000000000000000000000000008152600401610893929190614f93565b81546c01000000000000000000000000900463ffffffff166000613175338888886142f2565b90508163ffffffff168163ffffffff16116131d4576040517f9e03653a00000000000000000000000000000000000000000000000000000000815263ffffffff8087166004830152808316602483015283166044820152606401610893565b835463ffffffff9091166c01000000000000000000000000027fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff909116179092555050505050565b613224613979565b61322d81614297565b63ffffffff168163ffffffff16141580613252575060035463ffffffff908116908216105b15613291576040517f74af145000000000000000000000000000000000000000000000000000000000815263ffffffff82166004820152602401610893565b600380547fffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffff1664010000000063ffffffff8416908102919091179091556040519081527fe02644567ab9266166c374f84f05396b070729fc139339e70d0237bb37e59dc59060200161255d565b6001600160a01b0380841660009081526005602052604090206001810154909116613355576040517fce16ea8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600003613392576040517f88967d2f00000000000000000000000000000000000000000000000000000000815260048101839052602401610893565b336000908152602081815260408083206001600160a01b038816845282528083208684529091528120805490916bffffffffffffffffffffffff9091169003613407576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805470010000000000000000000000000000000090046bffffffffffffffffffffffff168311156134915780546040517f5f6faf860000000000000000000000000000000000000000000000000000000081527001000000000000000000000000000000009091046bffffffffffffffffffffffff16600482015260248101849052604401610893565b61349b8186613f1c565b8054839082906010906134d190849070010000000000000000000000000000000090046bffffffffffffffffffffffff16614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550828260000160008282829054906101000a90046bffffffffffffffffffffffff1661352a9190614fdf565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff16021790555083856001600160a01b0316336001600160a01b03167f6043289a72dfdddcba5a5eebd82a24572023a2344a1292dfcf3b56c1a142f6068660405161359e91815260200190565b60405180910390a460018201546135c0906001600160a01b03163330866142b9565b60018201546040517f942c5875000000000000000000000000000000000000000000000000000000008152600481018590526001600160a01b039091169063942c587590602401600060405180830381600087803b15801561362157600080fd5b505af1158015613635573d6000803e3d6000fd5b505050505050505050565b6007546001600160a01b03163314613684576040517fc670608600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805b825181101561388e5760008382815181106136a5576136a5615004565b602002602001015160000151905060008483815181106136c7576136c7615004565b6020908102919091018101518101516001600160a01b0380851660009081528084526040808220928b16825291845281812083825290935282208054919350916bffffffffffffffffffffffff909116900361374f576040517f411321ed00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018082015477010000000000000000000000000000000000000000000000900460ff16600381111561378457613784614bbc565b146137e2576001818101546040517f0c278ce100000000000000000000000000000000000000000000000000000000815261089392859277010000000000000000000000000000000000000000000000900460ff16916004016150f5565b6001810180547fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff16770200000000000000000000000000000000000000000000001790558054613840906bffffffffffffffffffffffff16866150d0565b945081876001600160a01b0316846001600160a01b03167f41f6c6b107a872f7e7a62127f1104669af1b4b25a8eba2a4207a8266bd2b2c6460405160405180910390a4505050600101613688565b506001600160a01b03831660009081526008602052604090208054829082906001906138ce90849061010090046bffffffffffffffffffffffff166150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550836001600160a01b03167fd9953834583f8ccc107d531dd2133b07f00bf5c8cebe8f594486930986996c988360405161394991906bffffffffffffffffffffffff91909116815260200190565b60405180910390a2600754612860906001600160a01b0386811691166bffffffffffffffffffffffff8516613ac6565b33613982612282565b6001600160a01b0316146120a7576040517f118cdaa7000000000000000000000000000000000000000000000000000000008152336004820152602401610893565b6001600160a01b0381166000908152600560205260408120906139e68361440b565b8254909150600090613a0e906c01000000000000000000000000900463ffffffff1642615116565b90506000613a228363ffffffff8416615033565b845490915081908590601090613a5a90849070010000000000000000000000000000000090046affffffffffffffffffffff16615133565b82546101009290920a6affffffffffffffffffffff818102199093169190921691909102179055505082547fffffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffff166c010000000000000000000000004263ffffffff160217909255505050565b6040516001600160a01b0383811660248301526044820183905261164891859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061452c565b613b426145a8565b6121078161460f565b6001600160a01b03831660009081526002602052604081205460ff1690816002811115613b7a57613b7a614bbc565b03613bbc576040517f06439c6b0000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610893565b60008263ffffffff16118015613be357506001816002811115613be157613be1614bbc565b145b15613c1e5783816040517ff8d3a936000000000000000000000000000000000000000000000000000000008152600401610893929190614f93565b6001600160a01b038616613c69576040517f9c64f6a10000000000000000000000000000000000000000000000000000000081526001600160a01b0387166004820152602401610893565b826bffffffffffffffffffffffff16600003613cc1576040517f88967d2f0000000000000000000000000000000000000000000000000000000081526bffffffffffffffffffffffff84166004820152602401610893565b60018054906000613cd183615157565b90915550506001546040516bffffffffffffffffffffffff8516815281906001600160a01b0380881691908a16907ff5681f9d0db1b911ac18ee83d515a1cf1051853a9eae418316a2fdf7dea427c59060200160405180910390a46040518060c00160405280856bffffffffffffffffffffffff168152602001613d57898885886142f2565b63ffffffff16815260006020820181905260408201819052606082018190526080909101526001600160a01b03808916600090815260208181526040808320938a1683529281528282208583528152908290208351815492850151938501516bffffffffffffffffffffffff908116700100000000000000000000000000000000027fffffffff000000000000000000000000ffffffffffffffffffffffffffffffff63ffffffff9096166c010000000000000000000000009081027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909616938316939093179490941794909416929092178155606084015160018201805460808701516affffffffffffffffffffff169094027fffffffffffffffffff000000000000000000000000000000000000000000000090941691909416179190911780835560a08401519192907fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff1677010000000000000000000000000000000000000000000000836003811115613ef157613ef1614bbc565b02179055506130649150506001600160a01b03861687306bffffffffffffffffffffffff88166142b9565b613f25816139c4565b6001600160a01b03818116600090815260056020908152604091829020825160e08101845281546bffffffffffffffffffffffff80821683526c0100000000000000000000000080830463ffffffff169584019590955270010000000000000000000000000000000082046affffffffffffffffffffff9081169684018790527b01000000000000000000000000000000000000000000000000000000830460ff90811660608601527c0100000000000000000000000000000000000000000000000000000000909304909216608084015260019384015496871660a08401527401000000000000000000000000000000000000000090960490951660c08201529086015490936140679361403e93909204169061518f565b845470010000000000000000000000000000000090046bffffffffffffffffffffffff1661465a565b6001840180546000906140899084906bffffffffffffffffffffffff166150d0565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550806040015183600101600c6101000a8154816affffffffffffffffffffff02191690836affffffffffffffffffffff160217905550505050565b600080846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614132573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141569190614f5b565b90506000846001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015614198573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141bc9190614f5b565b90508060ff168260ff161015614202576141d682826151b3565b6141e190600a6152ec565b6141f9906bffffffffffffffffffffffff86166152fb565b9250505061193b565b8060ff168260ff16111561423d5761421a81836151b3565b61422590600a6152ec565b6141f9906bffffffffffffffffffffffff8616615312565b5091949350505050565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080547fffffffffffffffffffffffff000000000000000000000000000000000000000016815561180c82614694565b60006142a662093a8083615326565b6142b39062093a8061533d565b92915050565b6040516001600160a01b0384811660248301528381166044830152606482018390526128609186918216906323b872dd90608401613af3565b60008163ffffffff16600003614309575042614403565b600061431483614297565b905063ffffffff81161580614334575060035463ffffffff908116908216105b80614352575060035463ffffffff6401000000009091048116908216115b15614391576040517f8dbf963f00000000000000000000000000000000000000000000000000000000815263ffffffff84166004820152602401610893565b600061439d824261535d565b905084866001600160a01b0316886001600160a01b03167f8b65b80ac62fde507cb8196bad6c93c114c2babc6ac846aae39ed6943ad36c4984866040516143f792919063ffffffff92831681529116602082015260400190565b60405180910390a49150505b949350505050565b6001600160a01b038082166000908152600560209081526040808320815160e08101835281546bffffffffffffffffffffffff808216835263ffffffff6c01000000000000000000000000830416958301959095526affffffffffffffffffffff7001000000000000000000000000000000008204169382019390935260ff7b0100000000000000000000000000000000000000000000000000000084048116606083018190527c010000000000000000000000000000000000000000000000000000000090940416608082015260019091015494851660a08201527401000000000000000000000000000000000000000090940490911660c08401529091906301e133809061452290662386f26fc10000615033565b61193b9190615092565b60006145416001600160a01b0384168361471d565b90508051600014158015614566575080806020019051810190614564919061537a565b155b15611648576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610893565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff166120a7576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6146176145a8565b6001600160a01b0381166120fe576040517f1e4fbdf700000000000000000000000000000000000000000000000000000000815260006004820152602401610893565b6000670de0b6b3a764000061468a6bffffffffffffffffffffffff84166affffffffffffffffffffff86166152fb565b61193b9190615312565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080547fffffffffffffffffffffffff000000000000000000000000000000000000000081166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b606061193b8383600084600080856001600160a01b031684866040516147439190615397565b60006040518083038185875af1925050503d8060008114614780576040519150601f19603f3d011682016040523d82523d6000602084013e614785565b606091505b509150915061479586838361479f565b9695505050505050565b6060826147b4576147af82614814565b61193b565b81511580156147cb57506001600160a01b0384163b155b1561480d576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610893565b508061193b565b8051156148245780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461486d57600080fd5b919050565b60ff8116811461210757600080fd5b6000806000806080858703121561489757600080fd5b6148a085614856565b935060208501356148b081614872565b925060408501356148c081614872565b91506148ce60608601614856565b905092959194509250565b6000604082840312156148eb57600080fd5b50919050565b60006020828403121561490357600080fd5b61193b82614856565b80356bffffffffffffffffffffffff8116811461486d57600080fd5b60008060006060848603121561493d57600080fd5b61494684614856565b92506020840135915061495b6040850161490c565b90509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040805190810167ffffffffffffffff811182821017156149b6576149b6614964565b60405290565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff81118282101715614a0357614a03614964565b604052919050565b600067ffffffffffffffff821115614a2557614a25614964565b5060051b60200190565b80356003811061486d57600080fd5b60006020808385031215614a5157600080fd5b823567ffffffffffffffff811115614a6857600080fd5b8301601f81018513614a7957600080fd5b8035614a8c614a8782614a0b565b6149bc565b81815260069190911b82018301908381019087831115614aab57600080fd5b928401925b82841015614afd5760408489031215614ac95760008081fd5b614ad1614993565b614ada85614856565b8152614ae7868601614a2f565b8187015282526040939093019290840190614ab0565b979650505050505050565b803563ffffffff8116811461486d57600080fd5b600080600060608486031215614b3157600080fd5b614b3a84614856565b9250614b486020850161490c565b915061495b60408501614b08565b60008060408385031215614b6957600080fd5b614b7283614856565b946020939093013593505050565b600080600060608486031215614b9557600080fd5b614b9e84614856565b9250614bac60208501614856565b9150604084013590509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60048110614bfb57614bfb614bbc565b9052565b600060c0820190506bffffffffffffffffffffffff80845116835263ffffffff6020850151166020840152806040850151166040840152806060850151166060840152506affffffffffffffffffffff608084015116608083015260a0830151614c6c60a0840182614beb565b5092915050565b801515811461210757600080fd5b60008060408385031215614c9457600080fd5b614c9d83614856565b91506020830135614cad81614c73565b809150509250929050565b6bffffffffffffffffffffffff878116825263ffffffff871660208301528581166040830152841660608201526affffffffffffffffffffff8316608082015260c08101614afd60a0830184614beb565b600080600060608486031215614d1e57600080fd5b614d2784614856565b95602085013595506040909401359392505050565b60038110614bfb57614bfb614bbc565b602081016142b38284614d3c565b600080600080600060808688031215614d7257600080fd5b614d7b86614856565b945060208601359350614d9060408701614856565b9250606086013567ffffffffffffffff80821115614dad57600080fd5b818801915088601f830112614dc157600080fd5b813581811115614dd057600080fd5b896020828501011115614de257600080fd5b9699959850939650602001949392505050565b600060208284031215614e0757600080fd5b61193b82614b08565b6000806040808486031215614e2457600080fd5b614e2d84614856565b925060208085013567ffffffffffffffff811115614e4a57600080fd5b8501601f81018713614e5b57600080fd5b8035614e69614a8782614a0b565b81815260069190911b82018301908381019089831115614e8857600080fd5b928401925b82841015614ecf5785848b031215614ea55760008081fd5b614ead614993565b614eb685614856565b8152848601358682015282529285019290840190614e8d565b8096505050505050509250929050565b60008060008060808587031215614ef557600080fd5b614efe85614856565b9350614f0c60208601614856565b9250614f1a6040860161490c565b91506148ce60608601614b08565b600080600060608486031215614f3d57600080fd5b614f4684614856565b92506020840135915061495b60408501614b08565b600060208284031215614f6d57600080fd5b815161193b81614872565b600060208284031215614f8a57600080fd5b61193b82614a2f565b6001600160a01b03831681526040810161193b6020830184614d3c565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6bffffffffffffffffffffffff828116828216039080821115614c6c57614c6c614fb0565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6bffffffffffffffffffffffff81811683821602808216919082811461505b5761505b614fb0565b505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006bffffffffffffffffffffffff808416806150b1576150b1615063565b92169190910492915050565b808201808211156142b3576142b3614fb0565b6bffffffffffffffffffffffff818116838216019080821115614c6c57614c6c614fb0565b838152606081016151096020830185614beb565b6144036040830184614beb565b63ffffffff828116828216039080821115614c6c57614c6c614fb0565b6affffffffffffffffffffff818116838216019080821115614c6c57614c6c614fb0565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361518857615188614fb0565b5060010190565b6affffffffffffffffffffff828116828216039080821115614c6c57614c6c614fb0565b60ff82811682821603908111156142b3576142b3614fb0565b600181815b8085111561522557817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561520b5761520b614fb0565b8085161561521857918102915b93841c93908002906151d1565b509250929050565b60008261523c575060016142b3565b81615249575060006142b3565b816001811461525f576002811461526957615285565b60019150506142b3565b60ff84111561527a5761527a614fb0565b50506001821b6142b3565b5060208310610133831016604e8410600b84101617156152a8575081810a6142b3565b6152b283836151cc565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156152e4576152e4614fb0565b029392505050565b600061193b60ff84168361522d565b80820281158282048414176142b3576142b3614fb0565b60008261532157615321615063565b500490565b600063ffffffff808416806150b1576150b1615063565b63ffffffff81811683821602808216919082811461505b5761505b614fb0565b63ffffffff818116838216019080821115614c6c57614c6c614fb0565b60006020828403121561538c57600080fd5b815161193b81614c73565b6000825160005b818110156153b8576020818601810151858301520161539e565b50600092019182525091905056fea26469706673582212204f4c8738f189afb9d4789abd80b92b01657882f015929fba454de37d775515b164736f6c63430008180033

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

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.