ETH Price: $2,947.21 (+0.80%)
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Claim Tokens239980022025-12-12 17:22:474 days ago1765560167IN
0x951f9935...BdfC17f12
0 ETH0.001734931.04689291
Claim Tokens239500202025-12-05 23:35:2311 days ago1764977723IN
0x951f9935...BdfC17f12
0 ETH0.000005120.01948647
Claim Tokens239500132025-12-05 23:33:5911 days ago1764977639IN
0x951f9935...BdfC17f12
0 ETH0.000028840.0220358
Claim Tokens239468162025-12-05 12:42:1111 days ago1764938531IN
0x951f9935...BdfC17f12
0 ETH0.000012860.0238548
Claim Token239406472025-12-04 15:44:1112 days ago1764863051IN
0x951f9935...BdfC17f12
0 ETH0.00001250.06041295
Claim Token239406402025-12-04 15:42:4712 days ago1764862967IN
0x951f9935...BdfC17f12
0 ETH0.000015440.06890019
Claim Tokens239405892025-12-04 15:32:3512 days ago1764862355IN
0x951f9935...BdfC17f12
0 ETH0.000023910.05898268
Claim Tokens239387442025-12-04 9:12:3512 days ago1764839555IN
0x951f9935...BdfC17f12
0 ETH0.000036730.0314923
Claim Tokens239173662025-12-01 8:12:5915 days ago1764576779IN
0x951f9935...BdfC17f12
0 ETH0.000520270.30283315
Claim Tokens238594242025-11-23 5:11:3524 days ago1763874695IN
0x951f9935...BdfC17f12
0 ETH0.001653770.98213772
Claim Token238218692025-11-17 22:32:1129 days ago1763418731IN
0x951f9935...BdfC17f12
0 ETH0.000476840.36234332
Claim Token238015462025-11-15 2:15:3532 days ago1763172935IN
0x951f9935...BdfC17f12
0 ETH0.000114240.10586653
Claim Token236663892025-10-27 4:25:4751 days ago1761539147IN
0x951f9935...BdfC17f12
0 ETH0.00015110.11550705
Claim Tokens236576202025-10-25 23:01:4752 days ago1761433307IN
0x951f9935...BdfC17f12
0 ETH0.000120180.08163672
Claim Tokens236448842025-10-24 4:16:1154 days ago1761279371IN
0x951f9935...BdfC17f12
0 ETH0.000133560.11377241
Claim Token236371372025-10-23 2:09:5955 days ago1761185399IN
0x951f9935...BdfC17f12
0 ETH0.000159220.08340333
Claim Tokens234526342025-09-27 6:43:5981 days ago1758955439IN
0x951f9935...BdfC17f12
0 ETH0.001934521.21627217
Claim Token233328732025-09-10 13:06:3597 days ago1757509595IN
0x951f9935...BdfC17f12
0 ETH0.002340971.81130688
Claim Token233192962025-09-08 15:33:2399 days ago1757345603IN
0x951f9935...BdfC17f12
0 ETH0.001099351.77205143
Claim Tokens233123052025-09-07 16:06:47100 days ago1757261207IN
0x951f9935...BdfC17f12
0 ETH0.000058260.20000417
Claim Tokens233123012025-09-07 16:05:59100 days ago1757261159IN
0x951f9935...BdfC17f12
0 ETH0.000075140.19233873
Claim Tokens233074842025-09-06 23:58:23101 days ago1757203103IN
0x951f9935...BdfC17f12
0 ETH0.00034030.20206214
Claim Token232740222025-09-02 7:46:47106 days ago1756799207IN
0x951f9935...BdfC17f12
0 ETH0.000371090.23522188
Claim Tokens232304312025-08-27 5:40:47112 days ago1756273247IN
0x951f9935...BdfC17f12
0 ETH0.000198250.21470484
Claim Token232268862025-08-26 17:49:11112 days ago1756230551IN
0x951f9935...BdfC17f12
0 ETH0.001168970.73615917
View all transactions

Latest 1 internal transaction

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x61018060165679532023-02-06 6:28:111045 days ago1675664891  Contract Creation0 ETH
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

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

Contract Source Code Verified (Exact Match)

Contract Name:
FeeDistributor

Compiler Version
v0.8.16+commit.07a7930e

Optimization Enabled:
Yes with 1000000 runs

Other Settings:
default evmVersion
// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import {Math} from "openzeppelin-contracts/utils/math/Math.sol";
import {SafeCast} from "openzeppelin-contracts/utils/math/SafeCast.sol";

import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";

import "./lib/OptionalOnlyCaller.sol";
import "./interfaces/IVotingEscrow.sol";
import "./interfaces/IFeeDistributor.sol";

// solhint-disable not-rely-on-time

/**
 * @title Fee Distributor
 * @notice Distributes any tokens transferred to the contract (e.g. Protocol fees and any token emissions) among vetoken
 * holders proportionally based on a snapshot of the week at which the tokens are sent to the FeeDistributor contract.
 * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to vetoken
 * holders simply transfer the tokens to the `FeeDistributor` contract and then call `checkpointToken`.
 */
contract FeeDistributor is IFeeDistributor, OptionalOnlyCaller, ReentrancyGuard {
    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------

    using SafeTransferLib for ERC20;

    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    error FeeDistributor__InputLengthMismatch();
    error FeeDistributor__VotingEscrowZeroTotalSupply();
    error FeeDistributor__CannotStartBeforeCurrentWeek();

    /// -----------------------------------------------------------------------
    /// Immutable params
    /// -----------------------------------------------------------------------

    uint256 private immutable _startTime;
    IVotingEscrow private immutable _votingEscrow;

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    // Global State
    uint256 private _timeCursor;
    mapping(uint256 => uint256) private _veSupplyCache;

    // Token State

    // `startTime` and `timeCursor` are both timestamps so comfortably fit in a uint64.
    // `cachedBalance` will comfortably fit the total supply of any meaningful token.
    // Should more than 2^128 tokens be sent to this contract then checkpointing this token will fail until enough
    // tokens have been claimed to bring the total balance back below 2^128.
    struct TokenState {
        uint64 startTime;
        uint64 timeCursor;
        uint128 cachedBalance;
    }

    mapping(ERC20 => TokenState) private _tokenState;
    mapping(ERC20 => mapping(uint256 => uint256)) private _tokensPerWeek;

    // User State

    // `startTime` and `timeCursor` are timestamps so will comfortably fit in a uint64.
    // For `lastEpochCheckpointed` to overflow would need over 2^128 transactions to the VotingEscrow contract.
    struct UserState {
        uint64 startTime;
        uint64 timeCursor;
        uint128 lastEpochCheckpointed;
    }

    mapping(address => UserState) internal _userState;
    mapping(address => mapping(uint256 => uint256)) private _userBalanceAtTimestamp;
    mapping(address => mapping(ERC20 => uint256)) private _userTokenTimeCursor;

    /// -----------------------------------------------------------------------
    /// Constructor
    /// -----------------------------------------------------------------------

    constructor(IVotingEscrow votingEscrow, uint256 startTime) EIP712("FeeDistributor", "1") {
        _votingEscrow = votingEscrow;

        startTime = _roundDownTimestamp(startTime);
        uint256 currentWeek = _roundDownTimestamp(block.timestamp);
        if (startTime < currentWeek) {
            revert FeeDistributor__CannotStartBeforeCurrentWeek();
        }
        if (startTime == currentWeek) {
            // We assume that `votingEscrow` has been deployed in a week previous to this one.
            // If `votingEscrow` did not have a non-zero supply at the beginning of the current week
            // then any tokens which are distributed this week will be lost permanently.
            if (votingEscrow.totalSupply(currentWeek) == 0) {
                revert FeeDistributor__VotingEscrowZeroTotalSupply();
            }
        }
        _startTime = startTime;
        _timeCursor = startTime;
    }

    /// -----------------------------------------------------------------------
    /// View functions
    /// -----------------------------------------------------------------------

    /**
     * @notice Returns the VotingEscrow (vetoken) token contract
     */
    function getVotingEscrow() external view override returns (IVotingEscrow) {
        return _votingEscrow;
    }

    /**
     * @notice Returns the global time cursor representing the most earliest uncheckpointed week.
     */
    function getTimeCursor() external view override returns (uint256) {
        return _timeCursor;
    }

    /**
     * @notice Returns the user-level time cursor representing the most earliest uncheckpointed week.
     * @param user - The address of the user to query.
     */
    function getUserTimeCursor(address user) external view override returns (uint256) {
        return _userState[user].timeCursor;
    }

    /**
     * @notice Returns the token-level time cursor storing the timestamp at up to which tokens have been distributed.
     * @param token - The ERC20 token address to query.
     */
    function getTokenTimeCursor(ERC20 token) external view override returns (uint256) {
        return _tokenState[token].timeCursor;
    }

    /**
     * @notice Returns the user-level time cursor storing the timestamp of the latest token distribution claimed.
     * @param user - The address of the user to query.
     * @param token - The ERC20 token address to query.
     */
    function getUserTokenTimeCursor(address user, ERC20 token) external view override returns (uint256) {
        return _getUserTokenTimeCursor(user, token);
    }

    /**
     * @notice Returns the user's cached balance of vetoken as of the provided timestamp.
     * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values.
     * This function requires `user` to have been checkpointed past `timestamp` so that their balance is cached.
     * @param user - The address of the user of which to read the cached balance of.
     * @param timestamp - The timestamp at which to read the `user`'s cached balance at.
     */
    function getUserBalanceAtTimestamp(address user, uint256 timestamp) external view override returns (uint256) {
        return _userBalanceAtTimestamp[user][timestamp];
    }

    /**
     * @notice Returns the cached total supply of vetoken as of the provided timestamp.
     * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values.
     * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached.
     * @param timestamp - The timestamp at which to read the cached total supply at.
     */
    function getTotalSupplyAtTimestamp(uint256 timestamp) external view override returns (uint256) {
        return _veSupplyCache[timestamp];
    }

    /**
     * @notice Returns the FeeDistributor's cached balance of `token`.
     */
    function getTokenLastBalance(ERC20 token) external view override returns (uint256) {
        return _tokenState[token].cachedBalance;
    }

    /**
     * @notice Returns the amount of `token` which the FeeDistributor received in the week beginning at `timestamp`.
     * @param token - The ERC20 token address to query.
     * @param timestamp - The timestamp corresponding to the beginning of the week of interest.
     */
    function getTokensDistributedInWeek(ERC20 token, uint256 timestamp) external view override returns (uint256) {
        return _tokensPerWeek[token][timestamp];
    }

    /// -----------------------------------------------------------------------
    /// User actions
    /// -----------------------------------------------------------------------

    // Depositing

    /**
     * @notice Deposits tokens to be distributed in the current week.
     * @dev Sending tokens directly to the FeeDistributor instead of using `depositToken` may result in tokens being
     * retroactively distributed to past weeks, or for the distribution to carry over to future weeks.
     *
     * If for some reason `depositToken` cannot be called, in order to ensure that all tokens are correctly distributed
     * manually call `checkpointToken` before and after the token transfer.
     * @param token - The ERC20 token address to distribute.
     * @param amount - The amount of tokens to deposit.
     */
    function depositToken(ERC20 token, uint256 amount) external override nonReentrant {
        _checkpointToken(token, false);
        token.safeTransferFrom(msg.sender, address(this), amount);
        _checkpointToken(token, true);
    }

    /**
     * @notice Deposits tokens to be distributed in the current week.
     * @dev A version of `depositToken` which supports depositing multiple `tokens` at once.
     * See `depositToken` for more details.
     * @param tokens - An array of ERC20 token addresses to distribute.
     * @param amounts - An array of token amounts to deposit.
     */
    function depositTokens(ERC20[] calldata tokens, uint256[] calldata amounts) external override nonReentrant {
        if (tokens.length != amounts.length) {
            revert FeeDistributor__InputLengthMismatch();
        }

        uint256 length = tokens.length;
        for (uint256 i = 0; i < length;) {
            _checkpointToken(tokens[i], false);
            tokens[i].safeTransferFrom(msg.sender, address(this), amounts[i]);
            _checkpointToken(tokens[i], true);

            unchecked {
                ++i;
            }
        }
    }

    // Checkpointing

    /**
     * @notice Caches the total supply of vetoken at the beginning of each week.
     * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
     */
    function checkpoint() external override nonReentrant {
        _checkpointTotalSupply();
    }

    /**
     * @notice Caches the user's balance of vetoken at the beginning of each week.
     * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
     * @param user - The address of the user to be checkpointed.
     */
    function checkpointUser(address user) external override nonReentrant {
        _checkpointUserBalance(user);
    }

    /**
     * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
     * @dev Any `token` balance held by the FeeDistributor above that which is returned by `getTokenLastBalance`
     * will be distributed evenly across the time period since `token` was last checkpointed.
     *
     * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
     * @param token - The ERC20 token address to be checkpointed.
     */
    function checkpointToken(ERC20 token) external override nonReentrant {
        _checkpointToken(token, true);
    }

    /**
     * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
     * @dev A version of `checkpointToken` which supports checkpointing multiple tokens.
     * See `checkpointToken` for more details.
     * @param tokens - An array of ERC20 token addresses to be checkpointed.
     */
    function checkpointTokens(ERC20[] calldata tokens) external override nonReentrant {
        uint256 tokensLength = tokens.length;
        for (uint256 i = 0; i < tokensLength;) {
            _checkpointToken(tokens[i], true);

            unchecked {
                ++i;
            }
        }
    }

    // Claiming

    /**
     * @notice Claims all pending distributions of the provided token for a user.
     * @dev It's not necessary to explicitly checkpoint before calling this function, it will ensure the FeeDistributor
     * is up to date before calculating the amount of tokens to be claimed.
     * @param user - The user on behalf of which to claim.
     * @param token - The ERC20 token address to be claimed.
     * @return The amount of `token` sent to `user` as a result of claiming.
     */
    function claimToken(address user, ERC20 token)
        external
        override
        nonReentrant
        optionalOnlyCaller(user)
        returns (uint256)
    {
        _checkpointTotalSupply();
        _checkpointUserBalance(user);
        _checkpointToken(token, false);

        uint256 amount = _claimToken(user, token);
        return amount;
    }

    /**
     * @notice Claims a number of tokens on behalf of a user.
     * @dev A version of `claimToken` which supports claiming multiple `tokens` on behalf of `user`.
     * See `claimToken` for more details.
     * @param user - The user on behalf of which to claim.
     * @param tokens - An array of ERC20 token addresses to be claimed.
     * @return An array of the amounts of each token in `tokens` sent to `user` as a result of claiming.
     */
    function claimTokens(address user, ERC20[] calldata tokens)
        external
        override
        nonReentrant
        optionalOnlyCaller(user)
        returns (uint256[] memory)
    {
        _checkpointTotalSupply();
        _checkpointUserBalance(user);

        uint256 tokensLength = tokens.length;
        uint256[] memory amounts = new uint256[](tokensLength);
        for (uint256 i = 0; i < tokensLength;) {
            _checkpointToken(tokens[i], false);
            amounts[i] = _claimToken(user, tokens[i]);

            unchecked {
                ++i;
            }
        }

        return amounts;
    }

    /// -----------------------------------------------------------------------
    /// Internal functions
    /// -----------------------------------------------------------------------

    /**
     * @dev It is required that both the global, token and user state have been properly checkpointed
     * before calling this function.
     */
    function _claimToken(address user, ERC20 token) internal returns (uint256) {
        TokenState storage tokenState = _tokenState[token];
        uint256 nextUserTokenWeekToClaim = _getUserTokenTimeCursor(user, token);

        // The first week which cannot be correctly claimed is the earliest of:
        // - A) The global or user time cursor (whichever is earliest), rounded up to the end of the week.
        // - B) The token time cursor, rounded down to the beginning of the week.
        //
        // This prevents the two failure modes:
        // - A) A user may claim a week for which we have not processed their balance, resulting in tokens being locked.
        // - B) A user may claim a week which then receives more tokens to be distributed. However the user has
        //      already claimed for that week so their share of these new tokens are lost.
        uint256 firstUnclaimableWeek = Math.min(
            _roundUpTimestamp(Math.min(_timeCursor, _userState[user].timeCursor)),
            _roundDownTimestamp(tokenState.timeCursor)
        );

        mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[token];
        mapping(uint256 => uint256) storage userBalanceAtTimestamp = _userBalanceAtTimestamp[user];

        uint256 amount;
        for (uint256 i = 0; i < 20;) {
            // We clearly cannot claim for `firstUnclaimableWeek` and so we break here.
            if (nextUserTokenWeekToClaim >= firstUnclaimableWeek) break;

            unchecked {
                amount += (tokensPerWeek[nextUserTokenWeekToClaim] * userBalanceAtTimestamp[nextUserTokenWeekToClaim])
                    / _veSupplyCache[nextUserTokenWeekToClaim];
                nextUserTokenWeekToClaim += 1 weeks;
                ++i;
            }
        }
        // Update the stored user-token time cursor to prevent this user claiming this week again.
        _userTokenTimeCursor[user][token] = nextUserTokenWeekToClaim;

        if (amount > 0) {
            unchecked {
                // For a token to be claimable it must have been added to the cached balance so this is safe.
                tokenState.cachedBalance = uint128(tokenState.cachedBalance - amount);
            }

            token.safeTransfer(user, amount);
            emit TokensClaimed(user, token, amount, nextUserTokenWeekToClaim);
        }

        return amount;
    }

    /**
     * @dev Calculate the amount of `token` to be distributed to `_votingEscrow` holders since the last checkpoint.
     */
    function _checkpointToken(ERC20 token, bool force) internal {
        TokenState storage tokenState = _tokenState[token];
        uint256 lastTokenTime = tokenState.timeCursor;
        uint256 timeSinceLastCheckpoint;
        if (lastTokenTime == 0) {
            // If it's the first time we're checkpointing this token then start distributing from now.
            // Also mark at which timestamp users should start attempts to claim this token from.
            lastTokenTime = block.timestamp;
            tokenState.startTime = uint64(_roundDownTimestamp(block.timestamp));

            // Prevent someone from assigning tokens to an inaccessible week.
            require(block.timestamp > _startTime, "Fee distribution has not started yet");
        } else {
            unchecked {
                timeSinceLastCheckpoint = block.timestamp - lastTokenTime;

                if (!force) {
                    // Checkpointing N times within a single week is completely equivalent to checkpointing once at the end.
                    // We then want to get as close as possible to a single checkpoint every Wed 23:59 UTC to save gas.

                    // We then skip checkpointing if we're in the same week as the previous checkpoint.
                    bool alreadyCheckpointedThisWeek =
                        _roundDownTimestamp(block.timestamp) == _roundDownTimestamp(lastTokenTime);
                    // However we want to ensure that all of this week's fees are assigned to the current week without
                    // overspilling into the next week. To mitigate this, we checkpoint if we're near the end of the week.
                    bool nearingEndOfWeek = _roundUpTimestamp(block.timestamp) - block.timestamp < 1 days;

                    // This ensures that we checkpoint once at the beginning of the week and again for each user interaction
                    // towards the end of the week to give an accurate final reading of the balance.
                    if (alreadyCheckpointedThisWeek && !nearingEndOfWeek) {
                        return;
                    }
                }
            }
        }

        tokenState.timeCursor = uint64(block.timestamp);

        uint256 tokenBalance = token.balanceOf(address(this));
        uint256 newTokensToDistribute = tokenBalance - tokenState.cachedBalance;
        if (newTokensToDistribute == 0) return;
        require(tokenBalance <= type(uint128).max, "Maximum token balance exceeded");
        tokenState.cachedBalance = uint128(tokenBalance);

        uint256 firstIncompleteWeek = _roundDownTimestamp(lastTokenTime);
        uint256 nextWeek = 0;

        // Distribute `newTokensToDistribute` evenly across the time period from `lastTokenTime` to now.
        // These tokens are assigned to weeks proportionally to how much of this period falls into each week.
        mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[token];
        for (uint256 i = 0; i < 20;) {
            unchecked {
                // This is safe as we're incrementing a timestamp.
                nextWeek = firstIncompleteWeek + 1 weeks;
                if (block.timestamp < nextWeek) {
                    // `firstIncompleteWeek` is now the beginning of the current week, i.e. this is the final iteration.
                    if (timeSinceLastCheckpoint == 0 && block.timestamp == lastTokenTime) {
                        tokensPerWeek[firstIncompleteWeek] += newTokensToDistribute;
                    } else {
                        // block.timestamp >= lastTokenTime by definition.
                        tokensPerWeek[firstIncompleteWeek] +=
                            (newTokensToDistribute * (block.timestamp - lastTokenTime)) / timeSinceLastCheckpoint;
                    }
                    // As we've caught up to the present then we should now break.
                    break;
                } else {
                    // We've gone a full week or more without checkpointing so need to distribute tokens to previous weeks.
                    if (timeSinceLastCheckpoint == 0 && nextWeek == lastTokenTime) {
                        // It shouldn't be possible to enter this block
                        tokensPerWeek[firstIncompleteWeek] += newTokensToDistribute;
                    } else {
                        // nextWeek > lastTokenTime by definition.
                        tokensPerWeek[firstIncompleteWeek] +=
                            (newTokensToDistribute * (nextWeek - lastTokenTime)) / timeSinceLastCheckpoint;
                    }
                }

                // We've now "checkpointed" up to the beginning of next week so must update timestamps appropriately.
                lastTokenTime = nextWeek;
                firstIncompleteWeek = nextWeek;

                ++i;
            }
        }

        emit TokenCheckpointed(token, newTokensToDistribute, lastTokenTime);
    }

    /**
     * @dev Cache the `user`'s balance of `_votingEscrow` at the beginning of each new week
     */
    function _checkpointUserBalance(address user) internal {
        uint256 maxUserEpoch = _votingEscrow.user_point_epoch(user);

        // If user has no epochs then they have never locked vetoken.
        // They clearly will not then receive fees.
        if (maxUserEpoch == 0) return;

        UserState storage userState = _userState[user];

        // `nextWeekToCheckpoint` represents the timestamp of the beginning of the first week
        // which we haven't checkpointed the user's VotingEscrow balance yet.
        uint256 nextWeekToCheckpoint = userState.timeCursor;

        uint256 userEpoch;
        if (nextWeekToCheckpoint == 0) {
            // First checkpoint for user so need to do the initial binary search
            userEpoch = _findTimestampUserEpoch(user, _startTime, 0, maxUserEpoch);
        } else {
            if (nextWeekToCheckpoint >= block.timestamp) {
                // User has checkpointed the current week already so perform early return.
                // This prevents a user from processing epochs created later in this week, however this is not an issue
                // as if a significant number of these builds up then the user will skip past them with a binary search.
                return;
            }

            // Otherwise use the value saved from last time
            userEpoch = userState.lastEpochCheckpointed;

            unchecked {
                // This optimizes a scenario common for power users, which have frequent `VotingEscrow` interactions in
                // the same week. We assume that any such user is also claiming fees every week, and so we only perform
                // a binary search here rather than integrating it into the main search algorithm, effectively skipping
                // most of the week's irrelevant checkpoints.
                // The slight tradeoff is that users who have multiple infrequent `VotingEscrow` interactions and also don't
                // claim frequently will also perform the binary search, despite it not leading to gas savings.
                if (maxUserEpoch - userEpoch > 20) {
                    userEpoch = _findTimestampUserEpoch(user, nextWeekToCheckpoint, userEpoch, maxUserEpoch);
                }
            }
        }

        // Epoch 0 is always empty so bump onto the next one so that we start on a valid epoch.
        if (userEpoch == 0) {
            userEpoch = 1;
        }

        IVotingEscrow.Point memory nextUserPoint = _votingEscrow.user_point_history(user, userEpoch);

        // If this is the first checkpoint for the user, calculate the first week they're eligible for.
        // i.e. the timestamp of the first Thursday after they locked.
        // If this is earlier then the first distribution then fast forward to then.
        if (nextWeekToCheckpoint == 0) {
            // Disallow checkpointing before `startTime`.
            require(block.timestamp > _startTime, "Fee distribution has not started yet");
            nextWeekToCheckpoint = Math.max(_startTime, _roundUpTimestamp(nextUserPoint.ts));
            userState.startTime = uint64(nextWeekToCheckpoint);
        }

        // It's safe to increment `userEpoch` and `nextWeekToCheckpoint` in this loop as epochs and timestamps
        // are always much smaller than 2^256 and are being incremented by small values.
        IVotingEscrow.Point memory currentUserPoint;
        for (uint256 i = 0; i < 50;) {
            unchecked {
                if (nextWeekToCheckpoint >= nextUserPoint.ts && userEpoch <= maxUserEpoch) {
                    // The week being considered is contained in a user epoch after that described by `currentUserPoint`.
                    // We then shift `nextUserPoint` into `currentUserPoint` and query the Point for the next user epoch.
                    // We do this in order to step though epochs until we find the first epoch starting after
                    // `nextWeekToCheckpoint`, making the previous epoch the one that contains `nextWeekToCheckpoint`.
                    userEpoch += 1;
                    currentUserPoint = nextUserPoint;
                    if (userEpoch > maxUserEpoch) {
                        nextUserPoint = IVotingEscrow.Point(0, 0, 0, 0);
                    } else {
                        nextUserPoint = _votingEscrow.user_point_history(user, userEpoch);
                    }
                } else {
                    // The week being considered lies inside the user epoch described by `oldUserPoint`
                    // we can then use it to calculate the user's balance at the beginning of the week.
                    if (nextWeekToCheckpoint >= block.timestamp) {
                        // Break if we're trying to cache the user's balance at a timestamp in the future.
                        // We only perform this check here to ensure that we can still process checkpoints created
                        // in the current week.
                        break;
                    }

                    int128 dt = SafeCast.toInt128(SafeCast.toInt256(nextWeekToCheckpoint - currentUserPoint.ts));
                    uint256 userBalance = currentUserPoint.bias > currentUserPoint.slope * dt
                        ? uint256(SafeCast.toUint256(currentUserPoint.bias - currentUserPoint.slope * dt))
                        : 0;

                    // User's lock has expired and they haven't relocked yet.
                    if (userBalance == 0 && userEpoch > maxUserEpoch) {
                        nextWeekToCheckpoint = _roundUpTimestamp(block.timestamp);
                        break;
                    }

                    // User had a nonzero lock and so is eligible to collect fees.
                    _userBalanceAtTimestamp[user][nextWeekToCheckpoint] = userBalance;

                    nextWeekToCheckpoint += 1 weeks;
                }

                ++i;
            }
        }

        // We subtract off 1 from the userEpoch to step back once so that on the next attempt to checkpoint
        // the current `currentUserPoint` will be loaded as `nextUserPoint`. This ensures that we can't skip over the
        // user epoch containing `nextWeekToCheckpoint`.
        unchecked {
            // userEpoch > 0 so this is safe.
            userState.lastEpochCheckpointed = uint64(userEpoch - 1);
        }
        userState.timeCursor = uint64(nextWeekToCheckpoint);
    }

    /**
     * @dev Cache the totalSupply of VotingEscrow token at the beginning of each new week
     */
    function _checkpointTotalSupply() internal {
        uint256 nextWeekToCheckpoint = _timeCursor;
        uint256 weekStart = _roundDownTimestamp(block.timestamp);

        // We expect `timeCursor == weekStart + 1 weeks` when fully up to date.
        if (nextWeekToCheckpoint > weekStart || weekStart == block.timestamp) {
            // We've already checkpointed up to this week so perform early return
            return;
        }

        _votingEscrow.checkpoint();

        // Step through the each week and cache the total supply at beginning of week on this contract
        for (uint256 i = 0; i < 20;) {
            unchecked {
                if (nextWeekToCheckpoint > weekStart) break;

                _veSupplyCache[nextWeekToCheckpoint] = _votingEscrow.totalSupply(nextWeekToCheckpoint);

                // This is safe as we're incrementing a timestamp
                nextWeekToCheckpoint += 1 weeks;

                ++i;
            }
        }
        // Update state to the end of the current week (`weekStart` + 1 weeks)
        _timeCursor = nextWeekToCheckpoint;
    }

    // Helper functions

    /**
     * @dev Wrapper around `_userTokenTimeCursor` which returns the start timestamp for `token`
     * if `user` has not attempted to interact with it previously.
     */
    function _getUserTokenTimeCursor(address user, ERC20 token) internal view returns (uint256) {
        uint256 userTimeCursor = _userTokenTimeCursor[user][token];
        if (userTimeCursor > 0) return userTimeCursor;
        // This is the first time that the user has interacted with this token.
        // We then start from the latest out of either when `user` first locked vetoken or `token` was first checkpointed.
        return Math.max(_userState[user].startTime, _tokenState[token].startTime);
    }

    /**
     * @dev Return the user epoch number for `user` corresponding to the provided `timestamp`
     */
    function _findTimestampUserEpoch(address user, uint256 timestamp, uint256 minUserEpoch, uint256 maxUserEpoch)
        internal
        view
        returns (uint256)
    {
        uint256 min = minUserEpoch;
        uint256 max = maxUserEpoch;

        // Perform binary search through epochs to find epoch containing `timestamp`
        for (uint256 i = 0; i < 128;) {
            unchecked {
                if (min >= max) break;

                // Algorithm assumes that inputs are less than 2^128 so this operation is safe.
                // +2 avoids getting stuck in min == mid < max
                uint256 mid = (min + max + 2) / 2;
                IVotingEscrow.Point memory pt = _votingEscrow.user_point_history(user, mid);
                if (pt.ts <= timestamp) {
                    min = mid;
                } else {
                    // max > min so this is safe.
                    max = mid - 1;
                }
            }
        }
        return min;
    }

    /**
     * @dev Rounds the provided timestamp down to the beginning of the previous week (Thurs 00:00 UTC)
     */
    function _roundDownTimestamp(uint256 timestamp) private pure returns (uint256) {
        unchecked {
            // Division by zero or overflows are impossible here.
            return (timestamp / 1 weeks) * 1 weeks;
        }
    }

    /**
     * @dev Rounds the provided timestamp up to the beginning of the next week (Thurs 00:00 UTC)
     */
    function _roundUpTimestamp(uint256 timestamp) private pure returns (uint256) {
        unchecked {
            // Overflows are impossible here for all realistic inputs.
            return _roundDownTimestamp(timestamp + 1 weeks - 1);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";

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

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/cryptography/ECDSA.sol)

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address, RecoverError) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(
        bytes32 hash,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./ECDSA.sol";

/**
 * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
 *
 * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
 * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
 * they need in their contracts using a combination of `abi.encode` and `keccak256`.
 *
 * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
 * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
 * ({_hashTypedDataV4}).
 *
 * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
 * the chain id to protect against replay attacks on an eventual fork of the chain.
 *
 * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
 * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
 *
 * _Available since v3.4._
 */
abstract contract EIP712 {
    /* solhint-disable var-name-mixedcase */
    // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
    // invalidate the cached domain separator if the chain id changes.
    bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
    uint256 private immutable _CACHED_CHAIN_ID;
    address private immutable _CACHED_THIS;

    bytes32 private immutable _HASHED_NAME;
    bytes32 private immutable _HASHED_VERSION;
    bytes32 private immutable _TYPE_HASH;

    /* solhint-enable var-name-mixedcase */

    /**
     * @dev Initializes the domain separator and parameter caches.
     *
     * The meaning of `name` and `version` is specified in
     * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
     *
     * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
     * - `version`: the current major version of the signing domain.
     *
     * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
     * contract upgrade].
     */
    constructor(string memory name, string memory version) {
        bytes32 hashedName = keccak256(bytes(name));
        bytes32 hashedVersion = keccak256(bytes(version));
        bytes32 typeHash = keccak256(
            "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
        );
        _HASHED_NAME = hashedName;
        _HASHED_VERSION = hashedVersion;
        _CACHED_CHAIN_ID = block.chainid;
        _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
        _CACHED_THIS = address(this);
        _TYPE_HASH = typeHash;
    }

    /**
     * @dev Returns the domain separator for the current chain.
     */
    function _domainSeparatorV4() internal view returns (bytes32) {
        if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
            return _CACHED_DOMAIN_SEPARATOR;
        } else {
            return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
        }
    }

    function _buildDomainSeparator(
        bytes32 typeHash,
        bytes32 nameHash,
        bytes32 versionHash
    ) private view returns (bytes32) {
        return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
    }

    /**
     * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
     * function returns the hash of the fully encoded EIP712 message for this domain.
     *
     * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
     *
     * ```solidity
     * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
     *     keccak256("Mail(address to,string contents)"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

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

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File 6 of 15 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.0;

/**
 * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
 * checks.
 *
 * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
 * easily result in undesired exploitation or bugs, since developers usually
 * assume that overflows raise errors. `SafeCast` restores this intuition by
 * reverting the transaction when such an operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 *
 * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
 * all math on `uint256` and `int256` and then downcasting.
 */
library SafeCast {
    /**
     * @dev Returns the downcasted uint248 from uint256, reverting on
     * overflow (when the input is greater than largest uint248).
     *
     * Counterpart to Solidity's `uint248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     *
     * _Available since v4.7._
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        require(value <= type(uint248).max, "SafeCast: value doesn't fit in 248 bits");
        return uint248(value);
    }

    /**
     * @dev Returns the downcasted uint240 from uint256, reverting on
     * overflow (when the input is greater than largest uint240).
     *
     * Counterpart to Solidity's `uint240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     *
     * _Available since v4.7._
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        require(value <= type(uint240).max, "SafeCast: value doesn't fit in 240 bits");
        return uint240(value);
    }

    /**
     * @dev Returns the downcasted uint232 from uint256, reverting on
     * overflow (when the input is greater than largest uint232).
     *
     * Counterpart to Solidity's `uint232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     *
     * _Available since v4.7._
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        require(value <= type(uint232).max, "SafeCast: value doesn't fit in 232 bits");
        return uint232(value);
    }

    /**
     * @dev Returns the downcasted uint224 from uint256, reverting on
     * overflow (when the input is greater than largest uint224).
     *
     * Counterpart to Solidity's `uint224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     *
     * _Available since v4.2._
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
        return uint224(value);
    }

    /**
     * @dev Returns the downcasted uint216 from uint256, reverting on
     * overflow (when the input is greater than largest uint216).
     *
     * Counterpart to Solidity's `uint216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     *
     * _Available since v4.7._
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        require(value <= type(uint216).max, "SafeCast: value doesn't fit in 216 bits");
        return uint216(value);
    }

    /**
     * @dev Returns the downcasted uint208 from uint256, reverting on
     * overflow (when the input is greater than largest uint208).
     *
     * Counterpart to Solidity's `uint208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     *
     * _Available since v4.7._
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        require(value <= type(uint208).max, "SafeCast: value doesn't fit in 208 bits");
        return uint208(value);
    }

    /**
     * @dev Returns the downcasted uint200 from uint256, reverting on
     * overflow (when the input is greater than largest uint200).
     *
     * Counterpart to Solidity's `uint200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     *
     * _Available since v4.7._
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        require(value <= type(uint200).max, "SafeCast: value doesn't fit in 200 bits");
        return uint200(value);
    }

    /**
     * @dev Returns the downcasted uint192 from uint256, reverting on
     * overflow (when the input is greater than largest uint192).
     *
     * Counterpart to Solidity's `uint192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     *
     * _Available since v4.7._
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        require(value <= type(uint192).max, "SafeCast: value doesn't fit in 192 bits");
        return uint192(value);
    }

    /**
     * @dev Returns the downcasted uint184 from uint256, reverting on
     * overflow (when the input is greater than largest uint184).
     *
     * Counterpart to Solidity's `uint184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     *
     * _Available since v4.7._
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        require(value <= type(uint184).max, "SafeCast: value doesn't fit in 184 bits");
        return uint184(value);
    }

    /**
     * @dev Returns the downcasted uint176 from uint256, reverting on
     * overflow (when the input is greater than largest uint176).
     *
     * Counterpart to Solidity's `uint176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     *
     * _Available since v4.7._
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        require(value <= type(uint176).max, "SafeCast: value doesn't fit in 176 bits");
        return uint176(value);
    }

    /**
     * @dev Returns the downcasted uint168 from uint256, reverting on
     * overflow (when the input is greater than largest uint168).
     *
     * Counterpart to Solidity's `uint168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     *
     * _Available since v4.7._
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        require(value <= type(uint168).max, "SafeCast: value doesn't fit in 168 bits");
        return uint168(value);
    }

    /**
     * @dev Returns the downcasted uint160 from uint256, reverting on
     * overflow (when the input is greater than largest uint160).
     *
     * Counterpart to Solidity's `uint160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     *
     * _Available since v4.7._
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        require(value <= type(uint160).max, "SafeCast: value doesn't fit in 160 bits");
        return uint160(value);
    }

    /**
     * @dev Returns the downcasted uint152 from uint256, reverting on
     * overflow (when the input is greater than largest uint152).
     *
     * Counterpart to Solidity's `uint152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     *
     * _Available since v4.7._
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        require(value <= type(uint152).max, "SafeCast: value doesn't fit in 152 bits");
        return uint152(value);
    }

    /**
     * @dev Returns the downcasted uint144 from uint256, reverting on
     * overflow (when the input is greater than largest uint144).
     *
     * Counterpart to Solidity's `uint144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     *
     * _Available since v4.7._
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        require(value <= type(uint144).max, "SafeCast: value doesn't fit in 144 bits");
        return uint144(value);
    }

    /**
     * @dev Returns the downcasted uint136 from uint256, reverting on
     * overflow (when the input is greater than largest uint136).
     *
     * Counterpart to Solidity's `uint136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     *
     * _Available since v4.7._
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        require(value <= type(uint136).max, "SafeCast: value doesn't fit in 136 bits");
        return uint136(value);
    }

    /**
     * @dev Returns the downcasted uint128 from uint256, reverting on
     * overflow (when the input is greater than largest uint128).
     *
     * Counterpart to Solidity's `uint128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v2.5._
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
        return uint128(value);
    }

    /**
     * @dev Returns the downcasted uint120 from uint256, reverting on
     * overflow (when the input is greater than largest uint120).
     *
     * Counterpart to Solidity's `uint120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     *
     * _Available since v4.7._
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        require(value <= type(uint120).max, "SafeCast: value doesn't fit in 120 bits");
        return uint120(value);
    }

    /**
     * @dev Returns the downcasted uint112 from uint256, reverting on
     * overflow (when the input is greater than largest uint112).
     *
     * Counterpart to Solidity's `uint112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     *
     * _Available since v4.7._
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        require(value <= type(uint112).max, "SafeCast: value doesn't fit in 112 bits");
        return uint112(value);
    }

    /**
     * @dev Returns the downcasted uint104 from uint256, reverting on
     * overflow (when the input is greater than largest uint104).
     *
     * Counterpart to Solidity's `uint104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     *
     * _Available since v4.7._
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
        return uint104(value);
    }

    /**
     * @dev Returns the downcasted uint96 from uint256, reverting on
     * overflow (when the input is greater than largest uint96).
     *
     * Counterpart to Solidity's `uint96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     *
     * _Available since v4.2._
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
        return uint96(value);
    }

    /**
     * @dev Returns the downcasted uint88 from uint256, reverting on
     * overflow (when the input is greater than largest uint88).
     *
     * Counterpart to Solidity's `uint88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     *
     * _Available since v4.7._
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        require(value <= type(uint88).max, "SafeCast: value doesn't fit in 88 bits");
        return uint88(value);
    }

    /**
     * @dev Returns the downcasted uint80 from uint256, reverting on
     * overflow (when the input is greater than largest uint80).
     *
     * Counterpart to Solidity's `uint80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     *
     * _Available since v4.7._
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        require(value <= type(uint80).max, "SafeCast: value doesn't fit in 80 bits");
        return uint80(value);
    }

    /**
     * @dev Returns the downcasted uint72 from uint256, reverting on
     * overflow (when the input is greater than largest uint72).
     *
     * Counterpart to Solidity's `uint72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     *
     * _Available since v4.7._
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        require(value <= type(uint72).max, "SafeCast: value doesn't fit in 72 bits");
        return uint72(value);
    }

    /**
     * @dev Returns the downcasted uint64 from uint256, reverting on
     * overflow (when the input is greater than largest uint64).
     *
     * Counterpart to Solidity's `uint64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v2.5._
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
        return uint64(value);
    }

    /**
     * @dev Returns the downcasted uint56 from uint256, reverting on
     * overflow (when the input is greater than largest uint56).
     *
     * Counterpart to Solidity's `uint56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     *
     * _Available since v4.7._
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        require(value <= type(uint56).max, "SafeCast: value doesn't fit in 56 bits");
        return uint56(value);
    }

    /**
     * @dev Returns the downcasted uint48 from uint256, reverting on
     * overflow (when the input is greater than largest uint48).
     *
     * Counterpart to Solidity's `uint48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     *
     * _Available since v4.7._
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        require(value <= type(uint48).max, "SafeCast: value doesn't fit in 48 bits");
        return uint48(value);
    }

    /**
     * @dev Returns the downcasted uint40 from uint256, reverting on
     * overflow (when the input is greater than largest uint40).
     *
     * Counterpart to Solidity's `uint40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     *
     * _Available since v4.7._
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        require(value <= type(uint40).max, "SafeCast: value doesn't fit in 40 bits");
        return uint40(value);
    }

    /**
     * @dev Returns the downcasted uint32 from uint256, reverting on
     * overflow (when the input is greater than largest uint32).
     *
     * Counterpart to Solidity's `uint32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v2.5._
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
        return uint32(value);
    }

    /**
     * @dev Returns the downcasted uint24 from uint256, reverting on
     * overflow (when the input is greater than largest uint24).
     *
     * Counterpart to Solidity's `uint24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     *
     * _Available since v4.7._
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        require(value <= type(uint24).max, "SafeCast: value doesn't fit in 24 bits");
        return uint24(value);
    }

    /**
     * @dev Returns the downcasted uint16 from uint256, reverting on
     * overflow (when the input is greater than largest uint16).
     *
     * Counterpart to Solidity's `uint16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v2.5._
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
        return uint16(value);
    }

    /**
     * @dev Returns the downcasted uint8 from uint256, reverting on
     * overflow (when the input is greater than largest uint8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     *
     * _Available since v2.5._
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     *
     * _Available since v3.0._
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(value);
    }

    /**
     * @dev Returns the downcasted int248 from int256, reverting on
     * overflow (when the input is less than smallest int248 or
     * greater than largest int248).
     *
     * Counterpart to Solidity's `int248` operator.
     *
     * Requirements:
     *
     * - input must fit into 248 bits
     *
     * _Available since v4.7._
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 248 bits");
    }

    /**
     * @dev Returns the downcasted int240 from int256, reverting on
     * overflow (when the input is less than smallest int240 or
     * greater than largest int240).
     *
     * Counterpart to Solidity's `int240` operator.
     *
     * Requirements:
     *
     * - input must fit into 240 bits
     *
     * _Available since v4.7._
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 240 bits");
    }

    /**
     * @dev Returns the downcasted int232 from int256, reverting on
     * overflow (when the input is less than smallest int232 or
     * greater than largest int232).
     *
     * Counterpart to Solidity's `int232` operator.
     *
     * Requirements:
     *
     * - input must fit into 232 bits
     *
     * _Available since v4.7._
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 232 bits");
    }

    /**
     * @dev Returns the downcasted int224 from int256, reverting on
     * overflow (when the input is less than smallest int224 or
     * greater than largest int224).
     *
     * Counterpart to Solidity's `int224` operator.
     *
     * Requirements:
     *
     * - input must fit into 224 bits
     *
     * _Available since v4.7._
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 224 bits");
    }

    /**
     * @dev Returns the downcasted int216 from int256, reverting on
     * overflow (when the input is less than smallest int216 or
     * greater than largest int216).
     *
     * Counterpart to Solidity's `int216` operator.
     *
     * Requirements:
     *
     * - input must fit into 216 bits
     *
     * _Available since v4.7._
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 216 bits");
    }

    /**
     * @dev Returns the downcasted int208 from int256, reverting on
     * overflow (when the input is less than smallest int208 or
     * greater than largest int208).
     *
     * Counterpart to Solidity's `int208` operator.
     *
     * Requirements:
     *
     * - input must fit into 208 bits
     *
     * _Available since v4.7._
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 208 bits");
    }

    /**
     * @dev Returns the downcasted int200 from int256, reverting on
     * overflow (when the input is less than smallest int200 or
     * greater than largest int200).
     *
     * Counterpart to Solidity's `int200` operator.
     *
     * Requirements:
     *
     * - input must fit into 200 bits
     *
     * _Available since v4.7._
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 200 bits");
    }

    /**
     * @dev Returns the downcasted int192 from int256, reverting on
     * overflow (when the input is less than smallest int192 or
     * greater than largest int192).
     *
     * Counterpart to Solidity's `int192` operator.
     *
     * Requirements:
     *
     * - input must fit into 192 bits
     *
     * _Available since v4.7._
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 192 bits");
    }

    /**
     * @dev Returns the downcasted int184 from int256, reverting on
     * overflow (when the input is less than smallest int184 or
     * greater than largest int184).
     *
     * Counterpart to Solidity's `int184` operator.
     *
     * Requirements:
     *
     * - input must fit into 184 bits
     *
     * _Available since v4.7._
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 184 bits");
    }

    /**
     * @dev Returns the downcasted int176 from int256, reverting on
     * overflow (when the input is less than smallest int176 or
     * greater than largest int176).
     *
     * Counterpart to Solidity's `int176` operator.
     *
     * Requirements:
     *
     * - input must fit into 176 bits
     *
     * _Available since v4.7._
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 176 bits");
    }

    /**
     * @dev Returns the downcasted int168 from int256, reverting on
     * overflow (when the input is less than smallest int168 or
     * greater than largest int168).
     *
     * Counterpart to Solidity's `int168` operator.
     *
     * Requirements:
     *
     * - input must fit into 168 bits
     *
     * _Available since v4.7._
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 168 bits");
    }

    /**
     * @dev Returns the downcasted int160 from int256, reverting on
     * overflow (when the input is less than smallest int160 or
     * greater than largest int160).
     *
     * Counterpart to Solidity's `int160` operator.
     *
     * Requirements:
     *
     * - input must fit into 160 bits
     *
     * _Available since v4.7._
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 160 bits");
    }

    /**
     * @dev Returns the downcasted int152 from int256, reverting on
     * overflow (when the input is less than smallest int152 or
     * greater than largest int152).
     *
     * Counterpart to Solidity's `int152` operator.
     *
     * Requirements:
     *
     * - input must fit into 152 bits
     *
     * _Available since v4.7._
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 152 bits");
    }

    /**
     * @dev Returns the downcasted int144 from int256, reverting on
     * overflow (when the input is less than smallest int144 or
     * greater than largest int144).
     *
     * Counterpart to Solidity's `int144` operator.
     *
     * Requirements:
     *
     * - input must fit into 144 bits
     *
     * _Available since v4.7._
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 144 bits");
    }

    /**
     * @dev Returns the downcasted int136 from int256, reverting on
     * overflow (when the input is less than smallest int136 or
     * greater than largest int136).
     *
     * Counterpart to Solidity's `int136` operator.
     *
     * Requirements:
     *
     * - input must fit into 136 bits
     *
     * _Available since v4.7._
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 136 bits");
    }

    /**
     * @dev Returns the downcasted int128 from int256, reverting on
     * overflow (when the input is less than smallest int128 or
     * greater than largest int128).
     *
     * Counterpart to Solidity's `int128` operator.
     *
     * Requirements:
     *
     * - input must fit into 128 bits
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
    }

    /**
     * @dev Returns the downcasted int120 from int256, reverting on
     * overflow (when the input is less than smallest int120 or
     * greater than largest int120).
     *
     * Counterpart to Solidity's `int120` operator.
     *
     * Requirements:
     *
     * - input must fit into 120 bits
     *
     * _Available since v4.7._
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 120 bits");
    }

    /**
     * @dev Returns the downcasted int112 from int256, reverting on
     * overflow (when the input is less than smallest int112 or
     * greater than largest int112).
     *
     * Counterpart to Solidity's `int112` operator.
     *
     * Requirements:
     *
     * - input must fit into 112 bits
     *
     * _Available since v4.7._
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 112 bits");
    }

    /**
     * @dev Returns the downcasted int104 from int256, reverting on
     * overflow (when the input is less than smallest int104 or
     * greater than largest int104).
     *
     * Counterpart to Solidity's `int104` operator.
     *
     * Requirements:
     *
     * - input must fit into 104 bits
     *
     * _Available since v4.7._
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 104 bits");
    }

    /**
     * @dev Returns the downcasted int96 from int256, reverting on
     * overflow (when the input is less than smallest int96 or
     * greater than largest int96).
     *
     * Counterpart to Solidity's `int96` operator.
     *
     * Requirements:
     *
     * - input must fit into 96 bits
     *
     * _Available since v4.7._
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 96 bits");
    }

    /**
     * @dev Returns the downcasted int88 from int256, reverting on
     * overflow (when the input is less than smallest int88 or
     * greater than largest int88).
     *
     * Counterpart to Solidity's `int88` operator.
     *
     * Requirements:
     *
     * - input must fit into 88 bits
     *
     * _Available since v4.7._
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 88 bits");
    }

    /**
     * @dev Returns the downcasted int80 from int256, reverting on
     * overflow (when the input is less than smallest int80 or
     * greater than largest int80).
     *
     * Counterpart to Solidity's `int80` operator.
     *
     * Requirements:
     *
     * - input must fit into 80 bits
     *
     * _Available since v4.7._
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 80 bits");
    }

    /**
     * @dev Returns the downcasted int72 from int256, reverting on
     * overflow (when the input is less than smallest int72 or
     * greater than largest int72).
     *
     * Counterpart to Solidity's `int72` operator.
     *
     * Requirements:
     *
     * - input must fit into 72 bits
     *
     * _Available since v4.7._
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 72 bits");
    }

    /**
     * @dev Returns the downcasted int64 from int256, reverting on
     * overflow (when the input is less than smallest int64 or
     * greater than largest int64).
     *
     * Counterpart to Solidity's `int64` operator.
     *
     * Requirements:
     *
     * - input must fit into 64 bits
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 64 bits");
    }

    /**
     * @dev Returns the downcasted int56 from int256, reverting on
     * overflow (when the input is less than smallest int56 or
     * greater than largest int56).
     *
     * Counterpart to Solidity's `int56` operator.
     *
     * Requirements:
     *
     * - input must fit into 56 bits
     *
     * _Available since v4.7._
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 56 bits");
    }

    /**
     * @dev Returns the downcasted int48 from int256, reverting on
     * overflow (when the input is less than smallest int48 or
     * greater than largest int48).
     *
     * Counterpart to Solidity's `int48` operator.
     *
     * Requirements:
     *
     * - input must fit into 48 bits
     *
     * _Available since v4.7._
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 48 bits");
    }

    /**
     * @dev Returns the downcasted int40 from int256, reverting on
     * overflow (when the input is less than smallest int40 or
     * greater than largest int40).
     *
     * Counterpart to Solidity's `int40` operator.
     *
     * Requirements:
     *
     * - input must fit into 40 bits
     *
     * _Available since v4.7._
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 40 bits");
    }

    /**
     * @dev Returns the downcasted int32 from int256, reverting on
     * overflow (when the input is less than smallest int32 or
     * greater than largest int32).
     *
     * Counterpart to Solidity's `int32` operator.
     *
     * Requirements:
     *
     * - input must fit into 32 bits
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 32 bits");
    }

    /**
     * @dev Returns the downcasted int24 from int256, reverting on
     * overflow (when the input is less than smallest int24 or
     * greater than largest int24).
     *
     * Counterpart to Solidity's `int24` operator.
     *
     * Requirements:
     *
     * - input must fit into 24 bits
     *
     * _Available since v4.7._
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 24 bits");
    }

    /**
     * @dev Returns the downcasted int16 from int256, reverting on
     * overflow (when the input is less than smallest int16 or
     * greater than largest int16).
     *
     * Counterpart to Solidity's `int16` operator.
     *
     * Requirements:
     *
     * - input must fit into 16 bits
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 16 bits");
    }

    /**
     * @dev Returns the downcasted int8 from int256, reverting on
     * overflow (when the input is less than smallest int8 or
     * greater than largest int8).
     *
     * Counterpart to Solidity's `int8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 8 bits");
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     *
     * _Available since v3.0._
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

File 8 of 15 : ReentrancyGuard.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import {ERC20} from "solmate/tokens/ERC20.sol";

import "./IVotingEscrow.sol";

/**
 * @title Fee Distributor
 * @notice Distributes any tokens transferred to the contract (e.g. Protocol fees and any BAL emissions) among veBAL
 * holders proportionally based on a snapshot of the week at which the tokens are sent to the FeeDistributor contract.
 * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBAL
 * holders simply transfer the tokens to the `FeeDistributor` contract and then call `checkpointToken`.
 */
interface IFeeDistributor {
    event TokenCheckpointed(ERC20 token, uint256 amount, uint256 lastCheckpointTimestamp);
    event TokensClaimed(address user, ERC20 token, uint256 amount, uint256 userTokenTimeCursor);

    /**
     * @notice Returns the VotingEscrow (veBAL) token contract
     */
    function getVotingEscrow() external view returns (IVotingEscrow);

    /**
     * @notice Returns the global time cursor representing the most earliest uncheckpointed week.
     */
    function getTimeCursor() external view returns (uint256);

    /**
     * @notice Returns the user-level time cursor representing the most earliest uncheckpointed week.
     * @param user - The address of the user to query.
     */
    function getUserTimeCursor(address user) external view returns (uint256);

    /**
     * @notice Returns the token-level time cursor storing the timestamp at up to which tokens have been distributed.
     * @param token - The ERC20 token address to query.
     */
    function getTokenTimeCursor(ERC20 token) external view returns (uint256);

    /**
     * @notice Returns the user-level time cursor storing the timestamp of the latest token distribution claimed.
     * @param user - The address of the user to query.
     * @param token - The ERC20 token address to query.
     */
    function getUserTokenTimeCursor(address user, ERC20 token) external view returns (uint256);

    /**
     * @notice Returns the user's cached balance of veBAL as of the provided timestamp.
     * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values.
     * This function requires `user` to have been checkpointed past `timestamp` so that their balance is cached.
     * @param user - The address of the user of which to read the cached balance of.
     * @param timestamp - The timestamp at which to read the `user`'s cached balance at.
     */
    function getUserBalanceAtTimestamp(address user, uint256 timestamp) external view returns (uint256);

    /**
     * @notice Returns the cached total supply of veBAL as of the provided timestamp.
     * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values.
     * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached.
     * @param timestamp - The timestamp at which to read the cached total supply at.
     */
    function getTotalSupplyAtTimestamp(uint256 timestamp) external view returns (uint256);

    /**
     * @notice Returns the FeeDistributor's cached balance of `token`.
     */
    function getTokenLastBalance(ERC20 token) external view returns (uint256);

    /**
     * @notice Returns the amount of `token` which the FeeDistributor received in the week beginning at `timestamp`.
     * @param token - The ERC20 token address to query.
     * @param timestamp - The timestamp corresponding to the beginning of the week of interest.
     */
    function getTokensDistributedInWeek(ERC20 token, uint256 timestamp) external view returns (uint256);

    // Depositing

    /**
     * @notice Deposits tokens to be distributed in the current week.
     * @dev Sending tokens directly to the FeeDistributor instead of using `depositTokens` may result in tokens being
     * retroactively distributed to past weeks, or for the distribution to carry over to future weeks.
     *
     * If for some reason `depositTokens` cannot be called, in order to ensure that all tokens are correctly distributed
     * manually call `checkpointToken` before and after the token transfer.
     * @param token - The ERC20 token address to distribute.
     * @param amount - The amount of tokens to deposit.
     */
    function depositToken(ERC20 token, uint256 amount) external;

    /**
     * @notice Deposits tokens to be distributed in the current week.
     * @dev A version of `depositToken` which supports depositing multiple `tokens` at once.
     * See `depositToken` for more details.
     * @param tokens - An array of ERC20 token addresses to distribute.
     * @param amounts - An array of token amounts to deposit.
     */
    function depositTokens(ERC20[] calldata tokens, uint256[] calldata amounts) external;

    // Checkpointing

    /**
     * @notice Caches the total supply of veBAL at the beginning of each week.
     * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
     */
    function checkpoint() external;

    /**
     * @notice Caches the user's balance of veBAL at the beginning of each week.
     * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
     * @param user - The address of the user to be checkpointed.
     */
    function checkpointUser(address user) external;

    /**
     * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
     * @dev Any `token` balance held by the FeeDistributor above that which is returned by `getTokenLastBalance`
     * will be distributed evenly across the time period since `token` was last checkpointed.
     *
     * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
     * @param token - The ERC20 token address to be checkpointed.
     */
    function checkpointToken(ERC20 token) external;

    /**
     * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
     * @dev A version of `checkpointToken` which supports checkpointing multiple tokens.
     * See `checkpointToken` for more details.
     * @param tokens - An array of ERC20 token addresses to be checkpointed.
     */
    function checkpointTokens(ERC20[] calldata tokens) external;

    // Claiming

    /**
     * @notice Claims all pending distributions of the provided token for a user.
     * @dev It's not necessary to explicitly checkpoint before calling this function, it will ensure the FeeDistributor
     * is up to date before calculating the amount of tokens to be claimed.
     * @param user - The user on behalf of which to claim.
     * @param token - The ERC20 token address to be claimed.
     * @return The amount of `token` sent to `user` as a result of claiming.
     */
    function claimToken(address user, ERC20 token) external returns (uint256);

    /**
     * @notice Claims a number of tokens on behalf of a user.
     * @dev A version of `claimToken` which supports claiming multiple `tokens` on behalf of `user`.
     * See `claimToken` for more details.
     * @param user - The user on behalf of which to claim.
     * @param tokens - An array of ERC20 token addresses to be claimed.
     * @return An array of the amounts of each token in `tokens` sent to `user` as a result of claiming.
     */
    function claimTokens(address user, ERC20[] calldata tokens) external returns (uint256[] memory);
}

// SPDX-License-Identifier: GPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.6.11;

/**
 * @dev Interface for the OptionalOnlyCaller helper, used to opt in to a caller
 * verification for a given address to methods that are otherwise callable by any address.
 */
interface IOptionalOnlyCaller {
    /**
     * @dev Emitted every time setOnlyCallerCheck is called.
     */
    event OnlyCallerOptIn(address user, bool enabled);

    /**
     * @dev Enables / disables verification mechanism for caller.
     * @param enabled - True if caller verification shall be enabled, false otherwise.
     */
    function setOnlyCallerCheck(bool enabled) external;

    function setOnlyCallerCheckWithSignature(address user, bool enabled, bytes memory signature) external;

    /**
     * @dev Returns true if caller verification is enabled for the given user, false otherwise.
     */
    function isOnlyCallerEnabled(address user) external view returns (bool);
}

// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.6.11;

/**
 * @dev Interface for the SignatureValidator helper, used to support meta-transactions.
 */
interface ISignaturesValidator {
    /**
     * @dev Returns the EIP712 domain separator.
     */
    function getDomainSeparator() external view returns (bytes32);

    /**
     * @dev Returns the next nonce used by an address to sign messages.
     */
    function getNextNonce(address user) external view returns (uint256);
}

// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity >=0.6.11;

pragma experimental ABIEncoderV2;

// For compatibility, we're keeping the same function names as in the original Curve code, including the mixed-case
// naming convention.
// solhint-disable func-name-mixedcase

interface IVotingEscrow {
    struct Point {
        int128 bias;
        int128 slope; // - dweight / dt
        uint256 ts;
        uint256 blk; // block
    }

    function epoch() external view returns (uint256);

    function totalSupply(uint256 timestamp) external view returns (uint256);

    function user_point_epoch(address user) external view returns (uint256);

    function point_history(uint256 timestamp) external view returns (Point memory);

    function user_point_history(address user, uint256 timestamp) external view returns (Point memory);

    function checkpoint() external;

    function admin() external view returns (address);

    function smart_wallet_checker() external view returns (address);

    function commit_smart_wallet_checker(address newSmartWalletChecker) external;

    function apply_smart_wallet_checker() external;
}

// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import "openzeppelin-contracts/utils/cryptography/EIP712.sol";

import "../interfaces/ISignaturesValidator.sol";

/**
 * @dev Utility for signing Solidity function calls.
 */
abstract contract EOASignaturesValidator is ISignaturesValidator, EIP712 {
    error EOASignaturesValidator__ExpiredSignature();
    error EOASignaturesValidator__MalformedSignature();
    error EOASignaturesValidator__RevertWithErrorCode(uint256 errorCode);

    // Replay attack prevention for each account.
    mapping(address => uint256) internal _nextNonce;

    function getDomainSeparator() public view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    function getNextNonce(address account) public view override returns (uint256) {
        return _nextNonce[account];
    }

    function _ensureValidSignature(address account, bytes32 structHash, bytes memory signature, uint256 errorCode)
        internal
    {
        return _ensureValidSignature(account, structHash, signature, type(uint256).max, errorCode);
    }

    function _ensureValidSignature(
        address account,
        bytes32 structHash,
        bytes memory signature,
        uint256 deadline,
        uint256 errorCode
    ) internal {
        bytes32 digest = _hashTypedDataV4(structHash);
        if (!_isValidSignature(account, digest, signature)) {
            revert EOASignaturesValidator__RevertWithErrorCode(errorCode);
        }

        // We could check for the deadline before validating the signature, but this leads to saner error processing (as
        // we only care about expired deadlines if the signature is correct) and only affects the gas cost of the revert
        // scenario, which will only occur infrequently, if ever.
        // The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy.
        // solhint-disable-next-line not-rely-on-time
        if (deadline < block.timestamp) {
            revert EOASignaturesValidator__ExpiredSignature();
        }

        // We only advance the nonce after validating the signature. This is irrelevant for this module, but it can be
        // important in derived contracts that override _isValidSignature (e.g. SignaturesValidator), as we want for
        // the observable state to still have the current nonce as the next valid one.
        _nextNonce[account] += 1;
    }

    function _isValidSignature(address account, bytes32 digest, bytes memory signature)
        internal
        view
        virtual
        returns (bool)
    {
        if (signature.length != 65) {
            revert EOASignaturesValidator__MalformedSignature();
        }

        bytes32 r;
        bytes32 s;
        uint8 v;

        // ecrecover takes the r, s and v signature parameters, and the only way to get them is to use assembly.
        // solhint-disable-next-line no-inline-assembly
        assembly {
            r := mload(add(signature, 0x20))
            s := mload(add(signature, 0x40))
            v := byte(0, mload(add(signature, 0x60)))
        }

        address recoveredAddress = ecrecover(digest, v, r, s);

        // ecrecover returns the zero address on recover failure, so we need to handle that explicitly.
        return (recoveredAddress != address(0) && recoveredAddress == account);
    }
}

// SPDX-License-Identifier: GPL-3.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.8.0;

import "./EOASignaturesValidator.sol";
import "../interfaces/IOptionalOnlyCaller.sol";

abstract contract OptionalOnlyCaller is IOptionalOnlyCaller, EOASignaturesValidator {
    error OptionalOnlyCaller__SenderNotAllowed();

    mapping(address => bool) private _isOnlyCallerEnabled;

    uint256 private constant INVALID_SIGNATURE_ERROR = 1;
    bytes32 private constant _SET_ONLY_CALLER_CHECK_TYPEHASH =
        keccak256("SetOnlyCallerCheck(address user,bool enabled,uint256 nonce)");

    /**
     * @dev Reverts if the verification mechanism is enabled and the given address is not the caller.
     * @param user - Address to validate as the only allowed caller, if the verification is enabled.
     */
    modifier optionalOnlyCaller(address user) {
        _verifyCaller(user);
        _;
    }

    function setOnlyCallerCheck(bool enabled) external override {
        _setOnlyCallerCheck(msg.sender, enabled);
    }

    function setOnlyCallerCheckWithSignature(address user, bool enabled, bytes memory signature) external override {
        bytes32 structHash = keccak256(abi.encode(_SET_ONLY_CALLER_CHECK_TYPEHASH, user, enabled, getNextNonce(user)));
        _ensureValidSignature(user, structHash, signature, INVALID_SIGNATURE_ERROR);
        _setOnlyCallerCheck(user, enabled);
    }

    function _setOnlyCallerCheck(address user, bool enabled) private {
        _isOnlyCallerEnabled[user] = enabled;
        emit OnlyCallerOptIn(user, enabled);
    }

    function isOnlyCallerEnabled(address user) external view override returns (bool) {
        return _isOnlyCallerEnabled[user];
    }

    function _verifyCaller(address user) private view {
        if (_isOnlyCallerEnabled[user]) {
            if (msg.sender != user) {
                revert OptionalOnlyCaller__SenderNotAllowed();
            }
        }
    }
}

Settings
{
  "remappings": [
    "create3-factory/=lib/create3-factory/src/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/",
    "solmate/=lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 1000000
  },
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract IVotingEscrow","name":"votingEscrow","type":"address"},{"internalType":"uint256","name":"startTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"EOASignaturesValidator__ExpiredSignature","type":"error"},{"inputs":[],"name":"EOASignaturesValidator__MalformedSignature","type":"error"},{"inputs":[{"internalType":"uint256","name":"errorCode","type":"uint256"}],"name":"EOASignaturesValidator__RevertWithErrorCode","type":"error"},{"inputs":[],"name":"FeeDistributor__CannotStartBeforeCurrentWeek","type":"error"},{"inputs":[],"name":"FeeDistributor__InputLengthMismatch","type":"error"},{"inputs":[],"name":"FeeDistributor__VotingEscrowZeroTotalSupply","type":"error"},{"inputs":[],"name":"OptionalOnlyCaller__SenderNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"bool","name":"enabled","type":"bool"}],"name":"OnlyCallerOptIn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract ERC20","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lastCheckpointTimestamp","type":"uint256"}],"name":"TokenCheckpointed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"contract ERC20","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"userTokenTimeCursor","type":"uint256"}],"name":"TokensClaimed","type":"event"},{"inputs":[],"name":"checkpoint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"}],"name":"checkpointToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20[]","name":"tokens","type":"address[]"}],"name":"checkpointTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"checkpointUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"contract ERC20","name":"token","type":"address"}],"name":"claimToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"contract ERC20[]","name":"tokens","type":"address[]"}],"name":"claimTokens","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ERC20[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"name":"depositTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getDomainSeparator","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"getNextNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTimeCursor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"}],"name":"getTokenLastBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"}],"name":"getTokenTimeCursor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract ERC20","name":"token","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"getTokensDistributedInWeek","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"getTotalSupplyAtTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"getUserBalanceAtTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserTimeCursor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"contract ERC20","name":"token","type":"address"}],"name":"getUserTokenTimeCursor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVotingEscrow","outputs":[{"internalType":"contract IVotingEscrow","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"isOnlyCallerEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled","type":"bool"}],"name":"setOnlyCallerCheck","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bool","name":"enabled","type":"bool"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"setOnlyCallerCheckWithSignature","outputs":[],"stateMutability":"nonpayable","type":"function"}]

61018060405260016002553480156200001757600080fd5b5060405162002d2738038062002d278339810160408190526200003a9162000213565b604080518082018252600e81526d2332b2a234b9ba3934b13aba37b960911b6020808301918252835180850190945260018452603160f81b908401528151902060e08190527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc66101008190524660a0529192917f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6200011e8184846040805160208101859052908101839052606081018290524660808201523060a082015260009060c0016040516020818303038152906040528051906020012090509392505050565b6080523060c05261012052505050506001600160a01b0382166101605262093a80908190048102904281900402808210156200016d576040516344473c4960e01b815260040160405180910390fd5b808203620002025760405163bd85b03960e01b8152600481018290526001600160a01b0384169063bd85b03990602401602060405180830381865afa158015620001bb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620001e191906200024f565b6000036200020257604051636e5832e160e01b815260040160405180910390fd5b506101408190526003555062000269565b600080604083850312156200022757600080fd5b82516001600160a01b03811681146200023f57600080fd5b6020939093015192949293505050565b6000602082840312156200026257600080fd5b5051919050565b60805160a05160c05160e05161010051610120516101405161016051612a196200030e600039600081816101a101528181610c8501528181610e280152818161108e01528181611917015281816119d70152611f23015260008181610d4e01528181610ea101528181610f5201526112d801526000611dea01526000611e3901526000611e1401526000611d6d01526000611d9701526000611dc10152612a196000f3fe608060405234801561001057600080fd5b50600436106101825760003560e01c806390193b7c116100d8578063ca31879d1161008c578063e811f44b11610066578063e811f44b146104ae578063ed24911d146104c1578063fcaa54ee146104c957600080fd5b8063ca31879d14610415578063d3dc4ca114610428578063de681faf1461046b57600080fd5b8063a1648aa5116100bd578063a1648aa514610378578063acbc1428146103c1578063c2c4c5c11461040d57600080fd5b806390193b7c1461032f578063905d10ac1461036557600080fd5b80634f3c50901161013a57806382aa5ad41161011457806382aa5ad4146102bb578063876e69a1146102c3578063887204671461030f57600080fd5b80634f3c5090146102755780637b8d6221146102955780638050a7ee146102a857600080fd5b80632308805b1161016b5780632308805b146101e5578063338b5dea1461024f5780633902b9bc1461026257600080fd5b806308b0308a1461018757806314866e08146101d0575b600080fd5b60405173ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001681526020015b60405180910390f35b6101e36101de3660046124eb565b6104dc565b005b6102416101f33660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526005602052604090205470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1690565b6040519081526020016101c7565b6101e361025d36600461250f565b610562565b6101e36102703660046124eb565b610613565b61024161028336600461253b565b60009081526004602052604090205490565b6101e36102a33660046125a0565b61068e565b6102416102b636600461260c565b61081d565b600354610241565b6102416102d13660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526007602052604090205468010000000000000000900467ffffffffffffffff1690565b61032261031d366004612645565b610832565b6040516101c7919061269a565b61024161033d3660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101e36103733660046126d2565b61098c565b6103b16103863660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526001602052604090205460ff1690565b60405190151581526020016101c7565b6102416103cf3660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526005602052604090205468010000000000000000900467ffffffffffffffff1690565b6101e3610a30565b61024161042336600461260c565b610aaf565b61024161043636600461250f565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152600660209081526040808320938352929052205490565b61024161047936600461250f565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152600860209081526040808320938352929052205490565b6101e36104bc366004612724565b610b61565b610241610b6e565b6101e36104d73660046127bd565b610b7d565b60025460011461054d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b6002805561055a81610c3d565b506001600255565b6002546001146105ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b600280556105dd82600061124f565b6105ff73ffffffffffffffffffffffffffffffffffffffff8316333084611718565b61060a82600161124f565b50506001600255565b60025460011461067f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b6002805561055a81600161124f565b6002546001146106fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b60028055828114610737576040517fb0208d4900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8260005b818110156108105761077486868381811061075857610758612893565b905060200201602081019061076d91906124eb565b600061124f565b6107d7333086868581811061078b5761078b612893565b905060200201358989868181106107a4576107a4612893565b90506020020160208101906107b991906124eb565b73ffffffffffffffffffffffffffffffffffffffff16929190611718565b6108088686838181106107ec576107ec612893565b905060200201602081019061080191906124eb565b600161124f565b60010161073b565b5050600160025550505050565b600061082983836117de565b90505b92915050565b60606002546001146108a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b60028055836108ae81611876565b6108b66118f3565b6108bf85610c3d565b8260008167ffffffffffffffff8111156108db576108db61273f565b604051908082528060200260200182016040528015610904578160200160208202803683370190505b50905060005b8281101561097c5761092787878381811061075857610758612893565b6109578888888481811061093d5761093d612893565b905060200201602081019061095291906124eb565b611a7d565b82828151811061096957610969612893565b602090810291909101015260010161090a565b5060016002559695505050505050565b6002546001146109f8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b600280558060005b81811015610a2557610a1d8484838181106107ec576107ec612893565b600101610a00565b505060016002555050565b600254600114610a9c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b60028055610aa86118f3565b6001600255565b6000600254600114610b1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b6002805582610b2b81611876565b610b336118f3565b610b3c84610c3d565b610b4783600061124f565b6000610b538585611a7d565b600160025595945050505050565b610b6b3382611cc5565b50565b6000610b78611d53565b905090565b60007fbd291ffccec065968fe20c5f8debdad73ab50837733f357eeae8814178015a908484610bce8773ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b60408051602081019590955273ffffffffffffffffffffffffffffffffffffffff9093169284019290925215156060830152608082015260a001604051602081830303815290604052805190602001209050610c2d8482846001611e87565b610c378484611cc5565b50505050565b6040517f010ae75700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82811660048301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063010ae75790602401602060405180830381865afa158015610cce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cf291906128c2565b905080600003610d00575050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600760205260408120805490916801000000000000000090910467ffffffffffffffff1690818103610d7c57610d75857f0000000000000000000000000000000000000000000000000000000000000000600087611eb4565b9050610dcd565b428210610d8a575050505050565b50815470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1660148185031115610dcd57610dca85838387611eb4565b90505b80600003610dd9575060015b6040517f28d09d4700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152602482018390526000917f0000000000000000000000000000000000000000000000000000000000000000909116906328d09d4790604401608060405180830381865afa158015610e71573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9591906128ed565b905082600003610fb8577f00000000000000000000000000000000000000000000000000000000000000004211610f4d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f46656520646973747269627574696f6e20686173206e6f74207374617274656460448201527f20796574000000000000000000000000000000000000000000000000000000006064820152608401610544565b610f837f0000000000000000000000000000000000000000000000000000000000000000610f7e8360400151611fbd565b611fd2565b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff821617855592505b6040805160808101825260008082526020820181905291810182905260608101829052905b60328110156111c45782604001518510158015610ffa5750868411155b1561110057600184019350829150868411156110425760405180608001604052806000600f0b81526020016000600f0b815260200160008152602001600081525092506111bc565b6040517f28d09d4700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8981166004830152602482018690527f000000000000000000000000000000000000000000000000000000000000000016906328d09d4790604401608060405180830381865afa1580156110d5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110f991906128ed565b92506111bc565b428510156111c457600061112161111c84604001518803611fe8565b61209e565b9050600081846020015102600f0b8460000151600f0b1361114357600061115b565b61115b82856020015102856000015103600f0b612137565b90508015801561116a57508886115b156111815761117842611fbd565b965050506111c4565b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526008602090815260408083208a84529091529020555062093a80909401935b600101610fdd565b5050835467ffffffffffffffff93841668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff7001000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9095018616949094029390931693169290921717909155505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600560205260408120805490916801000000000000000090910467ffffffffffffffff16908181036113895742915062093a808083040283547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff919091161783557f00000000000000000000000000000000000000000000000000000000000000004211611384576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f46656520646973747269627574696f6e20686173206e6f74207374617274656460448201527f20796574000000000000000000000000000000000000000000000000000000006064820152608401610544565b6113d9565b8142039050836113d957600062093a808084040262093a804281900402149050600062015180426113b942611fbd565b031090508180156113c8575080155b156113d65750505050505050565b50505b82547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16680100000000000000004267ffffffffffffffff16021783556040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015611483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114a791906128c2565b84549091506000906114df9070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff168361298e565b9050806000036114f25750505050505050565b6fffffffffffffffffffffffffffffffff82111561156c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f4d6178696d756d20746f6b656e2062616c616e636520657863656564656400006044820152606401610544565b84546fffffffffffffffffffffffffffffffff80841670010000000000000000000000000000000002911617855560006115ac8562093a80908190040290565b73ffffffffffffffffffffffffffffffffffffffff8916600090815260066020526040812091925090815b60148110156116b4578362093a800192508242101561164c57861580156115fd57508742145b1561161b5760008481526020839052604090208054860190556116b4565b8688420386028161162e5761162e6129a1565b600086815260208590526040902080549290910490910190556116b4565b8615801561165957508783145b156116775760008481526020839052604090208054860190556116a4565b8688840386028161168a5761168a6129a1565b600086815260208590526040902080549290910490910190555b91965086925082916001016115d7565b506040805173ffffffffffffffffffffffffffffffffffffffff8c168152602081018690529081018890527f9b7f1a85a4c9b4e59e1b6527d9969c50cdfb3a1a467d0c4a51fb0ed8bf07f1309060600160405180910390a150505050505050505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d11600160005114161716915050806117d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610544565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152600960209081526040808320938516835292905290812054801561182057905061082c565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600760209081526040808320549387168352600590915290205461186e9167ffffffffffffffff9081169116611fd2565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff1615610b6b573373ffffffffffffffffffffffffffffffffffffffff821614610b6b576040517ff93014bb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60035462093a8042819004028082118061190c57504281145b15611915575050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663c2c4c5c16040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561197d57600080fd5b505af1158015611991573d6000803e3d6000fd5b5050505060005b6014811015611a7657818311611a76576040517fbd85b039000000000000000000000000000000000000000000000000000000008152600481018490527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063bd85b03990602401602060405180830381865afa158015611a33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5791906128c2565b60008481526004602052604090205562093a8090920191600101611998565b5050600355565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260056020526040812081611aad85856117de565b60035473ffffffffffffffffffffffffffffffffffffffff871660009081526007602052604081205492935091611b3491611b0791611b02919068010000000000000000900467ffffffffffffffff166121a3565b611fbd565b8454611b2f9068010000000000000000900467ffffffffffffffff1662093a80908190040290565b6121a3565b73ffffffffffffffffffffffffffffffffffffffff8087166000908152600660209081526040808320938b16835260089091528120929350909190805b6014811015611bc75784861015611bc75760008681526004602090815260408083205486835281842054928890529220540281611bb057611bb06129a1565b62093a809790970196049190910190600101611b71565b5073ffffffffffffffffffffffffffffffffffffffff808a166000908152600960209081526040808320938c168352929052208590558015611cb95785546fffffffffffffffffffffffffffffffff70010000000000000000000000000000000080830482168490038216029116178655611c5973ffffffffffffffffffffffffffffffffffffffff89168a836121b2565b6040805173ffffffffffffffffffffffffffffffffffffffff808c1682528a166020820152908101829052606081018690527fff097c7d8b1957a4ff09ef1361b5fb54dcede3941ba836d0beb9d10bec725de69060800160405180910390a15b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff821660008181526001602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168515159081179091558251938452908301527fac9874a7a931a3f5c9f202c6d9cf40de5d21506993c9f9c38ca8265add89584c910160405180910390a15050565b60003073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016148015611db957507f000000000000000000000000000000000000000000000000000000000000000046145b15611de357507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b610c378484847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8561226b565b60008282825b6080811015611fb15781831015611fb1576040517f28d09d4700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8981166004830152600284860181010460248301819052916000917f000000000000000000000000000000000000000000000000000000000000000016906328d09d4790604401608060405180830381865afa158015611f6a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f8e91906128ed565b905088816040015111611fa357819450611faa565b6001820393505b5050611eba565b50909695505050505050565b600062093a8062093a7f83018190040261082c565b6000818311611fe15781610829565b5090919050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82111561209a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f53616665436173743a2076616c756520646f65736e27742066697420696e206160448201527f6e20696e743235360000000000000000000000000000000000000000000000006064820152608401610544565b5090565b80600f81900b8114612132576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201527f32382062697473000000000000000000000000000000000000000000000000006064820152608401610544565b919050565b60008082121561209a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f53616665436173743a2076616c7565206d75737420626520706f7369746976656044820152606401610544565b6000818310611fe15781610829565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d1160016000511416171691505080610c37576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610544565b600061227685612339565b90506122838682866123a2565b6122bc576040517fe9a06b8b00000000000000000000000000000000000000000000000000000000815260048101839052602401610544565b428310156122f6576040517f922f30c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8616600090815260208190526040812080546001929061232c9084906129d0565b9091555050505050505050565b600061082c612346611d53565b836040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b600081516041146123df576040517f98af90f600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602082810151604080850151606080870151835160008082529681018086528a905290861a938101849052908101849052608081018290529293909260019060a0016020604051602081039080840390855afa158015612443573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611cb957508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161498975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b6b57600080fd5b6000602082840312156124fd57600080fd5b8135612508816124c9565b9392505050565b6000806040838503121561252257600080fd5b823561252d816124c9565b946020939093013593505050565b60006020828403121561254d57600080fd5b5035919050565b60008083601f84011261256657600080fd5b50813567ffffffffffffffff81111561257e57600080fd5b6020830191508360208260051b850101111561259957600080fd5b9250929050565b600080600080604085870312156125b657600080fd5b843567ffffffffffffffff808211156125ce57600080fd5b6125da88838901612554565b909650945060208701359150808211156125f357600080fd5b5061260087828801612554565b95989497509550505050565b6000806040838503121561261f57600080fd5b823561262a816124c9565b9150602083013561263a816124c9565b809150509250929050565b60008060006040848603121561265a57600080fd5b8335612665816124c9565b9250602084013567ffffffffffffffff81111561268157600080fd5b61268d86828701612554565b9497909650939450505050565b6020808252825182820181905260009190848201906040850190845b81811015611fb1578351835292840192918401916001016126b6565b600080602083850312156126e557600080fd5b823567ffffffffffffffff8111156126fc57600080fd5b61270885828601612554565b90969095509350505050565b8035801515811461213257600080fd5b60006020828403121561273657600080fd5b61082982612714565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156127b5576127b561273f565b604052919050565b6000806000606084860312156127d257600080fd5b83356127dd816124c9565b925060206127ec858201612714565b9250604085013567ffffffffffffffff8082111561280957600080fd5b818701915087601f83011261281d57600080fd5b81358181111561282f5761282f61273f565b61285f847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161276e565b9150808252888482850101111561287557600080fd5b80848401858401376000848284010152508093505050509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156128d457600080fd5b5051919050565b8051600f81900b811461213257600080fd5b6000608082840312156128ff57600080fd5b6040516080810181811067ffffffffffffffff821117156129225761292261273f565b60405261292e836128db565b815261293c602084016128db565b602082015260408301516040820152606083015160608201528091505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561082c5761082c61295f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b8082018082111561082c5761082c61295f56fea2646970667358221220a428039de899a501f531932298382fcf2232ddb76930843b9efd1786d24c6c2e64736f6c63430008100033000000000000000000000000f17d23136b4fead139f54fb766c8795faae096600000000000000000000000000000000000000000000000000000000063e15c40

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101825760003560e01c806390193b7c116100d8578063ca31879d1161008c578063e811f44b11610066578063e811f44b146104ae578063ed24911d146104c1578063fcaa54ee146104c957600080fd5b8063ca31879d14610415578063d3dc4ca114610428578063de681faf1461046b57600080fd5b8063a1648aa5116100bd578063a1648aa514610378578063acbc1428146103c1578063c2c4c5c11461040d57600080fd5b806390193b7c1461032f578063905d10ac1461036557600080fd5b80634f3c50901161013a57806382aa5ad41161011457806382aa5ad4146102bb578063876e69a1146102c3578063887204671461030f57600080fd5b80634f3c5090146102755780637b8d6221146102955780638050a7ee146102a857600080fd5b80632308805b1161016b5780632308805b146101e5578063338b5dea1461024f5780633902b9bc1461026257600080fd5b806308b0308a1461018757806314866e08146101d0575b600080fd5b60405173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000f17d23136b4fead139f54fb766c8795faae096601681526020015b60405180910390f35b6101e36101de3660046124eb565b6104dc565b005b6102416101f33660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526005602052604090205470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1690565b6040519081526020016101c7565b6101e361025d36600461250f565b610562565b6101e36102703660046124eb565b610613565b61024161028336600461253b565b60009081526004602052604090205490565b6101e36102a33660046125a0565b61068e565b6102416102b636600461260c565b61081d565b600354610241565b6102416102d13660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526007602052604090205468010000000000000000900467ffffffffffffffff1690565b61032261031d366004612645565b610832565b6040516101c7919061269a565b61024161033d3660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101e36103733660046126d2565b61098c565b6103b16103863660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526001602052604090205460ff1690565b60405190151581526020016101c7565b6102416103cf3660046124eb565b73ffffffffffffffffffffffffffffffffffffffff1660009081526005602052604090205468010000000000000000900467ffffffffffffffff1690565b6101e3610a30565b61024161042336600461260c565b610aaf565b61024161043636600461250f565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152600660209081526040808320938352929052205490565b61024161047936600461250f565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152600860209081526040808320938352929052205490565b6101e36104bc366004612724565b610b61565b610241610b6e565b6101e36104d73660046127bd565b610b7d565b60025460011461054d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b6002805561055a81610c3d565b506001600255565b6002546001146105ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b600280556105dd82600061124f565b6105ff73ffffffffffffffffffffffffffffffffffffffff8316333084611718565b61060a82600161124f565b50506001600255565b60025460011461067f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b6002805561055a81600161124f565b6002546001146106fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b60028055828114610737576040517fb0208d4900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8260005b818110156108105761077486868381811061075857610758612893565b905060200201602081019061076d91906124eb565b600061124f565b6107d7333086868581811061078b5761078b612893565b905060200201358989868181106107a4576107a4612893565b90506020020160208101906107b991906124eb565b73ffffffffffffffffffffffffffffffffffffffff16929190611718565b6108088686838181106107ec576107ec612893565b905060200201602081019061080191906124eb565b600161124f565b60010161073b565b5050600160025550505050565b600061082983836117de565b90505b92915050565b60606002546001146108a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b60028055836108ae81611876565b6108b66118f3565b6108bf85610c3d565b8260008167ffffffffffffffff8111156108db576108db61273f565b604051908082528060200260200182016040528015610904578160200160208202803683370190505b50905060005b8281101561097c5761092787878381811061075857610758612893565b6109578888888481811061093d5761093d612893565b905060200201602081019061095291906124eb565b611a7d565b82828151811061096957610969612893565b602090810291909101015260010161090a565b5060016002559695505050505050565b6002546001146109f8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b600280558060005b81811015610a2557610a1d8484838181106107ec576107ec612893565b600101610a00565b505060016002555050565b600254600114610a9c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b60028055610aa86118f3565b6001600255565b6000600254600114610b1d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610544565b6002805582610b2b81611876565b610b336118f3565b610b3c84610c3d565b610b4783600061124f565b6000610b538585611a7d565b600160025595945050505050565b610b6b3382611cc5565b50565b6000610b78611d53565b905090565b60007fbd291ffccec065968fe20c5f8debdad73ab50837733f357eeae8814178015a908484610bce8773ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b60408051602081019590955273ffffffffffffffffffffffffffffffffffffffff9093169284019290925215156060830152608082015260a001604051602081830303815290604052805190602001209050610c2d8482846001611e87565b610c378484611cc5565b50505050565b6040517f010ae75700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82811660048301526000917f000000000000000000000000f17d23136b4fead139f54fb766c8795faae096609091169063010ae75790602401602060405180830381865afa158015610cce573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cf291906128c2565b905080600003610d00575050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600760205260408120805490916801000000000000000090910467ffffffffffffffff1690818103610d7c57610d75857f0000000000000000000000000000000000000000000000000000000063dafd00600087611eb4565b9050610dcd565b428210610d8a575050505050565b50815470010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff1660148185031115610dcd57610dca85838387611eb4565b90505b80600003610dd9575060015b6040517f28d09d4700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152602482018390526000917f000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660909116906328d09d4790604401608060405180830381865afa158015610e71573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e9591906128ed565b905082600003610fb8577f0000000000000000000000000000000000000000000000000000000063dafd004211610f4d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f46656520646973747269627574696f6e20686173206e6f74207374617274656460448201527f20796574000000000000000000000000000000000000000000000000000000006064820152608401610544565b610f837f0000000000000000000000000000000000000000000000000000000063dafd00610f7e8360400151611fbd565b611fd2565b84547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff821617855592505b6040805160808101825260008082526020820181905291810182905260608101829052905b60328110156111c45782604001518510158015610ffa5750868411155b1561110057600184019350829150868411156110425760405180608001604052806000600f0b81526020016000600f0b815260200160008152602001600081525092506111bc565b6040517f28d09d4700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8981166004830152602482018690527f000000000000000000000000f17d23136b4fead139f54fb766c8795faae0966016906328d09d4790604401608060405180830381865afa1580156110d5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110f991906128ed565b92506111bc565b428510156111c457600061112161111c84604001518803611fe8565b61209e565b9050600081846020015102600f0b8460000151600f0b1361114357600061115b565b61115b82856020015102856000015103600f0b612137565b90508015801561116a57508886115b156111815761117842611fbd565b965050506111c4565b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526008602090815260408083208a84529091529020555062093a80909401935b600101610fdd565b5050835467ffffffffffffffff93841668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff7001000000000000000000000000000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9095018616949094029390931693169290921717909155505050565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600560205260408120805490916801000000000000000090910467ffffffffffffffff16908181036113895742915062093a808083040283547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff919091161783557f0000000000000000000000000000000000000000000000000000000063dafd004211611384576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f46656520646973747269627574696f6e20686173206e6f74207374617274656460448201527f20796574000000000000000000000000000000000000000000000000000000006064820152608401610544565b6113d9565b8142039050836113d957600062093a808084040262093a804281900402149050600062015180426113b942611fbd565b031090508180156113c8575080155b156113d65750505050505050565b50505b82547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16680100000000000000004267ffffffffffffffff16021783556040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8716906370a0823190602401602060405180830381865afa158015611483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114a791906128c2565b84549091506000906114df9070010000000000000000000000000000000090046fffffffffffffffffffffffffffffffff168361298e565b9050806000036114f25750505050505050565b6fffffffffffffffffffffffffffffffff82111561156c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f4d6178696d756d20746f6b656e2062616c616e636520657863656564656400006044820152606401610544565b84546fffffffffffffffffffffffffffffffff80841670010000000000000000000000000000000002911617855560006115ac8562093a80908190040290565b73ffffffffffffffffffffffffffffffffffffffff8916600090815260066020526040812091925090815b60148110156116b4578362093a800192508242101561164c57861580156115fd57508742145b1561161b5760008481526020839052604090208054860190556116b4565b8688420386028161162e5761162e6129a1565b600086815260208590526040902080549290910490910190556116b4565b8615801561165957508783145b156116775760008481526020839052604090208054860190556116a4565b8688840386028161168a5761168a6129a1565b600086815260208590526040902080549290910490910190555b91965086925082916001016115d7565b506040805173ffffffffffffffffffffffffffffffffffffffff8c168152602081018690529081018890527f9b7f1a85a4c9b4e59e1b6527d9969c50cdfb3a1a467d0c4a51fb0ed8bf07f1309060600160405180910390a150505050505050505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d11600160005114161716915050806117d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610544565b5050505050565b73ffffffffffffffffffffffffffffffffffffffff8083166000908152600960209081526040808320938516835292905290812054801561182057905061082c565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600760209081526040808320549387168352600590915290205461186e9167ffffffffffffffff9081169116611fd2565b949350505050565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604090205460ff1615610b6b573373ffffffffffffffffffffffffffffffffffffffff821614610b6b576040517ff93014bb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60035462093a8042819004028082118061190c57504281145b15611915575050565b7f000000000000000000000000f17d23136b4fead139f54fb766c8795faae0966073ffffffffffffffffffffffffffffffffffffffff1663c2c4c5c16040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561197d57600080fd5b505af1158015611991573d6000803e3d6000fd5b5050505060005b6014811015611a7657818311611a76576040517fbd85b039000000000000000000000000000000000000000000000000000000008152600481018490527f000000000000000000000000f17d23136b4fead139f54fb766c8795faae0966073ffffffffffffffffffffffffffffffffffffffff169063bd85b03990602401602060405180830381865afa158015611a33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5791906128c2565b60008481526004602052604090205562093a8090920191600101611998565b5050600355565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260056020526040812081611aad85856117de565b60035473ffffffffffffffffffffffffffffffffffffffff871660009081526007602052604081205492935091611b3491611b0791611b02919068010000000000000000900467ffffffffffffffff166121a3565b611fbd565b8454611b2f9068010000000000000000900467ffffffffffffffff1662093a80908190040290565b6121a3565b73ffffffffffffffffffffffffffffffffffffffff8087166000908152600660209081526040808320938b16835260089091528120929350909190805b6014811015611bc75784861015611bc75760008681526004602090815260408083205486835281842054928890529220540281611bb057611bb06129a1565b62093a809790970196049190910190600101611b71565b5073ffffffffffffffffffffffffffffffffffffffff808a166000908152600960209081526040808320938c168352929052208590558015611cb95785546fffffffffffffffffffffffffffffffff70010000000000000000000000000000000080830482168490038216029116178655611c5973ffffffffffffffffffffffffffffffffffffffff89168a836121b2565b6040805173ffffffffffffffffffffffffffffffffffffffff808c1682528a166020820152908101829052606081018690527fff097c7d8b1957a4ff09ef1361b5fb54dcede3941ba836d0beb9d10bec725de69060800160405180910390a15b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff821660008181526001602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168515159081179091558251938452908301527fac9874a7a931a3f5c9f202c6d9cf40de5d21506993c9f9c38ca8265add89584c910160405180910390a15050565b60003073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000951f99350d816c0e160a2c71defe828bdfc17f1216148015611db957507f000000000000000000000000000000000000000000000000000000000000000146145b15611de357507f8bddfbe7a6a37f0262112d2cb0eefbcfe5248abd98001dd69e1c4bf08bffc3b290565b50604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6020808301919091527f0618c188edbe06a8ffa15e11b4f74493cfd6f23aba7fab610364d908072aac99828401527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b610c378484847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8561226b565b60008282825b6080811015611fb15781831015611fb1576040517f28d09d4700000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8981166004830152600284860181010460248301819052916000917f000000000000000000000000f17d23136b4fead139f54fb766c8795faae0966016906328d09d4790604401608060405180830381865afa158015611f6a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f8e91906128ed565b905088816040015111611fa357819450611faa565b6001820393505b5050611eba565b50909695505050505050565b600062093a8062093a7f83018190040261082c565b6000818311611fe15781610829565b5090919050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82111561209a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f53616665436173743a2076616c756520646f65736e27742066697420696e206160448201527f6e20696e743235360000000000000000000000000000000000000000000000006064820152608401610544565b5090565b80600f81900b8114612132576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602760248201527f53616665436173743a2076616c756520646f65736e27742066697420696e203160448201527f32382062697473000000000000000000000000000000000000000000000000006064820152608401610544565b919050565b60008082121561209a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f53616665436173743a2076616c7565206d75737420626520706f7369746976656044820152606401610544565b6000818310611fe15781610829565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d1160016000511416171691505080610c37576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610544565b600061227685612339565b90506122838682866123a2565b6122bc576040517fe9a06b8b00000000000000000000000000000000000000000000000000000000815260048101839052602401610544565b428310156122f6576040517f922f30c600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8616600090815260208190526040812080546001929061232c9084906129d0565b9091555050505050505050565b600061082c612346611d53565b836040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b600081516041146123df576040517f98af90f600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602082810151604080850151606080870151835160008082529681018086528a905290861a938101849052908101849052608081018290529293909260019060a0016020604051602081039080840390855afa158015612443573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590611cb957508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161498975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b6b57600080fd5b6000602082840312156124fd57600080fd5b8135612508816124c9565b9392505050565b6000806040838503121561252257600080fd5b823561252d816124c9565b946020939093013593505050565b60006020828403121561254d57600080fd5b5035919050565b60008083601f84011261256657600080fd5b50813567ffffffffffffffff81111561257e57600080fd5b6020830191508360208260051b850101111561259957600080fd5b9250929050565b600080600080604085870312156125b657600080fd5b843567ffffffffffffffff808211156125ce57600080fd5b6125da88838901612554565b909650945060208701359150808211156125f357600080fd5b5061260087828801612554565b95989497509550505050565b6000806040838503121561261f57600080fd5b823561262a816124c9565b9150602083013561263a816124c9565b809150509250929050565b60008060006040848603121561265a57600080fd5b8335612665816124c9565b9250602084013567ffffffffffffffff81111561268157600080fd5b61268d86828701612554565b9497909650939450505050565b6020808252825182820181905260009190848201906040850190845b81811015611fb1578351835292840192918401916001016126b6565b600080602083850312156126e557600080fd5b823567ffffffffffffffff8111156126fc57600080fd5b61270885828601612554565b90969095509350505050565b8035801515811461213257600080fd5b60006020828403121561273657600080fd5b61082982612714565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156127b5576127b561273f565b604052919050565b6000806000606084860312156127d257600080fd5b83356127dd816124c9565b925060206127ec858201612714565b9250604085013567ffffffffffffffff8082111561280957600080fd5b818701915087601f83011261281d57600080fd5b81358181111561282f5761282f61273f565b61285f847fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160161276e565b9150808252888482850101111561287557600080fd5b80848401858401376000848284010152508093505050509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6000602082840312156128d457600080fd5b5051919050565b8051600f81900b811461213257600080fd5b6000608082840312156128ff57600080fd5b6040516080810181811067ffffffffffffffff821117156129225761292261273f565b60405261292e836128db565b815261293c602084016128db565b602082015260408301516040820152606083015160608201528091505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561082c5761082c61295f565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b8082018082111561082c5761082c61295f56fea2646970667358221220a428039de899a501f531932298382fcf2232ddb76930843b9efd1786d24c6c2e64736f6c63430008100033

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

000000000000000000000000f17d23136b4fead139f54fb766c8795faae096600000000000000000000000000000000000000000000000000000000063e15c40

-----Decoded View---------------
Arg [0] : votingEscrow (address): 0xf17d23136B4FeAd139f54fB766c8795faae09660
Arg [1] : startTime (uint256): 1675713600

-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660
Arg [1] : 0000000000000000000000000000000000000000000000000000000063e15c40


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ 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.