S Price: $0.466927 (+1.58%)

Contract

0x605257994ffeF290c9eD8F51e0bF68b2735c17A9

Overview

S Balance

Sonic LogoSonic LogoSonic Logo0 S

S Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

1 Internal Transaction and > 10 Token Transfers found.

Latest 1 internal transaction

Parent Transaction Hash Block From To
157528982025-03-24 23:15:4625 days ago1742858146  Contract Creation0 S
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
ThenaAdapter

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 1000 runs

Other Settings:
cancun EvmVersion
File 1 of 21 : ThenaAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol";
import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { IRewardsDistributor } from "./interfaces/IRewardsDistributor.sol";
import { IVotingEscrow } from "./interfaces/IVotingEscrow.sol";
import { IVoter } from "./interfaces/IVoter.sol";
import { IMinter } from "./interfaces/IMinter.sol";
import { IAdapter, Adapter } from "src/libraries/Adapter.sol";
import { IRewardDistributor } from "src/interfaces/IRewardDistributor.sol";
import { IPuppeteer } from "src/interfaces/IPuppeteer.sol";
import { BaseAdapter, Marionette } from "src/adapters/BaseAdapter.sol";
import { IERC721Receiver } from "openzeppelin/token/ERC721/IERC721Receiver.sol";
import { IQuestRewardsDistributor } from "./interfaces/IQuestRewardsDistributor.sol";
import { ISwapper } from "./interfaces/ISwapper.sol";

/**
 * @title ThenaAdapter
 * @dev Marionette adapter for the Thena  protocol. It allows Puppeteer to manage veNFT tokens.
 * vote, claim and swap rewards, compound voting power, and manage lock duration.
 */
contract ThenaAdapter is BaseAdapter, IERC721Receiver {
    using SafeERC20 for IERC20;

    /**
     * @param active                     bool                  Whether or not the veNFT is active
     * @param initialEpoch               uint256               The initial epoch of the veNFT
     * @param lastTimestampExtended      uint256               The last timestamp the lock was extended
     * @param outstandingRewardsReceiver address               The address of the outstanding rewards receiver
     * @param rewardMode                 Marionette.RewardMode The reward mode of the veNFT
     * @param lockMode                   Marionette.LockMode   The lock mode of the veNFT
     */
    struct VeTokenInfo {
        bool active;
        uint256 initialEpoch;
        uint256 lastTimestampExtended;
        address outstandingRewardsReceiver;
        Marionette.RewardMode rewardMode;
        Marionette.LockMode lockMode;
        mapping(address => uint256) tokenLastEpochClaimed;
        mapping(Marionette.RewardMode => mapping(uint256 => uint256)) rewardModeEpochVotePower;
    }

    error InvalidInput();
    error InvalidLockEnd();
    error InvalidBalanceOfToken();
    error NotSubscribed();
    error NotApprovedOrOwner();
    error ZeroAmountReceived();
    error NotDistributorOrPuppeteer();

    /*//////////////////////////////////////////////////////////////
                          EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(uint256 veNftId, Marionette.RewardMode rewardMode, Marionette.LockMode lockMode);
    event Withdraw(uint256 veNftId);
    event Subscribe(uint256 veNftId, Marionette.RewardMode rewardMode, Marionette.LockMode lockMode);
    event Unsubscribe(uint256 veNftId);
    event Vote(uint256 veNftId, uint256 balanceOfNFT);
    event SetSwapper(address swapper);
    event SetGaugesAndWeights(address[] gauges, uint256[] weights);
    event IncreaseAmount(uint256 veNftId, uint256 amount);
    event IncreaseUnlockTime(uint256 veNftId, uint256 lockDuration);
    event InitiateWithdraw(uint256 veNftId, address receiver);
    event EmergencyWithdraw(address[] tokens, uint256[] amounts, address receiver);

    /*//////////////////////////////////////////////////////////////
                        ADAPTER STORAGE
    //////////////////////////////////////////////////////////////*/

    IVotingEscrow public constant VE = IVotingEscrow(0x0966CAE7338518961c2d35493D3EB481A75bb86B);
    IVoter public constant VOTER = IVoter(0xB84194E28f624BBBA3C9181F3a1120eE76469337);
    address public immutable THE;
    // Epoch duration
    uint256 internal constant EPOCH = 1 weeks;
    uint256 internal constant MAX_TIME = 52 * 7 * 86_400;

    mapping(uint256 => VeTokenInfo) public veTokenInfo;
    uint256 public minBalanceOfToken;
    address[] public gauges;
    uint256[] public weights;
    address public swapper;

    /*//////////////////////////////////////////////////////////////
                               MODIFIERS
    //////////////////////////////////////////////////////////////*/

    modifier onlySwapperOrPuppeteer() {
        if (msg.sender != swapper && msg.sender != address(PUPPETEER)) revert NotDistributorOrPuppeteer();
        _;
    }

    /*//////////////////////////////////////////////////////////////
                      CONSTRUCTOR AND INITIALIZATION
    //////////////////////////////////////////////////////////////*/

    constructor(IPuppeteer _puppeteer, address initialTHE) BaseAdapter(_puppeteer) {
        THE = initialTHE;
    }

    function getAdapterCalls() public pure override returns (Adapter.Calls memory) {
        return Adapter.Calls({
            beforeTransfer: false,
            beforeSetDefaultConfig: true,
            beforeSetCustomConfig: true,
            beforeSwap: false,
            beforeClaim: false,
            beforePause: false
        });
    }

    /*//////////////////////////////////////////////////////////////
                      ADAPTER FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IAdapter
    /// @dev set default gauges and weights, default reward token, swapper, and minimum balance of the veNFT
    function beforeInitialize(
        address,
        Marionette.VoteConfig memory _defaultVoteConfig,
        Marionette.RewardConfig memory _defaultRewardConfig,
        Marionette.LockConfig memory,
        bytes calldata _adapterData
    ) external override onlyPuppeteer returns (bytes4) {
        // set default vote config
        _setGaugesAndWeights(_defaultVoteConfig.gauges, _defaultVoteConfig.weights);
        // set swapper address
        if (_defaultRewardConfig.data.length > 0) {
            (address _swapper) = abi.decode(_defaultRewardConfig.data, (address));
            _setSwapper(_swapper);
        }
        // set minBalanceOfToken
        (uint256 _minBalanceOfToken) = abi.decode(_adapterData, (uint256));
        minBalanceOfToken = _minBalanceOfToken;
        // approve VE to THE
        IERC20(THE).approve(address(VE), type(uint256).max);

        return IAdapter.beforeInitialize.selector;
    }

    /// @inheritdoc IAdapter
    function beforeTransfer(address _from, address _to, uint256 _tokenId, bytes calldata _adapterData)
        external
        override
        onlyPuppeteer
        returns (bytes4)
    {
        // On mint check reward config to deposit or subscribe
        if (_from == address(0)) {
            (Marionette.RewardConfig memory _rewardConfig, Marionette.LockConfig memory _lockConfig) =
                abi.decode(_adapterData, (Marionette.RewardConfig, Marionette.LockConfig));
            if (_rewardConfig.rewardMode == Marionette.RewardMode.VoteOnly) {
                // If the reward mode is VoteOnly, subscribe veNFT
                _subscribe(_tokenId, _rewardConfig.rewardMode, _lockConfig.lockMode);
            } else {
                // Otherwise, deposit veNFT
                _deposit(_to, _tokenId, _rewardConfig.rewardMode, _lockConfig.lockMode);
            }
        } else if (_to == address(0)) {
            // On burn, unsubscribe or withdraw veNFT
            if (veTokenInfo[_tokenId].rewardMode == Marionette.RewardMode.VoteOnly) {
                _unsubscribe(_tokenId);
            } else if (_canBurn(_tokenId)) {
                // If the token can be burned, withdraw it
                _withdraw(_from, _tokenId);
            } else {
                // Otherwise, initiate a withdrawal
                _initiateWithdrawal(_tokenId, _from);
            }
        }

        return IAdapter.beforeTransfer.selector;
    }

    /// @inheritdoc IAdapter
    function beforeSetDefaultConfig(
        address,
        Marionette.ConfigType calldata _configType,
        bytes calldata _configData,
        bytes calldata _adapterData
    ) external override onlyPuppeteer returns (bytes4) {
        (Marionette.VoteConfig memory _voteConfig, Marionette.RewardConfig memory _rewardConfig,) =
            abi.decode(_configData, (Marionette.VoteConfig, Marionette.RewardConfig, Marionette.LockConfig));

        if (_configType.reward) {
            // Update swapper address
            if (_rewardConfig.data.length > 0) {
                (address _swapper) = abi.decode(_rewardConfig.data, (address));
                _setSwapper(_swapper);
            }
        }
        if (_configType.vote) {
            // Update gauges and weights arrays
            _setGaugesAndWeights(_voteConfig.gauges, _voteConfig.weights);
        }
        // Update minimum balance of token
        if (_adapterData.length > 0) {
            (uint256 _minBalanceOfToken) = abi.decode(_adapterData, (uint256));
            minBalanceOfToken = _minBalanceOfToken;
        }

        return IAdapter.beforeSetDefaultConfig.selector;
    }

    /// @inheritdoc IAdapter
    function beforeSetCustomConfig(
        address _sender,
        uint256 _tokenId,
        Marionette.ConfigType calldata _configType,
        bytes calldata _configData,
        bytes calldata
    ) external override onlyPuppeteer returns (bytes4) {
        VeTokenInfo storage veInfo = veTokenInfo[_tokenId];

        (Marionette.RewardConfig memory _rewardConfig, Marionette.LockConfig memory _lockConfig) =
            abi.decode(_configData, (Marionette.RewardConfig, Marionette.LockConfig));

        if (_rewardConfig.rewardMode == Marionette.RewardMode.Custom) {
            revert InvalidInput();
        }

        if (_configType.reward) {
            // if current reward mode is VoteOnly and new reward mode is not VoteOnly, unsubscribe and deposit
            if (
                veInfo.rewardMode == Marionette.RewardMode.VoteOnly
                    && _rewardConfig.rewardMode != Marionette.RewardMode.VoteOnly
            ) {
                _unsubscribe(_tokenId);
                _deposit(_sender, _tokenId, _rewardConfig.rewardMode, veInfo.lockMode);
            } else if (
                !(
                    veInfo.rewardMode == Marionette.RewardMode.Compound
                        && _rewardConfig.rewardMode == Marionette.RewardMode.Default
                )
                    && !(
                        veInfo.rewardMode == Marionette.RewardMode.Default
                            && _rewardConfig.rewardMode == Marionette.RewardMode.Compound
                    )
            ) {
                revert InvalidInput();
            }
        }
        if (_configType.lock) {
            if (veInfo.lockMode != Marionette.LockMode.Max && _lockConfig.lockMode == Marionette.LockMode.Max) {
                veInfo.lockMode = _lockConfig.lockMode;
                _increaseUnlockTime(_tokenId);
            } else if (
                veInfo.lockMode != Marionette.LockMode.Maintain && _lockConfig.lockMode == Marionette.LockMode.Maintain
            ) {
                veInfo.lastTimestampExtended = block.timestamp;
                veInfo.lockMode = _lockConfig.lockMode;
            } else {
                veInfo.lockMode = _lockConfig.lockMode;
            }
        }
        if (_configType.vote || (!_configType.lock && !_configType.reward)) {
            revert InvalidInput();
        }

        return IAdapter.beforeSetCustomConfig.selector;
    }

    ///@inheritdoc IAdapter
    function execute(address, bytes calldata adapterData) external override returns (bytes4) {
        // Decode the function signature from adapterData
        (bytes4 functionSignature) = abi.decode(adapterData, (bytes4));
        // Call the appropriate function based on the decoded function signature
        if (functionSignature == this.voteMany.selector) {
            (, uint256[] memory tokenIds) = abi.decode(adapterData, (bytes4, uint256[]));
            voteMany(tokenIds);
        } else if (functionSignature == this.claimQuests.selector) {
            (, address distributor, address[] memory tokens, IQuestRewardsDistributor.ClaimParams[] memory claimsData) =
                abi.decode(adapterData, (bytes4, address, address[], IQuestRewardsDistributor.ClaimParams[]));
            claimQuests(distributor, tokens, claimsData);
        } else if (functionSignature == this.increaseMany.selector) {
            (, uint256[] memory _tokenIds, uint256[] memory _amounts, address _depositor, bool pullToken) =
                abi.decode(adapterData, (bytes4, uint256[], uint256[], address, bool));
            increaseMany(_tokenIds, _amounts, _depositor, pullToken);
        } else if (functionSignature == this.extendMany.selector) {
            (, uint256[] memory _tokenIds) = abi.decode(adapterData, (bytes4, uint256[]));
            extendMany(_tokenIds);
        } else if (functionSignature == this.emergencyWithdraw.selector) {
            (, address[] memory tokens, uint256[] memory amounts, address receiver) =
                abi.decode(adapterData, (bytes4, address[], uint256[], address));
            emergencyWithdraw(tokens, amounts, receiver);
        } else {
            revert InvalidInput();
        }

        return IAdapter.execute.selector;
    }

    /**
     * @dev Vote for multiple tokens.
     * @param tokenIds An array of token IDs to vote for.
     */
    function voteMany(uint256[] memory tokenIds) public onlyPuppeteer {
        uint256 tokenIdsLength = tokenIds.length;
        for (uint256 i; i < tokenIdsLength; i++) {
            uint256 tokenId = tokenIds[i];
            if (veTokenInfo[tokenId].active) {
                // Check if the contract is approved or owner of the token
                if (!VE.isVotingApprovedOrOwner(address(this), tokenId)) {
                    // if not unsub
                    _unsubscribe(tokenId);
                } else {
                    _vote(tokenId);
                }
            }
        }
    }

    /**
     * @dev Claims bribes through rewards distributor and send them to the distributionswapper
     * @param distributor Address of the rewards distributor
     * @param tokens Array of tokens to claim
     * @param _claimsData Claim data for the quest merkle distributor
     */
    function claimQuests(
        address distributor,
        address[] memory tokens,
        IQuestRewardsDistributor.ClaimParams[] memory _claimsData
    ) public onlyPuppeteer {
        IQuestRewardsDistributor(distributor).multiClaim(address(this), _claimsData);

        uint256 length = tokens.length;
        for (uint256 i; i < length; ++i) {
            IERC20(tokens[i]).safeTransfer(swapper, _claimsData[i].amount);
        }
    }

    /**
     * @dev Deposits `_amount` additional tokens for `_tokenId` without modifying the unlock time
     * @param _tokenIds An array of token IDs to increase the balance of
     * @param _amounts An array of amounts to increase the balance of each corresponding token ID
     * @param _depositor The address of the depositor
     * @param pullToken Whether or not to pull the tokens from the depositor
     */
    function increaseMany(uint256[] memory _tokenIds, uint256[] memory _amounts, address _depositor, bool pullToken)
        public
        onlySwapperOrPuppeteer
    {
        uint256 tokenIdsLength = _tokenIds.length;

        if (pullToken) {
            uint256 totalAmount;
            // Get the total amount of THE to be increased
            for (uint256 i; i < tokenIdsLength; i++) {
                if (!veTokenInfo[_tokenIds[i]].active) revert NotSubscribed();
                totalAmount += _amounts[i];
            }

            // Transfer the total amount of THE to the contract
            IERC20(THE).safeTransferFrom(_depositor, address(this), totalAmount);
        }

        for (uint256 i; i < tokenIdsLength; i++) {
            _increaseAmount(_tokenIds[i], _amounts[i]);
        }
    }

    /**
     * @dev Extends the lock duration of multiple tokens to match the duration the user choose to maintain
     * Only tokens with a lock mode of `Maintain` will have their duration increased
     * @param _tokenIds An array of token IDs to extend the lock duration for
     */
    function extendMany(uint256[] memory _tokenIds) public onlyPuppeteer {
        uint256 tokenIdsLength = _tokenIds.length;

        // Loop through each token ID and extend the lock duration if the lock mode is Maintain
        for (uint256 i; i < tokenIdsLength; i++) {
            if (
                (
                    veTokenInfo[_tokenIds[i]].lockMode == Marionette.LockMode.Maintain
                        || veTokenInfo[_tokenIds[i]].lockMode == Marionette.LockMode.Max
                ) && veTokenInfo[_tokenIds[i]].active
            ) {
                _increaseUnlockTime(_tokenIds[i]);
            }
        }
    }

    /**
     * @dev Emergency withdraw of tokens
     */
    function emergencyWithdraw(address[] memory tokens, uint256[] memory amounts, address receiver)
        public
        onlyPuppeteer
    {
        uint256 tokensLength = tokens.length;
        for (uint256 i; i < tokensLength; i++) {
            IERC20(tokens[i]).safeTransfer(receiver, amounts[i]);
        }

        emit EmergencyWithdraw(tokens, amounts, receiver);
    }

    /*//////////////////////////////////////////////////////////////
                          VIEW FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Return whether or not a token can be burned
     * @param _tokenId The ID of the veNFT
     */
    function canBurn(uint256 _tokenId) public view returns (bool) {
        return _canBurn(_tokenId);
    }

    /*//////////////////////////////////////////////////////////////
                        ERC721 RECEIVER
    //////////////////////////////////////////////////////////////*/

    function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/
    /**
     * @dev Deposit `_amount` additional tokens for `_tokenId` without modifying the unlock time
     * @param _tokenId ID of the token to increase the amount of
     * @param _amount Amount of tokens to deposit and add to the lock
     */
    function _increaseAmount(uint256 _tokenId, uint256 _amount) internal {
        VE.increase_amount(_tokenId, _amount);

        emit IncreaseAmount(_tokenId, _amount);
    }

    /**
     * @dev Increases the unlock time of a token by a specified duration.
     * @param _tokenId The ID of the token to increase the unlock time for.
     */
    function _increaseUnlockTime(uint256 _tokenId) internal {
        VeTokenInfo storage veInfo = veTokenInfo[_tokenId];
        IVotingEscrow.LockedBalance memory lockedBalance = VE.locked(_tokenId);
        Marionette.LockMode lm = veInfo.lockMode;
        uint256 lastTimestampExtended = veInfo.lastTimestampExtended;
        uint256 lastExtendedEpoch = lastTimestampExtended / EPOCH;
        uint256 currentEpoch = block.timestamp / EPOCH;

        if (lastExtendedEpoch <= currentEpoch) {
            uint256 targetUnlockTime = (block.timestamp + MAX_TIME) / EPOCH * EPOCH;

            if (lm == Marionette.LockMode.Max && lockedBalance.end < targetUnlockTime) {
                uint256 lockDuration = targetUnlockTime - block.timestamp;

                VE.increase_unlock_time(_tokenId, lockDuration);

                emit IncreaseUnlockTime(_tokenId, lockDuration);
            } else if (lm == Marionette.LockMode.Maintain) {
                uint256 lockDuration = (lockedBalance.end - lastTimestampExtended) / EPOCH * EPOCH + EPOCH;
                if (lockDuration > MAX_TIME) {
                    if (lockedBalance.end < targetUnlockTime) {
                        lockDuration = targetUnlockTime - block.timestamp;
                    } else {
                        return;
                    }
                }

                veInfo.lastTimestampExtended = block.timestamp;

                VE.increase_unlock_time(_tokenId, lockDuration);

                emit IncreaseUnlockTime(_tokenId, lockDuration);
            }
        }
    }

    /**
     * @dev Vote using through VOTER.vote()
     * only votes is the user have not veted this epoch and the balance is greater than `minBalanceOfToken`
     * @param _tokenId Id of veNFT you are voting with.
     */
    function _vote(uint256 _tokenId) internal {
        uint256 balanceOfNFT = VE.balanceOfNFT(_tokenId);
        if (balanceOfNFT >= minBalanceOfToken) {
            VeTokenInfo storage veInfo = veTokenInfo[_tokenId];
            Marionette.RewardMode rm = veInfo.rewardMode;

            // if reward mode is not VoteOnly, update the total vote power for the active epoch
            // used to compute reward distribution
            if (rm != Marionette.RewardMode.VoteOnly) {
                uint256 activePeriod = _currentEpoch();
                veInfo.rewardModeEpochVotePower[rm][activePeriod] = balanceOfNFT;

                // if the veNFT has voted for the active epoch using another reward mode config
                // reset this vote power for the active epoch
                if (rm == Marionette.RewardMode.Compound) {
                    uint256 defaultVotePower =
                        veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Default][activePeriod];
                    if (defaultVotePower > 0) {
                        veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Default][activePeriod] = 0;
                    }
                }
                if (rm == Marionette.RewardMode.Default) {
                    uint256 compoundVotePower =
                        veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Compound][activePeriod];
                    if (compoundVotePower > 0) {
                        veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Compound][activePeriod] = 0;
                    }
                }
            }

            VOTER.vote(_tokenId, gauges, weights);

            emit Vote(_tokenId, balanceOfNFT);
        }
    }

    /**
     * @dev Deactivate veNFT and transfer it to the owner
     * @param _tokenId The ID of the veNFT
     * @param _owner The address of the veNFT owner
     */
    function _initiateWithdrawal(uint256 _tokenId, address _owner) internal {
        VeTokenInfo storage veInfo = veTokenInfo[_tokenId];
        veInfo.active = false;
        veInfo.outstandingRewardsReceiver = _owner;

        uint256 activePeriod = _currentEpoch();
        Marionette.RewardMode rm = veInfo.rewardMode;

        // clears vote power for the active epoch
        uint256 currVotePower = veInfo.rewardModeEpochVotePower[rm][activePeriod];
        if (currVotePower > 0) {
            veInfo.rewardModeEpochVotePower[rm][activePeriod] = 0;
        }

        // reset current vote to allow transfer
        VOTER.reset(_tokenId);
        // Transfer the veNFT back to owner
        VE.safeTransferFrom(address(this), _owner, _tokenId, "");

        emit InitiateWithdraw(_tokenId, _owner);
    }

    /**
     * @dev set swapper address
     */
    function _setSwapper(address _swapper) internal {
        swapper = _swapper;
        emit SetSwapper(_swapper);
    }

    /**
     * @dev require `_address` is approved to manage veNFT
     * @param _address address to check
     * @param _tokenId  veNFT id
     */
    function _isApprovedOrOwner(address _address, uint256 _tokenId) internal {
        if (!VE.isVotingApprovedOrOwner(_address, _tokenId)) revert NotApprovedOrOwner();
    }

    /**
     * @dev require ve position has enough lock duration left
     * @param _tokenId veNFT id
     */
    function _isAuthorizedBalanceAndDuration(uint256 _tokenId) internal view {
        IVotingEscrow.LockedBalance memory lockedBalance = VE.locked(_tokenId);
        if (lockedBalance.end <= (block.timestamp + EPOCH)) revert InvalidLockEnd();
        if (uint256(int256(lockedBalance.amount)) < minBalanceOfToken) revert InvalidBalanceOfToken();
    }

    /**
     * @dev get current epoch
     */
    function _currentEpoch() internal view returns (uint256) {
        return VOTER.currentPeriod();
    }

    /**
     * @dev Transfers veNFT this contract and record the user preferences
     * @param _owner The owner of the veNFT tokens
     * @param _tokenId The ID of the veNFT token being deposited
     * @param _rewardMode The reward mode selected by the subscriber
     * @param _lockMode The lock mode selected by the subscriber
     */
    function _deposit(
        address _owner,
        uint256 _tokenId,
        Marionette.RewardMode _rewardMode,
        Marionette.LockMode _lockMode
    ) internal {
        // check lock duration and balance requirements
        _isAuthorizedBalanceAndDuration(_tokenId);
        if (_rewardMode == Marionette.RewardMode.Custom) {
            revert InvalidInput();
        }

        if (_lockMode == Marionette.LockMode.Max) {
            _increaseUnlockTime(_tokenId);
        }

        // reset current vote to allow transfer
        if (VE.voted(_tokenId)) VOTER.reset(_tokenId);

        VE.safeTransferFrom(_owner, address(this), _tokenId, "");

        VeTokenInfo storage veInfo = veTokenInfo[_tokenId];

        // record subscriber veNFT preferences
        veInfo.active = true;
        veInfo.rewardMode = _rewardMode;
        veInfo.lockMode = _lockMode;
        veInfo.lastTimestampExtended = block.timestamp;
        veInfo.outstandingRewardsReceiver = address(0);
        if (veInfo.initialEpoch == 0) {
            veInfo.initialEpoch = _currentEpoch();
        }

        emit Deposit(_tokenId, _rewardMode, _lockMode);
    }

    /**
     * @dev Transfers the veNFT back to marionette owner
     * @param _owner The address of the owner of the veNFT token
     * @param _tokenId The ID of the veNFT token to be withdrawn
     */
    function _withdraw(address _owner, uint256 _tokenId) internal {
        if (veTokenInfo[_tokenId].active) {
            // reset current vote to allow transfer
            VOTER.reset(_tokenId);
            // Transfer the veNFT back to owner
            VE.safeTransferFrom(address(this), _owner, _tokenId, "");
        }

        delete veTokenInfo[_tokenId];

        emit Withdraw(_tokenId);
    }

    /**
     * @dev subscribe a veNFT by recording user preferences
     * @param _tokenId The ID of the veNFT to be subscriber
     * @param _rewardMode The reward mode for the subscriber
     * @param _lockMode The lock mode for the subscriber.
     */
    function _subscribe(uint256 _tokenId, Marionette.RewardMode _rewardMode, Marionette.LockMode _lockMode) internal {
        // check if sender is approved to manage veNFT
        _isApprovedOrOwner(address(this), _tokenId);
        // check lock duration and balance requirements
        _isAuthorizedBalanceAndDuration(_tokenId);
        if (_rewardMode == Marionette.RewardMode.Custom) {
            revert InvalidInput();
        }

        if (_lockMode == Marionette.LockMode.Max) {
            _increaseUnlockTime(_tokenId);
        }

        VeTokenInfo storage veInfo = veTokenInfo[_tokenId];

        veInfo.active = true;
        veInfo.rewardMode = _rewardMode;
        veInfo.lockMode = _lockMode;
        veInfo.initialEpoch = _currentEpoch();
        veInfo.lastTimestampExtended = block.timestamp;

        emit Subscribe(_tokenId, _rewardMode, _lockMode);
    }

    /**
     * @dev Unsubscribes a veNFT and claims rewards if any
     * @param _tokenId The ID of the veNFT
     */
    function _unsubscribe(uint256 _tokenId) internal {
        // delete veNFT preferences
        delete veTokenInfo[_tokenId];

        emit Unsubscribe(_tokenId);
    }

    /**
     * @dev Sets the gauges and weights for the adapter.
     * @param _gauges An array of gauge addresses.
     * @param _weights An array of gauge weights.
     */
    function _setGaugesAndWeights(address[] memory _gauges, uint256[] memory _weights) internal {
        // Check that the length of `_gauges` and `_weights` are the same
        if (_gauges.length != _weights.length) revert InvalidInput();

        gauges = _gauges;
        weights = _weights;

        emit SetGaugesAndWeights(_gauges, _weights);
    }

    /**
     * @dev Return whether or not a token can be burned
     * @param _tokenId The ID of the veNFT
     */
    function _canBurn(uint256 _tokenId) internal view returns (bool) {
        uint256 lastEpoch = _currentEpoch() - 1;

        VeTokenInfo storage veInfo = veTokenInfo[_tokenId];

        uint256 tokenVotesLastEpoch = veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Compound][lastEpoch]
            + veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Default][lastEpoch];

        uint256 tokenVotesEpochMinus2 = veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Compound][lastEpoch - 1]
            + veInfo.rewardModeEpochVotePower[Marionette.RewardMode.Default][lastEpoch - 1];

        // if token is vote only return true
        // if token has voted last epoch return false (cant claim)
        // if token has not voted voted 2 epochs ago
        // or rewards from two epoch ago have been claimed return true
        bool isVoteOnly = veInfo.rewardMode == Marionette.RewardMode.VoteOnly;
        bool hasVotedLastEpoch = tokenVotesLastEpoch > 0;
        bool hasNotVotedEpochMinus2 = tokenVotesEpochMinus2 == 0;

        return isVoteOnly || (!hasVotedLastEpoch && hasNotVotedEpochMinus2);
    }
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 4 of 21 : IRewardsDistributor.sol
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

/**
 * @title IRewardsDistributor
 * @author 0xMemoryGrinder
 * @dev MerkleDistributor interface which updates the merkle root when the claims are made
 */
interface IRewardsDistributor {
    struct ClaimParams {
        address token;
        uint256 epoch;
        uint256 amount;
        bytes32[] merkleProof;
    }

    struct ReclaimParams {
        address account;
        ClaimParams[] params;
    }

    /**
     * @dev Claim a token reward for a given account
     * @param epoch Epoch to claim rewards for
     * @param token Token address to claim
     * @param account Account to claim rewards for
     * @param amount Rewards amount to claim
     * @param merkleProof Merkle proof to validate the claim
     */
    function claim(
        uint256 epoch,
        address token,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external;

    /**
     * @dev Claim multiple token rewards for given accounts
     * @param account Account to claim rewards for
     * @param params ClaimParams array containing the tokens, amounts and merkle proofs
     */
    function multiClaim(address account, ClaimParams[] calldata params) external;

    /**
     * @dev Update the merkle root
     * @param epoch Epoch id
     * @param newMerkleRoot New merkle root after a harvest, set by the operator
     */
    function addEpochRewards(uint256 epoch, bytes32 newMerkleRoot) external;
}

File 5 of 21 : IVotingEscrow.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVotingEscrow {
    struct LockedBalance {
        int128 amount;
        uint256 end;
    }

    function ownerOf(uint256 tokenId) external view returns (address);
    function approve(address _spender, uint256 _tokenId) external;
    function isVotingApprovedOrOwner(address _spender, uint256 _tokenId) external returns (bool);
    function locked(uint256 _tokenId) external view returns (LockedBalance memory);
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
    function increase_amount(uint256 _tokenId, uint256 _value) external;
    function increase_unlock_time(uint256 _tokenId, uint256 _lockDuration) external;
    function balanceOfNFT(uint256 _tokenId) external view returns (uint256);
    function attachments(uint256 _tokenId) external view returns (uint256);
    function voted(uint256 _tokenId) external view returns (bool);
    function create_lock(uint _value, uint _lock_duration) external returns (uint);
    function approveVoting(address _approved, uint256 _tokenId) external;
}

File 6 of 21 : IVoter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVoter {
    function maxVotingNum() external view returns (uint256);
    function lastVoted(uint256 tokenId) external view returns (uint256);
    function vote(uint256 _tokenId, address[] calldata _poolVote, uint256[] calldata _weights) external;
    function reset(uint256 _tokenId) external;
    function claimBribes(address[] memory _bribes, address[][] memory _tokens) external;
    function claimFees(address[] memory _fees, address[][] memory _tokens) external;
    function internal_bribes(address _gauge) external view returns (address);
    function external_bribes(address _gauge) external view returns (address);
    function gauges(address _pair) external view returns (address);
    function getGaugeVotes(address _pool) external view returns (uint256);
    function poolVote(uint256 _tokenId, uint256 i) external view returns (address);
    function currentPeriod() external view returns (uint256);
}

File 7 of 21 : IMinter.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMinter {
    function active_period() external view returns (uint256);
    function update_period() external returns (uint256);
}

File 8 of 21 : Adapter.sol
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import { IAdapter } from "../interfaces/IAdapter.sol";

/// @notice The protocol decides whether to invoke specific adapter by inspecting the leading bits of the address that
/// the adapter contract is deployed to.
/// For example, a adapter contract deployed to address: 0x9000000000000000000000000000000000000000
/// has leading bits "1001" which would cause the "before initialize" and "after transfer" adapter to be used.
library Adapter {
    uint256 internal constant BEFORE_TRANSFER_FLAG = 1 << 159;
    uint256 internal constant BEFORE_SET_DEFAULT_FLAG = 1 << 158;
    uint256 internal constant BEFORE_SET_CUSTOM_FLAG = 1 << 157;
    uint256 internal constant BEFORE_SWAP_FLAG = 1 << 156;
    uint256 internal constant BEFORE_CLAIM_FLAG = 1 << 155;
    uint256 internal constant BEFORE_PAUSE_FLAG = 1 << 154;

    struct Calls {
        bool beforeTransfer;
        bool beforeSetDefaultConfig;
        bool beforeSetCustomConfig;
        bool beforeSwap;
        bool beforeClaim;
        bool beforePause;
    }

    /// @notice Thrown if the address will not lead to the specified adapter calls being called
    /// @param adapter The address of the adapter contract
    error AdapterAddressNotValid(address adapter);

    /// @notice Adapter did not return its selector
    error InvalidAdapterResponse();

    /// @notice Utility function intended to be used in adapter constructors to ensure
    /// the deployed adapter address causes the intended adapter to be called
    /// @param calls The adapter that are intended to be called
    /// @dev calls param is memory as the function will be called from constructors
    function validateAdapterAddress(IAdapter self, Calls memory calls) internal pure {
        if (
            calls.beforeTransfer != shouldCallBeforeTransfer(self)
                || calls.beforeSetDefaultConfig != shouldCallBeforeSetDefaultConfig(self)
                || calls.beforeSetCustomConfig != shouldCallBeforeSetCustomConfig(self)
                || calls.beforeSwap != shouldCallBeforeSwap(self) || calls.beforeClaim != shouldCallBeforeClaim(self)
                || calls.beforePause != shouldCallBeforePause(self)
        ) {
            revert AdapterAddressNotValid(address(self));
        }
    }

    /// @notice Ensures that the adapter address includes at least one adapter flag or dynamic fees, or is the 0 address
    /// @param adapter The adapter to verify
    function isValidAdapterAddress(IAdapter adapter) internal pure returns (bool) {
        return uint160(address(adapter)) >= BEFORE_PAUSE_FLAG;
    }

    function shouldCallBeforeTransfer(IAdapter self) internal pure returns (bool) {
        return uint256(uint160(address(self))) & BEFORE_TRANSFER_FLAG != 0;
    }

    function shouldCallBeforeSetDefaultConfig(IAdapter self) internal pure returns (bool) {
        return uint256(uint160(address(self))) & BEFORE_SET_DEFAULT_FLAG != 0;
    }

    function shouldCallBeforeSetCustomConfig(IAdapter self) internal pure returns (bool) {
        return uint256(uint160(address(self))) & BEFORE_SET_CUSTOM_FLAG != 0;
    }

    function shouldCallBeforeSwap(IAdapter self) internal pure returns (bool) {
        return uint256(uint160(address(self))) & BEFORE_SWAP_FLAG != 0;
    }

    function shouldCallBeforePause(IAdapter self) internal pure returns (bool) {
        return uint256(uint160(address(self))) & BEFORE_PAUSE_FLAG != 0;
    }

    function shouldCallBeforeClaim(IAdapter self) internal pure returns (bool) {
        return uint256(uint160(address(self))) & BEFORE_CLAIM_FLAG != 0;
    }
}

File 9 of 21 : IRewardDistributor.sol
//SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

/**
 * @title IRewardsDistributor
 * @author 0xMemoryGrinder
 * @dev MerkleDistributor interface which updates the merkle root when the claims are made
 */
interface IRewardDistributor {
    struct ClaimParams {
        address token;
        uint256 epoch;
        uint256 index;
        uint256 amount;
        bytes32[] merkleProof;
    }

    /**
     * @dev Claim a token reward for a given account
     * @param epoch Epoch to claim rewards for
     * @param index Index in the merkle tree to claim rewards for
     * @param token Token address to claim
     * @param account Account to claim rewards for
     * @param amount Rewards amount to claim
     * @param merkleProof Merkle proof to validate the claim
     */
    function claim(
        uint256 epoch,
        uint256 index,
        address token,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external;

    /**
     * @dev Claim multiple token rewards for given accounts
     * @param account Account to claim rewards for
     * @param params ClaimParams array containing the tokens, amounts and merkle proofs
     */
    function multiClaim(address account, ClaimParams[] calldata params) external;

    /**
     * @dev Update the merkle root
     * @param epoch Epoch id
     * @param newMerkleRoot New merkle root after a harvest, set by the operator
     */
    function addEpochRewards(uint256 epoch, bytes32 newMerkleRoot) external;
}

File 10 of 21 : IPuppeteer.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { MarionetteKey } from "../types/MarionetteKey.sol";
import { MarionetteId } from "../types/MarionetteId.sol";
import { Marionette } from "../libraries/Marionette.sol";
import { Incentive, IncentiveClaim } from "../types/Incentive.sol";

/**
 * @title IPuppeteer
 * @dev Interface for the Puppeteer contract.
 */
interface IPuppeteer {
    event Initialize(
        MarionetteKey indexed key,
        MarionetteId indexed id,
        string uri,
        address indexed admin,
        Marionette.VoteConfig voteConfig,
        Marionette.RewardConfig rewardConfig,
        Marionette.LockConfig lockConfig,
        bytes adapterData
    );

    event Mint(
        MarionetteId indexed id,
        uint256 indexed tokenId,
        uint256 indexed veId,
        address recipient,
        Marionette.RewardConfig rewardConfig,
        Marionette.LockConfig lockConfig,
        bytes adapterData
    );

    event Burn(MarionetteId indexed id, uint256 indexed tokenId, bytes adapterData);
    event SetCustomConfig(MarionetteId indexed id, uint256 tokenId, Marionette.ConfigType configType, bytes configData);
    event SetDefaultConfig(MarionetteId indexed id, Marionette.ConfigType configType, bytes configData);
    event SetPaused(MarionetteId indexed id, bool paused);
    event SetUri(MarionetteId indexed id, string uri);
    event Execute(MarionetteId indexed id, bytes adapterData);
    event Claim(MarionetteId indexed id, uint256 indexed tokenId, uint256 indexed veId, bytes adapterData);
    event Swap(MarionetteId indexed id, bytes adapterData);

    /**
     * @dev Initializes a new Marionette
     * @param key Marionette key
     * @param uri Marionette URI
     * @param defaultVoteConfig The default vote configuration
     * @param defaultRewardConfig The default reward configuration
     * @param defaultLockConfig The default lock configuration
     * @param adapterData Additional data to be used by the adapter contract
     */
    function initialize(
        MarionetteKey memory key,
        string calldata uri,
        Marionette.VoteConfig calldata defaultVoteConfig,
        Marionette.RewardConfig calldata defaultRewardConfig,
        Marionette.LockConfig calldata defaultLockConfig,
        bytes calldata adapterData
    ) external;

    /**
     * @dev Mints a new Marionette token and performs any additional adapter-specific actions
     * @param key Marionette key
     * @param veId veNFT ID in the voting escrow contract
     * @param rewardConfig Custom reward configuration
     * @param lockConfig Custom lock configuration
     * @param adapterData Additional data to be used by the adapter contract
     */
    function mint(
        MarionetteKey memory key,
        uint256 veId,
        Marionette.RewardConfig calldata rewardConfig,
        Marionette.LockConfig calldata lockConfig,
        bytes calldata adapterData
    ) external;

    /**
     * @dev Burns a Marionette NFT and performs any additional adapter-specific actions
     * @param key Marionette Key
     * @param tokenId The ID of the Marionette NFT to burn
     * @param adapterData Additional data to be used by the adapter contract
     */
    function burn(MarionetteKey memory key, uint256 tokenId, bytes calldata adapterData) external;

    /**
     * @dev Sets the default configuration for a Marionette instance.
     * @param key Marionette key
     * @param configType The type of configuration to set
     * @param configData The configuration data
     * @param adapterData Additional data to be used by the adapter contract
     */
    function setDefaultConfig(
        MarionetteKey memory key,
        Marionette.ConfigType calldata configType,
        bytes calldata configData,
        bytes calldata adapterData
    ) external;

    /**
     * @dev Sets the custom configuration for a Marionette
     * @param key Marionette key
     * @param tokenId The ID of the Marionette token
     * @param configType The type of configuration to set
     * @param configData The configuration data
     * @param adapterData Additional data to be used by the adapter contract
     */
    function setCustomConfig(
        MarionetteKey memory key,
        uint256 tokenId,
        Marionette.ConfigType calldata configType,
        bytes calldata configData,
        bytes calldata adapterData
    ) external;

    /**
     * @dev Executes a function on the Adapter contract using the provided key and adapter data
     * @param key Marionette key
     * @param adapterData Additional data to be used by the adapter contract
     */
    function execute(MarionetteKey memory key, bytes calldata adapterData) external;

    /**
     * @dev Sets Marionette paused state
     * @param key Marionette key
     * @param paused new paused state
     * @param adapterData Additional data to be used by the ISwapper contract
     */
    function setPaused(MarionetteKey memory key, bool paused, bytes calldata adapterData) external;

    /**
     * @dev Sets the URI for a given Marionette
     * @param key Marionette key
     * @param uri new URI
     */
    function setUri(MarionetteKey memory key, string calldata uri) external;

    /**
     * @dev Returns the current state of the Marionette
     * @param tokenId The ID of the Marionette to get the state of
     * @return A struct containing the current state of the Marionette
     */
    function getMarionette(uint256 tokenId) external view returns (Marionette.MarionetteState memory);

    /**
     * @dev Returns the current state of the Marionette key
     * @param key Marionette key
     * @return initialized Whether the key has been initialized
     * @return paused Whether the key is currently paused
     * @return uri The URI associated with the key
     * @return defaultVoteConfig The default vote configuration for the key
     * @return defaultRewardConfig The default reward configuration for the key
     * @return defaultLockConfig The default lock configuration for the key
     */
    function getState(MarionetteKey memory key)
        external
        view
        returns (
            bool initialized,
            bool paused,
            string memory uri,
            Marionette.VoteConfig memory defaultVoteConfig,
            Marionette.RewardConfig memory defaultRewardConfig,
            Marionette.LockConfig memory defaultLockConfig
        );
}

File 11 of 21 : BaseAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IPuppeteer } from "../interfaces/IPuppeteer.sol";
import { IAdapter } from "../interfaces/IAdapter.sol";
import { Adapter } from "../libraries/Adapter.sol";
import { MarionetteKey } from "../types/MarionetteKey.sol";
import { Marionette } from "../libraries/Marionette.sol";

abstract contract BaseAdapter is IAdapter {
    error NotPuppeteer();
    error NotSelf();
    error InvalidMarionette();
    error AdapterNotImplemented();

    /// @notice The address of the puppeteer
    IPuppeteer public immutable PUPPETEER;

    constructor(IPuppeteer _puppeteer) {
        PUPPETEER = _puppeteer;
        validateAdapterAddress(this);
    }

    /// @dev Only the puppeteer may call this function
    modifier onlyPuppeteer() {
        if (msg.sender != address(PUPPETEER)) revert NotPuppeteer();
        _;
    }

    /// @dev Only marionettes with adapter set to this contract may call this function
    modifier onlyValidMarionettes(IAdapter adapter) {
        if (adapter != this) revert InvalidMarionette();
        _;
    }

    function getAdapterCalls() public pure virtual returns (Adapter.Calls memory);

    // this function is virtual so that we can override it during testing,
    // which allows us to deploy an implementation to any address
    // and then etch the bytecode into the correct address
    function validateAdapterAddress(BaseAdapter _this) internal virtual {
        Adapter.validateAdapterAddress(_this, getAdapterCalls());
    }

    function beforeInitialize(
        address,
        Marionette.VoteConfig memory,
        Marionette.RewardConfig memory,
        Marionette.LockConfig memory,
        bytes calldata
    ) external virtual returns (bytes4) {
        revert AdapterNotImplemented();
    }

    function beforeTransfer(address, address, uint256, bytes calldata) external virtual returns (bytes4) {
        revert AdapterNotImplemented();
    }

    function beforeSetDefaultConfig(address, Marionette.ConfigType calldata, bytes calldata, bytes calldata)
        external
        virtual
        returns (bytes4)
    {
        revert AdapterNotImplemented();
    }

    function beforeSetCustomConfig(address, uint256, Marionette.ConfigType calldata, bytes calldata, bytes calldata)
        external
        virtual
        returns (bytes4)
    {
        revert AdapterNotImplemented();
    }

    function beforeSwap(address, bytes calldata) external virtual returns (bytes4) {
        revert AdapterNotImplemented();
    }

    function beforeClaim(address, uint256, bytes calldata) external virtual returns (bytes4) {
        revert AdapterNotImplemented();
    }

    function beforePause(address, bool, bytes calldata) external virtual returns (bytes4) {
        revert AdapterNotImplemented();
    }

    function execute(address, MarionetteKey calldata, bytes calldata) external virtual returns (bytes4) {
        revert AdapterNotImplemented();
    }
}

File 12 of 21 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.20;

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

File 13 of 21 : IQuestRewardsDistributor.sol
//SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

interface IQuestRewardsDistributor {
    // Events

    /**
     * @notice Event emitted when a user Claims
     */
    event Claimed(
        uint256 indexed questID,
        uint256 indexed period,
        uint256 index,
        uint256 amount,
        address rewardToken,
        address indexed account
    );
    /**
     * @notice Event emitted when a New Quest is added
     */
    event NewQuest(uint256 indexed questID, address rewardToken);
    /**
     * @notice Event emitted when a Period of a Quest is updated (when the Merkle Root is added)
     */
    event QuestPeriodUpdated(uint256 indexed questID, uint256 indexed period, bytes32 merkleRoot);
    /**
     * @notice Event emitted when the Loot Creator address is updated
     */
    event LootCreatorUpdated(address indexed oldCreator, address indexed newCreator);

    // Functions

    /**
     * @notice Checks if the rewards were claimed for a user on a given period
     * @dev Checks if the rewards were claimed for a user (based on the index) on a given period
     * @param questID ID of the Quest
     * @param period Amount of underlying to borrow
     * @param index Index of the claim
     * @return bool : true if already claimed
     */
    function isClaimed(uint256 questID, uint256 period, uint256 index) external view returns (bool);

    //Basic Claim
    /**
     * @notice Claims the reward for a user for a given period of a Quest
     * @dev Claims the reward for a user for a given period of a Quest if the correct proof was given
     * @param questID ID of the Quest
     * @param period Timestamp of the period
     * @param index Index in the Merkle Tree
     * @param account Address of the user claiming the rewards
     * @param amount Amount of rewards to claim
     * @param merkleProof Proof to claim the rewards
     */
    function claim(
        uint256 questID,
        uint256 period,
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external;

    //Struct ClaimParams
    struct ClaimParams {
        uint256 questID;
        uint256 period;
        uint256 index;
        uint256 amount;
        bytes32[] merkleProof;
    }

    //Multi Claim
    /**
     * @notice Claims multiple rewards for a given list
     * @dev Calls the claim() method for each entry in the claims array
     * @param account Address of the user claiming the rewards
     * @param claims List of ClaimParams struct data to claim
     */
    function multiClaim(address account, ClaimParams[] calldata claims) external;

    //FullQuest Claim (form of Multi Claim but for only one Quest => only one ERC20 transfer)
    //Only works for the given periods (in ClaimParams) for the Quest. Any omitted period will be skipped
    /**
     * @notice Claims the reward for all the given periods of a Quest, and transfer all the rewards at once
     * @dev Sums up all the rewards for given periods of a Quest, and executes only one transfer
     * @param account Address of the user claiming the rewards
     * @param questID ID of the Quest
     * @param claims List of ClaimParams struct data to claim
     */
    function claimQuest(address account, uint256 questID, ClaimParams[] calldata claims) external;

    /**
     * @notice Returns all current Closed periods for the given Quest ID
     * @dev Returns all current Closed periods for the given Quest ID
     * @param questID ID of the Quest
     * @return uint256[] : List of closed periods
     */
    function getClosedPeriodsByQuests(uint256 questID) external view returns (uint256[] memory);
}

File 14 of 21 : ISwapper.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

interface ISwapper {
    /**
     * @notice swap tokens according to the given adapter data
     * @param tokens Tokens to swap
     * @param callDatas Aggregator data for the swap
     */
    function swap(address[] calldata tokens, bytes[] calldata callDatas) external;
}

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

pragma solidity ^0.8.20;

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

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

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

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

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

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

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

File 17 of 21 : IAdapter.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { Marionette } from "../libraries/Marionette.sol";

interface IAdapter {
    function beforeInitialize(
        address sender,
        Marionette.VoteConfig memory defaultVoteConfig,
        Marionette.RewardConfig memory defaultRewardConfig,
        Marionette.LockConfig memory defaultLockConfig,
        bytes calldata adapterData
    ) external returns (bytes4);

    function beforeTransfer(address from, address to, uint256 veId, bytes calldata adapterData)
        external
        returns (bytes4);

    function beforeSetDefaultConfig(
        address sender,
        Marionette.ConfigType calldata configType,
        bytes calldata configData,
        bytes calldata adapterData
    ) external returns (bytes4);

    function beforeSetCustomConfig(
        address sender,
        uint256 tokenId,
        Marionette.ConfigType calldata configType,
        bytes calldata configData,
        bytes calldata adapterData
    ) external returns (bytes4);

    function beforeSwap(address sender, bytes calldata adapterData) external returns (bytes4);

    function beforeClaim(address sender, uint256 veId, bytes calldata adapterData) external returns (bytes4);

    function beforePause(address sender, bool paused, bytes calldata adapterData) external returns (bytes4);

    function execute(address sender, bytes calldata adapterData) external returns (bytes4);

    function increaseMany(uint256[] memory veNFTs, uint256[] memory amounts, address depositor, bool pullToken)
        external;
}

File 18 of 21 : MarionetteKey.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { IAdapter } from "../interfaces/IAdapter.sol";
import { IRewardDistributor } from "../interfaces/IRewardDistributor.sol";

struct Protocol {
    string protocol;
    address veAddress;
    uint24 version;
}

struct MarionetteKey {
    Protocol protocol;
    IAdapter adapter;
    IRewardDistributor rewardDistributor;
}

File 19 of 21 : MarionetteId.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { MarionetteKey } from "./MarionetteKey.sol";

type MarionetteId is bytes32;

library MarionetteIdLib {
    function toId(MarionetteKey memory marionetteKey) internal pure returns (MarionetteId) {
        return MarionetteId.wrap(keccak256(abi.encode(marionetteKey)));
    }
}

File 20 of 21 : Marionette.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

library Marionette {
    error InvalidStatus();

    struct ConfigType {
        bool vote;
        bool reward;
        bool lock;
    }

    enum RewardMode {
        VoteOnly,
        Default,
        Compound,
        Custom
    }

    struct RewardConfig {
        RewardMode rewardMode;
        bytes data;
    }

    struct VoteConfig {
        address[] gauges;
        uint256[] weights;
        bytes data;
    }

    enum LockMode {
        None,
        Max,
        Maintain,
        Custom
    }

    struct LockConfig {
        LockMode lockMode;
        bytes data;
    }

    struct MarionetteState {
        uint256 veId;
        RewardConfig customRewardConfig;
        LockConfig customLockConfig;
    }

    struct State {
        bool initialized;
        bool paused;
        string uri;
        VoteConfig defaultVoteConfig;
        RewardConfig defaultRewardConfig;
        LockConfig defaultLockConfig;
        mapping(uint256 => MarionetteState) marionette;
        mapping(uint256 veId => uint256 tokenId) veIdToTokenId;
    }

    function initialize(
        State storage state,
        string memory uri,
        VoteConfig memory defaultVoteConfig,
        RewardConfig memory defaultRewardConfig,
        LockConfig memory defaultLockConfig
    ) internal {
        state.initialized = true;
        state.paused = true;
        state.uri = uri;
        state.defaultVoteConfig = defaultVoteConfig;
        state.defaultRewardConfig = defaultRewardConfig;
        state.defaultLockConfig = defaultLockConfig;
    }

    function mint(
        State storage state,
        uint256 tokenId,
        uint256 veId,
        RewardConfig memory rewardConfig,
        LockConfig memory lockConfig
    ) internal {
        state.marionette[tokenId].veId = veId;
        state.marionette[tokenId].customRewardConfig = rewardConfig;
        state.marionette[tokenId].customLockConfig = lockConfig;
        state.veIdToTokenId[veId] = tokenId;
    }

    function burn(State storage state, uint256 tokenId) internal {
        delete state.marionette[tokenId];
    }

    function setPaused(State storage state, bool paused) internal {
        if (state.paused == paused) revert InvalidStatus();

        state.paused = paused;
    }

    function setUri(State storage state, string memory uri) internal {
        state.uri = uri;
    }

    function setDefaultConfig(State storage state, ConfigType memory configType, bytes memory configData) internal {
        if (configType.vote) {
            (VoteConfig memory config,,) = abi.decode(configData, (VoteConfig, RewardConfig, LockConfig));
            state.defaultVoteConfig = config;
        }
        if (configType.reward) {
            (, RewardConfig memory config,) = abi.decode(configData, (VoteConfig, RewardConfig, LockConfig));
            state.defaultRewardConfig = config;
        }
        if (configType.lock) {
            (,, LockConfig memory config) = abi.decode(configData, (VoteConfig, RewardConfig, LockConfig));
            state.defaultLockConfig = config;
        }
    }

    function setCustomConfig(
        State storage state,
        uint256 tokenId,
        ConfigType memory configType,
        bytes memory configData
    ) internal {
        if (configType.reward) {
            (RewardConfig memory config,) = abi.decode(configData, (RewardConfig, LockConfig));
            state.marionette[tokenId].customRewardConfig = config;
        }
        if (configType.lock) {
            (, LockConfig memory config) = abi.decode(configData, (RewardConfig, LockConfig));
            state.marionette[tokenId].customLockConfig = config;
        }
    }

    function getMarionette(State storage state, uint256 tokenId) internal view returns (MarionetteState memory) {
        return state.marionette[tokenId];
    }

    function getState(State storage state)
        internal
        view
        returns (
            bool initialized,
            bool paused,
            string memory uri,
            VoteConfig memory defaultVoteConfig,
            RewardConfig memory defaultRewardConfig,
            LockConfig memory defaultLockConfig
        )
    {
        return (
            state.initialized,
            state.paused,
            state.uri,
            state.defaultVoteConfig,
            state.defaultRewardConfig,
            state.defaultLockConfig
        );
    }

    function getTokenId(State storage state, uint256 veId) internal view returns (uint256 tokenId) {
        return state.veIdToTokenId[veId];
    }
}

File 21 of 21 : Incentive.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

struct Incentive {
    address token;
    bytes32 merkleRoot;
    uint256 activeAt;
}

struct IncentiveClaim {
    address token;
    address account;
    uint256 tokenId;
    uint256 amount;
    bytes32[] merkleProof;
}

Settings
{
  "remappings": [
    "ds-test/=lib/solmate/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "openzeppelin/=lib/openzeppelin-contracts/contracts/",
    "solmate/=lib/solmate/src/",
    "solady/=lib/solady/src/",
    "src/=src/",
    "test/=test/",
    "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 1000
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "none",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract IPuppeteer","name":"_puppeteer","type":"address"},{"internalType":"address","name":"initialTHE","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"adapter","type":"address"}],"name":"AdapterAddressNotValid","type":"error"},{"inputs":[],"name":"AdapterNotImplemented","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidBalanceOfToken","type":"error"},{"inputs":[],"name":"InvalidInput","type":"error"},{"inputs":[],"name":"InvalidLockEnd","type":"error"},{"inputs":[],"name":"InvalidMarionette","type":"error"},{"inputs":[],"name":"NotApprovedOrOwner","type":"error"},{"inputs":[],"name":"NotDistributorOrPuppeteer","type":"error"},{"inputs":[],"name":"NotPuppeteer","type":"error"},{"inputs":[],"name":"NotSelf","type":"error"},{"inputs":[],"name":"NotSubscribed","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"ZeroAmountReceived","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"},{"indexed":false,"internalType":"enum Marionette.RewardMode","name":"rewardMode","type":"uint8"},{"indexed":false,"internalType":"enum Marionette.LockMode","name":"lockMode","type":"uint8"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"}],"name":"EmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"IncreaseAmount","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lockDuration","type":"uint256"}],"name":"IncreaseUnlockTime","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"}],"name":"InitiateWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"gauges","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"weights","type":"uint256[]"}],"name":"SetGaugesAndWeights","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"swapper","type":"address"}],"name":"SetSwapper","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"},{"indexed":false,"internalType":"enum Marionette.RewardMode","name":"rewardMode","type":"uint8"},{"indexed":false,"internalType":"enum Marionette.LockMode","name":"lockMode","type":"uint8"}],"name":"Subscribe","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"}],"name":"Unsubscribe","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"balanceOfNFT","type":"uint256"}],"name":"Vote","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"veNftId","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"PUPPETEER","outputs":[{"internalType":"contract IPuppeteer","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"THE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VE","outputs":[{"internalType":"contract IVotingEscrow","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTER","outputs":[{"internalType":"contract IVoter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"beforeClaim","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"address[]","name":"gauges","type":"address[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Marionette.VoteConfig","name":"_defaultVoteConfig","type":"tuple"},{"components":[{"internalType":"enum Marionette.RewardMode","name":"rewardMode","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Marionette.RewardConfig","name":"_defaultRewardConfig","type":"tuple"},{"components":[{"internalType":"enum Marionette.LockMode","name":"lockMode","type":"uint8"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Marionette.LockConfig","name":"","type":"tuple"},{"internalType":"bytes","name":"_adapterData","type":"bytes"}],"name":"beforeInitialize","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bool","name":"","type":"bool"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"beforePause","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_sender","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"components":[{"internalType":"bool","name":"vote","type":"bool"},{"internalType":"bool","name":"reward","type":"bool"},{"internalType":"bool","name":"lock","type":"bool"}],"internalType":"struct Marionette.ConfigType","name":"_configType","type":"tuple"},{"internalType":"bytes","name":"_configData","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"beforeSetCustomConfig","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"bool","name":"vote","type":"bool"},{"internalType":"bool","name":"reward","type":"bool"},{"internalType":"bool","name":"lock","type":"bool"}],"internalType":"struct Marionette.ConfigType","name":"_configType","type":"tuple"},{"internalType":"bytes","name":"_configData","type":"bytes"},{"internalType":"bytes","name":"_adapterData","type":"bytes"}],"name":"beforeSetDefaultConfig","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"beforeSwap","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_adapterData","type":"bytes"}],"name":"beforeTransfer","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"canBurn","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"distributor","type":"address"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"components":[{"internalType":"uint256","name":"questID","type":"uint256"},{"internalType":"uint256","name":"period","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"internalType":"struct IQuestRewardsDistributor.ClaimParams[]","name":"_claimsData","type":"tuple[]"}],"name":"claimQuests","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address","name":"receiver","type":"address"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"components":[{"internalType":"string","name":"protocol","type":"string"},{"internalType":"address","name":"veAddress","type":"address"},{"internalType":"uint24","name":"version","type":"uint24"}],"internalType":"struct Protocol","name":"protocol","type":"tuple"},{"internalType":"contract IAdapter","name":"adapter","type":"address"},{"internalType":"contract IRewardDistributor","name":"rewardDistributor","type":"address"}],"internalType":"struct MarionetteKey","name":"","type":"tuple"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"bytes","name":"adapterData","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_tokenIds","type":"uint256[]"}],"name":"extendMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"gauges","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAdapterCalls","outputs":[{"components":[{"internalType":"bool","name":"beforeTransfer","type":"bool"},{"internalType":"bool","name":"beforeSetDefaultConfig","type":"bool"},{"internalType":"bool","name":"beforeSetCustomConfig","type":"bool"},{"internalType":"bool","name":"beforeSwap","type":"bool"},{"internalType":"bool","name":"beforeClaim","type":"bool"},{"internalType":"bool","name":"beforePause","type":"bool"}],"internalType":"struct Adapter.Calls","name":"","type":"tuple"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_tokenIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_amounts","type":"uint256[]"},{"internalType":"address","name":"_depositor","type":"address"},{"internalType":"bool","name":"pullToken","type":"bool"}],"name":"increaseMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minBalanceOfToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"swapper","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"veTokenInfo","outputs":[{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint256","name":"initialEpoch","type":"uint256"},{"internalType":"uint256","name":"lastTimestampExtended","type":"uint256"},{"internalType":"address","name":"outstandingRewardsReceiver","type":"address"},{"internalType":"enum Marionette.RewardMode","name":"rewardMode","type":"uint8"},{"internalType":"enum Marionette.LockMode","name":"lockMode","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"voteMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"weights","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

60c060405234801562000010575f80fd5b5060405162003e9038038062003e908339810160408190526200003391620001b3565b6001600160a01b038216608052816200004c3062000060565b506001600160a01b031660a05250620001f0565b620000d881620000d26040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152506040805160c0810182525f80825260016020830181905292820192909252606081018290526080810182905260a081019190915290565b620000db565b50565b805115156001609f1b83161515141580620001045750602081015115156001609e1b8316151514155b806200011e5750604081015115156001609d1b8316151514155b80620001385750606081015115156001609c1b8316151514155b80620001525750608081015115156001609b1b8316151514155b806200016c575060a081015115156001609a1b8316151514155b156200019a57604051635378ce4160e01b81526001600160a01b038316600482015260240160405180910390fd5b5050565b6001600160a01b0381168114620000d8575f80fd5b5f8060408385031215620001c5575f80fd5b8251620001d2816200019e565b6020840151909250620001e5816200019e565b809150509250929050565b60805160a051613c316200025f5f395f81816103c601528181610965015261113601525f818161027d0152818161054f0152818161088701528181610a1101528181610b5d01528181610cc201528181610ded01528181610ef90152818161102501526111c20152613c315ff3fe608060405234801561000f575f80fd5b50600436106101a5575f3560e01c806385ad8432116100e8578063b80a75a711610093578063cbcbecb91161006e578063cbcbecb9146103c1578063dd5d55fe146103e8578063e8868a7914610450578063fd6bfc5614610473575f80fd5b8063b80a75a714610380578063c027774f14610393578063c863657d146103a6575f80fd5b806399df0bd5116100c357806399df0bd514610347578063b05391871461035a578063b5f163ff1461036d575f80fd5b806385ad8432146103025780638e5ac18d146103155780638ebf2fd61461032c575f80fd5b80632fc0945c116101535780634f1951051161012e5780634f195105146102c5578063526e0705146102d35780636b10f373146102e1578063827e0eb2146102f4575f80fd5b80632fc0945c14610278578063343b278f1461029f57806346ebfce6146102b2575f80fd5b80631cff79cd116101835780631cff79cd146102275780631e817d7a1461023a5780632b3297f91461024d575f80fd5b806301552220146101a95780630c5bcdda146101da578063150b7a02146101ef575b5f80fd5b6101bc6101b7366004612b8c565b610511565b6040516001600160e01b031990911681526020015b60405180910390f35b6101ed6101e8366004612e60565b610544565b005b6101bc6101fd366004612ed1565b7f150b7a020000000000000000000000000000000000000000000000000000000095945050505050565b6101bc610235366004612f3f565b610681565b6101bc61024836600461315d565b61087b565b600454610260906001600160a01b031681565b6040516001600160a01b0390911681526020016101d1565b6102607f000000000000000000000000000000000000000000000000000000000000000081565b6101bc6102ad366004612ed1565b610a05565b6101ed6102c036600461321b565b610b52565b6101bc6101b7366004613262565b6101bc6101b7366004612f3f565b6101ed6102ef36600461321b565b610cb7565b6101bc6101b73660046132b7565b6101bc6103103660046132f7565b610de1565b61031e60015481565b6040519081526020016101d1565b61026073b84194e28f624bbba3c9181f3a1120ee7646933781565b6101ed610355366004613366565b610eee565b6102606103683660046133d9565b610fbd565b61031e61037b3660046133d9565b610fe5565b6101ed61038e3660046133f0565b611004565b6101bc6103a1366004613474565b6111b6565b610260730966cae7338518961c2d35493d3eb481a75bb86b81565b6102607f000000000000000000000000000000000000000000000000000000000000000081565b61043e6103f63660046133d9565b5f60208190529081526040902080546001820154600283015460039093015460ff9283169391926001600160a01b03821691600160a01b8104821691600160a81b9091041686565b6040516101d19695949392919061353b565b61046361045e3660046133d9565b61155f565b60405190151581526020016101d1565b6040805160c080820183525f80835260208084018290528385018290526060808501839052608080860184905260a0958601849052865180860188528481526001818501818152828a01918252828501878152838501888152938a018881528b5198895291511515968801969096529051151598860198909852925115159184019190915290511515908201529251151591830191909152016101d1565b5f6040517f31040f8000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461058d57604051633bb3ed7b60e21b815260040160405180910390fd5b6040517f6a9b8ce50000000000000000000000000000000000000000000000000000000081526001600160a01b03841690636a9b8ce5906105d4903090859060040161358a565b5f604051808303815f87803b1580156105eb575f80fd5b505af11580156105fd573d5f803e3d5ffd5b5050835191505f90505b8181101561067a576004548351610672916001600160a01b03169085908490811061063457610634613657565b60200260200101516060015186848151811061065257610652613657565b60200260200101516001600160a01b031661156f9092919063ffffffff16565b600101610607565b5050505050565b5f8061068f83850185613687565b90507f94ef0c8d000000000000000000000000000000000000000000000000000000006001600160e01b03198216016106e1575f6106cf848601866136a0565b9150506106db81610cb7565b5061084f565b7ff3a43226000000000000000000000000000000000000000000000000000000006001600160e01b031982160161073b575f8080610721868801886136eb565b93509350935050610733838383610544565b50505061084f565b7f47f58a59000000000000000000000000000000000000000000000000000000006001600160e01b031982160161079a575f80808061077c8789018961376d565b94509450945094505061079184848484611004565b5050505061084f565b7fb914031a000000000000000000000000000000000000000000000000000000006001600160e01b03198216016107e4575f6107d8848601866136a0565b9150506106db81610b52565b7f6620f42b000000000000000000000000000000000000000000000000000000006001600160e01b0319821601610836575f808061082486880188613803565b93509350935050610733838383610eee565b60405163b4fa3fb360e01b815260040160405180910390fd5b507f1cff79cd0000000000000000000000000000000000000000000000000000000090505b9392505050565b5f336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146108c557604051633bb3ed7b60e21b815260040160405180910390fd5b6108d6865f015187602001516115e3565b60208501515115610907575f85602001518060200190518101906108fa919061387a565b90506109058161166a565b505b5f610914838501856133d9565b60018190556040517f095ea7b3000000000000000000000000000000000000000000000000000000008152730966cae7338518961c2d35493d3eb481a75bb86b60048201525f1960248201529091507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063095ea7b3906044016020604051808303815f875af11580156109b3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109d79190613895565b507f1e817d7a0000000000000000000000000000000000000000000000000000000098975050505050505050565b5f336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610a4f57604051633bb3ed7b60e21b815260040160405180910390fd5b6001600160a01b038616610ab6575f80610a6b848601866138b0565b90925090505f82516003811115610a8457610a8461350b565b03610a9d5781518151610a989188916116cc565b610aaf565b610aaf8787845f0151845f01516117ec565b5050610b27565b6001600160a01b038516610b27575f80858152602081905260409020600390810154600160a01b900460ff1690811115610af257610af261350b565b03610b0557610b0084611ab2565b610b27565b610b0e84611b26565b15610b1d57610b008685611c5a565b610b278487611dc7565b507f343b278f0000000000000000000000000000000000000000000000000000000095945050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610b9b57604051633bb3ed7b60e21b815260040160405180910390fd5b80515f5b81811015610cb25760025f80858481518110610bbd57610bbd613657565b602002602001015181526020019081526020015f2060030160159054906101000a900460ff166003811115610bf457610bf461350b565b1480610c4a575060015f80858481518110610c1157610c11613657565b602002602001015181526020019081526020015f2060030160159054906101000a900460ff166003811115610c4857610c4861350b565b145b8015610c8357505f80848381518110610c6557610c65613657565b60209081029190910181015182528101919091526040015f205460ff165b15610caa57610caa838281518110610c9d57610c9d613657565b6020026020010151611fe9565b600101610b9f565b505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610d0057604051633bb3ed7b60e21b815260040160405180910390fd5b80515f5b81811015610cb2575f838281518110610d1f57610d1f613657565b6020908102919091018101515f8181529182905260409091205490915060ff1615610dd8576040516316b7a01160e01b815230600482015260248101829052730966cae7338518961c2d35493d3eb481a75bb86b906316b7a011906044016020604051808303815f875af1158015610d99573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610dbd9190613895565b610dcf57610dca81611ab2565b610dd8565b610dd8816122ec565b50600101610d04565b5f336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610e2b57604051633bb3ed7b60e21b815260040160405180910390fd5b5f80610e3986880188613906565b509092509050610e4f6040890160208a0161397e565b15610e855760208101515115610e85575f8160200151806020019051810190610e78919061387a565b9050610e838161166a565b505b610e92602089018961397e565b15610ea857610ea8825f015183602001516115e3565b8315610ec0575f610ebb858701876133d9565b600155505b507f85ad84320000000000000000000000000000000000000000000000000000000098975050505050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f3757604051633bb3ed7b60e21b815260040160405180910390fd5b82515f5b81811015610f7b57610f7383858381518110610f5957610f59613657565b602002602001015187848151811061065257610652613657565b600101610f3b565b507f770746e85ebd8efe8367457cb56498310d53aa9c0ed8005e1565137c963d55eb848484604051610faf93929190613a0b565b60405180910390a150505050565b60028181548110610fcc575f80fd5b5f918252602090912001546001600160a01b0316905081565b60038181548110610ff4575f80fd5b5f91825260209091200154905081565b6004546001600160a01b031633148015906110485750336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614155b1561107f576040517fe906503000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83518115611160575f805b82811015611128575f808883815181106110a6576110a6613657565b60209081029190910181015182528101919091526040015f205460ff166110f9576040517f237e6c2800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85818151811061110b5761110b613657565b60200260200101518261111e9190613a5d565b915060010161108a565b5061115e6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016853084612584565b505b5f5b818110156111ae576111a686828151811061117f5761117f613657565b602002602001015186838151811061119957611199613657565b60200260200101516125c3565b600101611162565b505050505050565b5f336001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000161461120057604051633bb3ed7b60e21b815260040160405180910390fd5b5f878152602081905260408120908061121b878901896138b0565b90925090506003825160038111156112355761123561350b565b036112535760405163b4fa3fb360e01b815260040160405180910390fd5b61126360408a0160208b0161397e565b15611384575f600384810154600160a01b900460ff16908111156112895761128961350b565b1480156112a857505f825160038111156112a5576112a561350b565b14155b156112da576112b68a611ab2565b815160038401546112d5918d918d9190600160a81b900460ff166117ec565b611384565b6002600384810154600160a01b900460ff16908111156112fc576112fc61350b565b14801561131b57506001825160038111156113195761131961350b565b145b15801561136657506001600384810154600160a01b900460ff16908111156113455761134561350b565b14801561136457506002825160038111156113625761136261350b565b145b155b156113845760405163b4fa3fb360e01b815260040160405180910390fd5b61139460608a0160408b0161397e565b156114d4576001600384810154600160a81b900460ff16908111156113bb576113bb61350b565b141580156113db57506001815160038111156113d9576113d961350b565b145b1561141f57805160038085018054909160ff60a81b1990911690600160a81b90849081111561140c5761140c61350b565b021790555061141a8a611fe9565b6114d4565b6002600384810154600160a81b900460ff16908111156114415761144161350b565b14158015611461575060028151600381111561145f5761145f61350b565b145b156114a257426002840155805160038085018054909160ff60a81b1990911690600160a81b9084908111156114985761149861350b565b02179055506114d4565b805160038085018054909160ff60a81b1990911690600160a81b9084908111156114ce576114ce61350b565b02179055505b6114e160208a018a61397e565b8061151157506114f760608a0160408b0161397e565b158015611511575061150f60408a0160208b0161397e565b155b1561152f5760405163b4fa3fb360e01b815260040160405180910390fd5b507fc027774f000000000000000000000000000000000000000000000000000000009a9950505050505050505050565b5f61156982611b26565b92915050565b6040516001600160a01b03838116602483015260448201839052610cb291859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061267a565b80518251146116055760405163b4fa3fb360e01b815260040160405180910390fd5b8151611618906002906020850190612a60565b50805161162c906003906020840190612ad0565b507f48401c66c59a655bc0d105d6c3c4c501203d6d6c4bd5774ac28592a2d5c56ecc828260405161165e929190613a70565b60405180910390a15050565b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f211f06c051495b535b79192c1a4531d819d569657ff4bd16daa8e9e5e6ed2bfd906020015b60405180910390a150565b6116d630846126f9565b6116df836127b1565b60038260038111156116f3576116f361350b565b036117115760405163b4fa3fb360e01b815260040160405180910390fd5b60018160038111156117255761172561350b565b036117335761173383611fe9565b5f838152602081905260409020805460ff1916600117815560038082018054859260ff60a01b1990911690600160a01b9084908111156117755761177561350b565b0217905550818160030160156101000a81548160ff021916908360038111156117a0576117a061350b565b02179055506117ad6128af565b60018201554260028201556040517ff104cdc51204c7bbdd44ca72ad5568fe0e9c646ff1b015c4c94495b6363e61f290610faf90869086908690613a9d565b6117f5836127b1565b60038260038111156118095761180961350b565b036118275760405163b4fa3fb360e01b815260040160405180910390fd5b600181600381111561183b5761183b61350b565b036118495761184983611fe9565b6040517f8fbb38ff00000000000000000000000000000000000000000000000000000000815260048101849052730966cae7338518961c2d35493d3eb481a75bb86b90638fbb38ff90602401602060405180830381865afa1580156118b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118d49190613895565b1561193a5760405163310bd74b60e01b81526004810184905273b84194e28f624bbba3c9181f3a1120ee764693379063310bd74b906024015f604051808303815f87803b158015611923575f80fd5b505af1158015611935573d5f803e3d5ffd5b505050505b604051635c46a7ef60e11b81526001600160a01b038516600482015230602482015260448101849052608060648201525f6084820152730966cae7338518961c2d35493d3eb481a75bb86b9063b88d4fde9060a4015f604051808303815f87803b1580156119a6575f80fd5b505af11580156119b8573d5f803e3d5ffd5b5050505f848152602081905260409020805460ff19166001178155600380820180549293508592909160ff60a01b1990911690600160a01b908490811115611a0257611a0261350b565b0217905550818160030160156101000a81548160ff02191690836003811115611a2d57611a2d61350b565b021790555042600282015560038101805473ffffffffffffffffffffffffffffffffffffffff1916905560018101545f03611a7057611a6a6128af565b60018201555b7fa9f990139fed927aa94ee2b7d6ccb4d0cb096c51726a86e0122ac2208c2dfe94848484604051611aa393929190613a9d565b60405180910390a15050505050565b5f81815260208181526040808320805460ff191681556001810184905560028101939093556003909201805475ffffffffffffffffffffffffffffffffffffffffffff1916905590518281527fb26e1d7de33ad12bab10008140743de8f6a72d95d6c4d2d3b793aab536dc7add91016116c1565b5f806001611b326128af565b611b3c9190613aca565b5f848152602081815260408083206001845260058101808452828520868652845282852054600286529084528285208686529093529083205493945092611b839190613a5d565b60015f818152600585016020526040812092935091908290611ba59087613aca565b81526020019081526020015f2054836005015f60026003811115611bcb57611bcb61350b565b6003811115611bdc57611bdc61350b565b81526020019081526020015f205f600187611bf79190613aca565b81526020019081526020015f2054611c0f9190613a5d565b90505f80600385810154600160a01b900460ff1690811115611c3357611c3361350b565b14905082151582158280611c4d575081158015611c4d5750805b9998505050505050505050565b5f8181526020819052604090205460ff1615611d535760405163310bd74b60e01b81526004810182905273b84194e28f624bbba3c9181f3a1120ee764693379063310bd74b906024015f604051808303815f87803b158015611cba575f80fd5b505af1158015611ccc573d5f803e3d5ffd5b5050604051635c46a7ef60e11b81523060048201526001600160a01b038516602482015260448101849052608060648201525f6084820152730966cae7338518961c2d35493d3eb481a75bb86b925063b88d4fde915060a4015f604051808303815f87803b158015611d3c575f80fd5b505af1158015611d4e573d5f803e3d5ffd5b505050505b5f81815260208181526040808320805460ff191681556001810184905560028101939093556003909201805475ffffffffffffffffffffffffffffffffffffffffffff1916905590518281527f5b6b431d4476a211bb7d41c20d1aab9ae2321deee0d20be3d9fc9b1093fa6e3d910161165e565b5f828152602081905260408120805460ff1916815560038101805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03851617905590611e0f6128af565b90505f8260030160149054906101000a900460ff1690505f836005015f836003811115611e3e57611e3e61350b565b6003811115611e4f57611e4f61350b565b81526020019081526020015f205f8481526020019081526020015f205490505f811115611ebd575f846005015f846003811115611e8e57611e8e61350b565b6003811115611e9f57611e9f61350b565b815260208082019290925260409081015f9081208782529092529020555b60405163310bd74b60e01b81526004810187905273b84194e28f624bbba3c9181f3a1120ee764693379063310bd74b906024015f604051808303815f87803b158015611f07575f80fd5b505af1158015611f19573d5f803e3d5ffd5b5050604051635c46a7ef60e11b81523060048201526001600160a01b038816602482015260448101899052608060648201525f6084820152730966cae7338518961c2d35493d3eb481a75bb86b925063b88d4fde915060a4015f604051808303815f87803b158015611f89575f80fd5b505af1158015611f9b573d5f803e3d5ffd5b5050604080518981526001600160a01b03891660208201527f08e544170e8e5e6c868a252670b8aa06077b96dee38dcbb4b645909db2fb3481935001905060405180910390a1505050505050565b5f818152602081905260408082209051635a2d1e0760e11b815260048101849052909190730966cae7338518961c2d35493d3eb481a75bb86b9063b45a3c0e906024016040805180830381865afa158015612046573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061206a9190613add565b60038301546002840154919250600160a81b900460ff16905f61209062093a8083613b1a565b90505f6120a062093a8042613b1a565b90508082116122e3575f62093a80806120bd6301dfe20042613a5d565b6120c79190613b1a565b6120d19190613b39565b905060018560038111156120e7576120e761350b565b1480156120f75750808660200151105b156121b1575f6121074283613aca565b60405163a4d855df60e01b8152600481018b905260248101829052909150730966cae7338518961c2d35493d3eb481a75bb86b9063a4d855df906044015f604051808303815f87803b15801561215b575f80fd5b505af115801561216d573d5f803e3d5ffd5b5050604080518c8152602081018590527f43cea24d3d066f77347a713abc68986d484cb771c1b6d8ffb6a63eb557ab593d935001905060405180910390a1506122e1565b60028560038111156121c5576121c561350b565b036122e1575f62093a808062093a80878a602001516121e49190613aca565b6121ee9190613b1a565b6121f89190613b39565b6122029190613a5d565b90506301dfe20081111561223857818760200151101561222d576122264283613aca565b9050612238565b505050505050505050565b42600289015560405163a4d855df60e01b8152600481018a905260248101829052730966cae7338518961c2d35493d3eb481a75bb86b9063a4d855df906044015f604051808303815f87803b15801561228f575f80fd5b505af11580156122a1573d5f803e3d5ffd5b5050604080518c8152602081018590527f43cea24d3d066f77347a713abc68986d484cb771c1b6d8ffb6a63eb557ab593d935001905060405180910390a1505b505b50505050505050565b6040517fe7e242d4000000000000000000000000000000000000000000000000000000008152600481018290525f90730966cae7338518961c2d35493d3eb481a75bb86b9063e7e242d490602401602060405180830381865afa158015612355573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123799190613b50565b90506001548110612580575f82815260208190526040812060038101549091600160a01b90910460ff16908160038111156123b6576123b661350b565b146124c6575f6123c46128af565b905083836005015f8460038111156123de576123de61350b565b60038111156123ef576123ef61350b565b815260208082019290925260409081015f90812085825290925290205560028260038111156124205761242061350b565b036124685760015f908152600584016020908152604080832084845290915290205480156124665760015f90815260058501602090815260408083208584529091528120555b505b600182600381111561247c5761247c61350b565b036124c45760025f908152600584016020908152604080832084845290915290205480156124c25760025f90815260058501602090815260408083208584529091528120555b505b505b6040517f7ac09bf700000000000000000000000000000000000000000000000000000000815273b84194e28f624bbba3c9181f3a1120ee7646933790637ac09bf79061251c908790600290600390600401613b67565b5f604051808303815f87803b158015612533575f80fd5b505af1158015612545573d5f803e3d5ffd5b505060408051878152602081018790527f33952ef907843fd2ddd118a92dd935debf65b72965a557a364ea08deffca032f9350019050610faf565b5050565b6040516001600160a01b0384811660248301528381166044830152606482018390526125bd9186918216906323b872dd9060840161159c565b50505050565b6040517fa183af520000000000000000000000000000000000000000000000000000000081526004810183905260248101829052730966cae7338518961c2d35493d3eb481a75bb86b9063a183af52906044015f604051808303815f87803b15801561262d575f80fd5b505af115801561263f573d5f803e3d5ffd5b505060408051858152602081018590527f61ab42f4ca689a8cdc8b5d584f8f3b6b6c10c5959daa531cc84386589b9bccd5935001905061165e565b5f61268e6001600160a01b03841683612929565b905080515f141580156126b25750808060200190518101906126b09190613895565b155b15610cb2576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b03841660048201526024015b60405180910390fd5b6040516316b7a01160e01b81526001600160a01b038316600482015260248101829052730966cae7338518961c2d35493d3eb481a75bb86b906316b7a011906044016020604051808303815f875af1158015612757573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061277b9190613895565b612580576040517fe433766c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604051635a2d1e0760e11b8152600481018290525f90730966cae7338518961c2d35493d3eb481a75bb86b9063b45a3c0e906024016040805180830381865afa158015612800573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128249190613add565b905061283362093a8042613a5d565b81602001511161286f576040517f0cc5b59800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001548151600f0b1015612580576040517f2566c43900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73b84194e28f624bbba3c9181f3a1120ee764693376001600160a01b031663060406186040518163ffffffff1660e01b8152600401602060405180830381865afa158015612900573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129249190613b50565b905090565b606061087483835f845f80856001600160a01b0316848660405161294d9190613bf8565b5f6040518083038185875af1925050503d805f8114612987576040519150601f19603f3d011682016040523d82523d5f602084013e61298c565b606091505b509150915061299c8683836129a6565b9695505050505050565b6060826129bb576129b682612a1b565b610874565b81511580156129d257506001600160a01b0384163b155b15612a14576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016126f0565b5080610874565b805115612a2b5780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b828054828255905f5260205f20908101928215612ac0579160200282015b82811115612ac0578251825473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116178255602090920191600190910190612a7e565b50612acc929150612b09565b5090565b828054828255905f5260205f20908101928215612ac0579160200282015b82811115612ac0578251825591602001919060010190612aee565b5b80821115612acc575f8155600101612b0a565b6001600160a01b0381168114612a5d575f80fd5b5f60608284031215612b41575f80fd5b50919050565b5f8083601f840112612b57575f80fd5b50813567ffffffffffffffff811115612b6e575f80fd5b602083019150836020828501011115612b85575f80fd5b9250929050565b5f805f8060608587031215612b9f575f80fd5b8435612baa81612b1d565b9350602085013567ffffffffffffffff80821115612bc6575f80fd5b612bd288838901612b31565b94506040870135915080821115612be7575f80fd5b50612bf487828801612b47565b95989497509550505050565b634e487b7160e01b5f52604160045260245ffd5b60405160a0810167ffffffffffffffff81118282101715612c3757612c37612c00565b60405290565b6040805190810167ffffffffffffffff81118282101715612c3757612c37612c00565b604051601f8201601f1916810167ffffffffffffffff81118282101715612c8957612c89612c00565b604052919050565b5f67ffffffffffffffff821115612caa57612caa612c00565b5060051b60200190565b5f82601f830112612cc3575f80fd5b81356020612cd8612cd383612c91565b612c60565b8083825260208201915060208460051b870101935086841115612cf9575f80fd5b602086015b84811015612d1e578035612d1181612b1d565b8352918301918301612cfe565b509695505050505050565b5f82601f830112612d38575f80fd5b81356020612d48612cd383612c91565b828152600592831b8501820192828201919087851115612d66575f80fd5b8387015b85811015612e5357803567ffffffffffffffff80821115612d89575f80fd5b9089019060a0828c03601f1901811315612da1575f80fd5b612da9612c14565b838901358152604080850135828b01526060808601358284015260808087013582850152938601359385851115612dde575f80fd5b84870196508f603f880112612df1575f80fd5b8b8701359550612e03612cd387612c91565b86815295891b87018301958c8101955091508f861115612e21575f80fd5b958201955b85871015612e3f5786358552958b0195938b0193612e26565b830152508752505050928401928401612d6a565b5090979650505050505050565b5f805f60608486031215612e72575f80fd5b8335612e7d81612b1d565b9250602084013567ffffffffffffffff80821115612e99575f80fd5b612ea587838801612cb4565b93506040860135915080821115612eba575f80fd5b50612ec786828701612d29565b9150509250925092565b5f805f805f60808688031215612ee5575f80fd5b8535612ef081612b1d565b94506020860135612f0081612b1d565b935060408601359250606086013567ffffffffffffffff811115612f22575f80fd5b612f2e88828901612b47565b969995985093965092949392505050565b5f805f60408486031215612f51575f80fd5b8335612f5c81612b1d565b9250602084013567ffffffffffffffff811115612f77575f80fd5b612f8386828701612b47565b9497909650939450505050565b5f82601f830112612f9f575f80fd5b81356020612faf612cd383612c91565b8083825260208201915060208460051b870101935086841115612fd0575f80fd5b602086015b84811015612d1e5780358352918301918301612fd5565b5f82601f830112612ffb575f80fd5b813567ffffffffffffffff81111561301557613015612c00565b613028601f8201601f1916602001612c60565b81815284602083860101111561303c575f80fd5b816020850160208301375f918101602001919091529392505050565b5f60608284031215613068575f80fd5b6040516060810167ffffffffffffffff828210818311171561308c5761308c612c00565b8160405282935084359150808211156130a3575f80fd5b6130af86838701612cb4565b835260208501359150808211156130c4575f80fd5b6130d086838701612f90565b602084015260408501359150808211156130e8575f80fd5b506130f585828601612fec565b6040830152505092915050565b5f60408284031215613112575f80fd5b61311a612c3d565b905081356004811061312a575f80fd5b8152602082013567ffffffffffffffff811115613145575f80fd5b61315184828501612fec565b60208301525092915050565b5f805f805f8060a08789031215613172575f80fd5b863561317d81612b1d565b9550602087013567ffffffffffffffff80821115613199575f80fd5b6131a58a838b01613058565b965060408901359150808211156131ba575f80fd5b6131c68a838b01613102565b955060608901359150808211156131db575f80fd5b6131e78a838b01613102565b945060808901359150808211156131fc575f80fd5b5061320989828a01612b47565b979a9699509497509295939492505050565b5f6020828403121561322b575f80fd5b813567ffffffffffffffff811115613241575f80fd5b61324d84828501612f90565b949350505050565b8015158114612a5d575f80fd5b5f805f8060608587031215613275575f80fd5b843561328081612b1d565b9350602085013561329081613255565b9250604085013567ffffffffffffffff8111156132ab575f80fd5b612bf487828801612b47565b5f805f80606085870312156132ca575f80fd5b84356132d581612b1d565b935060208501359250604085013567ffffffffffffffff8111156132ab575f80fd5b5f805f805f8060c0878903121561330c575f80fd5b863561331781612b1d565b95506133268860208901612b31565b9450608087013567ffffffffffffffff80821115613342575f80fd5b61334e8a838b01612b47565b909650945060a08901359150808211156131fc575f80fd5b5f805f60608486031215613378575f80fd5b833567ffffffffffffffff8082111561338f575f80fd5b61339b87838801612cb4565b945060208601359150808211156133b0575f80fd5b506133bd86828701612f90565b92505060408401356133ce81612b1d565b809150509250925092565b5f602082840312156133e9575f80fd5b5035919050565b5f805f8060808587031215613403575f80fd5b843567ffffffffffffffff8082111561341a575f80fd5b61342688838901612f90565b9550602087013591508082111561343b575f80fd5b5061344887828801612f90565b935050604085013561345981612b1d565b9150606085013561346981613255565b939692955090935050565b5f805f805f805f60e0888a03121561348a575f80fd5b873561349581612b1d565b9650602088013595506134ab8960408a01612b31565b945060a088013567ffffffffffffffff808211156134c7575f80fd5b6134d38b838c01612b47565b909650945060c08a01359150808211156134eb575f80fd5b506134f88a828b01612b47565b989b979a50959850939692959293505050565b634e487b7160e01b5f52602160045260245ffd5b60048110612a5d57634e487b7160e01b5f52602160045260245ffd5b861515815260208101869052604081018590526001600160a01b038416606082015260c0810161356a8461351f565b8360808301526135798361351f565b8260a0830152979650505050505050565b5f60408083016001600160a01b038616845260206040818601528186518084526060935060608701915060608160051b8801018389015f5b8381101561364757898303605f1901855281518051845286810151878501528881015189850152878101518885015260809081015160a0918501829052805191850182905287019060c08501905f905b808210156136325783518352928901929189019160019190910190613612565b505095870195935050908501906001016135c2565b50909a9950505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b80356001600160e01b031981168114613682575f80fd5b919050565b5f60208284031215613697575f80fd5b6108748261366b565b5f80604083850312156136b1575f80fd5b6136ba8361366b565b9150602083013567ffffffffffffffff8111156136d5575f80fd5b6136e185828601612f90565b9150509250929050565b5f805f80608085870312156136fe575f80fd5b6137078561366b565b9350602085013561371781612b1d565b9250604085013567ffffffffffffffff80821115613733575f80fd5b61373f88838901612cb4565b93506060870135915080821115613754575f80fd5b5061376187828801612d29565b91505092959194509250565b5f805f805f60a08688031215613781575f80fd5b61378a8661366b565b9450602086013567ffffffffffffffff808211156137a6575f80fd5b6137b289838a01612f90565b955060408801359150808211156137c7575f80fd5b506137d488828901612f90565b93505060608601356137e581612b1d565b915060808601356137f581613255565b809150509295509295909350565b5f805f8060808587031215613816575f80fd5b61381f8561366b565b9350602085013567ffffffffffffffff8082111561383b575f80fd5b61384788838901612cb4565b9450604087013591508082111561385c575f80fd5b5061386987828801612f90565b925050606085013561346981612b1d565b5f6020828403121561388a575f80fd5b815161087481612b1d565b5f602082840312156138a5575f80fd5b815161087481613255565b5f80604083850312156138c1575f80fd5b823567ffffffffffffffff808211156138d8575f80fd5b6138e486838701613102565b935060208501359150808211156138f9575f80fd5b506136e185828601613102565b5f805f60608486031215613918575f80fd5b833567ffffffffffffffff8082111561392f575f80fd5b61393b87838801613058565b94506020860135915080821115613950575f80fd5b61395c87838801613102565b93506040860135915080821115613971575f80fd5b50612ec786828701613102565b5f6020828403121561398e575f80fd5b813561087481613255565b5f815180845260208085019450602084015f5b838110156139d15781516001600160a01b0316875295820195908201906001016139ac565b509495945050505050565b5f815180845260208085019450602084015f5b838110156139d1578151875295820195908201906001016139ef565b606081525f613a1d6060830186613999565b8281036020840152613a2f81866139dc565b9150506001600160a01b0383166040830152949350505050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561156957611569613a49565b604081525f613a826040830185613999565b8281036020840152613a9481856139dc565b95945050505050565b83815260608101613aad8461351f565b836020830152613abc8361351f565b826040830152949350505050565b8181038181111561156957611569613a49565b5f60408284031215613aed575f80fd5b613af5612c3d565b825180600f0b8114613b05575f80fd5b81526020928301519281019290925250919050565b5f82613b3457634e487b7160e01b5f52601260045260245ffd5b500490565b808202811582820484141761156957611569613a49565b5f60208284031215613b60575f80fd5b5051919050565b5f606082018583526020606081850152818654808452608086019150875f52825f2093505f5b81811015613bb25784546001600160a01b031683526001948501949284019201613b8d565b5050848103604086015285548082525f87815283812092840194505b81811015613bea57825485529383019360019283019201613bce565b509298975050505050505050565b5f82515f5b81811015613c175760208186018101518583015201613bfd565b505f92019182525091905056fea164736f6c6343000818000a00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f720000000000000000000000004d85ba8c3918359c78ed09581e5bc7578ba932ba

Deployed Bytecode

0x608060405234801561000f575f80fd5b50600436106101a5575f3560e01c806385ad8432116100e8578063b80a75a711610093578063cbcbecb91161006e578063cbcbecb9146103c1578063dd5d55fe146103e8578063e8868a7914610450578063fd6bfc5614610473575f80fd5b8063b80a75a714610380578063c027774f14610393578063c863657d146103a6575f80fd5b806399df0bd5116100c357806399df0bd514610347578063b05391871461035a578063b5f163ff1461036d575f80fd5b806385ad8432146103025780638e5ac18d146103155780638ebf2fd61461032c575f80fd5b80632fc0945c116101535780634f1951051161012e5780634f195105146102c5578063526e0705146102d35780636b10f373146102e1578063827e0eb2146102f4575f80fd5b80632fc0945c14610278578063343b278f1461029f57806346ebfce6146102b2575f80fd5b80631cff79cd116101835780631cff79cd146102275780631e817d7a1461023a5780632b3297f91461024d575f80fd5b806301552220146101a95780630c5bcdda146101da578063150b7a02146101ef575b5f80fd5b6101bc6101b7366004612b8c565b610511565b6040516001600160e01b031990911681526020015b60405180910390f35b6101ed6101e8366004612e60565b610544565b005b6101bc6101fd366004612ed1565b7f150b7a020000000000000000000000000000000000000000000000000000000095945050505050565b6101bc610235366004612f3f565b610681565b6101bc61024836600461315d565b61087b565b600454610260906001600160a01b031681565b6040516001600160a01b0390911681526020016101d1565b6102607f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f7281565b6101bc6102ad366004612ed1565b610a05565b6101ed6102c036600461321b565b610b52565b6101bc6101b7366004613262565b6101bc6101b7366004612f3f565b6101ed6102ef36600461321b565b610cb7565b6101bc6101b73660046132b7565b6101bc6103103660046132f7565b610de1565b61031e60015481565b6040519081526020016101d1565b61026073b84194e28f624bbba3c9181f3a1120ee7646933781565b6101ed610355366004613366565b610eee565b6102606103683660046133d9565b610fbd565b61031e61037b3660046133d9565b610fe5565b6101ed61038e3660046133f0565b611004565b6101bc6103a1366004613474565b6111b6565b610260730966cae7338518961c2d35493d3eb481a75bb86b81565b6102607f0000000000000000000000004d85ba8c3918359c78ed09581e5bc7578ba932ba81565b61043e6103f63660046133d9565b5f60208190529081526040902080546001820154600283015460039093015460ff9283169391926001600160a01b03821691600160a01b8104821691600160a81b9091041686565b6040516101d19695949392919061353b565b61046361045e3660046133d9565b61155f565b60405190151581526020016101d1565b6040805160c080820183525f80835260208084018290528385018290526060808501839052608080860184905260a0958601849052865180860188528481526001818501818152828a01918252828501878152838501888152938a018881528b5198895291511515968801969096529051151598860198909852925115159184019190915290511515908201529251151591830191909152016101d1565b5f6040517f31040f8000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f72161461058d57604051633bb3ed7b60e21b815260040160405180910390fd5b6040517f6a9b8ce50000000000000000000000000000000000000000000000000000000081526001600160a01b03841690636a9b8ce5906105d4903090859060040161358a565b5f604051808303815f87803b1580156105eb575f80fd5b505af11580156105fd573d5f803e3d5ffd5b5050835191505f90505b8181101561067a576004548351610672916001600160a01b03169085908490811061063457610634613657565b60200260200101516060015186848151811061065257610652613657565b60200260200101516001600160a01b031661156f9092919063ffffffff16565b600101610607565b5050505050565b5f8061068f83850185613687565b90507f94ef0c8d000000000000000000000000000000000000000000000000000000006001600160e01b03198216016106e1575f6106cf848601866136a0565b9150506106db81610cb7565b5061084f565b7ff3a43226000000000000000000000000000000000000000000000000000000006001600160e01b031982160161073b575f8080610721868801886136eb565b93509350935050610733838383610544565b50505061084f565b7f47f58a59000000000000000000000000000000000000000000000000000000006001600160e01b031982160161079a575f80808061077c8789018961376d565b94509450945094505061079184848484611004565b5050505061084f565b7fb914031a000000000000000000000000000000000000000000000000000000006001600160e01b03198216016107e4575f6107d8848601866136a0565b9150506106db81610b52565b7f6620f42b000000000000000000000000000000000000000000000000000000006001600160e01b0319821601610836575f808061082486880188613803565b93509350935050610733838383610eee565b60405163b4fa3fb360e01b815260040160405180910390fd5b507f1cff79cd0000000000000000000000000000000000000000000000000000000090505b9392505050565b5f336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f7216146108c557604051633bb3ed7b60e21b815260040160405180910390fd5b6108d6865f015187602001516115e3565b60208501515115610907575f85602001518060200190518101906108fa919061387a565b90506109058161166a565b505b5f610914838501856133d9565b60018190556040517f095ea7b3000000000000000000000000000000000000000000000000000000008152730966cae7338518961c2d35493d3eb481a75bb86b60048201525f1960248201529091507f0000000000000000000000004d85ba8c3918359c78ed09581e5bc7578ba932ba6001600160a01b03169063095ea7b3906044016020604051808303815f875af11580156109b3573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109d79190613895565b507f1e817d7a0000000000000000000000000000000000000000000000000000000098975050505050505050565b5f336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f721614610a4f57604051633bb3ed7b60e21b815260040160405180910390fd5b6001600160a01b038616610ab6575f80610a6b848601866138b0565b90925090505f82516003811115610a8457610a8461350b565b03610a9d5781518151610a989188916116cc565b610aaf565b610aaf8787845f0151845f01516117ec565b5050610b27565b6001600160a01b038516610b27575f80858152602081905260409020600390810154600160a01b900460ff1690811115610af257610af261350b565b03610b0557610b0084611ab2565b610b27565b610b0e84611b26565b15610b1d57610b008685611c5a565b610b278487611dc7565b507f343b278f0000000000000000000000000000000000000000000000000000000095945050505050565b336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f721614610b9b57604051633bb3ed7b60e21b815260040160405180910390fd5b80515f5b81811015610cb25760025f80858481518110610bbd57610bbd613657565b602002602001015181526020019081526020015f2060030160159054906101000a900460ff166003811115610bf457610bf461350b565b1480610c4a575060015f80858481518110610c1157610c11613657565b602002602001015181526020019081526020015f2060030160159054906101000a900460ff166003811115610c4857610c4861350b565b145b8015610c8357505f80848381518110610c6557610c65613657565b60209081029190910181015182528101919091526040015f205460ff165b15610caa57610caa838281518110610c9d57610c9d613657565b6020026020010151611fe9565b600101610b9f565b505050565b336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f721614610d0057604051633bb3ed7b60e21b815260040160405180910390fd5b80515f5b81811015610cb2575f838281518110610d1f57610d1f613657565b6020908102919091018101515f8181529182905260409091205490915060ff1615610dd8576040516316b7a01160e01b815230600482015260248101829052730966cae7338518961c2d35493d3eb481a75bb86b906316b7a011906044016020604051808303815f875af1158015610d99573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610dbd9190613895565b610dcf57610dca81611ab2565b610dd8565b610dd8816122ec565b50600101610d04565b5f336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f721614610e2b57604051633bb3ed7b60e21b815260040160405180910390fd5b5f80610e3986880188613906565b509092509050610e4f6040890160208a0161397e565b15610e855760208101515115610e85575f8160200151806020019051810190610e78919061387a565b9050610e838161166a565b505b610e92602089018961397e565b15610ea857610ea8825f015183602001516115e3565b8315610ec0575f610ebb858701876133d9565b600155505b507f85ad84320000000000000000000000000000000000000000000000000000000098975050505050505050565b336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f721614610f3757604051633bb3ed7b60e21b815260040160405180910390fd5b82515f5b81811015610f7b57610f7383858381518110610f5957610f59613657565b602002602001015187848151811061065257610652613657565b600101610f3b565b507f770746e85ebd8efe8367457cb56498310d53aa9c0ed8005e1565137c963d55eb848484604051610faf93929190613a0b565b60405180910390a150505050565b60028181548110610fcc575f80fd5b5f918252602090912001546001600160a01b0316905081565b60038181548110610ff4575f80fd5b5f91825260209091200154905081565b6004546001600160a01b031633148015906110485750336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f721614155b1561107f576040517fe906503000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83518115611160575f805b82811015611128575f808883815181106110a6576110a6613657565b60209081029190910181015182528101919091526040015f205460ff166110f9576040517f237e6c2800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85818151811061110b5761110b613657565b60200260200101518261111e9190613a5d565b915060010161108a565b5061115e6001600160a01b037f0000000000000000000000004d85ba8c3918359c78ed09581e5bc7578ba932ba16853084612584565b505b5f5b818110156111ae576111a686828151811061117f5761117f613657565b602002602001015186838151811061119957611199613657565b60200260200101516125c3565b600101611162565b505050505050565b5f336001600160a01b037f00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f72161461120057604051633bb3ed7b60e21b815260040160405180910390fd5b5f878152602081905260408120908061121b878901896138b0565b90925090506003825160038111156112355761123561350b565b036112535760405163b4fa3fb360e01b815260040160405180910390fd5b61126360408a0160208b0161397e565b15611384575f600384810154600160a01b900460ff16908111156112895761128961350b565b1480156112a857505f825160038111156112a5576112a561350b565b14155b156112da576112b68a611ab2565b815160038401546112d5918d918d9190600160a81b900460ff166117ec565b611384565b6002600384810154600160a01b900460ff16908111156112fc576112fc61350b565b14801561131b57506001825160038111156113195761131961350b565b145b15801561136657506001600384810154600160a01b900460ff16908111156113455761134561350b565b14801561136457506002825160038111156113625761136261350b565b145b155b156113845760405163b4fa3fb360e01b815260040160405180910390fd5b61139460608a0160408b0161397e565b156114d4576001600384810154600160a81b900460ff16908111156113bb576113bb61350b565b141580156113db57506001815160038111156113d9576113d961350b565b145b1561141f57805160038085018054909160ff60a81b1990911690600160a81b90849081111561140c5761140c61350b565b021790555061141a8a611fe9565b6114d4565b6002600384810154600160a81b900460ff16908111156114415761144161350b565b14158015611461575060028151600381111561145f5761145f61350b565b145b156114a257426002840155805160038085018054909160ff60a81b1990911690600160a81b9084908111156114985761149861350b565b02179055506114d4565b805160038085018054909160ff60a81b1990911690600160a81b9084908111156114ce576114ce61350b565b02179055505b6114e160208a018a61397e565b8061151157506114f760608a0160408b0161397e565b158015611511575061150f60408a0160208b0161397e565b155b1561152f5760405163b4fa3fb360e01b815260040160405180910390fd5b507fc027774f000000000000000000000000000000000000000000000000000000009a9950505050505050505050565b5f61156982611b26565b92915050565b6040516001600160a01b03838116602483015260448201839052610cb291859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061267a565b80518251146116055760405163b4fa3fb360e01b815260040160405180910390fd5b8151611618906002906020850190612a60565b50805161162c906003906020840190612ad0565b507f48401c66c59a655bc0d105d6c3c4c501203d6d6c4bd5774ac28592a2d5c56ecc828260405161165e929190613a70565b60405180910390a15050565b6004805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f211f06c051495b535b79192c1a4531d819d569657ff4bd16daa8e9e5e6ed2bfd906020015b60405180910390a150565b6116d630846126f9565b6116df836127b1565b60038260038111156116f3576116f361350b565b036117115760405163b4fa3fb360e01b815260040160405180910390fd5b60018160038111156117255761172561350b565b036117335761173383611fe9565b5f838152602081905260409020805460ff1916600117815560038082018054859260ff60a01b1990911690600160a01b9084908111156117755761177561350b565b0217905550818160030160156101000a81548160ff021916908360038111156117a0576117a061350b565b02179055506117ad6128af565b60018201554260028201556040517ff104cdc51204c7bbdd44ca72ad5568fe0e9c646ff1b015c4c94495b6363e61f290610faf90869086908690613a9d565b6117f5836127b1565b60038260038111156118095761180961350b565b036118275760405163b4fa3fb360e01b815260040160405180910390fd5b600181600381111561183b5761183b61350b565b036118495761184983611fe9565b6040517f8fbb38ff00000000000000000000000000000000000000000000000000000000815260048101849052730966cae7338518961c2d35493d3eb481a75bb86b90638fbb38ff90602401602060405180830381865afa1580156118b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906118d49190613895565b1561193a5760405163310bd74b60e01b81526004810184905273b84194e28f624bbba3c9181f3a1120ee764693379063310bd74b906024015f604051808303815f87803b158015611923575f80fd5b505af1158015611935573d5f803e3d5ffd5b505050505b604051635c46a7ef60e11b81526001600160a01b038516600482015230602482015260448101849052608060648201525f6084820152730966cae7338518961c2d35493d3eb481a75bb86b9063b88d4fde9060a4015f604051808303815f87803b1580156119a6575f80fd5b505af11580156119b8573d5f803e3d5ffd5b5050505f848152602081905260409020805460ff19166001178155600380820180549293508592909160ff60a01b1990911690600160a01b908490811115611a0257611a0261350b565b0217905550818160030160156101000a81548160ff02191690836003811115611a2d57611a2d61350b565b021790555042600282015560038101805473ffffffffffffffffffffffffffffffffffffffff1916905560018101545f03611a7057611a6a6128af565b60018201555b7fa9f990139fed927aa94ee2b7d6ccb4d0cb096c51726a86e0122ac2208c2dfe94848484604051611aa393929190613a9d565b60405180910390a15050505050565b5f81815260208181526040808320805460ff191681556001810184905560028101939093556003909201805475ffffffffffffffffffffffffffffffffffffffffffff1916905590518281527fb26e1d7de33ad12bab10008140743de8f6a72d95d6c4d2d3b793aab536dc7add91016116c1565b5f806001611b326128af565b611b3c9190613aca565b5f848152602081815260408083206001845260058101808452828520868652845282852054600286529084528285208686529093529083205493945092611b839190613a5d565b60015f818152600585016020526040812092935091908290611ba59087613aca565b81526020019081526020015f2054836005015f60026003811115611bcb57611bcb61350b565b6003811115611bdc57611bdc61350b565b81526020019081526020015f205f600187611bf79190613aca565b81526020019081526020015f2054611c0f9190613a5d565b90505f80600385810154600160a01b900460ff1690811115611c3357611c3361350b565b14905082151582158280611c4d575081158015611c4d5750805b9998505050505050505050565b5f8181526020819052604090205460ff1615611d535760405163310bd74b60e01b81526004810182905273b84194e28f624bbba3c9181f3a1120ee764693379063310bd74b906024015f604051808303815f87803b158015611cba575f80fd5b505af1158015611ccc573d5f803e3d5ffd5b5050604051635c46a7ef60e11b81523060048201526001600160a01b038516602482015260448101849052608060648201525f6084820152730966cae7338518961c2d35493d3eb481a75bb86b925063b88d4fde915060a4015f604051808303815f87803b158015611d3c575f80fd5b505af1158015611d4e573d5f803e3d5ffd5b505050505b5f81815260208181526040808320805460ff191681556001810184905560028101939093556003909201805475ffffffffffffffffffffffffffffffffffffffffffff1916905590518281527f5b6b431d4476a211bb7d41c20d1aab9ae2321deee0d20be3d9fc9b1093fa6e3d910161165e565b5f828152602081905260408120805460ff1916815560038101805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03851617905590611e0f6128af565b90505f8260030160149054906101000a900460ff1690505f836005015f836003811115611e3e57611e3e61350b565b6003811115611e4f57611e4f61350b565b81526020019081526020015f205f8481526020019081526020015f205490505f811115611ebd575f846005015f846003811115611e8e57611e8e61350b565b6003811115611e9f57611e9f61350b565b815260208082019290925260409081015f9081208782529092529020555b60405163310bd74b60e01b81526004810187905273b84194e28f624bbba3c9181f3a1120ee764693379063310bd74b906024015f604051808303815f87803b158015611f07575f80fd5b505af1158015611f19573d5f803e3d5ffd5b5050604051635c46a7ef60e11b81523060048201526001600160a01b038816602482015260448101899052608060648201525f6084820152730966cae7338518961c2d35493d3eb481a75bb86b925063b88d4fde915060a4015f604051808303815f87803b158015611f89575f80fd5b505af1158015611f9b573d5f803e3d5ffd5b5050604080518981526001600160a01b03891660208201527f08e544170e8e5e6c868a252670b8aa06077b96dee38dcbb4b645909db2fb3481935001905060405180910390a1505050505050565b5f818152602081905260408082209051635a2d1e0760e11b815260048101849052909190730966cae7338518961c2d35493d3eb481a75bb86b9063b45a3c0e906024016040805180830381865afa158015612046573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061206a9190613add565b60038301546002840154919250600160a81b900460ff16905f61209062093a8083613b1a565b90505f6120a062093a8042613b1a565b90508082116122e3575f62093a80806120bd6301dfe20042613a5d565b6120c79190613b1a565b6120d19190613b39565b905060018560038111156120e7576120e761350b565b1480156120f75750808660200151105b156121b1575f6121074283613aca565b60405163a4d855df60e01b8152600481018b905260248101829052909150730966cae7338518961c2d35493d3eb481a75bb86b9063a4d855df906044015f604051808303815f87803b15801561215b575f80fd5b505af115801561216d573d5f803e3d5ffd5b5050604080518c8152602081018590527f43cea24d3d066f77347a713abc68986d484cb771c1b6d8ffb6a63eb557ab593d935001905060405180910390a1506122e1565b60028560038111156121c5576121c561350b565b036122e1575f62093a808062093a80878a602001516121e49190613aca565b6121ee9190613b1a565b6121f89190613b39565b6122029190613a5d565b90506301dfe20081111561223857818760200151101561222d576122264283613aca565b9050612238565b505050505050505050565b42600289015560405163a4d855df60e01b8152600481018a905260248101829052730966cae7338518961c2d35493d3eb481a75bb86b9063a4d855df906044015f604051808303815f87803b15801561228f575f80fd5b505af11580156122a1573d5f803e3d5ffd5b5050604080518c8152602081018590527f43cea24d3d066f77347a713abc68986d484cb771c1b6d8ffb6a63eb557ab593d935001905060405180910390a1505b505b50505050505050565b6040517fe7e242d4000000000000000000000000000000000000000000000000000000008152600481018290525f90730966cae7338518961c2d35493d3eb481a75bb86b9063e7e242d490602401602060405180830381865afa158015612355573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123799190613b50565b90506001548110612580575f82815260208190526040812060038101549091600160a01b90910460ff16908160038111156123b6576123b661350b565b146124c6575f6123c46128af565b905083836005015f8460038111156123de576123de61350b565b60038111156123ef576123ef61350b565b815260208082019290925260409081015f90812085825290925290205560028260038111156124205761242061350b565b036124685760015f908152600584016020908152604080832084845290915290205480156124665760015f90815260058501602090815260408083208584529091528120555b505b600182600381111561247c5761247c61350b565b036124c45760025f908152600584016020908152604080832084845290915290205480156124c25760025f90815260058501602090815260408083208584529091528120555b505b505b6040517f7ac09bf700000000000000000000000000000000000000000000000000000000815273b84194e28f624bbba3c9181f3a1120ee7646933790637ac09bf79061251c908790600290600390600401613b67565b5f604051808303815f87803b158015612533575f80fd5b505af1158015612545573d5f803e3d5ffd5b505060408051878152602081018790527f33952ef907843fd2ddd118a92dd935debf65b72965a557a364ea08deffca032f9350019050610faf565b5050565b6040516001600160a01b0384811660248301528381166044830152606482018390526125bd9186918216906323b872dd9060840161159c565b50505050565b6040517fa183af520000000000000000000000000000000000000000000000000000000081526004810183905260248101829052730966cae7338518961c2d35493d3eb481a75bb86b9063a183af52906044015f604051808303815f87803b15801561262d575f80fd5b505af115801561263f573d5f803e3d5ffd5b505060408051858152602081018590527f61ab42f4ca689a8cdc8b5d584f8f3b6b6c10c5959daa531cc84386589b9bccd5935001905061165e565b5f61268e6001600160a01b03841683612929565b905080515f141580156126b25750808060200190518101906126b09190613895565b155b15610cb2576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b03841660048201526024015b60405180910390fd5b6040516316b7a01160e01b81526001600160a01b038316600482015260248101829052730966cae7338518961c2d35493d3eb481a75bb86b906316b7a011906044016020604051808303815f875af1158015612757573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061277b9190613895565b612580576040517fe433766c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604051635a2d1e0760e11b8152600481018290525f90730966cae7338518961c2d35493d3eb481a75bb86b9063b45a3c0e906024016040805180830381865afa158015612800573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128249190613add565b905061283362093a8042613a5d565b81602001511161286f576040517f0cc5b59800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001548151600f0b1015612580576040517f2566c43900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73b84194e28f624bbba3c9181f3a1120ee764693376001600160a01b031663060406186040518163ffffffff1660e01b8152600401602060405180830381865afa158015612900573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906129249190613b50565b905090565b606061087483835f845f80856001600160a01b0316848660405161294d9190613bf8565b5f6040518083038185875af1925050503d805f8114612987576040519150601f19603f3d011682016040523d82523d5f602084013e61298c565b606091505b509150915061299c8683836129a6565b9695505050505050565b6060826129bb576129b682612a1b565b610874565b81511580156129d257506001600160a01b0384163b155b15612a14576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016126f0565b5080610874565b805115612a2b5780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b828054828255905f5260205f20908101928215612ac0579160200282015b82811115612ac0578251825473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b03909116178255602090920191600190910190612a7e565b50612acc929150612b09565b5090565b828054828255905f5260205f20908101928215612ac0579160200282015b82811115612ac0578251825591602001919060010190612aee565b5b80821115612acc575f8155600101612b0a565b6001600160a01b0381168114612a5d575f80fd5b5f60608284031215612b41575f80fd5b50919050565b5f8083601f840112612b57575f80fd5b50813567ffffffffffffffff811115612b6e575f80fd5b602083019150836020828501011115612b85575f80fd5b9250929050565b5f805f8060608587031215612b9f575f80fd5b8435612baa81612b1d565b9350602085013567ffffffffffffffff80821115612bc6575f80fd5b612bd288838901612b31565b94506040870135915080821115612be7575f80fd5b50612bf487828801612b47565b95989497509550505050565b634e487b7160e01b5f52604160045260245ffd5b60405160a0810167ffffffffffffffff81118282101715612c3757612c37612c00565b60405290565b6040805190810167ffffffffffffffff81118282101715612c3757612c37612c00565b604051601f8201601f1916810167ffffffffffffffff81118282101715612c8957612c89612c00565b604052919050565b5f67ffffffffffffffff821115612caa57612caa612c00565b5060051b60200190565b5f82601f830112612cc3575f80fd5b81356020612cd8612cd383612c91565b612c60565b8083825260208201915060208460051b870101935086841115612cf9575f80fd5b602086015b84811015612d1e578035612d1181612b1d565b8352918301918301612cfe565b509695505050505050565b5f82601f830112612d38575f80fd5b81356020612d48612cd383612c91565b828152600592831b8501820192828201919087851115612d66575f80fd5b8387015b85811015612e5357803567ffffffffffffffff80821115612d89575f80fd5b9089019060a0828c03601f1901811315612da1575f80fd5b612da9612c14565b838901358152604080850135828b01526060808601358284015260808087013582850152938601359385851115612dde575f80fd5b84870196508f603f880112612df1575f80fd5b8b8701359550612e03612cd387612c91565b86815295891b87018301958c8101955091508f861115612e21575f80fd5b958201955b85871015612e3f5786358552958b0195938b0193612e26565b830152508752505050928401928401612d6a565b5090979650505050505050565b5f805f60608486031215612e72575f80fd5b8335612e7d81612b1d565b9250602084013567ffffffffffffffff80821115612e99575f80fd5b612ea587838801612cb4565b93506040860135915080821115612eba575f80fd5b50612ec786828701612d29565b9150509250925092565b5f805f805f60808688031215612ee5575f80fd5b8535612ef081612b1d565b94506020860135612f0081612b1d565b935060408601359250606086013567ffffffffffffffff811115612f22575f80fd5b612f2e88828901612b47565b969995985093965092949392505050565b5f805f60408486031215612f51575f80fd5b8335612f5c81612b1d565b9250602084013567ffffffffffffffff811115612f77575f80fd5b612f8386828701612b47565b9497909650939450505050565b5f82601f830112612f9f575f80fd5b81356020612faf612cd383612c91565b8083825260208201915060208460051b870101935086841115612fd0575f80fd5b602086015b84811015612d1e5780358352918301918301612fd5565b5f82601f830112612ffb575f80fd5b813567ffffffffffffffff81111561301557613015612c00565b613028601f8201601f1916602001612c60565b81815284602083860101111561303c575f80fd5b816020850160208301375f918101602001919091529392505050565b5f60608284031215613068575f80fd5b6040516060810167ffffffffffffffff828210818311171561308c5761308c612c00565b8160405282935084359150808211156130a3575f80fd5b6130af86838701612cb4565b835260208501359150808211156130c4575f80fd5b6130d086838701612f90565b602084015260408501359150808211156130e8575f80fd5b506130f585828601612fec565b6040830152505092915050565b5f60408284031215613112575f80fd5b61311a612c3d565b905081356004811061312a575f80fd5b8152602082013567ffffffffffffffff811115613145575f80fd5b61315184828501612fec565b60208301525092915050565b5f805f805f8060a08789031215613172575f80fd5b863561317d81612b1d565b9550602087013567ffffffffffffffff80821115613199575f80fd5b6131a58a838b01613058565b965060408901359150808211156131ba575f80fd5b6131c68a838b01613102565b955060608901359150808211156131db575f80fd5b6131e78a838b01613102565b945060808901359150808211156131fc575f80fd5b5061320989828a01612b47565b979a9699509497509295939492505050565b5f6020828403121561322b575f80fd5b813567ffffffffffffffff811115613241575f80fd5b61324d84828501612f90565b949350505050565b8015158114612a5d575f80fd5b5f805f8060608587031215613275575f80fd5b843561328081612b1d565b9350602085013561329081613255565b9250604085013567ffffffffffffffff8111156132ab575f80fd5b612bf487828801612b47565b5f805f80606085870312156132ca575f80fd5b84356132d581612b1d565b935060208501359250604085013567ffffffffffffffff8111156132ab575f80fd5b5f805f805f8060c0878903121561330c575f80fd5b863561331781612b1d565b95506133268860208901612b31565b9450608087013567ffffffffffffffff80821115613342575f80fd5b61334e8a838b01612b47565b909650945060a08901359150808211156131fc575f80fd5b5f805f60608486031215613378575f80fd5b833567ffffffffffffffff8082111561338f575f80fd5b61339b87838801612cb4565b945060208601359150808211156133b0575f80fd5b506133bd86828701612f90565b92505060408401356133ce81612b1d565b809150509250925092565b5f602082840312156133e9575f80fd5b5035919050565b5f805f8060808587031215613403575f80fd5b843567ffffffffffffffff8082111561341a575f80fd5b61342688838901612f90565b9550602087013591508082111561343b575f80fd5b5061344887828801612f90565b935050604085013561345981612b1d565b9150606085013561346981613255565b939692955090935050565b5f805f805f805f60e0888a03121561348a575f80fd5b873561349581612b1d565b9650602088013595506134ab8960408a01612b31565b945060a088013567ffffffffffffffff808211156134c7575f80fd5b6134d38b838c01612b47565b909650945060c08a01359150808211156134eb575f80fd5b506134f88a828b01612b47565b989b979a50959850939692959293505050565b634e487b7160e01b5f52602160045260245ffd5b60048110612a5d57634e487b7160e01b5f52602160045260245ffd5b861515815260208101869052604081018590526001600160a01b038416606082015260c0810161356a8461351f565b8360808301526135798361351f565b8260a0830152979650505050505050565b5f60408083016001600160a01b038616845260206040818601528186518084526060935060608701915060608160051b8801018389015f5b8381101561364757898303605f1901855281518051845286810151878501528881015189850152878101518885015260809081015160a0918501829052805191850182905287019060c08501905f905b808210156136325783518352928901929189019160019190910190613612565b505095870195935050908501906001016135c2565b50909a9950505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b80356001600160e01b031981168114613682575f80fd5b919050565b5f60208284031215613697575f80fd5b6108748261366b565b5f80604083850312156136b1575f80fd5b6136ba8361366b565b9150602083013567ffffffffffffffff8111156136d5575f80fd5b6136e185828601612f90565b9150509250929050565b5f805f80608085870312156136fe575f80fd5b6137078561366b565b9350602085013561371781612b1d565b9250604085013567ffffffffffffffff80821115613733575f80fd5b61373f88838901612cb4565b93506060870135915080821115613754575f80fd5b5061376187828801612d29565b91505092959194509250565b5f805f805f60a08688031215613781575f80fd5b61378a8661366b565b9450602086013567ffffffffffffffff808211156137a6575f80fd5b6137b289838a01612f90565b955060408801359150808211156137c7575f80fd5b506137d488828901612f90565b93505060608601356137e581612b1d565b915060808601356137f581613255565b809150509295509295909350565b5f805f8060808587031215613816575f80fd5b61381f8561366b565b9350602085013567ffffffffffffffff8082111561383b575f80fd5b61384788838901612cb4565b9450604087013591508082111561385c575f80fd5b5061386987828801612f90565b925050606085013561346981612b1d565b5f6020828403121561388a575f80fd5b815161087481612b1d565b5f602082840312156138a5575f80fd5b815161087481613255565b5f80604083850312156138c1575f80fd5b823567ffffffffffffffff808211156138d8575f80fd5b6138e486838701613102565b935060208501359150808211156138f9575f80fd5b506136e185828601613102565b5f805f60608486031215613918575f80fd5b833567ffffffffffffffff8082111561392f575f80fd5b61393b87838801613058565b94506020860135915080821115613950575f80fd5b61395c87838801613102565b93506040860135915080821115613971575f80fd5b50612ec786828701613102565b5f6020828403121561398e575f80fd5b813561087481613255565b5f815180845260208085019450602084015f5b838110156139d15781516001600160a01b0316875295820195908201906001016139ac565b509495945050505050565b5f815180845260208085019450602084015f5b838110156139d1578151875295820195908201906001016139ef565b606081525f613a1d6060830186613999565b8281036020840152613a2f81866139dc565b9150506001600160a01b0383166040830152949350505050565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561156957611569613a49565b604081525f613a826040830185613999565b8281036020840152613a9481856139dc565b95945050505050565b83815260608101613aad8461351f565b836020830152613abc8361351f565b826040830152949350505050565b8181038181111561156957611569613a49565b5f60408284031215613aed575f80fd5b613af5612c3d565b825180600f0b8114613b05575f80fd5b81526020928301519281019290925250919050565b5f82613b3457634e487b7160e01b5f52601260045260245ffd5b500490565b808202811582820484141761156957611569613a49565b5f60208284031215613b60575f80fd5b5051919050565b5f606082018583526020606081850152818654808452608086019150875f52825f2093505f5b81811015613bb25784546001600160a01b031683526001948501949284019201613b8d565b5050848103604086015285548082525f87815283812092840194505b81811015613bea57825485529383019360019283019201613bce565b509298975050505050505050565b5f82515f5b81811015613c175760208186018101518583015201613bfd565b505f92019182525091905056fea164736f6c6343000818000a

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

00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f720000000000000000000000004d85ba8c3918359c78ed09581e5bc7578ba932ba

-----Decoded View---------------
Arg [0] : _puppeteer (address): 0x82136B5B2FA53AEFaB8d7C87467D8e7036Bb3f72
Arg [1] : initialTHE (address): 0x4D85bA8c3918359c78Ed09581E5bc7578ba932ba

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 00000000000000000000000082136b5b2fa53aefab8d7c87467d8e7036bb3f72
Arg [1] : 0000000000000000000000004d85ba8c3918359c78ed09581e5bc7578ba932ba


Block Transaction Gas Used Reward
view all blocks ##produced##

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

Validator Index Block Amount
View All Withdrawals

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