ETH Price: $3,646.27 (+1.86%)
Gas: 5.93 Gwei

Contract

0xBe951D1B791C6878eec5d9129ADEB72A28D59E68
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Withdraw212256132024-11-20 1:17:5910 days ago1732065479IN
0xBe951D1B...A28D59E68
0 ETH0.000947928.4062738
Withdraw210770932024-10-30 7:53:1130 days ago1730274791IN
0xBe951D1B...A28D59E68
0 ETH0.0015047212.77883791
Withdraw210650842024-10-28 15:40:1132 days ago1730130011IN
0xBe951D1B...A28D59E68
0 ETH0.0023416119.88614023
Add Nft And Dele...210650802024-10-28 15:39:2332 days ago1730129963IN
0xBe951D1B...A28D59E68
0 ETH0.0028845621.04137753
Withdraw207742642024-09-18 1:41:1173 days ago1726623671IN
0xBe951D1B...A28D59E68
0 ETH0.000523884.64588916
Withdraw207740182024-09-18 0:51:4773 days ago1726620707IN
0xBe951D1B...A28D59E68
0 ETH0.000626165.55283833
Withdraw206605292024-09-02 4:35:1188 days ago1725251711IN
0xBe951D1B...A28D59E68
0 ETH0.000056280.56806602
Withdraw206378132024-08-30 0:27:5992 days ago1724977679IN
0xBe951D1B...A28D59E68
0 ETH0.000146411.29839804
Withdraw206262472024-08-28 9:39:4793 days ago1724837987IN
0xBe951D1B...A28D59E68
0 ETH0.000188331.6701313
Withdraw205858812024-08-22 18:17:2399 days ago1724350643IN
0xBe951D1B...A28D59E68
0 ETH0.000185151.64194911
Withdraw204287962024-07-31 20:03:35121 days ago1722456215IN
0xBe951D1B...A28D59E68
0 ETH0.0015492213.73867868
Withdraw204169912024-07-30 4:26:59122 days ago1722313619IN
0xBe951D1B...A28D59E68
0 ETH0.000185461.644722
Withdraw204169622024-07-30 4:21:11122 days ago1722313271IN
0xBe951D1B...A28D59E68
0 ETH0.000211291.87375317
Withdraw203422262024-07-19 17:59:35133 days ago1721411975IN
0xBe951D1B...A28D59E68
0 ETH0.000607885.39074335
Withdraw203399122024-07-19 10:14:47133 days ago1721384087IN
0xBe951D1B...A28D59E68
0 ETH0.000635285.63378642
Withdraw203231952024-07-17 2:15:35136 days ago1721182535IN
0xBe951D1B...A28D59E68
0 ETH0.000645175.72146659
Withdraw202853652024-07-11 19:29:47141 days ago1720726187IN
0xBe951D1B...A28D59E68
0 ETH0.000753026.67784473
Withdraw202725552024-07-10 0:36:47143 days ago1720571807IN
0xBe951D1B...A28D59E68
0 ETH0.000353563.13543922
Withdraw202181222024-07-02 10:05:35150 days ago1719914735IN
0xBe951D1B...A28D59E68
0 ETH0.000590895.24013933
Withdraw202174482024-07-02 7:50:23150 days ago1719906623IN
0xBe951D1B...A28D59E68
0 ETH0.000387463.43609804
Withdraw201241502024-06-19 6:59:11163 days ago1718780351IN
0xBe951D1B...A28D59E68
0 ETH0.000321512.85118085
Withdraw201050272024-06-16 14:43:23166 days ago1718549003IN
0xBe951D1B...A28D59E68
0 ETH0.000511764.53834707
Withdraw201050032024-06-16 14:38:35166 days ago1718548715IN
0xBe951D1B...A28D59E68
0 ETH0.000522714.6354695
Withdraw201049872024-06-16 14:35:23166 days ago1718548523IN
0xBe951D1B...A28D59E68
0 ETH0.000553854.91162388
Withdraw200583592024-06-10 2:10:47173 days ago1717985447IN
0xBe951D1B...A28D59E68
0 ETH0.000641055.68494368
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
NFTBoostVault

Compiler Version
v0.8.18+commit.87f61d96

Optimization Enabled:
Yes with 999999 runs

Other Settings:
default evmVersion
File 1 of 15 : NFTBoostVault.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

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

import "./libraries/BoundedHistory.sol";
import "./external/council/libraries/Storage.sol";

import "./libraries/NFTBoostVaultStorage.sol";
import "./interfaces/INFTBoostVault.sol";
import "./BaseVotingVault.sol";

import {
    NBV_HasRegistration,
    NBV_AlreadyDelegated,
    NBV_InsufficientBalance,
    NBV_InsufficientWithdrawableBalance,
    NBV_MultiplierLimit,
    NBV_NoMultiplierSet,
    NBV_InvalidNft,
    NBV_ZeroAmount,
    NBV_ZeroAddress,
    NBV_ArrayTooManyElements,
    NBV_Locked,
    NBV_AlreadyUnlocked,
    NBV_NotAirdrop,
    NBV_NoRegistration,
    NBV_WrongDelegatee,
    NBV_InvalidExpiration,
    NBV_MultiplierSet
} from "./errors/Governance.sol";

/**
 * @title NFTBoostVault
 * @author Non-Fungible Technologies, Inc.
 *
 * The voting power for participants in this vault holding reputation ERC1155 nfts
 * is enhanced by a multiplier. This contract enables holders of specific ERC1155 nfts
 * to gain an advantage wrt voting power for participation in governance. Participants
 * send their ERC20 tokens to the contract and provide their ERC1155 nfts as calldata.
 * Once the contract confirms their ownership of the ERC1155 token id, and matches the
 * ERC1155 address and tokenId to a multiplier, they are able to delegate their voting
 * power for participation in governance.
 *
 * @dev There is no emergency withdrawal in this contract, any funds not sent via
 *      addNftAndDelegate() are unrecoverable by this version of the NFTBoostVault.
 */
contract NFTBoostVault is INFTBoostVault, BaseVotingVault {
    using SafeERC20 for IERC20;
    // ======================================== STATE ==================================================

    // Bring History library into scope
    using BoundedHistory for BoundedHistory.HistoricalBalances;

    // ======================================== STATE ==================================================

    /// @dev Determines the maximum multiplier for any given NFT.
    /* solhint-disable var-name-mixedcase */
    uint128 public constant MAX_MULTIPLIER = 1.5e3;

    /// @dev Precision of the multiplier.
    uint128 public constant MULTIPLIER_DENOMINATOR = 1e3;

    // ========================================== CONSTRUCTOR ===========================================

    /**
     * @notice Deploys a voting vault, setting immutable values for the token
     *         and staleBlockLag.
     *
     * @param token                     The external erc20 token contract.
     * @param staleBlockLag             The number of blocks before which the delegation history is forgotten.
     * @param timelock                  The address of the timelock who can update the manager address.
     * @param manager                   The address of the manager who can update the multiplier values.
     */
    constructor(
        IERC20 token,
        uint256 staleBlockLag,
        address timelock,
        address manager
    ) BaseVotingVault(token, staleBlockLag) {
        if (timelock == address(0)) revert NBV_ZeroAddress("timelock");
        if (manager == address(0)) revert NBV_ZeroAddress("manager");

        Storage.set(Storage.uint256Ptr("initialized"), 1);
        Storage.set(Storage.addressPtr("timelock"), timelock);
        Storage.set(Storage.addressPtr("manager"), manager);
        Storage.set(Storage.uint256Ptr("entered"), 1);
        Storage.set(Storage.uint256Ptr("locked"), 1);
    }

    // ===================================== USER FUNCTIONALITY =========================================

    /**
     * @notice Performs token and optional ERC1155 registration for the caller. The caller cannot have
     *         an existing registration.
     *
     * @dev User has to own ERC1155 nft for receiving the benefits of a multiplier.
     *
     * @param amount                    Amount of tokens sent to this contract by the user for locking
     *                                  in governance.
     * @param tokenId                   The id of the ERC1155 NFT.
     * @param tokenAddress              The address of the ERC1155 token the user is registering for multiplier
     *                                  access.
     * @param delegatee                 Optional param. The address to delegate the voting power associated
     *                                  with this registration.
     */
    function addNftAndDelegate(
        uint128 amount,
        uint128 tokenId,
        address tokenAddress,
        address delegatee
    ) external override nonReentrant {
        if (amount == 0) revert NBV_ZeroAmount();

        _registerAndDelegate(msg.sender, amount, tokenId, tokenAddress, delegatee);

        // transfer user ERC20 amount and ERC1155 nft into this contract
        _lockTokens(msg.sender, uint256(amount), tokenAddress, tokenId);
    }

    /**
     * @notice Function for an airdrop contract to call to register a user or update
     *         their registration with more tokens.
     *
     * @dev This function is only callable by the airdrop contract.
     * @dev If a user already has a registration, they cannot change their delegatee.
     *
     * @param user                      The address of the user to register.
     * @param amount                    Amount of token to transfer to this contract.
     * @param delegatee                 The address to delegate the voting power to.
     */
    function airdropReceive(
        address user,
        uint128 amount,
        address delegatee
    ) external override onlyAirdrop nonReentrant {
        if (amount == 0) revert NBV_ZeroAmount();
        if (user == address(0)) revert NBV_ZeroAddress("user");

        // load the registration
        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[user];

        // if user is not already registered, register them
        // else just update their registration
        if (registration.delegatee == address(0)) {
            _registerAndDelegate(user, amount, 0, address(0), delegatee);
        } else {
            // if user supplies new delegatee address revert
            if (delegatee != registration.delegatee) revert NBV_WrongDelegatee(delegatee, registration.delegatee);

            // get this contract's balance
            Storage.Uint256 storage balance = _balance();
            // update contract balance
            balance.data += amount;

            // update registration amount
            registration.amount += amount;

            // sync current delegatee's voting power
            _syncVotingPower(user, registration);
        }

        // transfer user ERC20 amount only into this contract
        _lockTokens(msg.sender, uint256(amount), address(0), 0);
    }

    /**
     * @notice Changes the caller's token voting power delegation.
     *
     * @dev The total voting power is not guaranteed to go up because the token
     *      multiplier can be updated at any time.
     *
     * @param to                        The address to delegate to.
     */
    function delegate(address to) external override {
        if (to == address(0)) revert NBV_ZeroAddress("to");

        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];

        // user must have an existing registration
        if (registration.delegatee == address(0)) revert NBV_NoRegistration();

        // If to address is already the delegate, don't send the tx
        if (to == registration.delegatee) revert NBV_AlreadyDelegated();

        BoundedHistory.HistoricalBalances memory votingPower = _votingPower();
        uint256 oldDelegateeVotes = votingPower.loadTop(registration.delegatee);

        // Remove voting power from old delegatee and emit event
        votingPower.push(
            registration.delegatee,
            oldDelegateeVotes - registration.latestVotingPower,
            MAX_HISTORY_LENGTH
        );
        emit VoteChange(msg.sender, registration.delegatee, -1 * int256(uint256(registration.latestVotingPower)));

        // Note - It is important that this is loaded here and not before the previous state change because if
        // to == registration.delegatee and re-delegation was allowed we could be working with out of date state
        uint256 newDelegateeVotes = votingPower.loadTop(to);
        // return the current voting power of the Registration. Varies based on the multiplier associated with the
        // user's ERC1155 token at the time of txn
        uint256 addedVotingPower = _currentVotingPower(registration);

        // add voting power to the target delegatee and emit event
        votingPower.push(to, newDelegateeVotes + addedVotingPower, MAX_HISTORY_LENGTH);

        // update registration properties
        registration.latestVotingPower = uint128(addedVotingPower);
        registration.delegatee = to;

        emit VoteChange(msg.sender, to, int256(addedVotingPower));
    }

    /**
     * @notice Removes a user's locked ERC20 tokens from this contract and if no tokens are remaining, the
     *         user's locked ERC1155 (if utilized) is also transferred back to them. Consequently, the user's
     *         delegatee loses the voting power associated with the aforementioned tokens.
     *
     * @dev Withdraw is unlocked when the locked state variable is set to 2.
     *
     * @param amount                      The amount of token to withdraw.
     */
    function withdraw(uint128 amount) external override nonReentrant {
        if (getIsLocked() == 1) revert NBV_Locked();
        if (amount == 0) revert NBV_ZeroAmount();

        // load the registration
        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];

        // get this contract's balance
        Storage.Uint256 storage balance = _balance();
        if (balance.data < amount) revert NBV_InsufficientBalance();

        // get the withdrawable amount
        uint256 withdrawable = _getWithdrawableAmount(registration);
        if (withdrawable < amount) revert NBV_InsufficientWithdrawableBalance(withdrawable);

        // update contract balance
        balance.data -= amount;
        // update withdrawn amount
        registration.withdrawn += amount;
        // update the delegatee's voting power. Varies based on the multiplier associated with the
        // user's ERC1155 token at the time of the call
        _syncVotingPower(msg.sender, registration);

        if (registration.withdrawn == registration.amount) {
            if (registration.tokenAddress != address(0) && registration.tokenId != 0) {
                _withdrawNft();
            }
            // delete registration. tokenId and token address already set to 0 in _withdrawNft()
            registration.amount = 0;
            registration.latestVotingPower = 0;
            registration.withdrawn = 0;
            registration.delegatee = address(0);
        }

        // transfer the token amount to the user
        token.safeTransfer(msg.sender, amount);
    }

    /**
     * @notice Adds tokens to a user's registration. The user must have an existing registration.
     *
     * @param amount                      The amount of tokens to add.
     */
    function addTokens(uint128 amount) external override nonReentrant {
        if (amount == 0) revert NBV_ZeroAmount();
        // load the registration
        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];

        // If the registration does not have a delegatee, revert because the Registration
        // is not initialized
        if (registration.delegatee == address(0)) revert NBV_NoRegistration();

        // get this contract's balance
        Storage.Uint256 storage balance = _balance();
        // update contract balance
        balance.data += amount;

        // update registration amount
        registration.amount += amount;
        // update the delegatee's voting power
        _syncVotingPower(msg.sender, registration);

        // transfer ERC20 amount into this contract
        _lockTokens(msg.sender, amount, address(0), 0);
    }

    /**
     * @notice Nonreentrant function that calls a helper when users want to withdraw
     *         the ERC1155 NFT they are using in their registration.
     */
    function withdrawNft() external override nonReentrant {
        _withdrawNft();
    }

    /**
     * @notice A function that allows a user's to change the ERC1155 nft they are using for
     *         accessing a voting power multiplier. Or if the users does not have a NFT
     *         registered, they can register one and their voting power will be updated.
     *         The provided ERC1155 token must have an associated multiplier to register it.
     *
     * @param newTokenAddress            Address of the new ERC1155 token the user wants to use.
     * @param newTokenId                 Id of the new ERC1155 token the user wants to use.
     */
    function updateNft(uint128 newTokenId, address newTokenAddress) external override nonReentrant {
        if (newTokenAddress == address(0) || newTokenId == 0) revert NBV_InvalidNft(newTokenAddress, newTokenId);

        // check there is a multiplier associated with the new NFT
        if (getMultiplier(newTokenAddress, newTokenId) == 0) revert NBV_NoMultiplierSet();

        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];

        // If the registration does not have a delegatee, revert because the Registration
        // is not initialized
        if (registration.delegatee == address(0)) revert NBV_NoRegistration();

        // if the user already has an ERC1155 registered, withdraw it
        if (registration.tokenAddress != address(0)) {
            // withdraw the current ERC1155 from the registration
            _withdrawNft();
        }

        // set the new ERC1155 values in the registration and lock the new ERC1155
        registration.tokenAddress = newTokenAddress;
        registration.tokenId = newTokenId;

        _lockNft(msg.sender, newTokenAddress, newTokenId);

        // update the delegatee's voting power based on new ERC1155 nft's multiplier
        _syncVotingPower(msg.sender, registration);
    }

    /**
     * @notice Update users' registration voting power.
     *
     * @dev Voting power is only updated for this block onward. See Council contract History.sol
     *      for more on how voting power is tracked and queried.
     *      Anybody can update up to 50 users' registration voting power.
     *
     * @param userAddresses             Array of addresses whose registration voting power this
     *                                  function updates.
     */
    function updateVotingPower(address[] calldata userAddresses) public override {
        if (userAddresses.length > 50) revert NBV_ArrayTooManyElements();

        for (uint256 i = 0; i < userAddresses.length; ++i) {
            NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[userAddresses[i]];
            _syncVotingPower(userAddresses[i], registration);
        }
    }

    // ===================================== ADMIN FUNCTIONALITY ========================================

    /**
     * @notice An onlyManager function for setting the multiplier value associated with an ERC1155
     *         contract address. The provided multiplier value must be less than or equal to 1.5x
     *         and greater than or equal to 1x. Every multiplier value has an associated expiration
     *         timestamp. Once a multiplier expires, the multiplier for the ERC1155 returns 1x.
     *         Once a multiplier is set, it cannot be modified.
     *
     * @param tokenAddress              ERC1155 token address to set the multiplier for.
     * @param tokenId                   The token ID of the ERC1155 for which the multiplier is being set.
     * @param multiplierValue           The multiplier value corresponding to the token address and ID.
     * @param expiration                The timestamp at which the multiplier expires.
     */
    function setMultiplier(
        address tokenAddress,
        uint128 tokenId,
        uint128 multiplierValue,
        uint128 expiration
    ) public override onlyManager {
        if (multiplierValue > MAX_MULTIPLIER) revert NBV_MultiplierLimit("high");
        if (multiplierValue < 1e3) revert NBV_MultiplierLimit("low");
        if (expiration <= block.timestamp) revert NBV_InvalidExpiration();

        if (tokenAddress == address(0) || tokenId == 0) revert NBV_InvalidNft(tokenAddress, tokenId);

        NFTBoostVaultStorage.MultiplierData storage multiplierData = _getMultipliers()[tokenAddress][tokenId];

        // cannot modify multiplier data if it is already set
        if (multiplierData.multiplier != 0) {
            revert NBV_MultiplierSet(multiplierData.multiplier, multiplierData.expiration);
        }

        // set multiplier data
        multiplierData.multiplier = multiplierValue;
        multiplierData.expiration = expiration;

        emit MultiplierSet(tokenAddress, tokenId, multiplierValue, expiration);
    }

    /**
     * @notice An Timelock only function for ERC20 allowing withdrawals.
     *
     * @dev Allows the timelock to unlock withdrawals. Cannot be reversed.
     */
    function unlock() external override onlyTimelock {
        if (getIsLocked() != 1) revert NBV_AlreadyUnlocked();
        Storage.set(Storage.uint256Ptr("locked"), 2);

        emit WithdrawalsUnlocked();
    }

    /**
     * @notice Manager-only airdrop contract address update function.
     *
     * @dev Allows the manager to update the airdrop contract address.
     *
     * @param newAirdropContract        The address of the new airdrop contract.
     */
    function setAirdropContract(address newAirdropContract) external override onlyManager {
        Storage.set(Storage.addressPtr("airdrop"), newAirdropContract);

        emit AirdropContractUpdated(newAirdropContract);
    }

    // ======================================= VIEW FUNCTIONS ===========================================

    /**
     * @notice Returns whether tokens can be withdrawn from the vault.
     *
     * @return locked                           Whether withdrawals are locked.
     */
    function getIsLocked() public view override returns (uint256) {
        return Storage.uint256Ptr("locked").data;
    }

    /**
     * @notice A function to access a NFT's voting power multiplier. If the user does not provide
     *         a token address and ID, the function returns the default 1x multiplier. This implies
     *         that a registration without a token address and ID have a default 1x multiplier.
     *
     * @param tokenAddress              ERC1155 token address to lookup.
     * @param tokenId                   The token ID of the ERC1155 to lookup.
     *
     * @return                          The token multiplier.
     */
    function getMultiplier(address tokenAddress, uint128 tokenId) public view override returns (uint128) {
        // if NFT is not registered, return 1x multiplier
        if (tokenAddress == address(0) && tokenId == 0) return 1e3;

        NFTBoostVaultStorage.MultiplierData storage multiplierData = _getMultipliers()[tokenAddress][tokenId];

        // if multiplier is not set, return 0
        if (multiplierData.expiration == 0) return 0;

        // if multiplier has expired, return 1x multiplier
        if (multiplierData.expiration <= block.timestamp) return 1e3;

        return multiplierData.multiplier;
    }

    /**
     * @notice A function to access the storage of the nft's multiplier expiration.
     *
     * @param tokenAddress              The address of the token.
     * @param tokenId                   The token ID.
     *
     * @return                          The multiplier's expiration.
     */
    function getMultiplierExpiration(address tokenAddress, uint128 tokenId) external view override returns (uint128) {
        NFTBoostVaultStorage.MultiplierData storage multiplierData = _getMultipliers()[tokenAddress][tokenId];

        return multiplierData.expiration;
    }

    /**
     * @notice Getter for the registrations mapping.
     *
     * @param who                               The owner of the registration to query.
     *
     * @return registration                     Registration of the provided address.
     */
    function getRegistration(address who) external view override returns (NFTBoostVaultStorage.Registration memory) {
        return _getRegistrations()[who];
    }

    /**
     * @notice A function to access the stored airdrop contract address.
     *
     * @return address                  The address of the airdrop contract.
     */
    function getAirdropContract() external view override returns (address) {
        return Storage.addressPtr("airdrop").data;
    }

    // =========================================== HELPERS ==============================================

    /**
     * @notice A helper function to register a user and delegate their voting power. This function is called
     *         when a user does not have a Registration created yet.
     *
     * @param user                          The address of the user to register.
     * @param _amount                       Amount of tokens to be locked.
     * @param _tokenId                      The id of the ERC1155 NFT.
     * @param _tokenAddress                 The address of the ERC1155 token.
     * @param _delegatee                    The address to delegate the voting power associated
     *                                      with this registration.
     */
    function _registerAndDelegate(
        address user,
        uint128 _amount,
        uint128 _tokenId,
        address _tokenAddress,
        address _delegatee
    ) internal {
        // check there is a multiplier associated with the ERC1155
        uint128 multiplier = getMultiplier(_tokenAddress, _tokenId);
        if (multiplier == 0) revert NBV_NoMultiplierSet();

        // load this contract's balance storage
        Storage.Uint256 storage balance = _balance();

        // load the registration
        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[user];

        // If the delegate address is not address zero, revert because the Registration
        // is already initialized. Only one Registration per user
        if (registration.delegatee != address(0)) revert NBV_HasRegistration();

        // load the delegate. Defaults to the registration owner
        _delegatee = _delegatee == address(0) ? user : _delegatee;

        // calculate the voting power provided by this registration
        uint128 newVotingPower = (_amount * multiplier) / MULTIPLIER_DENOMINATOR;

        // set the new registration
        registration.amount = _amount;
        registration.latestVotingPower = newVotingPower;
        registration.withdrawn = 0;
        registration.tokenId = _tokenId;
        registration.tokenAddress = _tokenAddress;
        registration.delegatee = _delegatee;

        // update this contract's balance
        balance.data += _amount;

        _grantVotingPower(_delegatee, newVotingPower);

        emit VoteChange(user, _delegatee, int256(uint256(newVotingPower)));
    }

    /**
     * @dev Grants the chosen delegate address voting power when a new user registers.
     *
     * @param delegatee                         The address to delegate the voting power associated
     *                                          with the Registration to.
     * @param newVotingPower                    Amount of votingPower associated with this Registration to
     *                                          be added to delegates existing votingPower.
     *
     */
    function _grantVotingPower(address delegatee, uint128 newVotingPower) internal {
        // update the delegatee's voting power
        BoundedHistory.HistoricalBalances memory votingPower = _votingPower();

        // loads the most recent timestamp of voting power for this delegate
        uint256 delegateeVotes = votingPower.loadTop(delegatee);

        // add block stamp indexed delegation power for this delegate to historical data array
        votingPower.push(delegatee, delegateeVotes + newVotingPower, MAX_HISTORY_LENGTH);
    }

    /**
     * @dev A single function endpoint for loading Registration storage
     *
     * @dev Only one Registration is allowed per user.
     *
     * @return registrations                 A storage mapping to look up registrations data
     */
    function _getRegistrations() internal pure returns (mapping(address => NFTBoostVaultStorage.Registration) storage) {
        // This call returns a storage mapping with a unique non overwrite-able storage location.
        return NFTBoostVaultStorage.mappingAddressToRegistrationPtr("registrations");
    }

    /**
     * @notice Helper function called when a user wants to withdraw the ERC1155 NFT
     *         they have registered for accessing a voting power multiplier.
     */
    function _withdrawNft() internal {
        // load the registration
        NFTBoostVaultStorage.Registration storage registration = _getRegistrations()[msg.sender];

        if (registration.tokenAddress == address(0)) {
            revert NBV_InvalidNft(registration.tokenAddress, registration.tokenId);
        }

        // transfer ERC1155 back to the user
        IERC1155(registration.tokenAddress).safeTransferFrom(
            address(this),
            msg.sender,
            registration.tokenId,
            1,
            bytes("")
        );

        // remove ERC1155 values from registration struct
        registration.tokenAddress = address(0);
        registration.tokenId = 0;

        // update the delegatee's voting power based on multiplier removal
        _syncVotingPower(msg.sender, registration);
    }

    /**
     * @dev Helper to update a delegatee's voting power.
     *
     * @param who                        The address who's voting power we need to sync.
     *
     * @param registration               The storage pointer to the registration of that user.
     */
    function _syncVotingPower(address who, NFTBoostVaultStorage.Registration storage registration) internal {
        BoundedHistory.HistoricalBalances memory votingPower = _votingPower();
        uint256 delegateeVotes = votingPower.loadTop(registration.delegatee);

        uint256 newVotingPower = _currentVotingPower(registration);
        // get the change in voting power. Negative if the voting power is reduced
        int256 change = int256(newVotingPower) - int256(uint256(registration.latestVotingPower));

        // do nothing if there is no change
        if (change == 0) return;
        if (change > 0) {
            votingPower.push(registration.delegatee, delegateeVotes + uint256(change), MAX_HISTORY_LENGTH);
        } else if (delegateeVotes > uint256(change * -1)) {
            // if the change is negative, we multiply by -1 to avoid underflow when casting
            votingPower.push(registration.delegatee, delegateeVotes - uint256(change * -1), MAX_HISTORY_LENGTH);
        } else {
            votingPower.push(registration.delegatee, 0, MAX_HISTORY_LENGTH);
        }

        registration.latestVotingPower = uint128(newVotingPower);

        emit VoteChange(who, registration.delegatee, change);
    }

    /**
     * @dev Calculates how much a user can withdraw.
     *
     * @param registration                The the memory location of the loaded registration.
     *
     * @return withdrawable               Amount which can be withdrawn.
     */
    function _getWithdrawableAmount(
        NFTBoostVaultStorage.Registration memory registration
    ) internal pure returns (uint256) {
        if (registration.withdrawn == registration.amount) {
            return 0;
        }

        return registration.amount - registration.withdrawn;
    }

    /**
     * @dev Helper that returns the current voting power of a registration.
     *
     * @dev This is not always the recorded voting power since it uses the latest multiplier.
     *
     * @param registration               The registration to check for voting power.
     *
     * @return                           The current voting power of the registration.
     */
    function _currentVotingPower(
        NFTBoostVaultStorage.Registration memory registration
    ) internal view virtual returns (uint256) {
        uint128 locked = registration.amount - registration.withdrawn;

        if (registration.tokenAddress != address(0) && registration.tokenId != 0) {
            return (locked * getMultiplier(registration.tokenAddress, registration.tokenId)) / MULTIPLIER_DENOMINATOR;
        }

        return locked;
    }

    /**
     * @notice An internal function for locking a user's ERC20 tokens in this contract
     *         for participation in governance. Calls _lockNft function if an ERC1155
     *         token address and ID are specified.
     *
     * @param from                      Address tokens are transferred from.
     * @param amount                    Amount of ERC20 tokens being transferred.
     * @param tokenAddress              Address of the ERC1155 token being transferred.
     * @param tokenId                   ID of the ERC1155 token being transferred.
     */
    function _lockTokens(address from, uint256 amount, address tokenAddress, uint128 tokenId) internal {
        token.transferFrom(from, address(this), amount);

        if (tokenAddress != address(0) && tokenId != 0) {
            _lockNft(from, tokenAddress, tokenId);
        }
    }

    /**
     * @dev A internal function for locking a user's ERC1155 token in this contract
     *         for participation in governance.
     *
     * @param from                      Address of owner token is transferred from.
     * @param tokenAddress              Address of the token being transferred.
     * @param tokenId                   Id of the token being transferred.
     */
    function _lockNft(address from, address tokenAddress, uint128 tokenId) internal {
        IERC1155(tokenAddress).safeTransferFrom(from, address(this), tokenId, 1, bytes(""));
    }

    /** @dev A single function endpoint for loading storage for multipliers.
     *
     * @return                          A storage mapping which can be used to lookup a
     *                                  token's multiplier data and token id data.
     */
    function _getMultipliers()
        internal
        pure
        returns (mapping(address => mapping(uint128 => NFTBoostVaultStorage.MultiplierData)) storage)
    {
        // This call returns a storage mapping with a unique non overwrite-able storage layout.
        return NFTBoostVaultStorage.mappingAddressToMultiplierData("multipliers");
    }

    /** @dev A function to handles the receipt of a single ERC1155 token. This function is called
     *       at the end of a safeTransferFrom after the balance has been updated. To accept the transfer,
     *       this must return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))
     *
     * @return                          0xf23a6e61
     */
    function onERC1155Received(address, address, uint256, uint256, bytes memory) public virtual returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    modifier onlyAirdrop() {
        if (msg.sender != Storage.addressPtr("airdrop").data) revert NBV_NotAirdrop();

        _;
    }
}

File 2 of 15 : IERC1155.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external
        view
        returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must be have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}

File 3 of 15 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

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

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

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

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

    /**
     * @dev 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);
}

File 4 of 15 : SafeERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

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

File 5 of 15 : Address.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 6 of 15 : IERC165.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

File 7 of 15 : BaseVotingVault.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

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

import "./libraries/BoundedHistory.sol";
import "./external/council/libraries/Storage.sol";

import "./libraries/HashedStorageReentrancyBlock.sol";

import "./interfaces/IBaseVotingVault.sol";

import { BVV_NotManager, BVV_NotTimelock, BVV_ZeroAddress, BVV_UpperLimitBlock } from "./errors/Governance.sol";

/**
 * @title BaseVotingVault
 * @author Non-Fungible Technologies, Inc.
 *
 * This contract is a base voting vault contract for use with Arcade voting vaults.
 * It includes basic voting vault functions like querying vote power, setting
 * the timelock and manager addresses, and getting the contracts token balance.
 */
abstract contract BaseVotingVault is HashedStorageReentrancyBlock, IBaseVotingVault {
    // ======================================== STATE ==================================================

    // Bring libraries into scope
    using BoundedHistory for BoundedHistory.HistoricalBalances;

    // ============================================ STATE ===============================================

    /// @notice The token used for voting in this vault.
    IERC20 public immutable token;

    /// @notice Number of blocks after which history can be pruned.
    uint256 public immutable staleBlockLag;

    /// @dev Max length of any voting history. Prevents gas exhaustion
    ///      attacks from having too-large history.
    uint256 public constant MAX_HISTORY_LENGTH = 256;

    // ============================================ EVENTS ==============================================

    // Event to track delegation data
    event VoteChange(address indexed from, address indexed to, int256 amount);

    // ========================================== CONSTRUCTOR ===========================================

    /**
     * @notice Deploys a base voting vault, setting immutable values for the token
     *         and staleBlockLag.
     *
     * @param _token                     The external erc20 token contract.
     * @param _staleBlockLag             The number of blocks before which the delegation history is forgotten.
     */
    constructor(IERC20 _token, uint256 _staleBlockLag) {
        if (address(_token) == address(0)) revert BVV_ZeroAddress("token");
        if (_staleBlockLag >= block.number) revert BVV_UpperLimitBlock(_staleBlockLag);

        token = _token;
        staleBlockLag = _staleBlockLag;
    }

    // ==================================== TIMELOCK FUNCTIONALITY ======================================

    /**
     * @notice Timelock-only timelock update function.
     * @dev Allows the timelock to update the timelock address.
     *
     * @param timelock_                  The new timelock.
     */
    function setTimelock(address timelock_) external onlyTimelock {
        if (timelock_ == address(0)) revert BVV_ZeroAddress("timelock");

        Storage.set(Storage.addressPtr("timelock"), timelock_);
    }

    /**
     * @notice Timelock-only manager update function.
     * @dev Allows the timelock to update the manager address.
     *
     * @param manager_                   The new manager address.
     */
    function setManager(address manager_) external onlyTimelock {
        if (manager_ == address(0)) revert BVV_ZeroAddress("manager");

        Storage.set(Storage.addressPtr("manager"), manager_);
    }

    // ======================================= VIEW FUNCTIONS ===========================================

    /**
     * @notice Loads the voting power of a user.
     *
     * @param user                       The address we want to load the voting power of.
     * @param blockNumber                Block number to query the user's voting power at.
     *
     * @return votes                     The number of votes.
     */
    function queryVotePower(address user, uint256 blockNumber, bytes calldata) external override returns (uint256) {
        // Get our reference to historical data
        BoundedHistory.HistoricalBalances memory votingPower = _votingPower();

        // Find the historical data and clear everything more than 'staleBlockLag' into the past
        return votingPower.findAndClear(user, blockNumber, block.number - staleBlockLag);
    }

    /**
     * @notice Loads the voting power of a user without changing state.
     *
     * @param user                       The address we want to load the voting power of.
     * @param blockNumber                Block number to query the user's voting power at.
     *
     * @return votes                     The number of votes.
     */
    function queryVotePowerView(address user, uint256 blockNumber) external view returns (uint256) {
        // Get our reference to historical data
        BoundedHistory.HistoricalBalances memory votingPower = _votingPower();

        // Find the historical datum
        return votingPower.find(user, blockNumber);
    }

    /**
     * @notice A function to access the storage of the timelock address.
     * @dev The timelock can access all functions with the onlyTimelock modifier.
     *
     * @return timelock                  The timelock address.
     */
    function timelock() public view returns (address) {
        return _timelock().data;
    }

    /**
     * @notice A function to access the storage of the manager address.
     *
     * @dev The manager can access all functions with the onlyManager modifier.
     *
     * @return manager                   The manager address.
     */
    function manager() public view returns (address) {
        return _manager().data;
    }

    // =========================================== HELPERS ==============================================

    /**
     * @notice A function to access the storage of the token value
     *
     * @return balance                    A struct containing the balance uint.
     */
    function _balance() internal pure returns (Storage.Uint256 storage) {
        return Storage.uint256Ptr("balance");
    }

    /**
     * @notice A function to access the storage of the timelock address.
     *
     * @dev The timelock can access all functions with the onlyTimelock modifier.
     *
     * @return timelock                   A struct containing the timelock address.
     */
    function _timelock() internal view returns (Storage.Address storage) {
        return Storage.addressPtr("timelock");
    }

    /**
     * @notice A function to access the storage of the manager address.
     *
     * @dev The manager can access all functions with the onlyManager modifier.
     *
     * @return manager                    A struct containing the manager address.
     */
    function _manager() internal view returns (Storage.Address storage) {
        return Storage.addressPtr("manager");
    }

    /**
     * @notice Returns the historical voting power tracker.
     *
     * @return votingPower              Historical voting power tracker.
     */
    function _votingPower() internal pure returns (BoundedHistory.HistoricalBalances memory) {
        // This call returns a storage mapping with a unique non overwrite-able storage location.
        return BoundedHistory.load("votingPower");
    }

    /**
     * @notice Modifier to check that the caller is the manager.
     */
    modifier onlyManager() {
        if (msg.sender != manager()) revert BVV_NotManager();

        _;
    }

    /**
     * @notice Modifier to check that the caller is the timelock.
     */
    modifier onlyTimelock() {
        if (msg.sender != timelock()) revert BVV_NotTimelock();

        _;
    }
}

File 8 of 15 : Governance.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title GovernanceErrors
 * @author Non-Fungible Technologies, Inc.
 *
 * This file contains custom errors for the Arcade governance vault contracts. All errors
 * are prefixed by the contract that throws them (e.g., "NBV_" for NFTBoostVault).
 * Errors located in one place to make it possible to holistically look at all
 * governance failure cases.
 */

// ======================================== NFT BOOST VAULT ==========================================
/// @notice All errors prefixed with NBV_, to separate from other contracts in governance.

/**
 * @notice Ensure caller has not already registered.
 */
error NBV_HasRegistration();

/**
 * @notice Caller has not already registered.
 */
error NBV_NoRegistration();

/**
 * @notice Ensure delegatee is not already registered as the delegate in user's Registration.
 */
error NBV_AlreadyDelegated();

/**
 * @notice Contract balance has to be bigger than amount being withdrawn.
 */
error NBV_InsufficientBalance();

/**
 * @notice Withdrawable tokens less than withdraw request amount.
 *
 * @param withdrawable              The returned withdrawable amount from
 *                                  a user's registration.
 */
error NBV_InsufficientWithdrawableBalance(uint256 withdrawable);

/**
 * @notice Multiplier limit exceeded.
 *
 * @param limitType                 Whether the multiplier is too high or too low.
 */
error NBV_MultiplierLimit(string limitType);

/**
 * @notice No multiplier has been set for the specified ERC1155 token.
 */
error NBV_NoMultiplierSet();

/**
 * @notice Multiplier has already been set for the specified ERC1155 token.
 */
error NBV_MultiplierSet(uint128 multiplier, uint128 expiration);

/**
 * @notice The provided token address and token id are invalid.
 *
 * @param tokenAddress              The token address provided.
 * @param tokenId                   The token id provided.
 */
error NBV_InvalidNft(address tokenAddress, uint256 tokenId);

/**
 * @notice User is calling withdraw() with zero amount.
 */
error NBV_ZeroAmount();

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                The name of the parameter for which
 *                                   a zero address was provided.
 */
error NBV_ZeroAddress(string addressType);

/**
 * @notice Provided addresses array holds more than 50 addresses.
 */
error NBV_ArrayTooManyElements();

/** @notice NFT Boost Voting Vault has already been unlocked.
 */
error NBV_AlreadyUnlocked();

/**
 * @notice ERC20 withdrawals from NFT Boost Voting Vault are frozen.
 */
error NBV_Locked();

/**
 * @notice Airdrop contract is not the caller.
 */
error NBV_NotAirdrop();

/**
 * @notice If a user already has a registration, they cannot change their
 *         delegatee when claiming subsequent airdrops.
 */
error NBV_WrongDelegatee(address newDelegate, address currentDelegate);

/**
 * @notice The multiplier expiration provided has already passed.
 */
error NBV_InvalidExpiration();

// ==================================== VESTING VOTING VAULT ======================================
/// @notice All errors prefixed with AVV_, to separate from other contracts in governance.

/**
 * @notice Block number parameters used to create a grant are invalid. Check that the start time is
 *         before the cliff, and the cliff is before the expiration.
 */
error AVV_InvalidSchedule();

/**
 * @notice The cliff block number cannot be less than the current block.
 */
error AVV_InvalidCliff();

/**
 * @notice Cliff amount should be less than the grant amount.
 */
error AVV_InvalidCliffAmount();

/**
 * @notice Insufficient balance to carry out the transaction.
 *
 * @param amountAvailable           The amount available in the vault.
 */
error AVV_InsufficientBalance(uint256 amountAvailable);

/**
 * @notice Grant has already been created for specified user.
 */
error AVV_HasGrant();

/**
 * @notice Grant has not been created for the specified user.
 */
error AVV_NoGrantSet();

/**
 * @notice Tokens cannot be claimed before the cliff.
 *
 * @param cliffBlock                The block number when grant claims begin.
 */
error AVV_CliffNotReached(uint256 cliffBlock);

/**
 * @notice Tokens cannot be re-delegated to the same address.
 */
error AVV_AlreadyDelegated();

/**
 * @notice Cannot withdraw zero tokens.
 */
error AVV_InvalidAmount();

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                The name of the parameter for which
 *                                   a zero address was provided.
 */
error AVV_ZeroAddress(string addressType);

// =================================== IMMUTABLE VESTING VAULT ===================================
/// @notice All errors prefixed with IVV_, to separate from other contracts in governance.

/**
 * @notice Grants cannot be revoked from the immutable vesting vault.
 */
error IVV_ImmutableGrants();

// ====================================== BASE VOTING VAULT ======================================
/// @notice All errors prefixed with BVV_, to separate from other contracts in governance.

/**
 * @notice Caller is not the manager.
 */
error BVV_NotManager();

/**
 * @notice Caller is not the timelock.
 */
error BVV_NotTimelock();

/**
 * @notice Zero address passed in where not allowed.
 *
 * @param addressType                The name of the parameter for which a zero
 *                                   address was provided.
 */
error BVV_ZeroAddress(string addressType);

/**
 * @notice The provided stale block number is too high.
 *
 * @param staleBlock                The block number in the past, provided at deployment
 *                                  before which a user's history is pruned.
 */
error BVV_UpperLimitBlock(uint256 staleBlock);

File 9 of 15 : History.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.3;

import "./Storage.sol";

// This library is an assembly optimized storage library which is designed
// to track timestamp history in a struct which uses hash derived pointers.
// WARNING - Developers using it should not access the underlying storage
// directly since we break some assumptions of high level solidity. Please
// note this library also increases the risk profile of memory manipulation
// please be cautious in your usage of uninitialized memory structs and other
// anti patterns.
library History {
    // The storage layout of the historical array looks like this
    // [(128 bit min index)(128 bit length)] [0][0] ... [(64 bit block num)(192 bit data)] .... [(64 bit block num)(192 bit data)]
    // We give the option to the invoker of the search function the ability to clear
    // stale storage. To find data we binary search for the block number we need
    // This library expects the blocknumber indexed data to be pushed in ascending block number
    // order and if data is pushed with the same blocknumber it only retains the most recent.
    // This ensures each blocknumber is unique and contains the most recent data at the end
    // of whatever block it indexes [as long as that block is not the current one].

    // A struct which wraps a memory pointer to a string and the pointer to storage
    // derived from that name string by the storage library
    // WARNING - For security purposes never directly construct this object always use load
    struct HistoricalBalances {
        string name;
        // Note - We use bytes32 to reduce how easy this is to manipulate in high level sol
        bytes32 cachedPointer;
    }

    /// @notice The method by which inheriting contracts init the HistoricalBalances struct
    /// @param name The name of the variable. Note - these are globals, any invocations of this
    ///             with the same name work on the same storage.
    /// @return The memory pointer to the wrapper of the storage pointer
    function load(string memory name)
        internal
        pure
        returns (HistoricalBalances memory)
    {
        mapping(address => uint256[]) storage storageData =
            Storage.mappingAddressToUnit256ArrayPtr(name);
        bytes32 pointer;
        assembly {
            pointer := storageData.slot
        }
        return HistoricalBalances(name, pointer);
    }

    /// @notice An unsafe method of attaching the cached ptr in a historical balance memory objects
    /// @param pointer cached pointer to storage
    /// @return storageData A storage array mapping pointer
    /// @dev PLEASE DO NOT USE THIS METHOD WITHOUT SERIOUS REVIEW. IF AN EXTERNAL ACTOR CAN CALL THIS WITH
    //       ARBITRARY DATA THEY MAY BE ABLE TO OVERWRITE ANY STORAGE IN THE CONTRACT.
    function _getMapping(bytes32 pointer)
        private
        pure
        returns (mapping(address => uint256[]) storage storageData)
    {
        assembly {
            storageData.slot := pointer
        }
    }

    /// @notice This function adds a block stamp indexed piece of data to a historical data array
    ///         To prevent duplicate entries if the top of the array has the same blocknumber
    ///         the value is updated instead
    /// @param wrapper The wrapper which hold the reference to the historical data storage pointer
    /// @param who The address which indexes the array we need to push to
    /// @param data The data to append, should be at most 192 bits and will revert if not
    function push(
        HistoricalBalances memory wrapper,
        address who,
        uint256 data
    ) internal {
        // Check preconditions
        // OoB = Out of Bounds, short for contract bytecode size reduction
        require(data <= type(uint192).max, "OoB");
        // Get the storage this is referencing
        mapping(address => uint256[]) storage storageMapping =
            _getMapping(wrapper.cachedPointer);
        // Get the array we need to push to
        uint256[] storage storageData = storageMapping[who];
        // We load the block number and then shift it to be in the top 64 bits
        uint256 blockNumber = block.number << 192;
        // We combine it with the data, because of our require this will have a clean
        // top 64 bits
        uint256 packedData = blockNumber | data;
        // Load the array length
        (uint256 minIndex, uint256 length) = _loadBounds(storageData);
        // On the first push we don't try to load
        uint256 loadedBlockNumber = 0;
        if (length != 0) {
            (loadedBlockNumber, ) = _loadAndUnpack(storageData, length - 1);
        }
        // The index we push to, note - we use this pattern to not branch the assembly
        uint256 index = length;
        // If the caller is changing data in the same block we change the entry for this block
        // instead of adding a new one. This ensures each block numb is unique in the array.
        if (loadedBlockNumber == block.number) {
            index = length - 1;
        }
        // We use assembly to write our data to the index
        assembly {
            // Stores packed data in the equivalent of storageData[length]
            sstore(
                add(
                    // The start of the data slots
                    add(storageData.slot, 1),
                    // index where we store
                    index
                ),
                packedData
            )
        }
        // Reset the boundaries if they changed
        if (loadedBlockNumber != block.number) {
            _setBounds(storageData, minIndex, length + 1);
        }
    }

    /// @notice Loads the most recent timestamp of delegation power
    /// @param wrapper The memory struct which we want to search for historical data
    /// @param who The user who's balance we want to load
    /// @return the top slot of the array
    function loadTop(HistoricalBalances memory wrapper, address who)
        internal
        view
        returns (uint256)
    {
        // Load the storage pointer
        uint256[] storage userData = _getMapping(wrapper.cachedPointer)[who];
        // Load the length
        (, uint256 length) = _loadBounds(userData);
        // If it's zero no data has ever been pushed so we return zero
        if (length == 0) {
            return 0;
        }
        // Load the current top
        (, uint256 storedData) = _loadAndUnpack(userData, length - 1);
        // and return it
        return (storedData);
    }

    /// @notice Finds the data stored with the highest block number which is less than or equal to a provided
    ///         blocknumber.
    /// @param wrapper The memory struct which we want to search for historical data
    /// @param who The address which indexes the array to be searched
    /// @param blocknumber The blocknumber we want to load the historical data of
    /// @return The loaded unpacked data at this point in time.
    function find(
        HistoricalBalances memory wrapper,
        address who,
        uint256 blocknumber
    ) internal view returns (uint256) {
        // Get the storage this is referencing
        mapping(address => uint256[]) storage storageMapping =
            _getMapping(wrapper.cachedPointer);
        // Get the array we need to push to
        uint256[] storage storageData = storageMapping[who];
        // Pre load the bounds
        (uint256 minIndex, uint256 length) = _loadBounds(storageData);
        // Search for the blocknumber
        (, uint256 loadedData) =
            _find(storageData, blocknumber, 0, minIndex, length);
        // In this function we don't have to change the stored length data
        return (loadedData);
    }

    /// @notice Finds the data stored with the highest blocknumber which is less than or equal to a provided block number
    ///         Opportunistically clears any data older than staleBlock which is possible to clear.
    /// @param wrapper The memory struct which points to the storage we want to search
    /// @param who The address which indexes the historical data we want to search
    /// @param blocknumber The blocknumber we want to load the historical state of
    /// @param staleBlock A block number which we can [but are not obligated to] delete history older than
    /// @return The found data
    function findAndClear(
        HistoricalBalances memory wrapper,
        address who,
        uint256 blocknumber,
        uint256 staleBlock
    ) internal returns (uint256) {
        // Get the storage this is referencing
        mapping(address => uint256[]) storage storageMapping =
            _getMapping(wrapper.cachedPointer);
        // Get the array we need to push to
        uint256[] storage storageData = storageMapping[who];
        // Pre load the bounds
        (uint256 minIndex, uint256 length) = _loadBounds(storageData);
        // Search for the blocknumber
        (uint256 staleIndex, uint256 loadedData) =
            _find(storageData, blocknumber, staleBlock, minIndex, length);
        // We clear any data in the stale region
        // Note - Since find returns 0 if no stale data is found and we use > instead of >=
        //        this won't trigger if no stale data is found. Plus it won't trigger on minIndex == staleIndex
        //        == maxIndex and clear the whole array.
        if (staleIndex > minIndex) {
            // Delete the outdated stored info
            _clear(minIndex, staleIndex, storageData);
            // Reset the array info with stale index as the new minIndex
            _setBounds(storageData, staleIndex, length);
        }
        return (loadedData);
    }

    /// @notice Searches for the data stored at the largest blocknumber index less than a provided parameter.
    ///         Allows specification of a expiration stamp and returns the greatest examined index which is
    ///         found to be older than that stamp.
    /// @param data The stored data
    /// @param blocknumber the blocknumber we want to load the historical data for.
    /// @param staleBlock The oldest block that we care about the data stored for, all previous data can be deleted
    /// @param startingMinIndex The smallest filled index in the array
    /// @param length the length of the array
    /// @return Returns the largest stale data index seen or 0 for no seen stale data and the stored data
    function _find(
        uint256[] storage data,
        uint256 blocknumber,
        uint256 staleBlock,
        uint256 startingMinIndex,
        uint256 length
    ) private view returns (uint256, uint256) {
        // We explicitly revert on the reading of memory which is uninitialized
        require(length != 0, "uninitialized");
        // Do some correctness checks
        require(staleBlock <= blocknumber);
        require(startingMinIndex < length);
        // Load the bounds of our binary search
        uint256 maxIndex = length - 1;
        uint256 minIndex = startingMinIndex;
        uint256 staleIndex = 0;

        // We run a binary search on the block number fields in the array between
        // the minIndex and maxIndex. If we find indexes with blocknumber < staleBlock
        // we set staleIndex to them and return that data for an optional clearing step
        // in the calling function.
        while (minIndex != maxIndex) {
            // We use the ceil instead of the floor because this guarantees that
            // we pick the highest blocknumber less than or equal the requested one
            uint256 mid = (minIndex + maxIndex + 1) / 2;
            // Load and unpack the data in the midpoint index
            (uint256 pastBlock, uint256 loadedData) = _loadAndUnpack(data, mid);

            //  If we've found the exact block we are looking for
            if (pastBlock == blocknumber) {
                // Then we just return the data
                return (staleIndex, loadedData);

                // Otherwise if the loaded block is smaller than the block number
            } else if (pastBlock < blocknumber) {
                // Then we first check if this is possibly a stale block
                if (pastBlock < staleBlock) {
                    // If it is we mark it for clearing
                    staleIndex = mid;
                }
                // We then repeat the search logic on the indices greater than the midpoint
                minIndex = mid;

                // In this case the pastBlock > blocknumber
            } else {
                // We then repeat the search on the indices below the midpoint
                maxIndex = mid - 1;
            }
        }

        // We load at the final index of the search
        (uint256 _pastBlock, uint256 _loadedData) =
            _loadAndUnpack(data, minIndex);
        // This will only be hit if a user has misconfigured the stale index and then
        // tried to load father into the past than has been preserved
        require(_pastBlock <= blocknumber, "Search Failure");
        return (staleIndex, _loadedData);
    }

    /// @notice Clears storage between two bounds in array
    /// @param oldMin The first index to set to zero
    /// @param newMin The new minimum filled index, ie clears to index < newMin
    /// @param data The storage array pointer
    function _clear(
        uint256 oldMin,
        uint256 newMin,
        uint256[] storage data
    ) private {
        // Correctness checks on this call
        require(oldMin <= newMin);
        // This function is private and trusted and should be only called by functions which ensure
        // that oldMin < newMin < length
        assembly {
            // The layout of arrays in solidity is [length][data]....[data] so this pointer is the
            // slot to write to data
            let dataLocation := add(data.slot, 1)
            // Loop through each index which is below new min and clear the storage
            // Note - Uses strict min so if given an input like oldMin = 5 newMin = 5 will be a no op
            for {
                let i := oldMin
            } lt(i, newMin) {
                i := add(i, 1)
            } {
                // store at the starting data pointer + i 256 bits of zero
                sstore(add(dataLocation, i), 0)
            }
        }
    }

    /// @notice Loads and unpacks the block number index and stored data from a data array
    /// @param data the storage array
    /// @param i the index to load and unpack
    /// @return (block number, stored data)
    function _loadAndUnpack(uint256[] storage data, uint256 i)
        private
        view
        returns (uint256, uint256)
    {
        // This function is trusted and should only be called after checking data lengths
        // we use assembly for the sload to avoid reloading length.
        uint256 loaded;
        assembly {
            loaded := sload(add(add(data.slot, 1), i))
        }
        // Unpack the packed 64 bit block number and 192 bit data field
        return (
            loaded >> 192, // block number of the data
            loaded &
                0x0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff // the data
        );
    }

    /// @notice This function sets our non standard bounds data field where a normal array
    ///         would have length
    /// @param data the pointer to the storage array
    /// @param minIndex The minimum non stale index
    /// @param length The length of the storage array
    function _setBounds(
        uint256[] storage data,
        uint256 minIndex,
        uint256 length
    ) private {
        // Correctness check
        require(minIndex < length);

        assembly {
            // Ensure data cleanliness
            let clearedLength := and(
                length,
                0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff
            )
            // We move the min index into the top 128 bits by shifting it left by 128 bits
            let minInd := shl(128, minIndex)
            // We pack the data using binary or
            let packed := or(minInd, clearedLength)
            // We store in the packed data in the length field of this storage array
            sstore(data.slot, packed)
        }
    }

    /// @notice This function loads and unpacks our packed min index and length for our custom storage array
    /// @param data The pointer to the storage location
    /// @return minInd the first filled index in the array
    /// @return length the length of the array
    function _loadBounds(uint256[] storage data)
        private
        view
        returns (uint256 minInd, uint256 length)
    {
        // Use assembly to manually load the length storage field
        uint256 packedData;
        assembly {
            packedData := sload(data.slot)
        }
        // We use a shift right to clear out the low order bits of the data field
        minInd = packedData >> 128;
        // We use a binary and to extract only the bottom 128 bits
        length =
            packedData &
            0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
    }
}

File 10 of 15 : Storage.sol
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.3;

// This library allows for secure storage pointers across proxy implementations
// It will return storage pointers based on a hashed name and type string.
library Storage {
    // This library follows a pattern which if solidity had higher level
    // type or macro support would condense quite a bit.

    // Each basic type which does not support storage locations is encoded as
    // a struct of the same name capitalized and has functions 'load' and 'set'
    // which load the data and set the data respectively.

    // All types will have a function of the form 'typename'Ptr('name') -> storage ptr
    // which will return a storage version of the type with slot which is the hash of
    // the variable name and type string. This pointer allows easy state management between
    // upgrades and overrides the default solidity storage slot system.

    /// @dev The address type container
    struct Address {
        address data;
    }

    /// @notice A function which turns a variable name for a storage address into a storage
    ///         pointer for its container.
    /// @param name the variable name
    /// @return data the storage pointer
    function addressPtr(string memory name)
        internal
        pure
        returns (Address storage data)
    {
        bytes32 typehash = keccak256("address");
        bytes32 offset = keccak256(abi.encodePacked(typehash, name));
        assembly {
            data.slot := offset
        }
    }

    /// @notice A function to load an address from the container struct
    /// @param input the storage pointer for the container
    /// @return the loaded address
    function load(Address storage input) internal view returns (address) {
        return input.data;
    }

    /// @notice A function to set the internal field of an address container
    /// @param input the storage pointer to the container
    /// @param to the address to set the container to
    function set(Address storage input, address to) internal {
        input.data = to;
    }

    /// @dev The uint256 type container
    struct Uint256 {
        uint256 data;
    }

    /// @notice A function which turns a variable name for a storage uint256 into a storage
    ///         pointer for its container.
    /// @param name the variable name
    /// @return data the storage pointer
    function uint256Ptr(string memory name)
        internal
        pure
        returns (Uint256 storage data)
    {
        bytes32 typehash = keccak256("uint256");
        bytes32 offset = keccak256(abi.encodePacked(typehash, name));
        assembly {
            data.slot := offset
        }
    }

    /// @notice A function to load an uint256 from the container struct
    /// @param input the storage pointer for the container
    /// @return the loaded uint256
    function load(Uint256 storage input) internal view returns (uint256) {
        return input.data;
    }

    /// @notice A function to set the internal field of a unit256 container
    /// @param input the storage pointer to the container
    /// @param to the address to set the container to
    function set(Uint256 storage input, uint256 to) internal {
        input.data = to;
    }

    /// @notice Returns the storage pointer for a named mapping of address to uint256
    /// @param name the variable name for the pointer
    /// @return data the mapping pointer
    function mappingAddressToUnit256Ptr(string memory name)
        internal
        pure
        returns (mapping(address => uint256) storage data)
    {
        bytes32 typehash = keccak256("mapping(address => uint256)");
        bytes32 offset = keccak256(abi.encodePacked(typehash, name));
        assembly {
            data.slot := offset
        }
    }

    /// @notice Returns the storage pointer for a named mapping of address to uint256[]
    /// @param name the variable name for the pointer
    /// @return data the mapping pointer
    function mappingAddressToUnit256ArrayPtr(string memory name)
        internal
        pure
        returns (mapping(address => uint256[]) storage data)
    {
        bytes32 typehash = keccak256("mapping(address => uint256[])");
        bytes32 offset = keccak256(abi.encodePacked(typehash, name));
        assembly {
            data.slot := offset
        }
    }

    /// @notice Allows external users to calculate the slot given by this lib
    /// @param typeString the string which encodes the type
    /// @param name the variable name
    /// @return the slot assigned by this lib
    function getPtr(string memory typeString, string memory name)
        external
        pure
        returns (uint256)
    {
        bytes32 typehash = keccak256(abi.encodePacked(typeString));
        bytes32 offset = keccak256(abi.encodePacked(typehash, name));
        return (uint256)(offset);
    }

    // A struct which represents 1 packed storage location with a compressed
    // address and uint96 pair
    struct AddressUint {
        address who;
        uint96 amount;
    }

    /// @notice Returns the storage pointer for a named mapping of address to uint256[]
    /// @param name the variable name for the pointer
    /// @return data the mapping pointer
    function mappingAddressToPackedAddressUint(string memory name)
        internal
        pure
        returns (mapping(address => AddressUint) storage data)
    {
        bytes32 typehash = keccak256("mapping(address => AddressUint)");
        bytes32 offset = keccak256(abi.encodePacked(typehash, name));
        assembly {
            data.slot := offset
        }
    }
}

File 11 of 15 : IBaseVotingVault.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

interface IBaseVotingVault {
    function queryVotePower(address user, uint256 blockNumber, bytes calldata extraData) external returns (uint256);

    function queryVotePowerView(address user, uint256 blockNumber) external view returns (uint256);

    function setTimelock(address timelock_) external;

    function setManager(address manager_) external;

    function timelock() external view returns (address);

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

File 12 of 15 : INFTBoostVault.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "../libraries/NFTBoostVaultStorage.sol";

interface INFTBoostVault {
    /**
     * @notice Events
     */
    event MultiplierSet(address tokenAddress, uint128 tokenId, uint128 multiplier, uint128 expiration);
    event WithdrawalsUnlocked();
    event AirdropContractUpdated(address newAirdropContract);

    /**
     * @notice View functions
     */
    function getIsLocked() external view returns (uint256);

    function getRegistration(address who) external view returns (NFTBoostVaultStorage.Registration memory);

    function getMultiplier(address tokenAddress, uint128 tokenId) external view returns (uint128);

    function getMultiplierExpiration(address tokenAddress, uint128 tokenId) external view returns (uint128);

    function getAirdropContract() external view returns (address);

    /**
     * @notice NFT boost vault functionality
     */
    function addNftAndDelegate(uint128 amount, uint128 tokenId, address tokenAddress, address delegatee) external;

    function airdropReceive(address user, uint128 amount, address delegatee) external;

    function delegate(address to) external;

    function withdraw(uint128 amount) external;

    function addTokens(uint128 amount) external;

    function withdrawNft() external;

    function updateNft(uint128 newTokenId, address newTokenAddress) external;

    function updateVotingPower(address[] memory userAddresses) external;

    /**
     * @notice Only Manager function
     */
    function setMultiplier(address tokenAddress, uint128 tokenId, uint128 multiplierValue, uint128 expiration) external;

    /**
     * @notice Only Timelock function
     */
    function unlock() external;

    /**
     * @notice Only Airdrop contract function
     */
    function setAirdropContract(address _newAirdropContract) external;
}

File 13 of 15 : BoundedHistory.sol
// SPDX-License-Identifier: MIT

/* solhint-disable max-line-length */
/* solhint-disable reason-string */

pragma solidity 0.8.18;

import "../external/council/libraries/Storage.sol";

// This library is an assembly optimized storage library which is designed
// to track timestamp history in a struct which uses hash derived pointers.
// WARNING - Developers using it should not access the underlying storage
// directly since we break some assumptions of high level solidity. Please
// note this library also increases the risk profile of memory manipulation
// please be cautious in your usage of uninitialized memory structs and other
// anti patterns.
library BoundedHistory {
    // The storage layout of the historical array looks like this
    // [(128 bit min index)(128 bit length)] [0][0] ... [(64 bit block num)(192 bit data)] .... [(64 bit block num)(192 bit data)]
    // We give the option to the invoker of the search function the ability to clear
    // stale storage. To find data we binary search for the block number we need
    // This library expects the blocknumber indexed data to be pushed in ascending block number
    // order and if data is pushed with the same blocknumber it only retains the most recent.
    // This ensures each blocknumber is unique and contains the most recent data at the end
    // of whatever block it indexes [as long as that block is not the current one].

    // A struct which wraps a memory pointer to a string and the pointer to storage
    // derived from that name string by the storage library
    // WARNING - For security purposes never directly construct this object always use load
    struct HistoricalBalances {
        string name;
        // Note - We use bytes32 to reduce how easy this is to manipulate in high level sol
        bytes32 cachedPointer;
    }

    /// @notice The method by which inheriting contracts init the HistoricalBalances struct
    /// @param name The name of the variable. Note - these are globals, any invocations of this
    ///             with the same name work on the same storage.
    /// @return The memory pointer to the wrapper of the storage pointer
    function load(string memory name) internal pure returns (HistoricalBalances memory) {
        mapping(address => uint256[]) storage storageData = Storage.mappingAddressToUnit256ArrayPtr(name);
        bytes32 pointer;
        assembly {
            pointer := storageData.slot
        }
        return HistoricalBalances(name, pointer);
    }

    /// @notice An unsafe method of attaching the cached ptr in a historical balance memory objects
    /// @param pointer cached pointer to storage
    /// @return storageData A storage array mapping pointer
    /// @dev PLEASE DO NOT USE THIS METHOD WITHOUT SERIOUS REVIEW. IF AN EXTERNAL ACTOR CAN CALL THIS WITH
    //       ARBITRARY DATA THEY MAY BE ABLE TO OVERWRITE ANY STORAGE IN THE CONTRACT.
    function _getMapping(bytes32 pointer) private pure returns (mapping(address => uint256[]) storage storageData) {
        assembly {
            storageData.slot := pointer
        }
    }

    /// @notice This function adds a block stamp indexed piece of data to a historical data array
    ///         To prevent duplicate entries if the top of the array has the same blocknumber
    ///         the value is updated instead
    /// @param wrapper The wrapper which hold the reference to the historical data storage pointer
    /// @param who The address which indexes the array we need to push to
    /// @param data The data to append, should be at most 192 bits and will revert if not
    /// @param maxLength The maximum length of history array, if at max, the oldest entry is removed
    function push(HistoricalBalances memory wrapper, address who, uint256 data, uint256 maxLength) internal {
        // Check preconditions
        // OoB = Out of Bounds, short for contract bytecode size reduction
        require(data <= type(uint192).max, "OoB");
        // Get the storage this is referencing
        mapping(address => uint256[]) storage storageMapping = _getMapping(wrapper.cachedPointer);
        // Get the array we need to push to
        uint256[] storage storageData = storageMapping[who];
        // We load the block number and then shift it to be in the top 64 bits
        uint256 blockNumber = block.number << 192;
        // We combine it with the data, because of our require this will have a clean
        // top 64 bits
        uint256 packedData = blockNumber | data;
        // Load the array length
        (uint256 minIndex, uint256 length) = _loadBounds(storageData);

        // On the first push we don't try to load
        uint256 loadedBlockNumber = 0;
        if (length != 0) {
            (loadedBlockNumber, ) = _loadAndUnpack(storageData, length - 1);
        }
        // The index we push to, note - we use this pattern to not branch the assembly
        uint256 index = length;

        if (loadedBlockNumber == block.number) {
            // If the caller is changing data in the same block we change the entry for this block
            // instead of adding a new one. This ensures each block numb is unique in the array.
            index = length - 1;
        } else if (length - minIndex >= maxLength) {
            // We need to push to the array, but if array is full to maxLength, so
            // we clear the oldest entry and increment the minIndex
            _clear(minIndex, ++minIndex, storageData);
        }

        // We use assembly to write our data to the index
        assembly {
            // Stores packed data in the equivalent of storageData[length]
            sstore(
                add(
                    // The start of the data slots
                    add(storageData.slot, 1),
                    // index where we store
                    index
                ),
                packedData
            )
        }

        // Reset the boundaries if they changed
        if (loadedBlockNumber != block.number) {
            _setBounds(storageData, minIndex, length + 1);
        }
    }

    /// @notice Loads the most recent timestamp of delegation power
    /// @param wrapper The memory struct which we want to search for historical data
    /// @param who The user who's balance we want to load
    /// @return the top slot of the array
    function loadTop(HistoricalBalances memory wrapper, address who) internal view returns (uint256) {
        // Load the storage pointer
        uint256[] storage userData = _getMapping(wrapper.cachedPointer)[who];
        // Load the length
        (, uint256 length) = _loadBounds(userData);
        // If it's zero no data has ever been pushed so we return zero
        if (length == 0) {
            return 0;
        }
        // Load the current top
        (, uint256 storedData) = _loadAndUnpack(userData, length - 1);
        // and return it
        return (storedData);
    }

    /// @notice Finds the data stored with the highest block number which is less than or equal to a provided
    ///         blocknumber.
    /// @param wrapper The memory struct which we want to search for historical data
    /// @param who The address which indexes the array to be searched
    /// @param blocknumber The blocknumber we want to load the historical data of
    /// @return The loaded unpacked data at this point in time.
    function find(HistoricalBalances memory wrapper, address who, uint256 blocknumber) internal view returns (uint256) {
        // Get the storage this is referencing
        mapping(address => uint256[]) storage storageMapping = _getMapping(wrapper.cachedPointer);
        // Get the array we need to push to
        uint256[] storage storageData = storageMapping[who];
        // Pre load the bounds
        (uint256 minIndex, uint256 length) = _loadBounds(storageData);
        // Search for the blocknumber
        (, uint256 loadedData) = _find(storageData, blocknumber, 0, minIndex, length);
        // In this function we don't have to change the stored length data
        return (loadedData);
    }

    /// @notice Finds the data stored with the highest blocknumber which is less than or equal to a provided block number
    ///         Opportunistically clears any data older than staleBlock which is possible to clear.
    /// @param wrapper The memory struct which points to the storage we want to search
    /// @param who The address which indexes the historical data we want to search
    /// @param blocknumber The blocknumber we want to load the historical state of
    /// @param staleBlock A block number which we can [but are not obligated to] delete history older than
    /// @return The found data
    function findAndClear(
        HistoricalBalances memory wrapper,
        address who,
        uint256 blocknumber,
        uint256 staleBlock
    ) internal returns (uint256) {
        // Get the storage this is referencing
        mapping(address => uint256[]) storage storageMapping = _getMapping(wrapper.cachedPointer);
        // Get the array we need to push to
        uint256[] storage storageData = storageMapping[who];
        // Pre load the bounds
        (uint256 minIndex, uint256 length) = _loadBounds(storageData);
        // Search for the blocknumber
        (uint256 staleIndex, uint256 loadedData) = _find(storageData, blocknumber, staleBlock, minIndex, length);
        // We clear any data in the stale region
        // Note - Since find returns 0 if no stale data is found and we use > instead of >=
        //        this won't trigger if no stale data is found. Plus it won't trigger on minIndex == staleIndex
        //        == maxIndex and clear the whole array.
        if (staleIndex > minIndex) {
            // Delete the outdated stored info
            _clear(minIndex, staleIndex, storageData);
            // Reset the array info with stale index as the new minIndex
            _setBounds(storageData, staleIndex, length);
        }
        return (loadedData);
    }

    /// @notice Searches for the data stored at the largest blocknumber index less than a provided parameter.
    ///         Allows specification of a expiration stamp and returns the greatest examined index which is
    ///         found to be older than that stamp.
    /// @param data The stored data
    /// @param blocknumber the blocknumber we want to load the historical data for.
    /// @param staleBlock The oldest block that we care about the data stored for, all previous data can be deleted
    /// @param startingMinIndex The smallest filled index in the array
    /// @param length the length of the array
    /// @return Returns the largest stale data index seen or 0 for no seen stale data and the stored data
    function _find(
        uint256[] storage data,
        uint256 blocknumber,
        uint256 staleBlock,
        uint256 startingMinIndex,
        uint256 length
    ) private view returns (uint256, uint256) {
        // We explicitly revert on the reading of memory which is uninitialized
        require(length != 0, "uninitialized");
        // Do some correctness checks
        require(staleBlock <= blocknumber);
        require(startingMinIndex < length);
        // Load the bounds of our binary search
        uint256 maxIndex = length - 1;
        uint256 minIndex = startingMinIndex;
        uint256 staleIndex = 0;

        // We run a binary search on the block number fields in the array between
        // the minIndex and maxIndex. If we find indexes with blocknumber < staleBlock
        // we set staleIndex to them and return that data for an optional clearing step
        // in the calling function.
        while (minIndex != maxIndex) {
            // We use the ceil instead of the floor because this guarantees that
            // we pick the highest blocknumber less than or equal the requested one
            uint256 mid = (minIndex + maxIndex + 1) / 2;
            // Load and unpack the data in the midpoint index
            (uint256 pastBlock, uint256 loadedData) = _loadAndUnpack(data, mid);

            //  If we've found the exact block we are looking for
            if (pastBlock == blocknumber) {
                // Then we just return the data
                return (staleIndex, loadedData);

                // Otherwise if the loaded block is smaller than the block number
            } else if (pastBlock < blocknumber) {
                // Then we first check if this is possibly a stale block
                if (pastBlock < staleBlock) {
                    // If it is we mark it for clearing
                    staleIndex = mid;
                }
                // We then repeat the search logic on the indices greater than the midpoint
                minIndex = mid;

                // In this case the pastBlock > blocknumber
            } else {
                // We then repeat the search on the indices below the midpoint
                maxIndex = mid - 1;
            }
        }

        // We load at the final index of the search
        (uint256 _pastBlock, uint256 _loadedData) = _loadAndUnpack(data, minIndex);
        // This will only be hit if a user has misconfigured the stale index and then
        // tried to load father into the past than has been preserved
        require(_pastBlock <= blocknumber, "Search Failure");
        return (staleIndex, _loadedData);
    }

    /// @notice Clears storage between two bounds in array
    /// @param oldMin The first index to set to zero
    /// @param newMin The new minimum filled index, ie clears to index < newMin
    /// @param data The storage array pointer
    function _clear(uint256 oldMin, uint256 newMin, uint256[] storage data) private {
        // Correctness checks on this call
        require(oldMin <= newMin);
        // This function is private and trusted and should be only called by functions which ensure
        // that oldMin < newMin < length
        assembly {
            // The layout of arrays in solidity is [length][data]....[data] so this pointer is the
            // slot to write to data
            let dataLocation := add(data.slot, 1)
            // Loop through each index which is below new min and clear the storage
            // Note - Uses strict min so if given an input like oldMin = 5 newMin = 5 will be a no op
            for {
                let i := oldMin
            } lt(i, newMin) {
                i := add(i, 1)
            } {
                // store at the starting data pointer + i 256 bits of zero
                sstore(add(dataLocation, i), 0)
            }
        }
    }

    /// @notice Loads and unpacks the block number index and stored data from a data array
    /// @param data the storage array
    /// @param i the index to load and unpack
    /// @return (block number, stored data)
    function _loadAndUnpack(uint256[] storage data, uint256 i) private view returns (uint256, uint256) {
        // This function is trusted and should only be called after checking data lengths
        // we use assembly for the sload to avoid reloading length.
        uint256 loaded;
        assembly {
            loaded := sload(add(add(data.slot, 1), i))
        }
        // Unpack the packed 64 bit block number and 192 bit data field
        return (
            loaded >> 192, // block number of the data
            loaded & 0x0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff // the data
        );
    }

    /// @notice This function sets our non standard bounds data field where a normal array
    ///         would have length
    /// @param data the pointer to the storage array
    /// @param minIndex The minimum non stale index
    /// @param length The length of the storage array
    function _setBounds(uint256[] storage data, uint256 minIndex, uint256 length) private {
        // Correctness check
        require(minIndex < length);

        assembly {
            // Ensure data cleanliness
            let clearedLength := and(length, 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff)
            // We move the min index into the top 128 bits by shifting it left by 128 bits
            let minInd := shl(128, minIndex)
            // We pack the data using binary or
            let packed := or(minInd, clearedLength)
            // We store in the packed data in the length field of this storage array
            sstore(data.slot, packed)
        }
    }

    /// @notice This function loads and unpacks our packed min index and length for our custom storage array
    /// @param data The pointer to the storage location
    /// @return minInd the first filled index in the array
    /// @return length the length of the array
    function _loadBounds(uint256[] storage data) private view returns (uint256 minInd, uint256 length) {
        // Use assembly to manually load the length storage field
        uint256 packedData;
        assembly {
            packedData := sload(data.slot)
        }
        // We use a shift right to clear out the low order bits of the data field
        minInd = packedData >> 128;
        // We use a binary and to extract only the bottom 128 bits
        length = packedData & 0x00000000000000000000000000000000ffffffffffffffffffffffffffffffff;
    }
}

File 14 of 15 : HashedStorageReentrancyBlock.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import "../external/council/libraries/History.sol";
import "../external/council/libraries/Storage.sol";

/**
 * @title HashedStorageReentrancyBlock
 * @author Non-Fungible Technologies, Inc.
 *
 * Helper contract to prevent reentrancy attacks using hashed storage. This contract is used
 * to protect against reentrancy attacks in the Arcade voting vault contracts.
 */
abstract contract HashedStorageReentrancyBlock {
    // =========================================== STATE ================================================

    // ============== CONSTANTS ==============

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

    // =========================================== HELPERS ==============================================

    /**
     * @dev Returns the storage pointer to the entered state variable.
     *
     * @return Storage              pointer to the entered state variable.
     */
    function _entered() internal pure returns (Storage.Uint256 storage) {
        return Storage.uint256Ptr("entered");
    }

    // ========================================= MODIFIERS =============================================

    /**
     * @dev Re-entrancy guard modifier using hashed storage.
     */
    modifier nonReentrant() {
        Storage.Uint256 storage entered = _entered();
        // Check the state variable before the call is entered
        require(entered.data == _NOT_ENTERED, "REENTRANCY");

        // Store that the function has been entered
        Storage.set(entered, _ENTERED);

        // Run the function code
        _;

        // Clear the state
        Storage.set(entered, _NOT_ENTERED);
    }
}

File 15 of 15 : NFTBoostVaultStorage.sol
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

/**
 * @title NFTBoostVaultStorage
 * @author Non-Fungible Technologies, Inc.
 *
 * Contract based on Council's `Storage.sol` with modified scope to match the NFTBoostVault
 * requirements. This library will return storage pointers based on a hashed name and type string.
 */
library NFTBoostVaultStorage {
    /**
    * This library follows a pattern which if solidity had higher level
    * type or macro support would condense quite a bit.

    * Each basic type which does not support storage locations is encoded as
    * a struct of the same name capitalized and has functions 'load' and 'set'
    * which load the data and set the data respectively.

    * All types will have a function of the form 'typename'Ptr('name') -> storage ptr
    * which will return a storage version of the type with slot which is the hash of
    * the variable name and type string. This pointer allows easy state management between
    * upgrades and overrides the default solidity storage slot system.
    */

    /// @dev typehash of the 'MultiplierData' mapping
    bytes32 public constant MULTIPLIER_TYPEHASH = keccak256("mapping(address => mapping(uint128 => MultiplierData))");

    /// @dev typehash of the 'Registration' mapping
    bytes32 public constant REGISTRATION_TYPEHASH = keccak256("mapping(address => Registration)");

    /// @dev struct which represents 1 packed storage location (Registration)
    struct Registration {
        uint128 amount; // token amount
        uint128 latestVotingPower;
        uint128 withdrawn; // amount of tokens withdrawn from voting vault
        uint128 tokenId; // ERC1155 token id
        address tokenAddress; // the address of the ERC1155 token
        address delegatee;
    }

    /// @dev struct which represents 1 packed storage location (MultiplierData)
    struct MultiplierData {
        uint128 multiplier;
        uint128 expiration;
    }

    /**
     * @notice Returns the storage pointer for a mapping of address to registration data
     *
     * @param name                      The variable name for the pointer.
     *
     * @return data                     The mapping pointer.
     */
    function mappingAddressToRegistrationPtr(
        string memory name
    ) internal pure returns (mapping(address => Registration) storage data) {
        bytes32 offset = keccak256(abi.encodePacked(REGISTRATION_TYPEHASH, name));
        assembly {
            data.slot := offset
        }
    }

    /**
     * @notice Returns the storage pointer for a mapping of address to a uint128 pair
     *
     * @param name                      The variable name for the pointer.
     *
     * @return data                     The mapping pointer.
     */
    function mappingAddressToMultiplierData(
        string memory name
    ) internal pure returns (mapping(address => mapping(uint128 => MultiplierData)) storage data) {
        bytes32 offset = keccak256(abi.encodePacked(MULTIPLIER_TYPEHASH, name));
        assembly {
            data.slot := offset
        }
    }
}

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

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"staleBlockLag","type":"uint256"},{"internalType":"address","name":"timelock","type":"address"},{"internalType":"address","name":"manager","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BVV_NotManager","type":"error"},{"inputs":[],"name":"BVV_NotTimelock","type":"error"},{"inputs":[{"internalType":"uint256","name":"staleBlock","type":"uint256"}],"name":"BVV_UpperLimitBlock","type":"error"},{"inputs":[{"internalType":"string","name":"addressType","type":"string"}],"name":"BVV_ZeroAddress","type":"error"},{"inputs":[],"name":"NBV_AlreadyDelegated","type":"error"},{"inputs":[],"name":"NBV_AlreadyUnlocked","type":"error"},{"inputs":[],"name":"NBV_ArrayTooManyElements","type":"error"},{"inputs":[],"name":"NBV_HasRegistration","type":"error"},{"inputs":[],"name":"NBV_InsufficientBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"withdrawable","type":"uint256"}],"name":"NBV_InsufficientWithdrawableBalance","type":"error"},{"inputs":[],"name":"NBV_InvalidExpiration","type":"error"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"NBV_InvalidNft","type":"error"},{"inputs":[],"name":"NBV_Locked","type":"error"},{"inputs":[{"internalType":"string","name":"limitType","type":"string"}],"name":"NBV_MultiplierLimit","type":"error"},{"inputs":[{"internalType":"uint128","name":"multiplier","type":"uint128"},{"internalType":"uint128","name":"expiration","type":"uint128"}],"name":"NBV_MultiplierSet","type":"error"},{"inputs":[],"name":"NBV_NoMultiplierSet","type":"error"},{"inputs":[],"name":"NBV_NoRegistration","type":"error"},{"inputs":[],"name":"NBV_NotAirdrop","type":"error"},{"inputs":[{"internalType":"address","name":"newDelegate","type":"address"},{"internalType":"address","name":"currentDelegate","type":"address"}],"name":"NBV_WrongDelegatee","type":"error"},{"inputs":[{"internalType":"string","name":"addressType","type":"string"}],"name":"NBV_ZeroAddress","type":"error"},{"inputs":[],"name":"NBV_ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAirdropContract","type":"address"}],"name":"AirdropContractUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"},{"indexed":false,"internalType":"uint128","name":"tokenId","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"multiplier","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"expiration","type":"uint128"}],"name":"MultiplierSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"int256","name":"amount","type":"int256"}],"name":"VoteChange","type":"event"},{"anonymous":false,"inputs":[],"name":"WithdrawalsUnlocked","type":"event"},{"inputs":[],"name":"MAX_HISTORY_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_MULTIPLIER","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MULTIPLIER_DENOMINATOR","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint128","name":"tokenId","type":"uint128"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"delegatee","type":"address"}],"name":"addNftAndDelegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"addTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"address","name":"delegatee","type":"address"}],"name":"airdropReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"delegate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAirdropContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getIsLocked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint128","name":"tokenId","type":"uint128"}],"name":"getMultiplier","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint128","name":"tokenId","type":"uint128"}],"name":"getMultiplierExpiration","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"who","type":"address"}],"name":"getRegistration","outputs":[{"components":[{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"uint128","name":"latestVotingPower","type":"uint128"},{"internalType":"uint128","name":"withdrawn","type":"uint128"},{"internalType":"uint128","name":"tokenId","type":"uint128"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"delegatee","type":"address"}],"internalType":"struct NFTBoostVaultStorage.Registration","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"manager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"queryVotePower","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"queryVotePowerView","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newAirdropContract","type":"address"}],"name":"setAirdropContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"manager_","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint128","name":"tokenId","type":"uint128"},{"internalType":"uint128","name":"multiplierValue","type":"uint128"},{"internalType":"uint128","name":"expiration","type":"uint128"}],"name":"setMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"timelock_","type":"address"}],"name":"setTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"staleBlockLag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timelock","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"newTokenId","type":"uint128"},{"internalType":"address","name":"newTokenAddress","type":"address"}],"name":"updateNft","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"userAddresses","type":"address[]"}],"name":"updateVotingPower","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawNft","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60c06040523480156200001157600080fd5b506040516200451538038062004515833981016040819052620000349162000366565b83836001600160a01b0382166200007b5760405163026cd36960e11b81526020600482015260056024820152643a37b5b2b760d91b60448201526064015b60405180910390fd5b438110620000a0576040516310fad7af60e01b81526004810182905260240162000072565b6001600160a01b0391821660805260a0528216620000ed57604051632c537de760e21b815260206004820152600860248201526774696d656c6f636b60c01b604482015260640162000072565b6001600160a01b0381166200013057604051632c537de760e21b815260206004820152600760248201526636b0b730b3b2b960c91b604482015260640162000072565b620001806200016d6040518060400160405280600b81526020016a1a5b9a5d1a585b1a5e995960aa1b8152506200029260201b6200233e1760201c565b6001620002ef60201b620023b71760201c565b620001cc620001ba6040518060400160405280600881526020016774696d656c6f636b60c01b815250620002f360201b620023bb1760201c565b836200033060201b620023f61760201c565b62000217620002056040518060400160405280600781526020016636b0b730b3b2b960c91b815250620002f360201b620023bb1760201c565b826200033060201b620023f61760201c565b620002506200016d60405180604001604052806007815260200166195b9d195c995960ca1b8152506200029260201b6200233e1760201c565b620002886200016d604051806040016040528060068152602001651b1bd8dad95960d21b8152506200029260201b6200233e1760201c565b50505050620003fc565b6000807fec13d6d12b88433319b64e1065a96ea19cd330ef6603f5f6fb685dde3959a320905060008184604051602001620002cf929190620003c2565b60408051601f198184030181529190528051602090910120949350505050565b9055565b6000807f421683f821a0574472445355be6d2b769119e8515f8376a1d7878523dfdecf7b905060008184604051602001620002cf929190620003c2565b81546001600160a01b0319166001600160a01b0391909116179055565b6001600160a01b03811681146200036357600080fd5b50565b600080600080608085870312156200037d57600080fd5b84516200038a816200034d565b602086015160408701519195509350620003a4816200034d565b6060860151909250620003b7816200034d565b939692955090935050565b8281526000825160005b81811015620003ea57602081860181015185830182015201620003cc565b50600092016020019182525092915050565b60805160a0516140de62000437600039600081816103b601526122100152600081816104ad015281816108c60152612b0f01526140de6000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80638ed013a6116100f9578063d33219b411610097578063f23a6e6111610071578063f23a6e611461042c578063fa2435fd14610495578063fc0c546a146104a8578063fe6ca782146104cf57600080fd5b8063d33219b4146103fe578063e7d2028314610406578063e91f32351461041957600080fd5b8063bdacb303116100d3578063bdacb3031461039e578063c2c94b88146103b1578063c428ee9d146103d8578063d0ebdbe7146103eb57600080fd5b80638ed013a61461037b578063a69df4b514610383578063b8a807c11461038b57600080fd5b80635c19a95c116101665780636f011538116101405780636f011538146102b757806372731062146102ca57806381360e7a1461035f5780638c55ea581461036857600080fd5b80635c19a95c146102885780635d6a618d1461029b5780635f041903146102a457600080fd5b8063481c6a75116101a2578063481c6a751461022a578063561c9f4e1461025757806357b9ca361461026a578063583b18961461027257600080fd5b806302387a7b146101c95780632dec063f146101de57806340d5b0d914610217575b600080fd5b6101dc6101d736600461397e565b6104d8565b005b6101f16101ec3660046139bd565b61090a565b6040516fffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6101dc6102253660046139f0565b610a1a565b610232610db5565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161020e565b6101dc61026536600461397e565b610ddb565b610232610fd6565b61027a611016565b60405190815260200161020e565b6101dc610296366004613a44565b61105c565b6101f16105dc81565b6101dc6102b2366004613a5f565b61144e565b6101dc6102c5366004613a44565b6116e8565b6102dd6102d8366004613a44565b611821565b60405161020e9190600060c0820190506fffffffffffffffffffffffffffffffff80845116835280602085015116602084015280604085015116604084015280606085015116606084015250608083015173ffffffffffffffffffffffffffffffffffffffff80821660808501528060a08601511660a0850152505092915050565b61027a61010081565b6101dc610376366004613a89565b6118fa565b6101dc611c66565b6101dc611cf3565b6101dc610399366004613acc565b611e12565b6101dc6103ac366004613a44565b611f11565b61027a7f000000000000000000000000000000000000000000000000000000000000000081565b6101f16103e63660046139bd565b61203b565b6101dc6103f9366004613a44565b6120a5565b6102326121cf565b61027a610414366004613b15565b6121d9565b61027a610427366004613b3f565b6121f9565b61046461043a366004613bf5565b7ff23a6e610000000000000000000000000000000000000000000000000000000095945050505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200161020e565b6101dc6104a3366004613cf9565b612248565b6102327f000000000000000000000000000000000000000000000000000000000000000081565b6101f16103e881565b60006104e2612438565b8054909150600114610555576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b60028155610561611016565b60010361059a576040517f94ceaa1f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816fffffffffffffffffffffffffffffffff166000036105e6576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006105f061247d565b33600090815260209190915260408120915061060a6124bd565b9050836fffffffffffffffffffffffffffffffff168160000154101561065c576040517f59cb57c000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160c08101825283546fffffffffffffffffffffffffffffffff8082168352700100000000000000000000000000000000918290048116602084015260018601548082169484019490945292049091166060820152600283015473ffffffffffffffffffffffffffffffffffffffff908116608083015260038401541660a08201526000906106ed906124fd565b9050846fffffffffffffffffffffffffffffffff1681101561073e576040517f5d9272870000000000000000000000000000000000000000000000000000000081526004810182905260240161054c565b846fffffffffffffffffffffffffffffffff168260000160008282546107649190613d9d565b90915550506001830180548691906000906107929084906fffffffffffffffffffffffffffffffff16613db0565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506107d23384612563565b825460018401546fffffffffffffffffffffffffffffffff9182169116036108ac57600283015473ffffffffffffffffffffffffffffffffffffffff16158015906108465750600183015470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1615155b15610853576108536127de565b600083556001830180547fffffffffffffffffffffffffffffffff000000000000000000000000000000001690556003830180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555b6108ff73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016336fffffffffffffffffffffffffffffffff88166129b0565b505060018255505050565b600073ffffffffffffffffffffffffffffffffffffffff831615801561094057506fffffffffffffffffffffffffffffffff8216155b1561094e57506103e8610a14565b6000610958612a3d565b73ffffffffffffffffffffffffffffffffffffffff85166000908152602091825260408082206fffffffffffffffffffffffffffffffff8088168452935281208054909350700100000000000000000000000000000000900490911690036109c4576000915050610a14565b8054427001000000000000000000000000000000009091046fffffffffffffffffffffffffffffffff16116109fe576103e8915050610a14565b546fffffffffffffffffffffffffffffffff1690505b92915050565b610a22610db5565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a86576040517fe927ce0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6105dc6fffffffffffffffffffffffffffffffff83161115610b06576040517fd0f03f9d00000000000000000000000000000000000000000000000000000000815260040161054c9060208082526004908201527f6869676800000000000000000000000000000000000000000000000000000000604082015260600190565b6103e8826fffffffffffffffffffffffffffffffff161015610b84576040517fd0f03f9d00000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f6c6f770000000000000000000000000000000000000000000000000000000000604482015260640161054c565b42816fffffffffffffffffffffffffffffffff1611610bcf576040517f1a37ebf400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84161580610c0257506fffffffffffffffffffffffffffffffff8316155b15610c69576040517f3205f1c600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526fffffffffffffffffffffffffffffffff8416602482015260440161054c565b6000610c73612a3d565b73ffffffffffffffffffffffffffffffffffffffff86166000908152602091825260408082206fffffffffffffffffffffffffffffffff80891684529352902080549092501615610d245780546040517ffda8d4740000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff8083166004830152700100000000000000000000000000000000909204909116602482015260440161054c565b6fffffffffffffffffffffffffffffffff828116700100000000000000000000000000000000810285831690811784556040805173ffffffffffffffffffffffffffffffffffffffff8a168152938816602085015283015260608201527fb6c4db7a42dfab7553d15168f88363d3af3f85739e3ec0d1f30c96028fe32f8f9060800160405180910390a15050505050565b6000610dbf612a7d565b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6000610de5612438565b8054909150600114610e53576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155816fffffffffffffffffffffffffffffffff16600003610ea3576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610ead61247d565b33600090815260209190915260409020600381015490915073ffffffffffffffffffffffffffffffffffffffff16610f11576040517ffa267cd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610f1b6124bd565b9050836fffffffffffffffffffffffffffffffff16816000016000828254610f439190613de0565b9091555050815484908390600090610f6e9084906fffffffffffffffffffffffffffffffff16613db0565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550610fae3383612563565b610fcd33856fffffffffffffffffffffffffffffffff16600080612abd565b50506001905550565b6000610dbf6040518060400160405280600781526020017f61697264726f70000000000000000000000000000000000000000000000000008152506123bb565b60006110566040518060400160405280600681526020017f6c6f636b6564000000000000000000000000000000000000000000000000000081525061233e565b54919050565b73ffffffffffffffffffffffffffffffffffffffff81166110d9576040517fb14df79c00000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f746f000000000000000000000000000000000000000000000000000000000000604482015260640161054c565b60006110e361247d565b33600090815260209190915260409020600381015490915073ffffffffffffffffffffffffffffffffffffffff16611147576040517ffa267cd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600381015473ffffffffffffffffffffffffffffffffffffffff9081169083160361119e576040517fa7b752f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006111a8612bc3565b60038301549091506000906111d490839073ffffffffffffffffffffffffffffffffffffffff16612c16565b600384015484549192506112369173ffffffffffffffffffffffffffffffffffffffff9091169061122b9070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1684613d9d565b849190610100612cba565b6003830154835473ffffffffffffffffffffffffffffffffffffffff9091169033907f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e0906112ca9070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613df3565b60405190815260200160405180910390a360006112e78386612c16565b6040805160c08101825286546fffffffffffffffffffffffffffffffff8082168352700100000000000000000000000000000000918290048116602084015260018901548082169484019490945292049091166060820152600286015473ffffffffffffffffffffffffffffffffffffffff908116608083015260038701541660a082015290915060009061137b90612e23565b90506113968661138b8385613de0565b869190610100612cba565b84546fffffffffffffffffffffffffffffffff80831670010000000000000000000000000000000002911617855560038501805473ffffffffffffffffffffffffffffffffffffffff88167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116811790915560405133907f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e09061143e9085815260200190565b60405180910390a3505050505050565b6000611458612438565b80549091506001146114c6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b6002815573ffffffffffffffffffffffffffffffffffffffff821615806114fd57506fffffffffffffffffffffffffffffffff8316155b15611564576040517f3205f1c600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526fffffffffffffffffffffffffffffffff8416602482015260440161054c565b61156e828461090a565b6fffffffffffffffffffffffffffffffff166000036115b9576040517fc985ca0800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006115c361247d565b33600090815260209190915260409020600381015490915073ffffffffffffffffffffffffffffffffffffffff16611627576040517ffa267cd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600281015473ffffffffffffffffffffffffffffffffffffffff161561164f5761164f6127de565b60028101805473ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff00000000000000000000000000000000000000009091161790556001810180546fffffffffffffffffffffffffffffffff8087167001000000000000000000000000000000000291161790556116d0338486612ec2565b6116da3382612563565b50600190555050565b505050565b6116f0610db5565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611754576040517fe927ce0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6117d56117956040518060400160405280600781526020017f61697264726f70000000000000000000000000000000000000000000000000008152506123bb565b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8416179055565b60405173ffffffffffffffffffffffffffffffffffffffff821681527fabe40da5dd07060349f485f71bd70d796ba49859153e786fe58a1d11f86c4ac99060200160405180910390a150565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a081019190915261185b61247d565b73ffffffffffffffffffffffffffffffffffffffff92831660009081526020918252604090819020815160c08101835281546fffffffffffffffffffffffffffffffff808216835270010000000000000000000000000000000091829004811695830195909552600183015480861694830194909452909204909216606082015260028201548416608082015260039091015490921660a08301525090565b6119386040518060400160405280600781526020017f61697264726f70000000000000000000000000000000000000000000000000008152506123bb565b5473ffffffffffffffffffffffffffffffffffffffff163314611987576040517fab04925b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611991612438565b80549091506001146119ff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155826fffffffffffffffffffffffffffffffff16600003611a4f576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416611ace576040517fb14df79c00000000000000000000000000000000000000000000000000000000815260040161054c9060208082526004908201527f7573657200000000000000000000000000000000000000000000000000000000604082015260600190565b6000611ad861247d565b73ffffffffffffffffffffffffffffffffffffffff80871660009081526020929092526040909120600381015490925016611b2057611b1b858560008087612f60565b611c3b565b600381015473ffffffffffffffffffffffffffffffffffffffff848116911614611b9c5760038101546040517fa044d4d100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8086166004830152909116602482015260440161054c565b6000611ba66124bd565b9050846fffffffffffffffffffffffffffffffff16816000016000828254611bce9190613de0565b9091555050815485908390600090611bf99084906fffffffffffffffffffffffffffffffff16613db0565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550611c398683612563565b505b611c5a33856fffffffffffffffffffffffffffffffff16600080612abd565b50600181555b50505050565b6000611c70612438565b8054909150600114611cde576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155611cea6127de565b60019055565b50565b611cfb6121cf565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611d5f576040517f88fe7b4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611d67611016565b600114611da0576040517fedc4227400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611de7611de16040518060400160405280600681526020017f6c6f636b6564000000000000000000000000000000000000000000000000000081525061233e565b60029055565b6040517f15fa93f6df3cbaf0b9ddb555fa798ba412e2bbc86e60aa62d8aade4c3052cd6290600090a1565b6000611e1c612438565b8054909150600114611e8a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155846fffffffffffffffffffffffffffffffff16600003611eda576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ee73386868686612f60565b611f0533866fffffffffffffffffffffffffffffffff168587612abd565b600181555b5050505050565b611f196121cf565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f7d576040517f88fe7b4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611ffa576040517f04d9a6d200000000000000000000000000000000000000000000000000000000815260206004820152600860248201527f74696d656c6f636b000000000000000000000000000000000000000000000000604482015260640161054c565b611cf06117956040518060400160405280600881526020017f74696d656c6f636b0000000000000000000000000000000000000000000000008152506123bb565b600080612046612a3d565b73ffffffffffffffffffffffffffffffffffffffff85166000908152602091825260408082206fffffffffffffffffffffffffffffffff8088168452935290205470010000000000000000000000000000000090041691505092915050565b6120ad6121cf565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612111576040517f88fe7b4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811661218e576040517f04d9a6d200000000000000000000000000000000000000000000000000000000815260206004820152600760248201527f6d616e6167657200000000000000000000000000000000000000000000000000604482015260640161054c565b611cf06117956040518060400160405280600781526020017f6d616e61676572000000000000000000000000000000000000000000000000008152506123bb565b6000610dbf6131b4565b6000806121e4612bc3565b90506121f18185856131f4565b949350505050565b600080612204612bc3565b905061223e86866122357f000000000000000000000000000000000000000000000000000000000000000043613d9d565b84929190613265565b9695505050505050565b6032811115612283576040517f48a3bbbc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156116e357600061229861247d565b60008585858181106122ac576122ac613e3f565b90506020020160208101906122c19190613a44565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905061232d84848481811061231257612312613e3f565b90506020020160208101906123279190613a44565b82612563565b5061233781613e6e565b9050612286565b6000807fec13d6d12b88433319b64e1065a96ea19cd330ef6603f5f6fb685dde3959a320905060008184604051602001612379929190613eca565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190528051602090910120949350505050565b9055565b6000807f421683f821a0574472445355be6d2b769119e8515f8376a1d7878523dfdecf7b905060008184604051602001612379929190613eca565b81547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff91909116179055565b60006124786040518060400160405280600781526020017f656e74657265640000000000000000000000000000000000000000000000000081525061233e565b905090565b60006124786040518060400160405280600d81526020017f726567697374726174696f6e73000000000000000000000000000000000000008152506132f7565b60006124786040518060400160405280600781526020017f62616c616e63650000000000000000000000000000000000000000000000000081525061233e565b600081600001516fffffffffffffffffffffffffffffffff1682604001516fffffffffffffffffffffffffffffffff160361253a57506000919050565b6040820151825161254b9190613ef0565b6fffffffffffffffffffffffffffffffff1692915050565b600061256d612bc3565b600383015490915060009061259990839073ffffffffffffffffffffffffffffffffffffffff16612c16565b6040805160c08101825285546fffffffffffffffffffffffffffffffff8082168352700100000000000000000000000000000000918290048116602084015260018801548082169484019490945292049091166060820152600285015473ffffffffffffffffffffffffffffffffffffffff908116608083015260038601541660a082015290915060009061262d90612e23565b84549091506000906126659070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1683613f19565b90508060000361267757505050505050565b60008113156126ae5760038501546126a99073ffffffffffffffffffffffffffffffffffffffff1661138b8386613de0565b61275d565b6126d8817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613df3565b8311156127325760038501546126a99073ffffffffffffffffffffffffffffffffffffffff16612728837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613df3565b61138b9086613d9d565b600385015461275d90859073ffffffffffffffffffffffffffffffffffffffff166000610100612cba565b84546fffffffffffffffffffffffffffffffff808416700100000000000000000000000000000000029116178555600385015460405173ffffffffffffffffffffffffffffffffffffffff918216918816907f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e09061143e9085815260200190565b60006127e861247d565b33600090815260209190915260409020600281015490915073ffffffffffffffffffffffffffffffffffffffff1661289a57600281015460018201546040517f3205f1c600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909216600483015270010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16602482015260440161054c565b6002810154600180830154604080516020810182526000815290517ff242432a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9094169363f242432a9361293093309333937001000000000000000000000000000000009092046fffffffffffffffffffffffffffffffff169291600401613f83565b600060405180830381600087803b15801561294a57600080fd5b505af115801561295e573d6000803e3d6000fd5b5050506002820180547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055506001810180546fffffffffffffffffffffffffffffffff169055611cf03382612563565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790526116e390849061336a565b60006124786040518060400160405280600b81526020017f6d756c7469706c69657273000000000000000000000000000000000000000000815250613476565b60006124786040518060400160405280600781526020017f6d616e61676572000000000000000000000000000000000000000000000000008152506123bb565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8581166004830152306024830152604482018590527f000000000000000000000000000000000000000000000000000000000000000016906323b872dd906064016020604051808303816000875af1158015612b58573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b7c9190613fda565b5073ffffffffffffffffffffffffffffffffffffffff821615801590612bb357506fffffffffffffffffffffffffffffffff811615155b15611c6057611c60848383612ec2565b6040805180820190915260608152600060208201526124786040518060400160405280600b81526020017f766f74696e67506f7765720000000000000000000000000000000000000000008152506134ac565b600080612c24846020015190565b73ffffffffffffffffffffffffffffffffffffffff841660009081526020919091526040812080549092506fffffffffffffffffffffffffffffffff1690819003612c7457600092505050610a14565b6000612caf83612c85600185613d9d565b016001015460c081901c9177ffffffffffffffffffffffffffffffffffffffffffffffff90911690565b979650505050505050565b77ffffffffffffffffffffffffffffffffffffffffffffffff821115612d3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f4f6f420000000000000000000000000000000000000000000000000000000000604482015260640161054c565b6000612d49856020015190565b73ffffffffffffffffffffffffffffffffffffffff851660009081526020829052604081208054929350914360c01b9186831791608081901c916fffffffffffffffffffffffffffffffff909116908115612db157612dad86612c85600185613d9d565b5090505b81438203612dcb57612dc4600184613d9d565b9050612df1565b88612dd68585613d9d565b10612df157612df184612de881613e6e565b955085896134e5565b8481600189010155438214612e1557612e158785612e10866001613de0565b61350e565b505050505050505050505050565b60008082604001518360000151612e3a9190613ef0565b608084015190915073ffffffffffffffffffffffffffffffffffffffff1615801590612e7b575060608301516fffffffffffffffffffffffffffffffff1615155b1561254b576103e8612e958460800151856060015161090a565b612e9f9083613ffc565b612ea9919061405f565b6fffffffffffffffffffffffffffffffff169392505050565b604080516020810182526000815290517ff242432a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84169163f242432a91612f299187913091879160019190600401613f83565b600060405180830381600087803b158015612f4357600080fd5b505af1158015612f57573d6000803e3d6000fd5b50505050505050565b6000612f6c838561090a565b9050806fffffffffffffffffffffffffffffffff16600003612fba576040517fc985ca0800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612fc46124bd565b90506000612fd061247d565b73ffffffffffffffffffffffffffffffffffffffff808a166000908152602092909252604090912060038101549092501615613038576040517f9e2b1ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84161561305a578361305c565b875b935060006103e861306d858a613ffc565b613077919061405f565b6fffffffffffffffffffffffffffffffff808216700100000000000000000000000000000000908102828c169081178655918a1602600185015560028401805473ffffffffffffffffffffffffffffffffffffffff808b167fffffffffffffffffffffffff000000000000000000000000000000000000000092831617909255600386018054928a16929091169190911790558454919250908490600090613120908490613de0565b9091555061313090508582613537565b8473ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff167f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e0836fffffffffffffffffffffffffffffffff166040516131a191815260200190565b60405180910390a3505050505050505050565b60006124786040518060400160405280600881526020017f74696d656c6f636b0000000000000000000000000000000000000000000000008152506123bb565b600080613202856020015190565b73ffffffffffffffffffffffffffffffffffffffff85166000908152602082905260408120805492935091608081901c916fffffffffffffffffffffffffffffffff909116906132558488838686613571565b96505050505050505b9392505050565b600080613273866020015190565b73ffffffffffffffffffffffffffffffffffffffff86166000908152602082905260408120805492935091608081901c916fffffffffffffffffffffffffffffffff90911690806132c7858a8a8787613571565b91509150838211156132e9576132de8483876134e5565b6132e985838561350e565b9a9950505050505050505050565b6000807ff162ab2a93c7e839fe65139803001cff34be31efd14d829854af05b9911b6e848360405160200161332d929190613eca565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101209392505050565b60006133cc826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff1661374c9092919063ffffffff16565b8051909150156116e357808060200190518101906133ea9190613fda565b6116e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f74207375636365656400000000000000000000000000000000000000000000606482015260840161054c565b6000807fafcb3a8867bd263fd503fc00b0ffd9a453f69f3268e9d48926d67e3802a891d98360405160200161332d929190613eca565b60408051808201909152606081526000602082015260006134cc8361375b565b6040805180820190915293845260208401525090919050565b818311156134f257600080fd5b60018101835b83811015611f0a576000828201556001016134f8565b80821061351a57600080fd5b6fffffffffffffffffffffffffffffffff1660809190911b179055565b6000613541612bc3565b9050600061354f8285612c16565b9050611c608461122b6fffffffffffffffffffffffffffffffff861684613de0565b600080826000036135de576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f756e696e697469616c697a656400000000000000000000000000000000000000604482015260640161054c565b858511156135eb57600080fd5b8284106135f757600080fd5b6000613604600185613d9d565b90508460005b8282146136a7576000600261361f8585613de0565b61362a906001613de0565b613634919061408e565b6001818d01015490915060c081901c9077ffffffffffffffffffffffffffffffffffffffffffffffff168b8203613675579296509194506137429350505050565b8b821015613691578a821015613689578293505b82945061369f565b61369c600184613d9d565b95505b50505061360a565b60018a8301015460c081901c9077ffffffffffffffffffffffffffffffffffffffffffffffff168a821115613738576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f536561726368204661696c757265000000000000000000000000000000000000604482015260640161054c565b9195509093505050505b9550959350505050565b60606121f18484600085613796565b6000807f7b1a68ec3e3284b167e69db1c622dcfa612281976b71d7e2d239dbe16a75891a905060008184604051602001612379929190613eca565b606082471015613828576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c0000000000000000000000000000000000000000000000000000606482015260840161054c565b843b613890576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161054c565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516138b991906140a2565b60006040518083038185875af1925050503d80600081146138f6576040519150601f19603f3d011682016040523d82523d6000602084013e6138fb565b606091505b5091509150612caf8282866060831561391557508161325e565b8251156139255782518084602001fd5b816040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161054c91906140be565b80356fffffffffffffffffffffffffffffffff8116811461397957600080fd5b919050565b60006020828403121561399057600080fd5b61325e82613959565b803573ffffffffffffffffffffffffffffffffffffffff8116811461397957600080fd5b600080604083850312156139d057600080fd5b6139d983613999565b91506139e760208401613959565b90509250929050565b60008060008060808587031215613a0657600080fd5b613a0f85613999565b9350613a1d60208601613959565b9250613a2b60408601613959565b9150613a3960608601613959565b905092959194509250565b600060208284031215613a5657600080fd5b61325e82613999565b60008060408385031215613a7257600080fd5b613a7b83613959565b91506139e760208401613999565b600080600060608486031215613a9e57600080fd5b613aa784613999565b9250613ab560208501613959565b9150613ac360408501613999565b90509250925092565b60008060008060808587031215613ae257600080fd5b613aeb85613959565b9350613af960208601613959565b9250613b0760408601613999565b9150613a3960608601613999565b60008060408385031215613b2857600080fd5b613b3183613999565b946020939093013593505050565b60008060008060608587031215613b5557600080fd5b613b5e85613999565b935060208501359250604085013567ffffffffffffffff80821115613b8257600080fd5b818701915087601f830112613b9657600080fd5b813581811115613ba557600080fd5b886020828501011115613bb757600080fd5b95989497505060200194505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080600080600060a08688031215613c0d57600080fd5b613c1686613999565b9450613c2460208701613999565b93506040860135925060608601359150608086013567ffffffffffffffff80821115613c4f57600080fd5b818801915088601f830112613c6357600080fd5b813581811115613c7557613c75613bc6565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715613cbb57613cbb613bc6565b816040528281528b6020848701011115613cd457600080fd5b8260208601602083013760006020848301015280955050505050509295509295909350565b60008060208385031215613d0c57600080fd5b823567ffffffffffffffff80821115613d2457600080fd5b818501915085601f830112613d3857600080fd5b813581811115613d4757600080fd5b8660208260051b8501011115613d5c57600080fd5b60209290920196919550909350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610a1457610a14613d6e565b6fffffffffffffffffffffffffffffffff818116838216019080821115613dd957613dd9613d6e565b5092915050565b80820180821115610a1457610a14613d6e565b808202600082127f800000000000000000000000000000000000000000000000000000000000000084141615613e2b57613e2b613d6e565b8181058314821517610a1457610a14613d6e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e9f57613e9f613d6e565b5060010190565b60005b83811015613ec1578181015183820152602001613ea9565b50506000910152565b82815260008251613ee2816020850160208701613ea6565b919091016020019392505050565b6fffffffffffffffffffffffffffffffff828116828216039080821115613dd957613dd9613d6e565b8181036000831280158383131683831282161715613dd957613dd9613d6e565b60008151808452613f51816020860160208601613ea6565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600073ffffffffffffffffffffffffffffffffffffffff80881683528087166020840152506fffffffffffffffffffffffffffffffff8516604083015283606083015260a06080830152612caf60a0830184613f39565b600060208284031215613fec57600080fd5b8151801515811461325e57600080fd5b6fffffffffffffffffffffffffffffffff81811683821602808216919082811461402857614028613d6e565b505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006fffffffffffffffffffffffffffffffff8084168061408257614082614030565b92169190910492915050565b60008261409d5761409d614030565b500490565b600082516140b4818460208701613ea6565b9190910192915050565b60208152600061325e6020830184613f3956fea164736f6c6343000812000a000000000000000000000000e020b01b6fbd83066aa2e8ee0ccd1eb8d9cc70bf0000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000002c845ac4bac48a6cd1e1c88a84195b7d5805b8200000000000000000000000002c845ac4bac48a6cd1e1c88a84195b7d5805b82

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101c45760003560e01c80638ed013a6116100f9578063d33219b411610097578063f23a6e6111610071578063f23a6e611461042c578063fa2435fd14610495578063fc0c546a146104a8578063fe6ca782146104cf57600080fd5b8063d33219b4146103fe578063e7d2028314610406578063e91f32351461041957600080fd5b8063bdacb303116100d3578063bdacb3031461039e578063c2c94b88146103b1578063c428ee9d146103d8578063d0ebdbe7146103eb57600080fd5b80638ed013a61461037b578063a69df4b514610383578063b8a807c11461038b57600080fd5b80635c19a95c116101665780636f011538116101405780636f011538146102b757806372731062146102ca57806381360e7a1461035f5780638c55ea581461036857600080fd5b80635c19a95c146102885780635d6a618d1461029b5780635f041903146102a457600080fd5b8063481c6a75116101a2578063481c6a751461022a578063561c9f4e1461025757806357b9ca361461026a578063583b18961461027257600080fd5b806302387a7b146101c95780632dec063f146101de57806340d5b0d914610217575b600080fd5b6101dc6101d736600461397e565b6104d8565b005b6101f16101ec3660046139bd565b61090a565b6040516fffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b6101dc6102253660046139f0565b610a1a565b610232610db5565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161020e565b6101dc61026536600461397e565b610ddb565b610232610fd6565b61027a611016565b60405190815260200161020e565b6101dc610296366004613a44565b61105c565b6101f16105dc81565b6101dc6102b2366004613a5f565b61144e565b6101dc6102c5366004613a44565b6116e8565b6102dd6102d8366004613a44565b611821565b60405161020e9190600060c0820190506fffffffffffffffffffffffffffffffff80845116835280602085015116602084015280604085015116604084015280606085015116606084015250608083015173ffffffffffffffffffffffffffffffffffffffff80821660808501528060a08601511660a0850152505092915050565b61027a61010081565b6101dc610376366004613a89565b6118fa565b6101dc611c66565b6101dc611cf3565b6101dc610399366004613acc565b611e12565b6101dc6103ac366004613a44565b611f11565b61027a7f0000000000000000000000000000000000000000000000000000000000030d4081565b6101f16103e63660046139bd565b61203b565b6101dc6103f9366004613a44565b6120a5565b6102326121cf565b61027a610414366004613b15565b6121d9565b61027a610427366004613b3f565b6121f9565b61046461043a366004613bf5565b7ff23a6e610000000000000000000000000000000000000000000000000000000095945050505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200161020e565b6101dc6104a3366004613cf9565b612248565b6102327f000000000000000000000000e020b01b6fbd83066aa2e8ee0ccd1eb8d9cc70bf81565b6101f16103e881565b60006104e2612438565b8054909150600114610555576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b60028155610561611016565b60010361059a576040517f94ceaa1f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b816fffffffffffffffffffffffffffffffff166000036105e6576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006105f061247d565b33600090815260209190915260408120915061060a6124bd565b9050836fffffffffffffffffffffffffffffffff168160000154101561065c576040517f59cb57c000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805160c08101825283546fffffffffffffffffffffffffffffffff8082168352700100000000000000000000000000000000918290048116602084015260018601548082169484019490945292049091166060820152600283015473ffffffffffffffffffffffffffffffffffffffff908116608083015260038401541660a08201526000906106ed906124fd565b9050846fffffffffffffffffffffffffffffffff1681101561073e576040517f5d9272870000000000000000000000000000000000000000000000000000000081526004810182905260240161054c565b846fffffffffffffffffffffffffffffffff168260000160008282546107649190613d9d565b90915550506001830180548691906000906107929084906fffffffffffffffffffffffffffffffff16613db0565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506107d23384612563565b825460018401546fffffffffffffffffffffffffffffffff9182169116036108ac57600283015473ffffffffffffffffffffffffffffffffffffffff16158015906108465750600183015470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1615155b15610853576108536127de565b600083556001830180547fffffffffffffffffffffffffffffffff000000000000000000000000000000001690556003830180547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555b6108ff73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000e020b01b6fbd83066aa2e8ee0ccd1eb8d9cc70bf16336fffffffffffffffffffffffffffffffff88166129b0565b505060018255505050565b600073ffffffffffffffffffffffffffffffffffffffff831615801561094057506fffffffffffffffffffffffffffffffff8216155b1561094e57506103e8610a14565b6000610958612a3d565b73ffffffffffffffffffffffffffffffffffffffff85166000908152602091825260408082206fffffffffffffffffffffffffffffffff8088168452935281208054909350700100000000000000000000000000000000900490911690036109c4576000915050610a14565b8054427001000000000000000000000000000000009091046fffffffffffffffffffffffffffffffff16116109fe576103e8915050610a14565b546fffffffffffffffffffffffffffffffff1690505b92915050565b610a22610db5565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a86576040517fe927ce0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6105dc6fffffffffffffffffffffffffffffffff83161115610b06576040517fd0f03f9d00000000000000000000000000000000000000000000000000000000815260040161054c9060208082526004908201527f6869676800000000000000000000000000000000000000000000000000000000604082015260600190565b6103e8826fffffffffffffffffffffffffffffffff161015610b84576040517fd0f03f9d00000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f6c6f770000000000000000000000000000000000000000000000000000000000604482015260640161054c565b42816fffffffffffffffffffffffffffffffff1611610bcf576040517f1a37ebf400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84161580610c0257506fffffffffffffffffffffffffffffffff8316155b15610c69576040517f3205f1c600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851660048201526fffffffffffffffffffffffffffffffff8416602482015260440161054c565b6000610c73612a3d565b73ffffffffffffffffffffffffffffffffffffffff86166000908152602091825260408082206fffffffffffffffffffffffffffffffff80891684529352902080549092501615610d245780546040517ffda8d4740000000000000000000000000000000000000000000000000000000081526fffffffffffffffffffffffffffffffff8083166004830152700100000000000000000000000000000000909204909116602482015260440161054c565b6fffffffffffffffffffffffffffffffff828116700100000000000000000000000000000000810285831690811784556040805173ffffffffffffffffffffffffffffffffffffffff8a168152938816602085015283015260608201527fb6c4db7a42dfab7553d15168f88363d3af3f85739e3ec0d1f30c96028fe32f8f9060800160405180910390a15050505050565b6000610dbf612a7d565b5473ffffffffffffffffffffffffffffffffffffffff16919050565b6000610de5612438565b8054909150600114610e53576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155816fffffffffffffffffffffffffffffffff16600003610ea3576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610ead61247d565b33600090815260209190915260409020600381015490915073ffffffffffffffffffffffffffffffffffffffff16610f11576040517ffa267cd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610f1b6124bd565b9050836fffffffffffffffffffffffffffffffff16816000016000828254610f439190613de0565b9091555050815484908390600090610f6e9084906fffffffffffffffffffffffffffffffff16613db0565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550610fae3383612563565b610fcd33856fffffffffffffffffffffffffffffffff16600080612abd565b50506001905550565b6000610dbf6040518060400160405280600781526020017f61697264726f70000000000000000000000000000000000000000000000000008152506123bb565b60006110566040518060400160405280600681526020017f6c6f636b6564000000000000000000000000000000000000000000000000000081525061233e565b54919050565b73ffffffffffffffffffffffffffffffffffffffff81166110d9576040517fb14df79c00000000000000000000000000000000000000000000000000000000815260206004820152600260248201527f746f000000000000000000000000000000000000000000000000000000000000604482015260640161054c565b60006110e361247d565b33600090815260209190915260409020600381015490915073ffffffffffffffffffffffffffffffffffffffff16611147576040517ffa267cd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600381015473ffffffffffffffffffffffffffffffffffffffff9081169083160361119e576040517fa7b752f200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006111a8612bc3565b60038301549091506000906111d490839073ffffffffffffffffffffffffffffffffffffffff16612c16565b600384015484549192506112369173ffffffffffffffffffffffffffffffffffffffff9091169061122b9070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1684613d9d565b849190610100612cba565b6003830154835473ffffffffffffffffffffffffffffffffffffffff9091169033907f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e0906112ca9070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613df3565b60405190815260200160405180910390a360006112e78386612c16565b6040805160c08101825286546fffffffffffffffffffffffffffffffff8082168352700100000000000000000000000000000000918290048116602084015260018901548082169484019490945292049091166060820152600286015473ffffffffffffffffffffffffffffffffffffffff908116608083015260038701541660a082015290915060009061137b90612e23565b90506113968661138b8385613de0565b869190610100612cba565b84546fffffffffffffffffffffffffffffffff80831670010000000000000000000000000000000002911617855560038501805473ffffffffffffffffffffffffffffffffffffffff88167fffffffffffffffffffffffff0000000000000000000000000000000000000000909116811790915560405133907f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e09061143e9085815260200190565b60405180910390a3505050505050565b6000611458612438565b80549091506001146114c6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b6002815573ffffffffffffffffffffffffffffffffffffffff821615806114fd57506fffffffffffffffffffffffffffffffff8316155b15611564576040517f3205f1c600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff831660048201526fffffffffffffffffffffffffffffffff8416602482015260440161054c565b61156e828461090a565b6fffffffffffffffffffffffffffffffff166000036115b9576040517fc985ca0800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006115c361247d565b33600090815260209190915260409020600381015490915073ffffffffffffffffffffffffffffffffffffffff16611627576040517ffa267cd100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600281015473ffffffffffffffffffffffffffffffffffffffff161561164f5761164f6127de565b60028101805473ffffffffffffffffffffffffffffffffffffffff85167fffffffffffffffffffffffff00000000000000000000000000000000000000009091161790556001810180546fffffffffffffffffffffffffffffffff8087167001000000000000000000000000000000000291161790556116d0338486612ec2565b6116da3382612563565b50600190555050565b505050565b6116f0610db5565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611754576040517fe927ce0900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6117d56117956040518060400160405280600781526020017f61697264726f70000000000000000000000000000000000000000000000000008152506123bb565b80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8416179055565b60405173ffffffffffffffffffffffffffffffffffffffff821681527fabe40da5dd07060349f485f71bd70d796ba49859153e786fe58a1d11f86c4ac99060200160405180910390a150565b6040805160c081018252600080825260208201819052918101829052606081018290526080810182905260a081019190915261185b61247d565b73ffffffffffffffffffffffffffffffffffffffff92831660009081526020918252604090819020815160c08101835281546fffffffffffffffffffffffffffffffff808216835270010000000000000000000000000000000091829004811695830195909552600183015480861694830194909452909204909216606082015260028201548416608082015260039091015490921660a08301525090565b6119386040518060400160405280600781526020017f61697264726f70000000000000000000000000000000000000000000000000008152506123bb565b5473ffffffffffffffffffffffffffffffffffffffff163314611987576040517fab04925b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611991612438565b80549091506001146119ff576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155826fffffffffffffffffffffffffffffffff16600003611a4f576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416611ace576040517fb14df79c00000000000000000000000000000000000000000000000000000000815260040161054c9060208082526004908201527f7573657200000000000000000000000000000000000000000000000000000000604082015260600190565b6000611ad861247d565b73ffffffffffffffffffffffffffffffffffffffff80871660009081526020929092526040909120600381015490925016611b2057611b1b858560008087612f60565b611c3b565b600381015473ffffffffffffffffffffffffffffffffffffffff848116911614611b9c5760038101546040517fa044d4d100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8086166004830152909116602482015260440161054c565b6000611ba66124bd565b9050846fffffffffffffffffffffffffffffffff16816000016000828254611bce9190613de0565b9091555050815485908390600090611bf99084906fffffffffffffffffffffffffffffffff16613db0565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff160217905550611c398683612563565b505b611c5a33856fffffffffffffffffffffffffffffffff16600080612abd565b50600181555b50505050565b6000611c70612438565b8054909150600114611cde576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155611cea6127de565b60019055565b50565b611cfb6121cf565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611d5f576040517f88fe7b4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611d67611016565b600114611da0576040517fedc4227400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611de7611de16040518060400160405280600681526020017f6c6f636b6564000000000000000000000000000000000000000000000000000081525061233e565b60029055565b6040517f15fa93f6df3cbaf0b9ddb555fa798ba412e2bbc86e60aa62d8aade4c3052cd6290600090a1565b6000611e1c612438565b8054909150600114611e8a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e435900000000000000000000000000000000000000000000604482015260640161054c565b60028155846fffffffffffffffffffffffffffffffff16600003611eda576040517fe11543e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ee73386868686612f60565b611f0533866fffffffffffffffffffffffffffffffff168587612abd565b600181555b5050505050565b611f196121cf565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611f7d576040517f88fe7b4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611ffa576040517f04d9a6d200000000000000000000000000000000000000000000000000000000815260206004820152600860248201527f74696d656c6f636b000000000000000000000000000000000000000000000000604482015260640161054c565b611cf06117956040518060400160405280600881526020017f74696d656c6f636b0000000000000000000000000000000000000000000000008152506123bb565b600080612046612a3d565b73ffffffffffffffffffffffffffffffffffffffff85166000908152602091825260408082206fffffffffffffffffffffffffffffffff8088168452935290205470010000000000000000000000000000000090041691505092915050565b6120ad6121cf565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612111576040517f88fe7b4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811661218e576040517f04d9a6d200000000000000000000000000000000000000000000000000000000815260206004820152600760248201527f6d616e6167657200000000000000000000000000000000000000000000000000604482015260640161054c565b611cf06117956040518060400160405280600781526020017f6d616e61676572000000000000000000000000000000000000000000000000008152506123bb565b6000610dbf6131b4565b6000806121e4612bc3565b90506121f18185856131f4565b949350505050565b600080612204612bc3565b905061223e86866122357f0000000000000000000000000000000000000000000000000000000000030d4043613d9d565b84929190613265565b9695505050505050565b6032811115612283576040517f48a3bbbc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156116e357600061229861247d565b60008585858181106122ac576122ac613e3f565b90506020020160208101906122c19190613a44565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905061232d84848481811061231257612312613e3f565b90506020020160208101906123279190613a44565b82612563565b5061233781613e6e565b9050612286565b6000807fec13d6d12b88433319b64e1065a96ea19cd330ef6603f5f6fb685dde3959a320905060008184604051602001612379929190613eca565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190528051602090910120949350505050565b9055565b6000807f421683f821a0574472445355be6d2b769119e8515f8376a1d7878523dfdecf7b905060008184604051602001612379929190613eca565b81547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff91909116179055565b60006124786040518060400160405280600781526020017f656e74657265640000000000000000000000000000000000000000000000000081525061233e565b905090565b60006124786040518060400160405280600d81526020017f726567697374726174696f6e73000000000000000000000000000000000000008152506132f7565b60006124786040518060400160405280600781526020017f62616c616e63650000000000000000000000000000000000000000000000000081525061233e565b600081600001516fffffffffffffffffffffffffffffffff1682604001516fffffffffffffffffffffffffffffffff160361253a57506000919050565b6040820151825161254b9190613ef0565b6fffffffffffffffffffffffffffffffff1692915050565b600061256d612bc3565b600383015490915060009061259990839073ffffffffffffffffffffffffffffffffffffffff16612c16565b6040805160c08101825285546fffffffffffffffffffffffffffffffff8082168352700100000000000000000000000000000000918290048116602084015260018801548082169484019490945292049091166060820152600285015473ffffffffffffffffffffffffffffffffffffffff908116608083015260038601541660a082015290915060009061262d90612e23565b84549091506000906126659070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1683613f19565b90508060000361267757505050505050565b60008113156126ae5760038501546126a99073ffffffffffffffffffffffffffffffffffffffff1661138b8386613de0565b61275d565b6126d8817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613df3565b8311156127325760038501546126a99073ffffffffffffffffffffffffffffffffffffffff16612728837fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613df3565b61138b9086613d9d565b600385015461275d90859073ffffffffffffffffffffffffffffffffffffffff166000610100612cba565b84546fffffffffffffffffffffffffffffffff808416700100000000000000000000000000000000029116178555600385015460405173ffffffffffffffffffffffffffffffffffffffff918216918816907f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e09061143e9085815260200190565b60006127e861247d565b33600090815260209190915260409020600281015490915073ffffffffffffffffffffffffffffffffffffffff1661289a57600281015460018201546040517f3205f1c600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff909216600483015270010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff16602482015260440161054c565b6002810154600180830154604080516020810182526000815290517ff242432a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9094169363f242432a9361293093309333937001000000000000000000000000000000009092046fffffffffffffffffffffffffffffffff169291600401613f83565b600060405180830381600087803b15801561294a57600080fd5b505af115801561295e573d6000803e3d6000fd5b5050506002820180547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055506001810180546fffffffffffffffffffffffffffffffff169055611cf03382612563565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790526116e390849061336a565b60006124786040518060400160405280600b81526020017f6d756c7469706c69657273000000000000000000000000000000000000000000815250613476565b60006124786040518060400160405280600781526020017f6d616e61676572000000000000000000000000000000000000000000000000008152506123bb565b6040517f23b872dd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8581166004830152306024830152604482018590527f000000000000000000000000e020b01b6fbd83066aa2e8ee0ccd1eb8d9cc70bf16906323b872dd906064016020604051808303816000875af1158015612b58573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b7c9190613fda565b5073ffffffffffffffffffffffffffffffffffffffff821615801590612bb357506fffffffffffffffffffffffffffffffff811615155b15611c6057611c60848383612ec2565b6040805180820190915260608152600060208201526124786040518060400160405280600b81526020017f766f74696e67506f7765720000000000000000000000000000000000000000008152506134ac565b600080612c24846020015190565b73ffffffffffffffffffffffffffffffffffffffff841660009081526020919091526040812080549092506fffffffffffffffffffffffffffffffff1690819003612c7457600092505050610a14565b6000612caf83612c85600185613d9d565b016001015460c081901c9177ffffffffffffffffffffffffffffffffffffffffffffffff90911690565b979650505050505050565b77ffffffffffffffffffffffffffffffffffffffffffffffff821115612d3c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600360248201527f4f6f420000000000000000000000000000000000000000000000000000000000604482015260640161054c565b6000612d49856020015190565b73ffffffffffffffffffffffffffffffffffffffff851660009081526020829052604081208054929350914360c01b9186831791608081901c916fffffffffffffffffffffffffffffffff909116908115612db157612dad86612c85600185613d9d565b5090505b81438203612dcb57612dc4600184613d9d565b9050612df1565b88612dd68585613d9d565b10612df157612df184612de881613e6e565b955085896134e5565b8481600189010155438214612e1557612e158785612e10866001613de0565b61350e565b505050505050505050505050565b60008082604001518360000151612e3a9190613ef0565b608084015190915073ffffffffffffffffffffffffffffffffffffffff1615801590612e7b575060608301516fffffffffffffffffffffffffffffffff1615155b1561254b576103e8612e958460800151856060015161090a565b612e9f9083613ffc565b612ea9919061405f565b6fffffffffffffffffffffffffffffffff169392505050565b604080516020810182526000815290517ff242432a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84169163f242432a91612f299187913091879160019190600401613f83565b600060405180830381600087803b158015612f4357600080fd5b505af1158015612f57573d6000803e3d6000fd5b50505050505050565b6000612f6c838561090a565b9050806fffffffffffffffffffffffffffffffff16600003612fba576040517fc985ca0800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000612fc46124bd565b90506000612fd061247d565b73ffffffffffffffffffffffffffffffffffffffff808a166000908152602092909252604090912060038101549092501615613038576040517f9e2b1ab100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84161561305a578361305c565b875b935060006103e861306d858a613ffc565b613077919061405f565b6fffffffffffffffffffffffffffffffff808216700100000000000000000000000000000000908102828c169081178655918a1602600185015560028401805473ffffffffffffffffffffffffffffffffffffffff808b167fffffffffffffffffffffffff000000000000000000000000000000000000000092831617909255600386018054928a16929091169190911790558454919250908490600090613120908490613de0565b9091555061313090508582613537565b8473ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff167f33161cf2da28d747be9df136b6f3729390298494947268743193c53d73d3c2e0836fffffffffffffffffffffffffffffffff166040516131a191815260200190565b60405180910390a3505050505050505050565b60006124786040518060400160405280600881526020017f74696d656c6f636b0000000000000000000000000000000000000000000000008152506123bb565b600080613202856020015190565b73ffffffffffffffffffffffffffffffffffffffff85166000908152602082905260408120805492935091608081901c916fffffffffffffffffffffffffffffffff909116906132558488838686613571565b96505050505050505b9392505050565b600080613273866020015190565b73ffffffffffffffffffffffffffffffffffffffff86166000908152602082905260408120805492935091608081901c916fffffffffffffffffffffffffffffffff90911690806132c7858a8a8787613571565b91509150838211156132e9576132de8483876134e5565b6132e985838561350e565b9a9950505050505050505050565b6000807ff162ab2a93c7e839fe65139803001cff34be31efd14d829854af05b9911b6e848360405160200161332d929190613eca565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101209392505050565b60006133cc826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff1661374c9092919063ffffffff16565b8051909150156116e357808060200190518101906133ea9190613fda565b6116e3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f74207375636365656400000000000000000000000000000000000000000000606482015260840161054c565b6000807fafcb3a8867bd263fd503fc00b0ffd9a453f69f3268e9d48926d67e3802a891d98360405160200161332d929190613eca565b60408051808201909152606081526000602082015260006134cc8361375b565b6040805180820190915293845260208401525090919050565b818311156134f257600080fd5b60018101835b83811015611f0a576000828201556001016134f8565b80821061351a57600080fd5b6fffffffffffffffffffffffffffffffff1660809190911b179055565b6000613541612bc3565b9050600061354f8285612c16565b9050611c608461122b6fffffffffffffffffffffffffffffffff861684613de0565b600080826000036135de576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f756e696e697469616c697a656400000000000000000000000000000000000000604482015260640161054c565b858511156135eb57600080fd5b8284106135f757600080fd5b6000613604600185613d9d565b90508460005b8282146136a7576000600261361f8585613de0565b61362a906001613de0565b613634919061408e565b6001818d01015490915060c081901c9077ffffffffffffffffffffffffffffffffffffffffffffffff168b8203613675579296509194506137429350505050565b8b821015613691578a821015613689578293505b82945061369f565b61369c600184613d9d565b95505b50505061360a565b60018a8301015460c081901c9077ffffffffffffffffffffffffffffffffffffffffffffffff168a821115613738576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f536561726368204661696c757265000000000000000000000000000000000000604482015260640161054c565b9195509093505050505b9550959350505050565b60606121f18484600085613796565b6000807f7b1a68ec3e3284b167e69db1c622dcfa612281976b71d7e2d239dbe16a75891a905060008184604051602001612379929190613eca565b606082471015613828576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c0000000000000000000000000000000000000000000000000000606482015260840161054c565b843b613890576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015260640161054c565b6000808673ffffffffffffffffffffffffffffffffffffffff1685876040516138b991906140a2565b60006040518083038185875af1925050503d80600081146138f6576040519150601f19603f3d011682016040523d82523d6000602084013e6138fb565b606091505b5091509150612caf8282866060831561391557508161325e565b8251156139255782518084602001fd5b816040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161054c91906140be565b80356fffffffffffffffffffffffffffffffff8116811461397957600080fd5b919050565b60006020828403121561399057600080fd5b61325e82613959565b803573ffffffffffffffffffffffffffffffffffffffff8116811461397957600080fd5b600080604083850312156139d057600080fd5b6139d983613999565b91506139e760208401613959565b90509250929050565b60008060008060808587031215613a0657600080fd5b613a0f85613999565b9350613a1d60208601613959565b9250613a2b60408601613959565b9150613a3960608601613959565b905092959194509250565b600060208284031215613a5657600080fd5b61325e82613999565b60008060408385031215613a7257600080fd5b613a7b83613959565b91506139e760208401613999565b600080600060608486031215613a9e57600080fd5b613aa784613999565b9250613ab560208501613959565b9150613ac360408501613999565b90509250925092565b60008060008060808587031215613ae257600080fd5b613aeb85613959565b9350613af960208601613959565b9250613b0760408601613999565b9150613a3960608601613999565b60008060408385031215613b2857600080fd5b613b3183613999565b946020939093013593505050565b60008060008060608587031215613b5557600080fd5b613b5e85613999565b935060208501359250604085013567ffffffffffffffff80821115613b8257600080fd5b818701915087601f830112613b9657600080fd5b813581811115613ba557600080fd5b886020828501011115613bb757600080fd5b95989497505060200194505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080600080600060a08688031215613c0d57600080fd5b613c1686613999565b9450613c2460208701613999565b93506040860135925060608601359150608086013567ffffffffffffffff80821115613c4f57600080fd5b818801915088601f830112613c6357600080fd5b813581811115613c7557613c75613bc6565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715613cbb57613cbb613bc6565b816040528281528b6020848701011115613cd457600080fd5b8260208601602083013760006020848301015280955050505050509295509295909350565b60008060208385031215613d0c57600080fd5b823567ffffffffffffffff80821115613d2457600080fd5b818501915085601f830112613d3857600080fd5b813581811115613d4757600080fd5b8660208260051b8501011115613d5c57600080fd5b60209290920196919550909350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610a1457610a14613d6e565b6fffffffffffffffffffffffffffffffff818116838216019080821115613dd957613dd9613d6e565b5092915050565b80820180821115610a1457610a14613d6e565b808202600082127f800000000000000000000000000000000000000000000000000000000000000084141615613e2b57613e2b613d6e565b8181058314821517610a1457610a14613d6e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613e9f57613e9f613d6e565b5060010190565b60005b83811015613ec1578181015183820152602001613ea9565b50506000910152565b82815260008251613ee2816020850160208701613ea6565b919091016020019392505050565b6fffffffffffffffffffffffffffffffff828116828216039080821115613dd957613dd9613d6e565b8181036000831280158383131683831282161715613dd957613dd9613d6e565b60008151808452613f51816020860160208601613ea6565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600073ffffffffffffffffffffffffffffffffffffffff80881683528087166020840152506fffffffffffffffffffffffffffffffff8516604083015283606083015260a06080830152612caf60a0830184613f39565b600060208284031215613fec57600080fd5b8151801515811461325e57600080fd5b6fffffffffffffffffffffffffffffffff81811683821602808216919082811461402857614028613d6e565b505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60006fffffffffffffffffffffffffffffffff8084168061408257614082614030565b92169190910492915050565b60008261409d5761409d614030565b500490565b600082516140b4818460208701613ea6565b9190910192915050565b60208152600061325e6020830184613f3956fea164736f6c6343000812000a

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

000000000000000000000000e020b01b6fbd83066aa2e8ee0ccd1eb8d9cc70bf0000000000000000000000000000000000000000000000000000000000030d4000000000000000000000000002c845ac4bac48a6cd1e1c88a84195b7d5805b8200000000000000000000000002c845ac4bac48a6cd1e1c88a84195b7d5805b82

-----Decoded View---------------
Arg [0] : token (address): 0xe020B01B6fbD83066aa2e8ee0CCD1eB8d9Cc70bF
Arg [1] : staleBlockLag (uint256): 200000
Arg [2] : timelock (address): 0x02C845ac4baC48A6CD1e1c88a84195B7d5805B82
Arg [3] : manager (address): 0x02C845ac4baC48A6CD1e1c88a84195B7d5805B82

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 000000000000000000000000e020b01b6fbd83066aa2e8ee0ccd1eb8d9cc70bf
Arg [1] : 0000000000000000000000000000000000000000000000000000000000030d40
Arg [2] : 00000000000000000000000002c845ac4bac48a6cd1e1c88a84195b7d5805b82
Arg [3] : 00000000000000000000000002c845ac4bac48a6cd1e1c88a84195b7d5805b82


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

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

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