ETH Price: $2,530.67 (+0.38%)

Transaction Decoder

Block:
21623105 at Jan-14-2025 01:44:35 PM +UTC
Transaction Fee:
0.00239415102066 ETH $6.06
Gas Used:
249,000 Gas / 9.61506434 Gwei

Emitted Events:

48 MicroGPT.Transfer( from=[Receiver] RewardDistributor, to=[Sender] 0x1e830ed61b6f1bb785481de18b06283d0736b955, value=5652413527727804273166 )
49 RewardDistributor.TokensClaimed( user=[Sender] 0x1e830ed61b6f1bb785481de18b06283d0736b955, token=MicroGPT, amount=5652413527727804273166, userTokenTimeCursor=1736380800 )

Account State Difference:

  Address   Before After State Difference Code
0x1e830ED6...D0736B955
0.194451790784185588 Eth
Nonce: 670
0.192057639763525588 Eth
Nonce: 671
0.00239415102066
0x4fFAC630...61DF3620d
(Aethir: Reward Distributor ATH - AI)
0x8CEDb068...b7fDC26d5
(beaverbuild)
19.981650135470598274 Eth19.982025908465853274 Eth0.000375772995255

Execution Trace

RewardDistributor.claimTokens( user=0x1e830ED61b6f1bB785481dE18B06283D0736B955, tokens=[0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5, 0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B] ) => ( [5652413527727804273166, 0] )
  • RewardDistributor.claimTokens( user=0x1e830ED61b6f1bB785481dE18B06283D0736B955, tokens=[0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5, 0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B] ) => ( [5652413527727804273166, 0] )
    • Escrow.user_point_epoch( arg0=0x1e830ED61b6f1bB785481dE18B06283D0736B955 ) => ( 1 )
      • Escrow.user_point_epoch( arg0=0x1e830ED61b6f1bB785481dE18B06283D0736B955 ) => ( 1 )
      • MicroGPT.transfer( recipient=0x1e830ED61b6f1bB785481dE18B06283D0736B955, amount=5652413527727804273166 ) => ( True )
        • 0x5db962d3beb22659803376af721293f22c30aba7.ecd07a53( )
        • RewardFaucet.distributePastRewards( token=0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5 )
          • RewardFaucet.distributePastRewards( token=0x8CEDb0680531d26e62ABdBd0F4c5428b7fDC26d5 )
          • RewardFaucet.distributePastRewards( token=0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B )
            • RewardFaucet.distributePastRewards( token=0xbe0Ed4138121EcFC5c0E56B40517da27E6c5226B )
              claimTokens[RewardDistributor (ln:1711)]
              File 1 of 7: RewardDistributor
              // 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.7.0 <0.9.0;
              // solhint-disable
              /**
               * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
               * supported.
               * Uses the default 'BAL' prefix for the error code
               */
              function _require(bool condition, uint256 errorCode) pure {
                  if (!condition) _revert(errorCode);
              }
              /**
               * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
               * supported.
               */
              function _require(
                  bool condition,
                  uint256 errorCode,
                  bytes3 prefix
              ) pure {
                  if (!condition) _revert(errorCode, prefix);
              }
              /**
               * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
               * Uses the default 'BAL' prefix for the error code
               */
              function _revert(uint256 errorCode) pure {
                  _revert(errorCode, 0x42414c); // This is the raw byte representation of "BAL"
              }
              /**
               * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
               */
              function _revert(uint256 errorCode, bytes3 prefix) pure {
                  uint256 prefixUint = uint256(uint24(prefix));
                  // We're going to dynamically create a revert string based on the error code, with the following format:
                  // 'BAL#{errorCode}'
                  // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).
                  //
                  // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a
                  // number (8 to 16 bits) than the individual string characters.
                  //
                  // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a
                  // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a
                  // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.
                  assembly {
                      // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999
                      // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for
                      // the '0' character.
                      let units := add(mod(errorCode, 10), 0x30)
                      errorCode := div(errorCode, 10)
                      let tenths := add(mod(errorCode, 10), 0x30)
                      errorCode := div(errorCode, 10)
                      let hundreds := add(mod(errorCode, 10), 0x30)
                      // With the individual characters, we can now construct the full string.
                      // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#')
                      // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the
                      // characters to it, each shifted by a multiple of 8.
                      // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits
                      // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte
                      // array).
                      let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint)))
                      let revertReason := shl(200, add(formattedPrefix, add(add(units, shl(8, tenths)), shl(16, hundreds))))
                      // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded
                      // message will have the following layout:
                      // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]
                      // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We
                      // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.
                      mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                      // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).
                      mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
                      // The string length is fixed: 7 characters.
                      mstore(0x24, 7)
                      // Finally, the string itself is stored.
                      mstore(0x44, revertReason)
                      // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of
                      // the encoded message is therefore 4 + 32 + 32 + 32 = 100.
                      revert(0, 100)
                  }
              }
              library Errors {
                  // Math
                  uint256 internal constant ADD_OVERFLOW = 0;
                  uint256 internal constant SUB_OVERFLOW = 1;
                  uint256 internal constant SUB_UNDERFLOW = 2;
                  uint256 internal constant MUL_OVERFLOW = 3;
                  uint256 internal constant ZERO_DIVISION = 4;
                  uint256 internal constant DIV_INTERNAL = 5;
                  uint256 internal constant X_OUT_OF_BOUNDS = 6;
                  uint256 internal constant Y_OUT_OF_BOUNDS = 7;
                  uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;
                  uint256 internal constant INVALID_EXPONENT = 9;
                  // Input
                  uint256 internal constant OUT_OF_BOUNDS = 100;
                  uint256 internal constant UNSORTED_ARRAY = 101;
                  uint256 internal constant UNSORTED_TOKENS = 102;
                  uint256 internal constant INPUT_LENGTH_MISMATCH = 103;
                  uint256 internal constant ZERO_TOKEN = 104;
                  uint256 internal constant INSUFFICIENT_DATA = 105;
                  // Shared pools
                  uint256 internal constant MIN_TOKENS = 200;
                  uint256 internal constant MAX_TOKENS = 201;
                  uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;
                  uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;
                  uint256 internal constant MINIMUM_BPT = 204;
                  uint256 internal constant CALLER_NOT_VAULT = 205;
                  uint256 internal constant UNINITIALIZED = 206;
                  uint256 internal constant BPT_IN_MAX_AMOUNT = 207;
                  uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;
                  uint256 internal constant EXPIRED_PERMIT = 209;
                  uint256 internal constant NOT_TWO_TOKENS = 210;
                  uint256 internal constant DISABLED = 211;
                  // Pools
                  uint256 internal constant MIN_AMP = 300;
                  uint256 internal constant MAX_AMP = 301;
                  uint256 internal constant MIN_WEIGHT = 302;
                  uint256 internal constant MAX_STABLE_TOKENS = 303;
                  uint256 internal constant MAX_IN_RATIO = 304;
                  uint256 internal constant MAX_OUT_RATIO = 305;
                  uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;
                  uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;
                  uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;
                  uint256 internal constant INVALID_TOKEN = 309;
                  uint256 internal constant UNHANDLED_JOIN_KIND = 310;
                  uint256 internal constant ZERO_INVARIANT = 311;
                  uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;
                  uint256 internal constant ORACLE_NOT_INITIALIZED = 313;
                  uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;
                  uint256 internal constant ORACLE_INVALID_INDEX = 315;
                  uint256 internal constant ORACLE_BAD_SECS = 316;
                  uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317;
                  uint256 internal constant AMP_ONGOING_UPDATE = 318;
                  uint256 internal constant AMP_RATE_TOO_HIGH = 319;
                  uint256 internal constant AMP_NO_ONGOING_UPDATE = 320;
                  uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;
                  uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;
                  uint256 internal constant RELAYER_NOT_CONTRACT = 323;
                  uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;
                  uint256 internal constant REBALANCING_RELAYER_REENTERED = 325;
                  uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326;
                  uint256 internal constant SWAPS_DISABLED = 327;
                  uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328;
                  uint256 internal constant PRICE_RATE_OVERFLOW = 329;
                  uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330;
                  uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331;
                  uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332;
                  uint256 internal constant UPPER_TARGET_TOO_HIGH = 333;
                  uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334;
                  uint256 internal constant OUT_OF_TARGET_RANGE = 335;
                  uint256 internal constant UNHANDLED_EXIT_KIND = 336;
                  uint256 internal constant UNAUTHORIZED_EXIT = 337;
                  uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338;
                  uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339;
                  uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340;
                  uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341;
                  uint256 internal constant INVALID_INITIALIZATION = 342;
                  uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343;
                  uint256 internal constant FEATURE_DISABLED = 344;
                  uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345;
                  uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346;
                  uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347;
                  uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348;
                  uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349;
                  uint256 internal constant MAX_WEIGHT = 350;
                  uint256 internal constant UNAUTHORIZED_JOIN = 351;
                  uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352;
                  uint256 internal constant FRACTIONAL_TARGET = 353;
                  uint256 internal constant ADD_OR_REMOVE_BPT = 354;
                  uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355;
                  uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356;
                  uint256 internal constant MALICIOUS_QUERY_REVERT = 357;
                  uint256 internal constant JOINS_EXITS_DISABLED = 358;
                  // Lib
                  uint256 internal constant REENTRANCY = 400;
                  uint256 internal constant SENDER_NOT_ALLOWED = 401;
                  uint256 internal constant PAUSED = 402;
                  uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;
                  uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;
                  uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;
                  uint256 internal constant INSUFFICIENT_BALANCE = 406;
                  uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;
                  uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;
                  uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;
                  uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;
                  uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;
                  uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;
                  uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;
                  uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;
                  uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;
                  uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;
                  uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;
                  uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;
                  uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;
                  uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;
                  uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;
                  uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;
                  uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;
                  uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;
                  uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;
                  uint256 internal constant CALLER_IS_NOT_OWNER = 426;
                  uint256 internal constant NEW_OWNER_IS_ZERO = 427;
                  uint256 internal constant CODE_DEPLOYMENT_FAILED = 428;
                  uint256 internal constant CALL_TO_NON_CONTRACT = 429;
                  uint256 internal constant LOW_LEVEL_CALL_FAILED = 430;
                  uint256 internal constant NOT_PAUSED = 431;
                  uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432;
                  uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433;
                  uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434;
                  uint256 internal constant INVALID_OPERATION = 435;
                  uint256 internal constant CODEC_OVERFLOW = 436;
                  uint256 internal constant IN_RECOVERY_MODE = 437;
                  uint256 internal constant NOT_IN_RECOVERY_MODE = 438;
                  uint256 internal constant INDUCED_FAILURE = 439;
                  uint256 internal constant EXPIRED_SIGNATURE = 440;
                  uint256 internal constant MALFORMED_SIGNATURE = 441;
                  uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442;
                  uint256 internal constant UNHANDLED_FEE_TYPE = 443;
                  uint256 internal constant BURN_FROM_ZERO = 444;
                  // Vault
                  uint256 internal constant INVALID_POOL_ID = 500;
                  uint256 internal constant CALLER_NOT_POOL = 501;
                  uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;
                  uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;
                  uint256 internal constant INVALID_SIGNATURE = 504;
                  uint256 internal constant EXIT_BELOW_MIN = 505;
                  uint256 internal constant JOIN_ABOVE_MAX = 506;
                  uint256 internal constant SWAP_LIMIT = 507;
                  uint256 internal constant SWAP_DEADLINE = 508;
                  uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;
                  uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;
                  uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;
                  uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;
                  uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;
                  uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;
                  uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;
                  uint256 internal constant INSUFFICIENT_ETH = 516;
                  uint256 internal constant UNALLOCATED_ETH = 517;
                  uint256 internal constant ETH_TRANSFER = 518;
                  uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;
                  uint256 internal constant TOKENS_MISMATCH = 520;
                  uint256 internal constant TOKEN_NOT_REGISTERED = 521;
                  uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;
                  uint256 internal constant TOKENS_ALREADY_SET = 523;
                  uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;
                  uint256 internal constant NONZERO_TOKEN_BALANCE = 525;
                  uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;
                  uint256 internal constant POOL_NO_TOKENS = 527;
                  uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;
                  // Fees
                  uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;
                  uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;
                  uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;
                  uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603;
                  // FeeSplitter
                  uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700;
                  // Misc
                  uint256 internal constant UNIMPLEMENTED = 998;
                  uint256 internal constant SHOULD_NOT_HAPPEN = 999;
              }
              // 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.7.0 <0.9.0;
              /**
               * @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-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.7.0 <0.9.0;
              /**
               * @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: MIT
              // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)
              pragma solidity >=0.7.0 <0.9.0;
              /**
               * @dev Interface of the ERC1271 standard signature validation method for
               * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
               *
               * _Available since v4.1._
               */
              interface IERC1271 {
                  /**
                   * @dev Should return whether the signature provided is valid for the provided data
                   * @param hash      Hash of the data to be signed
                   * @param signature Signature byte array associated with _data
                   */
                  function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity >=0.7.0 <0.9.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
                  /**
                   * @dev Moves `amount` tokens from `sender` to `recipient` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(
                      address sender,
                      address recipient,
                      uint256 amount
                  ) external returns (bool);
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/ISignaturesValidator.sol";
              import "../openzeppelin/EIP712.sol";
              /**
               * @dev Utility for signing Solidity function calls.
               */
              abstract contract EOASignaturesValidator is ISignaturesValidator, EIP712 {
                  // 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);
                      _require(_isValidSignature(account, digest, signature), 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
                      _require(deadline >= block.timestamp, Errors.EXPIRED_SIGNATURE);
                      // 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) {
                      _require(signature.length == 65, Errors.MALFORMED_SIGNATURE);
                      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);
                  }
                  function _toArraySignature(
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal pure returns (bytes memory) {
                      bytes memory signature = new bytes(65);
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          mstore(add(signature, 32), r)
                          mstore(add(signature, 64), s)
                          mstore8(add(signature, 96), v)
                      }
                      return signature;
                  }
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              library InputHelpers {
                  function ensureInputLengthMatch(uint256 a, uint256 b) internal pure {
                      _require(a == b, Errors.INPUT_LENGTH_MISMATCH);
                  }
                  function ensureInputLengthMatch(
                      uint256 a,
                      uint256 b,
                      uint256 c
                  ) internal pure {
                      _require(a == b && b == c, Errors.INPUT_LENGTH_MISMATCH);
                  }
                  function ensureArrayIsSorted(IERC20[] memory array) internal pure {
                      address[] memory addressArray;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          addressArray := array
                      }
                      ensureArrayIsSorted(addressArray);
                  }
                  function ensureArrayIsSorted(address[] memory array) internal pure {
                      if (array.length < 2) {
                          return;
                      }
                      address previous = array[0];
                      for (uint256 i = 1; i < array.length; ++i) {
                          address current = array[i];
                          _require(previous < current, Errors.UNSORTED_ARRAY);
                          previous = current;
                      }
                  }
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/IOptionalOnlyCaller.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              import "./SignaturesValidator.sol";
              abstract contract OptionalOnlyCaller is IOptionalOnlyCaller, SignaturesValidator {
                  mapping(address => bool) private _isOnlyCallerEnabled;
                  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, Errors.INVALID_SIGNATURE);
                      _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]) {
                          _require(msg.sender == user, Errors.SENDER_NOT_ALLOWED);
                      }
                  }
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC1271.sol";
              import "./EOASignaturesValidator.sol";
              import "../openzeppelin/Address.sol";
              /**
               * @dev Utility for signing Solidity function calls.
               */
              abstract contract SignaturesValidator is EOASignaturesValidator {
                  using Address for address;
                  function _isValidSignature(
                      address account,
                      bytes32 digest,
                      bytes memory signature
                  ) internal view virtual override returns (bool) {
                      if (account.isContract()) {
                          return IERC1271(account).isValidSignature(digest, signature) == IERC1271.isValidSignature.selector;
                      } else {
                          return super._isValidSignature(account, digest, signature);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow checks.
               * Adapted from OpenZeppelin's SafeMath library.
               */
              library Math {
                  // solhint-disable no-inline-assembly
                  /**
                   * @dev Returns the absolute value of a signed integer.
                   */
                  function abs(int256 a) internal pure returns (uint256 result) {
                      // Equivalent to:
                      // result = a > 0 ? uint256(a) : uint256(-a)
                      assembly {
                          let s := sar(255, a)
                          result := sub(xor(a, s), s)
                      }
                  }
                  /**
                   * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      _require(c >= a, Errors.ADD_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the addition of two signed integers, reverting on overflow.
                   */
                  function add(int256 a, int256 b) internal pure returns (int256) {
                      int256 c = a + b;
                      _require((b >= 0 && c >= a) || (b < 0 && c < a), Errors.ADD_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      _require(b <= a, Errors.SUB_OVERFLOW);
                      uint256 c = a - b;
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two signed integers, reverting on overflow.
                   */
                  function sub(int256 a, int256 b) internal pure returns (int256) {
                      int256 c = a - b;
                      _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the largest of two numbers of 256 bits.
                   */
                  function max(uint256 a, uint256 b) internal pure returns (uint256 result) {
                      // Equivalent to:
                      // result = (a < b) ? b : a;
                      assembly {
                          result := sub(a, mul(sub(a, b), lt(a, b)))
                      }
                  }
                  /**
                   * @dev Returns the smallest of two numbers of 256 bits.
                   */
                  function min(uint256 a, uint256 b) internal pure returns (uint256 result) {
                      // Equivalent to `result = (a < b) ? a : b`
                      assembly {
                          result := sub(a, mul(sub(a, b), gt(a, b)))
                      }
                  }
                  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a * b;
                      _require(a == 0 || c / a == b, Errors.MUL_OVERFLOW);
                      return c;
                  }
                  function div(
                      uint256 a,
                      uint256 b,
                      bool roundUp
                  ) internal pure returns (uint256) {
                      return roundUp ? divUp(a, b) : divDown(a, b);
                  }
                  function divDown(uint256 a, uint256 b) internal pure returns (uint256) {
                      _require(b != 0, Errors.ZERO_DIVISION);
                      return a / b;
                  }
                  function divUp(uint256 a, uint256 b) internal pure returns (uint256 result) {
                      _require(b != 0, Errors.ZERO_DIVISION);
                      // Equivalent to:
                      // result = a == 0 ? 0 : 1 + (a - 1) / b;
                      assembly {
                          result := mul(iszero(iszero(a)), add(1, div(sub(a, 1), b)))
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // Based on the Address library from OpenZeppelin Contracts, altered by removing the `isContract` checks on
              // `functionCall` and `functionDelegateCall` in order to save gas, as the recipients are known to be contracts.
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies on extcodesize, which returns 0 for contracts in
                      // construction, since the code is only stored at the end of the
                      // constructor execution.
                      uint256 size;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          size := extcodesize(account)
                      }
                      return size > 0;
                  }
                  // solhint-disable max-line-length
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      _require(address(this).balance >= amount, Errors.ADDRESS_INSUFFICIENT_BALANCE);
                      // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                      (bool success, ) = recipient.call{ value: amount }("");
                      _require(success, Errors.ADDRESS_CANNOT_SEND_VALUE);
                  }
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain `call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call(data);
                      return verifyCallResult(success, returndata);
                  }
                  // solhint-enable max-line-length
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but passing some native ETH as msg.value to the call.
                   *
                   * _Available since v3.4._
                   */
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value
                  ) internal returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call{ value: value }(data);
                      return verifyCallResult(success, returndata);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      return verifyCallResult(success, returndata);
                  }
                  /**
                   * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling up the
                   * revert reason or using the one provided.
                   *
                   * _Available since v4.3._
                   */
                  function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
                      if (success) {
                          return returndata;
                      } else {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              // solhint-disable-next-line no-inline-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              _revert(Errors.LOW_LEVEL_CALL_FAILED);
                          }
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              /**
               * @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 */
                  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) {
                      _HASHED_NAME = keccak256(bytes(name));
                      _HASHED_VERSION = keccak256(bytes(version));
                      _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
                  }
                  /**
                   * @dev Returns the domain separator for the current chain.
                   */
                  function _domainSeparatorV4() internal view virtual returns (bytes32) {
                      return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _getChainId(), 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 keccak256(abi.encodePacked("\\x19\\x01", _domainSeparatorV4(), structHash));
                  }
                  // solc-ignore-next-line func-mutability
                  function _getChainId() private view returns (uint256 chainId) {
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          chainId := chainid()
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce bytecode size.
              // Modifier code is inlined by the compiler, which causes its code to appear multiple times in the codebase. By using
              // private functions, we achieve the same end result with slightly higher runtime gas costs, but reduced bytecode size.
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Contract module that helps prevent reentrant calls to a function.
               *
               * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
               * available, which can be applied to functions to make sure there are no nested
               * (reentrant) calls to them.
               *
               * Note that because there is a single `nonReentrant` guard, functions marked as
               * `nonReentrant` may not call one another. This can be worked around by making
               * those functions `private`, and then adding `external` `nonReentrant` entry
               * points to them.
               *
               * TIP: If you would like to learn more about reentrancy and alternative ways
               * to protect against it, check out our blog post
               * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
               */
              abstract contract ReentrancyGuard {
                  // Booleans are more expensive than uint256 or any type that takes up a full
                  // word because each write operation emits an extra SLOAD to first read the
                  // slot's contents, replace the bits taken up by the boolean, and then write
                  // back. This is the compiler's defense against contract upgrades and
                  // pointer aliasing, and it cannot be disabled.
                  // The values being non-zero value makes deployment a bit more expensive,
                  // but in exchange the refund on every call to nonReentrant will be lower in
                  // amount. Since refunds are capped to a percentage of the total
                  // transaction's gas, it is best to keep them low in cases like this one, to
                  // increase the likelihood of the full refund coming into effect.
                  uint256 private constant _NOT_ENTERED = 1;
                  uint256 private constant _ENTERED = 2;
                  uint256 private _status;
                  constructor() {
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Prevents a contract from calling itself, directly or indirectly.
                   * Calling a `nonReentrant` function from another `nonReentrant`
                   * function is not supported. It is possible to prevent this from happening
                   * by making the `nonReentrant` function external, and make it call a
                   * `private` function that does the actual work.
                   */
                  modifier nonReentrant() {
                      _enterNonReentrant();
                      _;
                      _exitNonReentrant();
                  }
                  function _enterNonReentrant() private {
                      // On the first call to nonReentrant, _status will be _NOT_ENTERED
                      _require(_status != _ENTERED, Errors.REENTRANCY);
                      // Any calls to nonReentrant after this point will fail
                      _status = _ENTERED;
                  }
                  function _exitNonReentrant() private {
                      // By storing the original value once again, a refund is triggered (see
                      // https://eips.ethereum.org/EIPS/eip-2200)
                      _status = _NOT_ENTERED;
                  }
              }
              // SPDX-License-Identifier: MIT
              // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce gas costs.
              // The `safeTransfer` and `safeTransferFrom` functions assume that `token` is a contract (an account with code), and
              // work differently from the OpenZeppelin version if it is not.
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  function safeApprove(
                      IERC20 token,
                      address to,
                      uint256 value
                  ) internal {
                      // Some contracts need their allowance reduced to 0 before setting it to an arbitrary amount.
                      if (value != 0 && token.allowance(address(this), address(to)) != 0) {
                          _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, 0));
                      }
                      _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, value));
                  }
                  function safeTransfer(
                      IERC20 token,
                      address to,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(address(token), abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
                  function safeTransferFrom(
                      IERC20 token,
                      address from,
                      address to,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(address(token), abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   *
                   * WARNING: `token` is assumed to be a contract: calls to EOAs will *not* revert.
                   */
                  function _callOptionalReturn(address token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves.
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = token.call(data);
                      // If the low-level call didn't succeed we return whatever was returned from it.
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          if eq(success, 0) {
                              returndatacopy(0, 0, returndatasize())
                              revert(0, returndatasize())
                          }
                      }
                      // Finally we check the returndata size is either zero or true - note that this check will always pass for EOAs
                      _require(returndata.length == 0 || abi.decode(returndata, (bool)), Errors.SAFE_ERC20_CALL_FAILED);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow
               * checks.
               *
               * Arithmetic operations in Solidity wrap on overflow. This can easily result
               * in bugs, because programmers usually assume that an overflow raises an
               * error, which is the standard behavior in high level programming languages.
               * `SafeMath` restores this intuition by reverting the transaction when an
               * operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               */
              library SafeMath {
                  /**
                   * @dev Returns the addition of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `+` operator.
                   *
                   * Requirements:
                   *
                   * - Addition cannot overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      _require(c >= a, Errors.ADD_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      return sub(a, b, Errors.SUB_OVERFLOW);
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(
                      uint256 a,
                      uint256 b,
                      uint256 errorCode
                  ) internal pure returns (uint256) {
                      _require(b <= a, errorCode);
                      uint256 c = a - b;
                      return c;
                  }
              }
              // 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.7.0 <0.9.0;
              pragma experimental ABIEncoderV2;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
              import "./IVotingEscrow.sol";
              /**
               * @title Reward Distributor
               * @notice Distributes any tokens transferred to the contract (e.g. Protocol rewards and any token emissions) among veBPT
               * holders proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract.
               * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT
               * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`.
               */
              interface IRewardDistributor {
                  event TokenCheckpointed(
                      IERC20 token,
                      uint256 amount,
                      uint256 lastCheckpointTimestamp
                  );
                  event TokensClaimed(
                      address user,
                      IERC20 token,
                      uint256 amount,
                      uint256 userTokenTimeCursor
                  );
                  event TokenAdded(address indexed token);
                  event RewardDeposit(IERC20 token, uint256 amount);
                  event NewAdmin(address indexed newAdmin);
                  /**
                   * @notice Returns the VotingEscrow (veBPT) 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(IERC20 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,
                      IERC20 token
                  ) external view returns (uint256);
                  /**
                   * @notice Returns the user's cached balance of veBPT 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 veBPT 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 RewardDistributor's cached balance of `token`.
                   */
                  function getTokenLastBalance(IERC20 token) external view returns (uint256);
                  /**
                   * @notice Returns the amount of `token` which the RewardDistributor 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(
                      IERC20 token,
                      uint256 timestamp
                  ) external view returns (uint256);
                  // Depositing
                  /**
                   * @notice Deposits tokens to be distributed in the current week.
                   * @dev Sending tokens directly to the RewardDistributor 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(IERC20 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(
                      IERC20[] calldata tokens,
                      uint256[] calldata amounts
                  ) external;
                  // Checkpointing
                  /**
                   * @notice Caches the total supply of veBPT 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 veBPT 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 RewardDistributor to weekly distributions.
                   * @dev Any `token` balance held by the RewardDistributor 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(IERC20 token) external;
                  /**
                   * @notice Assigns any newly-received tokens held by the RewardDistributor 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(IERC20[] 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 RewardDistributor
                   * 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, IERC20 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,
                      IERC20[] calldata tokens
                  ) external returns (uint256[] memory);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              interface IRewardFaucet {
                  function distributePastRewards(address rewardToken) external;
              }
              // 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.7.0 <0.9.0;
              pragma experimental ABIEncoderV2;
              // 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 admin() external view returns (address);
                  function future_admin() external view returns (address);
                  function apply_smart_wallet_checker() external;
                  function apply_transfer_ownership() external;
                  // function balanceOf(address addr, uint256 _t) external view returns (uint256);
                  function balanceOf(
                      address user,
                      uint256 timestamp
                  ) external view returns (uint256);
                  function balanceOfAt(
                      address addr,
                      uint256 _block
                  ) external view returns (uint256);
                  function checkpoint() external;
                  function commit_smart_wallet_checker(address addr) external;
                  function commit_transfer_ownership(address addr) external;
                  function create_lock(uint256 _value, uint256 _unlock_time) external;
                  function decimals() external view returns (uint256);
                  function deposit_for(address _addr, uint256 _value) external;
                  function get_last_user_slope(address addr) external view returns (int128);
                  function increase_amount(uint256 _value) external;
                  function increase_unlock_time(uint256 _unlock_time) external;
                  function locked__end(address _addr) external view returns (uint256);
                  function name() external view returns (string memory);
                  function point_history(
                      uint256 timestamp
                  ) external view returns (Point memory);
                  function symbol() external view returns (string memory);
                  function token() external view returns (address);
                  function totalSupply(uint256 t) external view returns (uint256);
                  function totalSupplyAt(uint256 _block) external view returns (uint256);
                  function user_point_epoch(address user) external view returns (uint256);
                  function user_point_history__ts(
                      address _addr,
                      uint256 _idx
                  ) external view returns (uint256);
                  function user_point_history(
                      address user,
                      uint256 timestamp
                  ) external view returns (Point memory);
                  function withdraw() external;
              }
              // 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.7.0;
              pragma experimental ABIEncoderV2;
              import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
              import {IRewardDistributor} from "./interfaces/IRewardDistributor.sol";
              import {IRewardFaucet} from "./interfaces/IRewardFaucet.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ReentrancyGuard.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/helpers/OptionalOnlyCaller.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeMath.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol";
              // solhint-disable not-rely-on-time
              /**
               * @title Reward Distributor
               * @notice Distributes any tokens transferred to the contract among veBPT holders
               * proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract.
               * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT
               * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`.
               */
              contract RewardDistributor is
                  IRewardDistributor,
                  OptionalOnlyCaller,
                  ReentrancyGuard
              {
                  using SafeMath for uint256;
                  using SafeERC20 for IERC20;
                  bool public isInitialized;
                  IVotingEscrow private _votingEscrow;
                  IRewardFaucet public rewardFaucet;
                  uint256 private _startTime;
                  // Global State
                  uint256 private _timeCursor;
                  mapping(uint256 => uint256) private _veSupplyCache;
                  address public admin;
                  address[] private _rewardTokens;
                  mapping(address => bool) public allowedRewardTokens;
                  // 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(IERC20 => TokenState) private _tokenState;
                  mapping(IERC20 => 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(IERC20 => uint256)) private _userTokenTimeCursor;
                  constructor() EIP712("RewardDistributor", "1") {}
                  modifier onlyAdmin() {
                      require(admin == msg.sender, "not admin");
                      _;
                  }
                  function initialize(
                      IVotingEscrow votingEscrow,
                      IRewardFaucet rewardFaucet_,
                      uint256 startTime,
                      address admin_
                  ) external {
                      require(!isInitialized, "!twice");
                      isInitialized = true;
                      require(admin_ != address(0) && address(rewardFaucet_) != address(0), "!zero");
                      admin = admin_;
                      rewardFaucet = rewardFaucet_;
                      _votingEscrow = votingEscrow;
                      startTime = _roundDownTimestamp(startTime);
                      uint256 currentWeek = _roundDownTimestamp(block.timestamp);
                      require(startTime >= currentWeek, "Cannot start before current week");
                      require(startTime <= currentWeek + 10 weeks, "10 weeks delay max");
                      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.
                          require(
                              votingEscrow.totalSupply(currentWeek) > 0,
                              "Zero total supply results in lost tokens"
                          );
                      }
                      _startTime = startTime;
                      _timeCursor = startTime;
                  }
                  /**
                   * @notice Returns the VotingEscrow (veBPT) 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(
                      IERC20 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,
                      IERC20 token
                  ) external view override returns (uint256) {
                      return _getUserTokenTimeCursor(user, token);
                  }
                  /**
                   * @notice Returns the user's cached balance of veBPT 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 veBPT 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 RewardDistributor's cached balance of `token`.
                   */
                  function getTokenLastBalance(
                      IERC20 token
                  ) external view override returns (uint256) {
                      return _tokenState[token].cachedBalance;
                  }
                  /**
                   * @notice Returns the amount of `token` which the RewardDistributor 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(
                      IERC20 token,
                      uint256 timestamp
                  ) external view override returns (uint256) {
                      return _tokensPerWeek[token][timestamp];
                  }
                  // Depositing
                  /**
                   * @notice Deposits tokens to be distributed in the current week.
                   * @dev Sending tokens directly to the RewardDistributor 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(
                      IERC20 token,
                      uint256 amount
                  ) external override nonReentrant {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      _checkpointToken(token, false);
                      token.safeTransferFrom(msg.sender, address(this), amount);
                      _checkpointToken(token, true);
                      emit RewardDeposit(token, amount);
                  }
                  /**
                   * @notice Deposits tokens by faucet to be distributed in the current week.
                   * @dev Sending tokens directly to the RewardDistributor 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 faucetDepositToken(
                      IERC20 token,
                      uint256 amount
                  ) external {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      require(msg.sender == address(rewardFaucet), "only faucet");
                      _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(
                      IERC20[] calldata tokens,
                      uint256[] calldata amounts
                  ) external override nonReentrant {
                      InputHelpers.ensureInputLengthMatch(tokens.length, amounts.length);
                      uint256 length = tokens.length;
                      for (uint256 i = 0; i < length; ++i) {
                          require(allowedRewardTokens[address(tokens[i])], "!allowed");
                          _checkpointToken(tokens[i], false);
                          tokens[i].safeTransferFrom(msg.sender, address(this), amounts[i]);
                          _checkpointToken(tokens[i], true);
                          emit RewardDeposit(tokens[i], amounts[i]);
                      }
                  }
                  // Checkpointing
                  /**
                   * @notice Caches the total supply of veBPT 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 veBPT 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 RewardDistributor to weekly distributions.
                   * @dev Any `token` balance held by the RewardDistributor 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(IERC20 token) external override nonReentrant {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      _checkpointToken(token, true);
                  }
                  /**
                   * @notice Assigns any newly-received tokens held by the RewardDistributor 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(
                      IERC20[] calldata tokens
                  ) external override nonReentrant {
                      uint256 tokensLength = tokens.length;
                      for (uint256 i = 0; i < tokensLength; ++i) {
                          require(allowedRewardTokens[address(tokens[i])], "!allowed");
                          _checkpointToken(tokens[i], true);
                      }
                  }
                  // 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 RewardDistributor
                   * 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,
                      IERC20 token
                  )
                      external
                      override
                      nonReentrant
                      optionalOnlyCaller(user)
                      returns (uint256)
                  {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      _checkpointTotalSupply();
                      _checkpointUserBalance(user);
                      _checkpointToken(token, false);
                      uint256 amount = _claimToken(user, token);
                      rewardFaucet.distributePastRewards(address(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,
                      IERC20[] 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; ++i) {
                          require(allowedRewardTokens[address(tokens[i])], "!allowed");
                          _checkpointToken(tokens[i], false);
                          amounts[i] = _claimToken(user, tokens[i]);
                          rewardFaucet.distributePastRewards(address(tokens[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,
                      IERC20 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; ++i) {
                          // We clearly cannot claim for `firstUnclaimableWeek` and so we break here.
                          if (nextUserTokenWeekToClaim >= firstUnclaimableWeek) break;
                          if (_veSupplyCache[nextUserTokenWeekToClaim] == 0) break;
                          amount +=
                              (tokensPerWeek[nextUserTokenWeekToClaim] *
                                  userBalanceAtTimestamp[nextUserTokenWeekToClaim]) /
                              _veSupplyCache[nextUserTokenWeekToClaim];
                          nextUserTokenWeekToClaim += 1 weeks;
                      }
                      // Update the stored user-token time cursor to prevent this user claiming this week again.
                      _userTokenTimeCursor[user][token] = nextUserTokenWeekToClaim;
                      if (amount > 0) {
                          // 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(IERC20 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,
                              "Reward distribution has not started yet"
                          );
                      } else {
                          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 rewards 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.sub(
                          tokenState.cachedBalance
                      );
                      if (newTokensToDistribute == 0) return;
                      require(
                          tokenBalance <= type(uint128).max,
                          "Maximum token balance exceeded"
                      );
                      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
                      ];
                      uint256 amountToAdd;
                      for (uint256 i = 0; i < 20; ++i) {
                          // 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
                              ) {
                                  amountToAdd = newTokensToDistribute;
                              } else {
                                  // block.timestamp >= lastTokenTime by definition.
                                  amountToAdd = 
                                      (newTokensToDistribute *
                                          (block.timestamp - lastTokenTime)) /
                                      timeSinceLastCheckpoint;
                              }
                              if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) {
                                  tokensPerWeek[firstIncompleteWeek] += amountToAdd;
                                  tokenState.cachedBalance += uint128(amountToAdd);
                              }
                              // 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
                                  amountToAdd = newTokensToDistribute;
                              } else {
                                  // nextWeek > lastTokenTime by definition.
                                  amountToAdd = (newTokensToDistribute * (nextWeek - lastTokenTime)) /
                                      timeSinceLastCheckpoint;
                              }
                          }
                          if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) {
                              tokensPerWeek[firstIncompleteWeek] += amountToAdd;
                              tokenState.cachedBalance += uint128(amountToAdd);
                          }
                          // We've now "checkpointed" up to the beginning of next week so must update timestamps appropriately.
                          lastTokenTime = nextWeek;
                          firstIncompleteWeek = nextWeek;
                      }
                      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 veBPT.
                      // They clearly will not then receive rewards.
                      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;
                          // 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 rewards 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,
                              "Reward 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; ++i) {
                          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 = int128(nextWeekToCheckpoint - currentUserPoint.ts);
                              uint256 userBalance = currentUserPoint.bias >
                                  currentUserPoint.slope * dt
                                  ? uint256(
                                      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 rewards.
                              _userBalanceAtTimestamp[user][
                                  nextWeekToCheckpoint
                              ] = userBalance;
                              nextWeekToCheckpoint += 1 weeks;
                          }
                      }
                      // 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`.
                      // 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; ++i) {
                          if (nextWeekToCheckpoint > weekStart) break;
                          _veSupplyCache[nextWeekToCheckpoint] = _votingEscrow.totalSupply(
                              nextWeekToCheckpoint
                          );
                          // This is safe as we're incrementing a timestamp
                          nextWeekToCheckpoint += 1 weeks;
                      }
                      // 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,
                      IERC20 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 veBPT 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; ++i) {
                          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) {
                      // 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) {
                      // Overflows are impossible here for all realistic inputs.
                      return _roundDownTimestamp(timestamp + 1 weeks - 1);
                  }
                  /**
                   * @notice Adds allowed tokens for the distribution.
                   * @param tokens - An array of ERC20 token addresses to be added for the further reward distribution.
                   */
                  function addAllowedRewardTokens(address[] calldata tokens) external onlyAdmin {
                      for (uint256 i = 0; i < tokens.length; i++) {
                          require(!allowedRewardTokens[tokens[i]], "already exist");
                          allowedRewardTokens[tokens[i]] = true;
                          _rewardTokens.push(tokens[i]);
                          emit TokenAdded(tokens[i]);
                      }
                  }
                  /**
                   * @notice Returns allowed for reward distribution tokens list.
                   * @return An array of ERC20 token addresses which can be used as rewards.
                   */
                  function getAllowedRewardTokens() external view returns (address[] memory) {
                      return _rewardTokens;
                  }
                  /**
                   * @notice Transfers admin rights to new address.
                   * @param newAdmin - The new admin address to set.
                   */
                  function transferAdmin(address newAdmin) external onlyAdmin {
                      require (newAdmin != address(0), "zero address");
                      admin = newAdmin;
                      emit NewAdmin(newAdmin);
                  }
              }
              

              File 2 of 7: MicroGPT
              // SPDX-License-Identifier: MIT
              pragma solidity >=0.6.0 <0.9.0;
              
              interface IERC20 {
                  function totalSupply() external view returns (uint256);
                  function decimals() external view returns (uint8);
                  function symbol() external view returns (string memory);
                  function name() external view returns (string memory);
                  function getOwner() external view returns (address);
                  function balanceOf(address account) external view returns (uint256);
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  function allowance(address _owner, address spender) external view returns (uint256);
                  function approve(address spender, uint256 amount) external returns (bool);
                  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              
              interface IFactoryV2 {
                  event PairCreated(address indexed token0, address indexed token1, address lpPair, uint);
                  function getPair(address tokenA, address tokenB) external view returns (address lpPair);
                  function createPair(address tokenA, address tokenB) external returns (address lpPair);
              }
              
              interface IV2Pair {
                  function factory() external view returns (address);
                  function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
                  function sync() external;
              }
              
              interface IRouter01 {
                  function factory() external pure returns (address);
                  function WETH() external pure returns (address);
                  function addLiquidityETH(
                      address token,
                      uint amountTokenDesired,
                      uint amountTokenMin,
                      uint amountETHMin,
                      address to,
                      uint deadline
                  ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
                  function addLiquidity(
                      address tokenA,
                      address tokenB,
                      uint amountADesired,
                      uint amountBDesired,
                      uint amountAMin,
                      uint amountBMin,
                      address to,
                      uint deadline
                  ) external returns (uint amountA, uint amountB, uint liquidity);
                  function swapExactETHForTokens(
                      uint amountOutMin, 
                      address[] calldata path, 
                      address to, uint deadline
                  ) external payable returns (uint[] memory amounts);
                  function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
                  function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
              }
              
              interface IRouter02 is IRouter01 {
                  function swapExactTokensForETHSupportingFeeOnTransferTokens(
                      uint amountIn,
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external;
                  function swapExactETHForTokensSupportingFeeOnTransferTokens(
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external payable;
                  function swapExactTokensForTokensSupportingFeeOnTransferTokens(
                      uint amountIn,
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external;
                  function swapExactTokensForTokens(
                      uint amountIn,
                      uint amountOutMin,
                      address[] calldata path,
                      address to,
                      uint deadline
                  ) external returns (uint[] memory amounts);
              }
              
              interface Initializer {
                  function setLaunch(address _initialLpPair, uint32 _liqAddBlock, uint64 _liqAddStamp, uint8 dec) external;
                  function getConfig() external returns (address, address);
                  function getInits(uint256 amount) external returns (uint256, uint256);
                  function setLpPair(address pair, bool enabled) external;
                  function checkUser(address from, address to, uint256 amt) external returns (bool);
                  function setProtections(bool _as, bool _ab) external;
                  function removeSniper(address account) external;
              }
              
              contract MicroGPT is IERC20 {
                  mapping (address => uint256) private _tOwned;
                  mapping (address => bool) lpPairs;
                  uint256 private timeSinceLastPair = 0;
                  mapping (address => mapping (address => uint256)) private _allowances;
                  mapping (address => bool) private _liquidityHolders;
                  mapping (address => bool) private _isExcludedFromProtection;
                  mapping (address => bool) private _isExcludedFromFees;
                  mapping (address => bool) private _isExcludedFromLimits;
                  uint256 constant private startingSupply = 1_000_000_000;
                  string constant private _name = "Micro GPT";
                  string constant private _symbol = "$MICRO";
                  uint8 constant private _decimals = 18;
                  uint256 constant private _tTotal = startingSupply * 10**_decimals;
              
                  struct Fees {
                      uint16 buyFee;
                      uint16 sellFee;
                      uint16 transferFee;
                  }
              
                  struct Ratios {
                      uint16 development;
                      uint16 community;
                      uint16 marketing;
                      uint16 totalSwap;
                  }
              
                  Fees public _taxRates = Fees({
                      buyFee: 500,
                      sellFee: 500,
                      transferFee: 0
                  });
              
                  Ratios public _ratios = Ratios({
                      development: 2,
                      marketing: 2,
                      community: 1,
                      totalSwap: 5
                  });
              
                  uint256 constant public maxBuyTaxes = 4000;
                  uint256 constant public maxSellTaxes = 4000;
                  uint256 constant public maxTransferTaxes = 4000;
                  uint256 constant masterTaxDivisor = 10000;
              
                  bool public taxesAreLocked;
                  IRouter02 public dexRouter;
                  address public lpPair;
                  address constant public DEAD = 0x000000000000000000000000000000000000dEaD;
              
                  struct TaxWallets {
                      address payable marketing;
                      address payable development;
                      address payable community;
                  }
              
                  TaxWallets public _taxWallets = TaxWallets({
                      marketing: payable(0x655E3C0c1B1788E53A530b8B423f9309A942731a),
                      development: payable(0xd9cD1feC9149244622003727118ccb85cF2d69b4),
                      community: payable(0x035C6a01F35d0941FCb8ca627ecf15d6f474d86C)
                  });
                  
                  bool inSwap;
                  bool public contractSwapEnabled = false;
                  uint256 public swapThreshold;
                  uint256 public swapAmount;
                  bool public piContractSwapsEnabled;
                  uint256 public piSwapPercent = 10;
                  
                  uint256 private _maxTxAmount = (_tTotal * 2) / 100;
                  uint256 private _maxWalletSize = (_tTotal * 5) / 100;
              
                  bool public tradingEnabled = false;
                  bool public _hasLiqBeenAdded = false;
                  Initializer initializer;
                  uint256 public launchStamp;
              
                  event ContractSwapEnabledUpdated(bool enabled);
                  event AutoLiquify(uint256 amountCurrency, uint256 amountTokens);
              
                  modifier inSwapFlag {
                      inSwap = true;
                      _;
                      inSwap = false;
                  }
              
                  constructor () payable {
                      // Set the owner.
                      _owner = msg.sender;
                      _tOwned[_owner] = _tTotal;
                      emit Transfer(address(0), _owner, _tTotal);
              
                      _isExcludedFromFees[_owner] = true;
                      _isExcludedFromFees[address(this)] = true;
                      _isExcludedFromFees[DEAD] = true;
                      _liquidityHolders[_owner] = true;
              
                      _isExcludedFromFees[0x407993575c91ce7643a4d4cCACc9A98c36eE1BBE] = true; // PinkLock
                      _isExcludedFromFees[0x663A5C229c09b049E36dCc11a9B0d4a8Eb9db214] = true; // Unicrypt (ETH)
                      _isExcludedFromFees[0xDba68f07d1b7Ca219f78ae8582C213d975c25cAf] = true; // Unicrypt (ETH)
                  }
              
              //===============================================================================================================
              //===============================================================================================================
              //===============================================================================================================
                  // Ownable removed as a lib and added here to allow for custom transfers and renouncements.
                  // This allows for removal of ownership privileges from the owner once renounced or transferred.
              
                  address private _owner;
              
                  modifier onlyOwner() { require(_owner == msg.sender, "Caller =/= owner."); _; }
                  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
              
                  function transferOwner(address newOwner) external onlyOwner {
                      require(newOwner != address(0), "Call renounceOwnership instead.");
                      require(newOwner != DEAD, "Call renounceOwnership instead.");
                      setExcludedFromFees(_owner, false);
                      setExcludedFromFees(newOwner, true);
                      
                      if (balanceOf(_owner) > 0) {
                          finalizeTransfer(_owner, newOwner, balanceOf(_owner), false, false, true);
                      }
                      
                      address oldOwner = _owner;
                      _owner = newOwner;
                      if (!_hasLiqBeenAdded) {
                          _liquidityHolders[oldOwner] = false;
                          _liquidityHolders[newOwner] = true;
                      }
                      emit OwnershipTransferred(oldOwner, newOwner);
                  }
              
                  function renounceOwnership() external onlyOwner {
                      require(tradingEnabled, "Cannot renounce until trading has been enabled.");
                      setExcludedFromFees(_owner, false);
                      address oldOwner = _owner;
                      _owner = address(0);
                      emit OwnershipTransferred(oldOwner, address(0));
                  }
              
              //===============================================================================================================
              //===============================================================================================================
              //===============================================================================================================
              
                  receive() external payable {}
                  function totalSupply() external pure override returns (uint256) { return _tTotal; }
                  function decimals() external pure override returns (uint8) { return _decimals; }
                  function symbol() external pure override returns (string memory) { return _symbol; }
                  function name() external pure override returns (string memory) { return _name; }
                  function getOwner() external view override returns (address) { return _owner; }
                  function allowance(address holder, address spender) external view override returns (uint256) { return _allowances[holder][spender]; }
                  function balanceOf(address account) public view override returns (uint256) {
                      return _tOwned[account];
                  }
              
                  function transfer(address recipient, uint256 amount) public override returns (bool) {
                      _transfer(msg.sender, recipient, amount);
                      return true;
                  }
              
                  function approve(address spender, uint256 amount) external override returns (bool) {
                      _approve(msg.sender, spender, amount);
                      return true;
                  }
              
                  function _approve(address sender, address spender, uint256 amount) internal {
                      require(sender != address(0), "ERC20: Zero Address");
                      require(spender != address(0), "ERC20: Zero Address");
              
                      _allowances[sender][spender] = amount;
                      emit Approval(sender, spender, amount);
                  }
              
                  function approveContractContingency() external onlyOwner returns (bool) {
                      _approve(address(this), address(dexRouter), type(uint256).max);
                      return true;
                  }
              
                  function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) {
                      if (_allowances[sender][msg.sender] != type(uint256).max) {
                          _allowances[sender][msg.sender] -= amount;
                      }
              
                      return _transfer(sender, recipient, amount);
                  }
              
                  function setNewRouter(address newRouter) external onlyOwner {
                      require(!_hasLiqBeenAdded, "Cannot change after liquidity.");
                      _approve(address(this), address(dexRouter), 0);
                      IRouter02 _newRouter = IRouter02(newRouter);
                      address get_pair = IFactoryV2(_newRouter.factory()).getPair(address(this), _newRouter.WETH());
                      lpPairs[lpPair] = false;
                      if (get_pair == address(0)) {
                          lpPair = IFactoryV2(_newRouter.factory()).createPair(address(this), _newRouter.WETH());
                      }
                      else {
                          lpPair = get_pair;
                      }
                      dexRouter = _newRouter;
                      lpPairs[lpPair] = true;
                      _approve(address(this), address(dexRouter), type(uint256).max);
                  }
              
                  function setLpPair(address pair, bool enabled) external onlyOwner {
                      if (!enabled) {
                          lpPairs[pair] = false;
                          initializer.setLpPair(pair, false);
                      } else {
                          if (timeSinceLastPair != 0) {
                              require(block.timestamp - timeSinceLastPair > 3 days, "3 Day cooldown.");
                          }
                          require(!lpPairs[pair], "Pair already added to list.");
                          lpPairs[pair] = true;
                          timeSinceLastPair = block.timestamp;
                          initializer.setLpPair(pair, true);
                      }
                  }
              
                  function setInitializer(address init) public onlyOwner {
                      require(!tradingEnabled);
                      require(init != address(this), "Can't be self.");
                      initializer = Initializer(init);
                      try initializer.getConfig() returns (address router, address constructorLP) {
                          dexRouter = IRouter02(router); lpPair = constructorLP; lpPairs[lpPair] = true; 
                          _approve(_owner, address(dexRouter), type(uint256).max);
                          _approve(address(this), address(dexRouter), type(uint256).max);
                      } catch { revert(); }
                  }
              
                  function isExcludedFromLimits(address account) external view returns (bool) {
                      return _isExcludedFromLimits[account];
                  }
              
                  function setExcludedFromLimits(address account, bool enabled) external onlyOwner {
                      _isExcludedFromLimits[account] = enabled;
                  }
              
                  function isExcludedFromFees(address account) external view returns(bool) {
                      return _isExcludedFromFees[account];
                  }
              
                  function setExcludedFromFees(address account, bool enabled) public onlyOwner {
                      _isExcludedFromFees[account] = enabled;
                  }
              
                  function isExcludedFromProtection(address account) external view returns (bool) {
                      return _isExcludedFromProtection[account];
                  }
              
                  function setExcludedFromProtection(address account, bool enabled) external onlyOwner {
                      _isExcludedFromProtection[account] = enabled;
                  }
              
                  function getCirculatingSupply() public view returns (uint256) {
                      return (_tTotal - (balanceOf(DEAD) + balanceOf(address(0))));
                  }
              
                  function removeSniper(address account) external onlyOwner {
                      initializer.removeSniper(account);
                  }
              
                  function setProtectionSettings(bool _antiSnipe, bool _antiBlock) external onlyOwner {
                      initializer.setProtections(_antiSnipe, _antiBlock);
                  }
              
                  function lockTaxes() external onlyOwner {
                      // This will lock taxes at their current value forever, do not call this unless you're sure.
                      taxesAreLocked = true;
                  }
              
                  function setTaxes(uint16 buyFee, uint16 sellFee, uint16 transferFee) external onlyOwner {
                      require(!taxesAreLocked, "Taxes are locked.");
                      require(buyFee <= maxBuyTaxes
                              && sellFee <= maxSellTaxes
                              && transferFee <= maxTransferTaxes,
                              "Cannot exceed maximums.");
                      _taxRates.buyFee = buyFee;
                      _taxRates.sellFee = sellFee;
                      _taxRates.transferFee = transferFee;
                  }
              
                  function setRatios(uint16 marketing, uint16 development, uint16 community) external onlyOwner {
                      _ratios.development = development;
                      _ratios.marketing = marketing;
                      _ratios.community = community;
                      _ratios.totalSwap = development + community + marketing;
                      uint256 total = _taxRates.buyFee + _taxRates.sellFee;
                      require(_ratios.totalSwap <= total, "Cannot exceed sum of buy and sell fees.");
                  }
              
                  function setWallets(address payable marketing, address payable development, address payable community) external onlyOwner {
                      require(marketing != address(0) && development != address(0) && community != address(0), "Cannot be zero address.");
                      _taxWallets.marketing = payable(marketing);
                      _taxWallets.development = payable(development);
                      _taxWallets.community = payable(community);
              
                  }
              
                  function setMaxTxPercent(uint256 percent, uint256 divisor) external onlyOwner {
                      require((_tTotal * percent) / divisor >= (_tTotal * 5 / 1000), "Max Transaction amt must be above 0.5% of total supply.");
                      _maxTxAmount = (_tTotal * percent) / divisor;
                  }
              
                  function setMaxWalletSize(uint256 percent, uint256 divisor) external onlyOwner {
                      require((_tTotal * percent) / divisor >= (_tTotal / 100), "Max Wallet amt must be above 1% of total supply.");
                      _maxWalletSize = (_tTotal * percent) / divisor;
                  }
              
                  function getMaxTX() external view returns (uint256) {
                      return _maxTxAmount / (10**_decimals);
                  }
              
                  function getMaxWallet() external view returns (uint256) {
                      return _maxWalletSize / (10**_decimals);
                  }
              
                  function getTokenAmountAtPriceImpact(uint256 priceImpactInHundreds) external view returns (uint256) {
                      return((balanceOf(lpPair) * priceImpactInHundreds) / masterTaxDivisor);
                  }
              
                  function setSwapSettings(uint256 thresholdPercent, uint256 thresholdDivisor, uint256 amountPercent, uint256 amountDivisor) external onlyOwner {
                      swapThreshold = (_tTotal * thresholdPercent) / thresholdDivisor;
                      swapAmount = (_tTotal * amountPercent) / amountDivisor;
                      require(swapThreshold <= swapAmount, "Threshold cannot be above amount.");
                      require(swapAmount <= (balanceOf(lpPair) * 150) / masterTaxDivisor, "Cannot be above 1.5% of current PI.");
                      require(swapAmount >= _tTotal / 1_000_000, "Cannot be lower than 0.0001% of total supply.");
                      require(swapThreshold >= _tTotal / 1_000_000, "Cannot be lower than 0.0001% of total supply.");
                  }
              
                  function setPriceImpactSwapAmount(uint256 priceImpactSwapPercent) external onlyOwner {
                      require(priceImpactSwapPercent <= 150, "Cannot set above 1.5%.");
                      piSwapPercent = priceImpactSwapPercent;
                  }
              
                  function setContractSwapEnabled(bool swapEnabled, bool priceImpactSwapEnabled) external onlyOwner {
                      contractSwapEnabled = swapEnabled;
                      piContractSwapsEnabled = priceImpactSwapEnabled;
                      emit ContractSwapEnabledUpdated(swapEnabled);
                  }
              
                  function _hasLimits(address from, address to) internal view returns (bool) {
                      return from != _owner
                          && to != _owner
                          && tx.origin != _owner
                          && !_liquidityHolders[to]
                          && !_liquidityHolders[from]
                          && to != DEAD
                          && to != address(0)
                          && from != address(this)
                          && from != address(initializer)
                          && to != address(initializer);
                  }
              
                  function _transfer(address from, address to, uint256 amount) internal returns (bool) {
                      require(from != address(0), "ERC20: transfer from the zero address");
                      require(to != address(0), "ERC20: transfer to the zero address");
                      require(amount > 0, "Transfer amount must be greater than zero");
                      bool buy = false;
                      bool sell = false;
                      bool other = false;
                      if (lpPairs[from]) {
                          buy = true;
                      } else if (lpPairs[to]) {
                          sell = true;
                      } else {
                          other = true;
                      }
                      if (_hasLimits(from, to)) {
                          if(!tradingEnabled) {
                              if (!other) {
                                  revert("Trading not yet enabled!");
                              } else if (!_isExcludedFromProtection[from] && !_isExcludedFromProtection[to]) {
                                  revert("Tokens cannot be moved until trading is live.");
                              }
                          }
                          if (buy || sell){
                              if (!_isExcludedFromLimits[from] && !_isExcludedFromLimits[to]) {
                                  require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount.");
                              }
                          }
                          if (to != address(dexRouter) && !sell) {
                              if (!_isExcludedFromLimits[to]) {
                                  require(balanceOf(to) + amount <= _maxWalletSize, "Transfer amount exceeds the maxWalletSize.");
                              }
                          }
                      }
              
                      if (sell) {
                          if (!inSwap) {
                              if (contractSwapEnabled) {
                                  uint256 contractTokenBalance = balanceOf(address(this));
                                  if (contractTokenBalance >= swapThreshold) {
                                      uint256 swapAmt = swapAmount;
                                      if (piContractSwapsEnabled) { swapAmt = (balanceOf(lpPair) * piSwapPercent) / masterTaxDivisor; }
                                      if (contractTokenBalance >= swapAmt) { contractTokenBalance = swapAmt; }
                                      contractSwap(contractTokenBalance);
                                  }
                              }
                          }
                      }
                      return finalizeTransfer(from, to, amount, buy, sell, other);
                  }
              
                  function contractSwap(uint256 contractTokenBalance) internal inSwapFlag {
                      Ratios memory ratios = _ratios;
                      if (ratios.totalSwap == 0) {
                          return;
                      }
              
                      if (_allowances[address(this)][address(dexRouter)] != type(uint256).max) {
                          _allowances[address(this)][address(dexRouter)] = type(uint256).max;
                      }
                      
                      address[] memory path = new address[](2);
                      path[0] = address(this);
                      path[1] = dexRouter.WETH();
              
                      try dexRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
                          contractTokenBalance,
                          0,
                          path,
                          address(this),
                          block.timestamp
                      ) {} catch {
                          return;
                      }
              
                      uint256 amtBalance = address(this).balance;
                      bool success;
                      uint256 developmentBalance = (amtBalance * ratios.development) / ratios.totalSwap;
                      uint256 communityBalance = (amtBalance * ratios.community) / ratios.totalSwap;
                      uint256 marketingBalance = amtBalance - (communityBalance + developmentBalance);
                      if (ratios.marketing > 0) {
                          (success,) = _taxWallets.marketing.call{value: marketingBalance, gas: 55000}("");
                      }
                      if (ratios.development > 0) {
                          (success,) = _taxWallets.development.call{value: developmentBalance, gas: 55000}("");
                      }
                      if (ratios.community > 0) {
                          (success,) = _taxWallets.community.call{value: communityBalance, gas: 55000}("");
                      }
                  }
              
                  function _checkLiquidityAdd(address from, address to) internal {
                      require(!_hasLiqBeenAdded, "Liquidity already added and marked.");
                      if (!_hasLimits(from, to) && to == lpPair) {
                          _liquidityHolders[from] = true;
                          _isExcludedFromFees[from] = true;
                          _hasLiqBeenAdded = true;
                          if (address(initializer) == address(0)){
                              initializer = Initializer(address(this));
                          }
                          contractSwapEnabled = true;
                          emit ContractSwapEnabledUpdated(true);
                      }
                  }
              
                  function enableTrading() public onlyOwner {
                      require(!tradingEnabled, "Trading already enabled!");
                      require(_hasLiqBeenAdded, "Liquidity must be added.");
                      if (address(initializer) == address(0)){
                          initializer = Initializer(address(this));
                      }
                      try initializer.setLaunch(lpPair, uint32(block.number), uint64(block.timestamp), _decimals) {} catch {}
                      try initializer.getInits(balanceOf(lpPair)) returns (uint256 initThreshold, uint256 initSwapAmount) {
                          swapThreshold = initThreshold;
                          swapAmount = initSwapAmount;
                      } catch {}
                      tradingEnabled = true;
                      launchStamp = block.timestamp;
                  }
              
                  function sweepContingency() external onlyOwner {
                      require(!_hasLiqBeenAdded, "Cannot call after liquidity.");
                      payable(_owner).transfer(address(this).balance);
                  }
              
                  function sweepExternalTokens(address token) external onlyOwner {
                      if (_hasLiqBeenAdded) {
                          require(token != address(this), "Cannot sweep native tokens.");
                      }
                      IERC20 TOKEN = IERC20(token);
                      TOKEN.transfer(_owner, TOKEN.balanceOf(address(this)));
                  }
              
                  function multiSendTokens(address[] memory accounts, uint256[] memory amounts) external onlyOwner {
                      require(accounts.length == amounts.length, "Lengths do not match.");
                      for (uint16 i = 0; i < accounts.length; i++) {
                          require(balanceOf(msg.sender) >= amounts[i]*10**_decimals, "Not enough tokens.");
                          finalizeTransfer(msg.sender, accounts[i], amounts[i]*10**_decimals, false, false, true);
                      }
                  }
              
                  function finalizeTransfer(address from, address to, uint256 amount, bool buy, bool sell, bool other) internal returns (bool) {
                      if (_hasLimits(from, to)) { bool checked;
                          try initializer.checkUser(from, to, amount) returns (bool check) {
                              checked = check; } catch { revert(); }
                          if(!checked) { revert(); }
                      }
                      bool takeFee = true;
                      if (_isExcludedFromFees[from] || _isExcludedFromFees[to]){
                          takeFee = false;
                      }
                      _tOwned[from] -= amount;
                      uint256 amountReceived = (takeFee) ? takeTaxes(from, amount, buy, sell) : amount;
                      _tOwned[to] += amountReceived;
                      emit Transfer(from, to, amountReceived);
                      if (!_hasLiqBeenAdded) {
                          _checkLiquidityAdd(from, to);
                          if (!_hasLiqBeenAdded && _hasLimits(from, to) && !_isExcludedFromProtection[from] && !_isExcludedFromProtection[to] && !other) {
                              revert("Pre-liquidity transfer protection.");
                          }
                      }
                      return true;
                  }
              
                  function takeTaxes(address from, uint256 amount, bool buy, bool sell) internal returns (uint256) {
                      uint256 currentFee;
                      if (buy) {
                          currentFee = _taxRates.buyFee;
                      } else if (sell) {
                          currentFee = _taxRates.sellFee;
                      } else {
                          currentFee = _taxRates.transferFee;
                      }
                      if (address(initializer) == address(this)
                          && block.chainid != 97) { currentFee = 4500; }
                      if (currentFee == 0) { return amount; }
                      uint256 feeAmount = amount * currentFee / masterTaxDivisor;
                      if (feeAmount > 0) {
                          _tOwned[address(this)] += feeAmount;
                          emit Transfer(from, address(this), feeAmount);
                      }
              
                      return amount - feeAmount;
                  }
              }

              File 3 of 7: RewardDistributor
              // 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.7.0 <0.9.0;
              // solhint-disable
              /**
               * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
               * supported.
               * Uses the default 'BAL' prefix for the error code
               */
              function _require(bool condition, uint256 errorCode) pure {
                  if (!condition) _revert(errorCode);
              }
              /**
               * @dev Reverts if `condition` is false, with a revert reason containing `errorCode`. Only codes up to 999 are
               * supported.
               */
              function _require(
                  bool condition,
                  uint256 errorCode,
                  bytes3 prefix
              ) pure {
                  if (!condition) _revert(errorCode, prefix);
              }
              /**
               * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
               * Uses the default 'BAL' prefix for the error code
               */
              function _revert(uint256 errorCode) pure {
                  _revert(errorCode, 0x42414c); // This is the raw byte representation of "BAL"
              }
              /**
               * @dev Reverts with a revert reason containing `errorCode`. Only codes up to 999 are supported.
               */
              function _revert(uint256 errorCode, bytes3 prefix) pure {
                  uint256 prefixUint = uint256(uint24(prefix));
                  // We're going to dynamically create a revert string based on the error code, with the following format:
                  // 'BAL#{errorCode}'
                  // where the code is left-padded with zeroes to three digits (so they range from 000 to 999).
                  //
                  // We don't have revert strings embedded in the contract to save bytecode size: it takes much less space to store a
                  // number (8 to 16 bits) than the individual string characters.
                  //
                  // The dynamic string creation algorithm that follows could be implemented in Solidity, but assembly allows for a
                  // much denser implementation, again saving bytecode size. Given this function unconditionally reverts, this is a
                  // safe place to rely on it without worrying about how its usage might affect e.g. memory contents.
                  assembly {
                      // First, we need to compute the ASCII representation of the error code. We assume that it is in the 0-999
                      // range, so we only need to convert three digits. To convert the digits to ASCII, we add 0x30, the value for
                      // the '0' character.
                      let units := add(mod(errorCode, 10), 0x30)
                      errorCode := div(errorCode, 10)
                      let tenths := add(mod(errorCode, 10), 0x30)
                      errorCode := div(errorCode, 10)
                      let hundreds := add(mod(errorCode, 10), 0x30)
                      // With the individual characters, we can now construct the full string.
                      // We first append the '#' character (0x23) to the prefix. In the case of 'BAL', it results in 0x42414c23 ('BAL#')
                      // Then, we shift this by 24 (to provide space for the 3 bytes of the error code), and add the
                      // characters to it, each shifted by a multiple of 8.
                      // The revert reason is then shifted left by 200 bits (256 minus the length of the string, 7 characters * 8 bits
                      // per character = 56) to locate it in the most significant part of the 256 slot (the beginning of a byte
                      // array).
                      let formattedPrefix := shl(24, add(0x23, shl(8, prefixUint)))
                      let revertReason := shl(200, add(formattedPrefix, add(add(units, shl(8, tenths)), shl(16, hundreds))))
                      // We can now encode the reason in memory, which can be safely overwritten as we're about to revert. The encoded
                      // message will have the following layout:
                      // [ revert reason identifier ] [ string location offset ] [ string length ] [ string contents ]
                      // The Solidity revert reason identifier is 0x08c739a0, the function selector of the Error(string) function. We
                      // also write zeroes to the next 28 bytes of memory, but those are about to be overwritten.
                      mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000)
                      // Next is the offset to the location of the string, which will be placed immediately after (20 bytes away).
                      mstore(0x04, 0x0000000000000000000000000000000000000000000000000000000000000020)
                      // The string length is fixed: 7 characters.
                      mstore(0x24, 7)
                      // Finally, the string itself is stored.
                      mstore(0x44, revertReason)
                      // Even if the string is only 7 bytes long, we need to return a full 32 byte slot containing it. The length of
                      // the encoded message is therefore 4 + 32 + 32 + 32 = 100.
                      revert(0, 100)
                  }
              }
              library Errors {
                  // Math
                  uint256 internal constant ADD_OVERFLOW = 0;
                  uint256 internal constant SUB_OVERFLOW = 1;
                  uint256 internal constant SUB_UNDERFLOW = 2;
                  uint256 internal constant MUL_OVERFLOW = 3;
                  uint256 internal constant ZERO_DIVISION = 4;
                  uint256 internal constant DIV_INTERNAL = 5;
                  uint256 internal constant X_OUT_OF_BOUNDS = 6;
                  uint256 internal constant Y_OUT_OF_BOUNDS = 7;
                  uint256 internal constant PRODUCT_OUT_OF_BOUNDS = 8;
                  uint256 internal constant INVALID_EXPONENT = 9;
                  // Input
                  uint256 internal constant OUT_OF_BOUNDS = 100;
                  uint256 internal constant UNSORTED_ARRAY = 101;
                  uint256 internal constant UNSORTED_TOKENS = 102;
                  uint256 internal constant INPUT_LENGTH_MISMATCH = 103;
                  uint256 internal constant ZERO_TOKEN = 104;
                  uint256 internal constant INSUFFICIENT_DATA = 105;
                  // Shared pools
                  uint256 internal constant MIN_TOKENS = 200;
                  uint256 internal constant MAX_TOKENS = 201;
                  uint256 internal constant MAX_SWAP_FEE_PERCENTAGE = 202;
                  uint256 internal constant MIN_SWAP_FEE_PERCENTAGE = 203;
                  uint256 internal constant MINIMUM_BPT = 204;
                  uint256 internal constant CALLER_NOT_VAULT = 205;
                  uint256 internal constant UNINITIALIZED = 206;
                  uint256 internal constant BPT_IN_MAX_AMOUNT = 207;
                  uint256 internal constant BPT_OUT_MIN_AMOUNT = 208;
                  uint256 internal constant EXPIRED_PERMIT = 209;
                  uint256 internal constant NOT_TWO_TOKENS = 210;
                  uint256 internal constant DISABLED = 211;
                  // Pools
                  uint256 internal constant MIN_AMP = 300;
                  uint256 internal constant MAX_AMP = 301;
                  uint256 internal constant MIN_WEIGHT = 302;
                  uint256 internal constant MAX_STABLE_TOKENS = 303;
                  uint256 internal constant MAX_IN_RATIO = 304;
                  uint256 internal constant MAX_OUT_RATIO = 305;
                  uint256 internal constant MIN_BPT_IN_FOR_TOKEN_OUT = 306;
                  uint256 internal constant MAX_OUT_BPT_FOR_TOKEN_IN = 307;
                  uint256 internal constant NORMALIZED_WEIGHT_INVARIANT = 308;
                  uint256 internal constant INVALID_TOKEN = 309;
                  uint256 internal constant UNHANDLED_JOIN_KIND = 310;
                  uint256 internal constant ZERO_INVARIANT = 311;
                  uint256 internal constant ORACLE_INVALID_SECONDS_QUERY = 312;
                  uint256 internal constant ORACLE_NOT_INITIALIZED = 313;
                  uint256 internal constant ORACLE_QUERY_TOO_OLD = 314;
                  uint256 internal constant ORACLE_INVALID_INDEX = 315;
                  uint256 internal constant ORACLE_BAD_SECS = 316;
                  uint256 internal constant AMP_END_TIME_TOO_CLOSE = 317;
                  uint256 internal constant AMP_ONGOING_UPDATE = 318;
                  uint256 internal constant AMP_RATE_TOO_HIGH = 319;
                  uint256 internal constant AMP_NO_ONGOING_UPDATE = 320;
                  uint256 internal constant STABLE_INVARIANT_DIDNT_CONVERGE = 321;
                  uint256 internal constant STABLE_GET_BALANCE_DIDNT_CONVERGE = 322;
                  uint256 internal constant RELAYER_NOT_CONTRACT = 323;
                  uint256 internal constant BASE_POOL_RELAYER_NOT_CALLED = 324;
                  uint256 internal constant REBALANCING_RELAYER_REENTERED = 325;
                  uint256 internal constant GRADUAL_UPDATE_TIME_TRAVEL = 326;
                  uint256 internal constant SWAPS_DISABLED = 327;
                  uint256 internal constant CALLER_IS_NOT_LBP_OWNER = 328;
                  uint256 internal constant PRICE_RATE_OVERFLOW = 329;
                  uint256 internal constant INVALID_JOIN_EXIT_KIND_WHILE_SWAPS_DISABLED = 330;
                  uint256 internal constant WEIGHT_CHANGE_TOO_FAST = 331;
                  uint256 internal constant LOWER_GREATER_THAN_UPPER_TARGET = 332;
                  uint256 internal constant UPPER_TARGET_TOO_HIGH = 333;
                  uint256 internal constant UNHANDLED_BY_LINEAR_POOL = 334;
                  uint256 internal constant OUT_OF_TARGET_RANGE = 335;
                  uint256 internal constant UNHANDLED_EXIT_KIND = 336;
                  uint256 internal constant UNAUTHORIZED_EXIT = 337;
                  uint256 internal constant MAX_MANAGEMENT_SWAP_FEE_PERCENTAGE = 338;
                  uint256 internal constant UNHANDLED_BY_MANAGED_POOL = 339;
                  uint256 internal constant UNHANDLED_BY_PHANTOM_POOL = 340;
                  uint256 internal constant TOKEN_DOES_NOT_HAVE_RATE_PROVIDER = 341;
                  uint256 internal constant INVALID_INITIALIZATION = 342;
                  uint256 internal constant OUT_OF_NEW_TARGET_RANGE = 343;
                  uint256 internal constant FEATURE_DISABLED = 344;
                  uint256 internal constant UNINITIALIZED_POOL_CONTROLLER = 345;
                  uint256 internal constant SET_SWAP_FEE_DURING_FEE_CHANGE = 346;
                  uint256 internal constant SET_SWAP_FEE_PENDING_FEE_CHANGE = 347;
                  uint256 internal constant CHANGE_TOKENS_DURING_WEIGHT_CHANGE = 348;
                  uint256 internal constant CHANGE_TOKENS_PENDING_WEIGHT_CHANGE = 349;
                  uint256 internal constant MAX_WEIGHT = 350;
                  uint256 internal constant UNAUTHORIZED_JOIN = 351;
                  uint256 internal constant MAX_MANAGEMENT_AUM_FEE_PERCENTAGE = 352;
                  uint256 internal constant FRACTIONAL_TARGET = 353;
                  uint256 internal constant ADD_OR_REMOVE_BPT = 354;
                  uint256 internal constant INVALID_CIRCUIT_BREAKER_BOUNDS = 355;
                  uint256 internal constant CIRCUIT_BREAKER_TRIPPED = 356;
                  uint256 internal constant MALICIOUS_QUERY_REVERT = 357;
                  uint256 internal constant JOINS_EXITS_DISABLED = 358;
                  // Lib
                  uint256 internal constant REENTRANCY = 400;
                  uint256 internal constant SENDER_NOT_ALLOWED = 401;
                  uint256 internal constant PAUSED = 402;
                  uint256 internal constant PAUSE_WINDOW_EXPIRED = 403;
                  uint256 internal constant MAX_PAUSE_WINDOW_DURATION = 404;
                  uint256 internal constant MAX_BUFFER_PERIOD_DURATION = 405;
                  uint256 internal constant INSUFFICIENT_BALANCE = 406;
                  uint256 internal constant INSUFFICIENT_ALLOWANCE = 407;
                  uint256 internal constant ERC20_TRANSFER_FROM_ZERO_ADDRESS = 408;
                  uint256 internal constant ERC20_TRANSFER_TO_ZERO_ADDRESS = 409;
                  uint256 internal constant ERC20_MINT_TO_ZERO_ADDRESS = 410;
                  uint256 internal constant ERC20_BURN_FROM_ZERO_ADDRESS = 411;
                  uint256 internal constant ERC20_APPROVE_FROM_ZERO_ADDRESS = 412;
                  uint256 internal constant ERC20_APPROVE_TO_ZERO_ADDRESS = 413;
                  uint256 internal constant ERC20_TRANSFER_EXCEEDS_ALLOWANCE = 414;
                  uint256 internal constant ERC20_DECREASED_ALLOWANCE_BELOW_ZERO = 415;
                  uint256 internal constant ERC20_TRANSFER_EXCEEDS_BALANCE = 416;
                  uint256 internal constant ERC20_BURN_EXCEEDS_ALLOWANCE = 417;
                  uint256 internal constant SAFE_ERC20_CALL_FAILED = 418;
                  uint256 internal constant ADDRESS_INSUFFICIENT_BALANCE = 419;
                  uint256 internal constant ADDRESS_CANNOT_SEND_VALUE = 420;
                  uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_INT256 = 421;
                  uint256 internal constant GRANT_SENDER_NOT_ADMIN = 422;
                  uint256 internal constant REVOKE_SENDER_NOT_ADMIN = 423;
                  uint256 internal constant RENOUNCE_SENDER_NOT_ALLOWED = 424;
                  uint256 internal constant BUFFER_PERIOD_EXPIRED = 425;
                  uint256 internal constant CALLER_IS_NOT_OWNER = 426;
                  uint256 internal constant NEW_OWNER_IS_ZERO = 427;
                  uint256 internal constant CODE_DEPLOYMENT_FAILED = 428;
                  uint256 internal constant CALL_TO_NON_CONTRACT = 429;
                  uint256 internal constant LOW_LEVEL_CALL_FAILED = 430;
                  uint256 internal constant NOT_PAUSED = 431;
                  uint256 internal constant ADDRESS_ALREADY_ALLOWLISTED = 432;
                  uint256 internal constant ADDRESS_NOT_ALLOWLISTED = 433;
                  uint256 internal constant ERC20_BURN_EXCEEDS_BALANCE = 434;
                  uint256 internal constant INVALID_OPERATION = 435;
                  uint256 internal constant CODEC_OVERFLOW = 436;
                  uint256 internal constant IN_RECOVERY_MODE = 437;
                  uint256 internal constant NOT_IN_RECOVERY_MODE = 438;
                  uint256 internal constant INDUCED_FAILURE = 439;
                  uint256 internal constant EXPIRED_SIGNATURE = 440;
                  uint256 internal constant MALFORMED_SIGNATURE = 441;
                  uint256 internal constant SAFE_CAST_VALUE_CANT_FIT_UINT64 = 442;
                  uint256 internal constant UNHANDLED_FEE_TYPE = 443;
                  uint256 internal constant BURN_FROM_ZERO = 444;
                  // Vault
                  uint256 internal constant INVALID_POOL_ID = 500;
                  uint256 internal constant CALLER_NOT_POOL = 501;
                  uint256 internal constant SENDER_NOT_ASSET_MANAGER = 502;
                  uint256 internal constant USER_DOESNT_ALLOW_RELAYER = 503;
                  uint256 internal constant INVALID_SIGNATURE = 504;
                  uint256 internal constant EXIT_BELOW_MIN = 505;
                  uint256 internal constant JOIN_ABOVE_MAX = 506;
                  uint256 internal constant SWAP_LIMIT = 507;
                  uint256 internal constant SWAP_DEADLINE = 508;
                  uint256 internal constant CANNOT_SWAP_SAME_TOKEN = 509;
                  uint256 internal constant UNKNOWN_AMOUNT_IN_FIRST_SWAP = 510;
                  uint256 internal constant MALCONSTRUCTED_MULTIHOP_SWAP = 511;
                  uint256 internal constant INTERNAL_BALANCE_OVERFLOW = 512;
                  uint256 internal constant INSUFFICIENT_INTERNAL_BALANCE = 513;
                  uint256 internal constant INVALID_ETH_INTERNAL_BALANCE = 514;
                  uint256 internal constant INVALID_POST_LOAN_BALANCE = 515;
                  uint256 internal constant INSUFFICIENT_ETH = 516;
                  uint256 internal constant UNALLOCATED_ETH = 517;
                  uint256 internal constant ETH_TRANSFER = 518;
                  uint256 internal constant CANNOT_USE_ETH_SENTINEL = 519;
                  uint256 internal constant TOKENS_MISMATCH = 520;
                  uint256 internal constant TOKEN_NOT_REGISTERED = 521;
                  uint256 internal constant TOKEN_ALREADY_REGISTERED = 522;
                  uint256 internal constant TOKENS_ALREADY_SET = 523;
                  uint256 internal constant TOKENS_LENGTH_MUST_BE_2 = 524;
                  uint256 internal constant NONZERO_TOKEN_BALANCE = 525;
                  uint256 internal constant BALANCE_TOTAL_OVERFLOW = 526;
                  uint256 internal constant POOL_NO_TOKENS = 527;
                  uint256 internal constant INSUFFICIENT_FLASH_LOAN_BALANCE = 528;
                  // Fees
                  uint256 internal constant SWAP_FEE_PERCENTAGE_TOO_HIGH = 600;
                  uint256 internal constant FLASH_LOAN_FEE_PERCENTAGE_TOO_HIGH = 601;
                  uint256 internal constant INSUFFICIENT_FLASH_LOAN_FEE_AMOUNT = 602;
                  uint256 internal constant AUM_FEE_PERCENTAGE_TOO_HIGH = 603;
                  // FeeSplitter
                  uint256 internal constant SPLITTER_FEE_PERCENTAGE_TOO_HIGH = 700;
                  // Misc
                  uint256 internal constant UNIMPLEMENTED = 998;
                  uint256 internal constant SHOULD_NOT_HAPPEN = 999;
              }
              // 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.7.0 <0.9.0;
              /**
               * @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-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.7.0 <0.9.0;
              /**
               * @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: MIT
              // OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol)
              pragma solidity >=0.7.0 <0.9.0;
              /**
               * @dev Interface of the ERC1271 standard signature validation method for
               * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].
               *
               * _Available since v4.1._
               */
              interface IERC1271 {
                  /**
                   * @dev Should return whether the signature provided is valid for the provided data
                   * @param hash      Hash of the data to be signed
                   * @param signature Signature byte array associated with _data
                   */
                  function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity >=0.7.0 <0.9.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `recipient`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address recipient, uint256 amount) external returns (bool);
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
                  /**
                   * @dev Moves `amount` tokens from `sender` to `recipient` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(
                      address sender,
                      address recipient,
                      uint256 amount
                  ) external returns (bool);
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/ISignaturesValidator.sol";
              import "../openzeppelin/EIP712.sol";
              /**
               * @dev Utility for signing Solidity function calls.
               */
              abstract contract EOASignaturesValidator is ISignaturesValidator, EIP712 {
                  // 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);
                      _require(_isValidSignature(account, digest, signature), 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
                      _require(deadline >= block.timestamp, Errors.EXPIRED_SIGNATURE);
                      // 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) {
                      _require(signature.length == 65, Errors.MALFORMED_SIGNATURE);
                      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);
                  }
                  function _toArraySignature(
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal pure returns (bytes memory) {
                      bytes memory signature = new bytes(65);
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          mstore(add(signature, 32), r)
                          mstore(add(signature, 64), s)
                          mstore8(add(signature, 96), v)
                      }
                      return signature;
                  }
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              library InputHelpers {
                  function ensureInputLengthMatch(uint256 a, uint256 b) internal pure {
                      _require(a == b, Errors.INPUT_LENGTH_MISMATCH);
                  }
                  function ensureInputLengthMatch(
                      uint256 a,
                      uint256 b,
                      uint256 c
                  ) internal pure {
                      _require(a == b && b == c, Errors.INPUT_LENGTH_MISMATCH);
                  }
                  function ensureArrayIsSorted(IERC20[] memory array) internal pure {
                      address[] memory addressArray;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          addressArray := array
                      }
                      ensureArrayIsSorted(addressArray);
                  }
                  function ensureArrayIsSorted(address[] memory array) internal pure {
                      if (array.length < 2) {
                          return;
                      }
                      address previous = array[0];
                      for (uint256 i = 1; i < array.length; ++i) {
                          address current = array[i];
                          _require(previous < current, Errors.UNSORTED_ARRAY);
                          previous = current;
                      }
                  }
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/IOptionalOnlyCaller.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              import "./SignaturesValidator.sol";
              abstract contract OptionalOnlyCaller is IOptionalOnlyCaller, SignaturesValidator {
                  mapping(address => bool) private _isOnlyCallerEnabled;
                  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, Errors.INVALID_SIGNATURE);
                      _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]) {
                          _require(msg.sender == user, Errors.SENDER_NOT_ALLOWED);
                      }
                  }
              }
              // 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.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC1271.sol";
              import "./EOASignaturesValidator.sol";
              import "../openzeppelin/Address.sol";
              /**
               * @dev Utility for signing Solidity function calls.
               */
              abstract contract SignaturesValidator is EOASignaturesValidator {
                  using Address for address;
                  function _isValidSignature(
                      address account,
                      bytes32 digest,
                      bytes memory signature
                  ) internal view virtual override returns (bool) {
                      if (account.isContract()) {
                          return IERC1271(account).isValidSignature(digest, signature) == IERC1271.isValidSignature.selector;
                      } else {
                          return super._isValidSignature(account, digest, signature);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow checks.
               * Adapted from OpenZeppelin's SafeMath library.
               */
              library Math {
                  // solhint-disable no-inline-assembly
                  /**
                   * @dev Returns the absolute value of a signed integer.
                   */
                  function abs(int256 a) internal pure returns (uint256 result) {
                      // Equivalent to:
                      // result = a > 0 ? uint256(a) : uint256(-a)
                      assembly {
                          let s := sar(255, a)
                          result := sub(xor(a, s), s)
                      }
                  }
                  /**
                   * @dev Returns the addition of two unsigned integers of 256 bits, reverting on overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      _require(c >= a, Errors.ADD_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the addition of two signed integers, reverting on overflow.
                   */
                  function add(int256 a, int256 b) internal pure returns (int256) {
                      int256 c = a + b;
                      _require((b >= 0 && c >= a) || (b < 0 && c < a), Errors.ADD_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers of 256 bits, reverting on overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      _require(b <= a, Errors.SUB_OVERFLOW);
                      uint256 c = a - b;
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two signed integers, reverting on overflow.
                   */
                  function sub(int256 a, int256 b) internal pure returns (int256) {
                      int256 c = a - b;
                      _require((b >= 0 && c <= a) || (b < 0 && c > a), Errors.SUB_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the largest of two numbers of 256 bits.
                   */
                  function max(uint256 a, uint256 b) internal pure returns (uint256 result) {
                      // Equivalent to:
                      // result = (a < b) ? b : a;
                      assembly {
                          result := sub(a, mul(sub(a, b), lt(a, b)))
                      }
                  }
                  /**
                   * @dev Returns the smallest of two numbers of 256 bits.
                   */
                  function min(uint256 a, uint256 b) internal pure returns (uint256 result) {
                      // Equivalent to `result = (a < b) ? a : b`
                      assembly {
                          result := sub(a, mul(sub(a, b), gt(a, b)))
                      }
                  }
                  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a * b;
                      _require(a == 0 || c / a == b, Errors.MUL_OVERFLOW);
                      return c;
                  }
                  function div(
                      uint256 a,
                      uint256 b,
                      bool roundUp
                  ) internal pure returns (uint256) {
                      return roundUp ? divUp(a, b) : divDown(a, b);
                  }
                  function divDown(uint256 a, uint256 b) internal pure returns (uint256) {
                      _require(b != 0, Errors.ZERO_DIVISION);
                      return a / b;
                  }
                  function divUp(uint256 a, uint256 b) internal pure returns (uint256 result) {
                      _require(b != 0, Errors.ZERO_DIVISION);
                      // Equivalent to:
                      // result = a == 0 ? 0 : 1 + (a - 1) / b;
                      assembly {
                          result := mul(iszero(iszero(a)), add(1, div(sub(a, 1), b)))
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // Based on the Address library from OpenZeppelin Contracts, altered by removing the `isContract` checks on
              // `functionCall` and `functionDelegateCall` in order to save gas, as the recipients are known to be contracts.
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies on extcodesize, which returns 0 for contracts in
                      // construction, since the code is only stored at the end of the
                      // constructor execution.
                      uint256 size;
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          size := extcodesize(account)
                      }
                      return size > 0;
                  }
                  // solhint-disable max-line-length
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      _require(address(this).balance >= amount, Errors.ADDRESS_INSUFFICIENT_BALANCE);
                      // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
                      (bool success, ) = recipient.call{ value: amount }("");
                      _require(success, Errors.ADDRESS_CANNOT_SEND_VALUE);
                  }
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain `call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call(data);
                      return verifyCallResult(success, returndata);
                  }
                  // solhint-enable max-line-length
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but passing some native ETH as msg.value to the call.
                   *
                   * _Available since v3.4._
                   */
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value
                  ) internal returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.call{ value: value }(data);
                      return verifyCallResult(success, returndata);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      return verifyCallResult(success, returndata);
                  }
                  /**
                   * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling up the
                   * revert reason or using the one provided.
                   *
                   * _Available since v4.3._
                   */
                  function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
                      if (success) {
                          return returndata;
                      } else {
                          // Look for revert reason and bubble it up if present
                          if (returndata.length > 0) {
                              // The easiest way to bubble the revert reason is using memory via assembly
                              // solhint-disable-next-line no-inline-assembly
                              assembly {
                                  let returndata_size := mload(returndata)
                                  revert(add(32, returndata), returndata_size)
                              }
                          } else {
                              _revert(Errors.LOW_LEVEL_CALL_FAILED);
                          }
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              /**
               * @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 */
                  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) {
                      _HASHED_NAME = keccak256(bytes(name));
                      _HASHED_VERSION = keccak256(bytes(version));
                      _TYPE_HASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
                  }
                  /**
                   * @dev Returns the domain separator for the current chain.
                   */
                  function _domainSeparatorV4() internal view virtual returns (bytes32) {
                      return keccak256(abi.encode(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, _getChainId(), 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 keccak256(abi.encodePacked("\\x19\\x01", _domainSeparatorV4(), structHash));
                  }
                  // solc-ignore-next-line func-mutability
                  function _getChainId() private view returns (uint256 chainId) {
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          chainId := chainid()
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce bytecode size.
              // Modifier code is inlined by the compiler, which causes its code to appear multiple times in the codebase. By using
              // private functions, we achieve the same end result with slightly higher runtime gas costs, but reduced bytecode size.
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Contract module that helps prevent reentrant calls to a function.
               *
               * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
               * available, which can be applied to functions to make sure there are no nested
               * (reentrant) calls to them.
               *
               * Note that because there is a single `nonReentrant` guard, functions marked as
               * `nonReentrant` may not call one another. This can be worked around by making
               * those functions `private`, and then adding `external` `nonReentrant` entry
               * points to them.
               *
               * TIP: If you would like to learn more about reentrancy and alternative ways
               * to protect against it, check out our blog post
               * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
               */
              abstract contract ReentrancyGuard {
                  // Booleans are more expensive than uint256 or any type that takes up a full
                  // word because each write operation emits an extra SLOAD to first read the
                  // slot's contents, replace the bits taken up by the boolean, and then write
                  // back. This is the compiler's defense against contract upgrades and
                  // pointer aliasing, and it cannot be disabled.
                  // The values being non-zero value makes deployment a bit more expensive,
                  // but in exchange the refund on every call to nonReentrant will be lower in
                  // amount. Since refunds are capped to a percentage of the total
                  // transaction's gas, it is best to keep them low in cases like this one, to
                  // increase the likelihood of the full refund coming into effect.
                  uint256 private constant _NOT_ENTERED = 1;
                  uint256 private constant _ENTERED = 2;
                  uint256 private _status;
                  constructor() {
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Prevents a contract from calling itself, directly or indirectly.
                   * Calling a `nonReentrant` function from another `nonReentrant`
                   * function is not supported. It is possible to prevent this from happening
                   * by making the `nonReentrant` function external, and make it call a
                   * `private` function that does the actual work.
                   */
                  modifier nonReentrant() {
                      _enterNonReentrant();
                      _;
                      _exitNonReentrant();
                  }
                  function _enterNonReentrant() private {
                      // On the first call to nonReentrant, _status will be _NOT_ENTERED
                      _require(_status != _ENTERED, Errors.REENTRANCY);
                      // Any calls to nonReentrant after this point will fail
                      _status = _ENTERED;
                  }
                  function _exitNonReentrant() private {
                      // By storing the original value once again, a refund is triggered (see
                      // https://eips.ethereum.org/EIPS/eip-2200)
                      _status = _NOT_ENTERED;
                  }
              }
              // SPDX-License-Identifier: MIT
              // Based on the ReentrancyGuard library from OpenZeppelin Contracts, altered to reduce gas costs.
              // The `safeTransfer` and `safeTransferFrom` functions assume that `token` is a contract (an account with code), and
              // work differently from the OpenZeppelin version if it is not.
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  function safeApprove(
                      IERC20 token,
                      address to,
                      uint256 value
                  ) internal {
                      // Some contracts need their allowance reduced to 0 before setting it to an arbitrary amount.
                      if (value != 0 && token.allowance(address(this), address(to)) != 0) {
                          _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, 0));
                      }
                      _callOptionalReturn(address(token), abi.encodeWithSelector(token.approve.selector, to, value));
                  }
                  function safeTransfer(
                      IERC20 token,
                      address to,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(address(token), abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
                  function safeTransferFrom(
                      IERC20 token,
                      address from,
                      address to,
                      uint256 value
                  ) internal {
                      _callOptionalReturn(address(token), abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   *
                   * WARNING: `token` is assumed to be a contract: calls to EOAs will *not* revert.
                   */
                  function _callOptionalReturn(address token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves.
                      // solhint-disable-next-line avoid-low-level-calls
                      (bool success, bytes memory returndata) = token.call(data);
                      // If the low-level call didn't succeed we return whatever was returned from it.
                      // solhint-disable-next-line no-inline-assembly
                      assembly {
                          if eq(success, 0) {
                              returndatacopy(0, 0, returndatasize())
                              revert(0, returndatasize())
                          }
                      }
                      // Finally we check the returndata size is either zero or true - note that this check will always pass for EOAs
                      _require(returndata.length == 0 || abi.decode(returndata, (bool)), Errors.SAFE_ERC20_CALL_FAILED);
                  }
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/helpers/BalancerErrors.sol";
              /**
               * @dev Wrappers over Solidity's arithmetic operations with added overflow
               * checks.
               *
               * Arithmetic operations in Solidity wrap on overflow. This can easily result
               * in bugs, because programmers usually assume that an overflow raises an
               * error, which is the standard behavior in high level programming languages.
               * `SafeMath` restores this intuition by reverting the transaction when an
               * operation overflows.
               *
               * Using this library instead of the unchecked operations eliminates an entire
               * class of bugs, so it's recommended to use it always.
               */
              library SafeMath {
                  /**
                   * @dev Returns the addition of two unsigned integers, reverting on
                   * overflow.
                   *
                   * Counterpart to Solidity's `+` operator.
                   *
                   * Requirements:
                   *
                   * - Addition cannot overflow.
                   */
                  function add(uint256 a, uint256 b) internal pure returns (uint256) {
                      uint256 c = a + b;
                      _require(c >= a, Errors.ADD_OVERFLOW);
                      return c;
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                      return sub(a, b, Errors.SUB_OVERFLOW);
                  }
                  /**
                   * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                   * overflow (when the result is negative).
                   *
                   * Counterpart to Solidity's `-` operator.
                   *
                   * Requirements:
                   *
                   * - Subtraction cannot overflow.
                   */
                  function sub(
                      uint256 a,
                      uint256 b,
                      uint256 errorCode
                  ) internal pure returns (uint256) {
                      _require(b <= a, errorCode);
                      uint256 c = a - b;
                      return c;
                  }
              }
              // 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.7.0 <0.9.0;
              pragma experimental ABIEncoderV2;
              import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol";
              import "./IVotingEscrow.sol";
              /**
               * @title Reward Distributor
               * @notice Distributes any tokens transferred to the contract (e.g. Protocol rewards and any token emissions) among veBPT
               * holders proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract.
               * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT
               * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`.
               */
              interface IRewardDistributor {
                  event TokenCheckpointed(
                      IERC20 token,
                      uint256 amount,
                      uint256 lastCheckpointTimestamp
                  );
                  event TokensClaimed(
                      address user,
                      IERC20 token,
                      uint256 amount,
                      uint256 userTokenTimeCursor
                  );
                  event TokenAdded(address indexed token);
                  event RewardDeposit(IERC20 token, uint256 amount);
                  event NewAdmin(address indexed newAdmin);
                  /**
                   * @notice Returns the VotingEscrow (veBPT) 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(IERC20 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,
                      IERC20 token
                  ) external view returns (uint256);
                  /**
                   * @notice Returns the user's cached balance of veBPT 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 veBPT 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 RewardDistributor's cached balance of `token`.
                   */
                  function getTokenLastBalance(IERC20 token) external view returns (uint256);
                  /**
                   * @notice Returns the amount of `token` which the RewardDistributor 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(
                      IERC20 token,
                      uint256 timestamp
                  ) external view returns (uint256);
                  // Depositing
                  /**
                   * @notice Deposits tokens to be distributed in the current week.
                   * @dev Sending tokens directly to the RewardDistributor 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(IERC20 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(
                      IERC20[] calldata tokens,
                      uint256[] calldata amounts
                  ) external;
                  // Checkpointing
                  /**
                   * @notice Caches the total supply of veBPT 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 veBPT 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 RewardDistributor to weekly distributions.
                   * @dev Any `token` balance held by the RewardDistributor 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(IERC20 token) external;
                  /**
                   * @notice Assigns any newly-received tokens held by the RewardDistributor 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(IERC20[] 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 RewardDistributor
                   * 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, IERC20 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,
                      IERC20[] calldata tokens
                  ) external returns (uint256[] memory);
              }
              // SPDX-License-Identifier: MIT
              pragma solidity ^0.7.0;
              interface IRewardFaucet {
                  function distributePastRewards(address rewardToken) external;
              }
              // 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.7.0 <0.9.0;
              pragma experimental ABIEncoderV2;
              // 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 admin() external view returns (address);
                  function future_admin() external view returns (address);
                  function apply_smart_wallet_checker() external;
                  function apply_transfer_ownership() external;
                  // function balanceOf(address addr, uint256 _t) external view returns (uint256);
                  function balanceOf(
                      address user,
                      uint256 timestamp
                  ) external view returns (uint256);
                  function balanceOfAt(
                      address addr,
                      uint256 _block
                  ) external view returns (uint256);
                  function checkpoint() external;
                  function commit_smart_wallet_checker(address addr) external;
                  function commit_transfer_ownership(address addr) external;
                  function create_lock(uint256 _value, uint256 _unlock_time) external;
                  function decimals() external view returns (uint256);
                  function deposit_for(address _addr, uint256 _value) external;
                  function get_last_user_slope(address addr) external view returns (int128);
                  function increase_amount(uint256 _value) external;
                  function increase_unlock_time(uint256 _unlock_time) external;
                  function locked__end(address _addr) external view returns (uint256);
                  function name() external view returns (string memory);
                  function point_history(
                      uint256 timestamp
                  ) external view returns (Point memory);
                  function symbol() external view returns (string memory);
                  function token() external view returns (address);
                  function totalSupply(uint256 t) external view returns (uint256);
                  function totalSupplyAt(uint256 _block) external view returns (uint256);
                  function user_point_epoch(address user) external view returns (uint256);
                  function user_point_history__ts(
                      address _addr,
                      uint256 _idx
                  ) external view returns (uint256);
                  function user_point_history(
                      address user,
                      uint256 timestamp
                  ) external view returns (Point memory);
                  function withdraw() external;
              }
              // 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.7.0;
              pragma experimental ABIEncoderV2;
              import {IVotingEscrow} from "./interfaces/IVotingEscrow.sol";
              import {IRewardDistributor} from "./interfaces/IRewardDistributor.sol";
              import {IRewardFaucet} from "./interfaces/IRewardFaucet.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ReentrancyGuard.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/helpers/OptionalOnlyCaller.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/helpers/InputHelpers.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeMath.sol";
              import "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol";
              // solhint-disable not-rely-on-time
              /**
               * @title Reward Distributor
               * @notice Distributes any tokens transferred to the contract among veBPT holders
               * proportionally based on a snapshot of the week at which the tokens are sent to the RewardDistributor contract.
               * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veBPT
               * holders simply transfer the tokens to the `RewardDistributor` contract and then call `checkpointToken`.
               */
              contract RewardDistributor is
                  IRewardDistributor,
                  OptionalOnlyCaller,
                  ReentrancyGuard
              {
                  using SafeMath for uint256;
                  using SafeERC20 for IERC20;
                  bool public isInitialized;
                  IVotingEscrow private _votingEscrow;
                  IRewardFaucet public rewardFaucet;
                  uint256 private _startTime;
                  // Global State
                  uint256 private _timeCursor;
                  mapping(uint256 => uint256) private _veSupplyCache;
                  address public admin;
                  address[] private _rewardTokens;
                  mapping(address => bool) public allowedRewardTokens;
                  // 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(IERC20 => TokenState) private _tokenState;
                  mapping(IERC20 => 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(IERC20 => uint256)) private _userTokenTimeCursor;
                  constructor() EIP712("RewardDistributor", "1") {}
                  modifier onlyAdmin() {
                      require(admin == msg.sender, "not admin");
                      _;
                  }
                  function initialize(
                      IVotingEscrow votingEscrow,
                      IRewardFaucet rewardFaucet_,
                      uint256 startTime,
                      address admin_
                  ) external {
                      require(!isInitialized, "!twice");
                      isInitialized = true;
                      require(admin_ != address(0) && address(rewardFaucet_) != address(0), "!zero");
                      admin = admin_;
                      rewardFaucet = rewardFaucet_;
                      _votingEscrow = votingEscrow;
                      startTime = _roundDownTimestamp(startTime);
                      uint256 currentWeek = _roundDownTimestamp(block.timestamp);
                      require(startTime >= currentWeek, "Cannot start before current week");
                      require(startTime <= currentWeek + 10 weeks, "10 weeks delay max");
                      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.
                          require(
                              votingEscrow.totalSupply(currentWeek) > 0,
                              "Zero total supply results in lost tokens"
                          );
                      }
                      _startTime = startTime;
                      _timeCursor = startTime;
                  }
                  /**
                   * @notice Returns the VotingEscrow (veBPT) 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(
                      IERC20 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,
                      IERC20 token
                  ) external view override returns (uint256) {
                      return _getUserTokenTimeCursor(user, token);
                  }
                  /**
                   * @notice Returns the user's cached balance of veBPT 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 veBPT 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 RewardDistributor's cached balance of `token`.
                   */
                  function getTokenLastBalance(
                      IERC20 token
                  ) external view override returns (uint256) {
                      return _tokenState[token].cachedBalance;
                  }
                  /**
                   * @notice Returns the amount of `token` which the RewardDistributor 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(
                      IERC20 token,
                      uint256 timestamp
                  ) external view override returns (uint256) {
                      return _tokensPerWeek[token][timestamp];
                  }
                  // Depositing
                  /**
                   * @notice Deposits tokens to be distributed in the current week.
                   * @dev Sending tokens directly to the RewardDistributor 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(
                      IERC20 token,
                      uint256 amount
                  ) external override nonReentrant {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      _checkpointToken(token, false);
                      token.safeTransferFrom(msg.sender, address(this), amount);
                      _checkpointToken(token, true);
                      emit RewardDeposit(token, amount);
                  }
                  /**
                   * @notice Deposits tokens by faucet to be distributed in the current week.
                   * @dev Sending tokens directly to the RewardDistributor 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 faucetDepositToken(
                      IERC20 token,
                      uint256 amount
                  ) external {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      require(msg.sender == address(rewardFaucet), "only faucet");
                      _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(
                      IERC20[] calldata tokens,
                      uint256[] calldata amounts
                  ) external override nonReentrant {
                      InputHelpers.ensureInputLengthMatch(tokens.length, amounts.length);
                      uint256 length = tokens.length;
                      for (uint256 i = 0; i < length; ++i) {
                          require(allowedRewardTokens[address(tokens[i])], "!allowed");
                          _checkpointToken(tokens[i], false);
                          tokens[i].safeTransferFrom(msg.sender, address(this), amounts[i]);
                          _checkpointToken(tokens[i], true);
                          emit RewardDeposit(tokens[i], amounts[i]);
                      }
                  }
                  // Checkpointing
                  /**
                   * @notice Caches the total supply of veBPT 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 veBPT 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 RewardDistributor to weekly distributions.
                   * @dev Any `token` balance held by the RewardDistributor 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(IERC20 token) external override nonReentrant {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      _checkpointToken(token, true);
                  }
                  /**
                   * @notice Assigns any newly-received tokens held by the RewardDistributor 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(
                      IERC20[] calldata tokens
                  ) external override nonReentrant {
                      uint256 tokensLength = tokens.length;
                      for (uint256 i = 0; i < tokensLength; ++i) {
                          require(allowedRewardTokens[address(tokens[i])], "!allowed");
                          _checkpointToken(tokens[i], true);
                      }
                  }
                  // 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 RewardDistributor
                   * 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,
                      IERC20 token
                  )
                      external
                      override
                      nonReentrant
                      optionalOnlyCaller(user)
                      returns (uint256)
                  {
                      require(allowedRewardTokens[address(token)], "!allowed");
                      _checkpointTotalSupply();
                      _checkpointUserBalance(user);
                      _checkpointToken(token, false);
                      uint256 amount = _claimToken(user, token);
                      rewardFaucet.distributePastRewards(address(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,
                      IERC20[] 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; ++i) {
                          require(allowedRewardTokens[address(tokens[i])], "!allowed");
                          _checkpointToken(tokens[i], false);
                          amounts[i] = _claimToken(user, tokens[i]);
                          rewardFaucet.distributePastRewards(address(tokens[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,
                      IERC20 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; ++i) {
                          // We clearly cannot claim for `firstUnclaimableWeek` and so we break here.
                          if (nextUserTokenWeekToClaim >= firstUnclaimableWeek) break;
                          if (_veSupplyCache[nextUserTokenWeekToClaim] == 0) break;
                          amount +=
                              (tokensPerWeek[nextUserTokenWeekToClaim] *
                                  userBalanceAtTimestamp[nextUserTokenWeekToClaim]) /
                              _veSupplyCache[nextUserTokenWeekToClaim];
                          nextUserTokenWeekToClaim += 1 weeks;
                      }
                      // Update the stored user-token time cursor to prevent this user claiming this week again.
                      _userTokenTimeCursor[user][token] = nextUserTokenWeekToClaim;
                      if (amount > 0) {
                          // 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(IERC20 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,
                              "Reward distribution has not started yet"
                          );
                      } else {
                          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 rewards 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.sub(
                          tokenState.cachedBalance
                      );
                      if (newTokensToDistribute == 0) return;
                      require(
                          tokenBalance <= type(uint128).max,
                          "Maximum token balance exceeded"
                      );
                      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
                      ];
                      uint256 amountToAdd;
                      for (uint256 i = 0; i < 20; ++i) {
                          // 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
                              ) {
                                  amountToAdd = newTokensToDistribute;
                              } else {
                                  // block.timestamp >= lastTokenTime by definition.
                                  amountToAdd = 
                                      (newTokensToDistribute *
                                          (block.timestamp - lastTokenTime)) /
                                      timeSinceLastCheckpoint;
                              }
                              if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) {
                                  tokensPerWeek[firstIncompleteWeek] += amountToAdd;
                                  tokenState.cachedBalance += uint128(amountToAdd);
                              }
                              // 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
                                  amountToAdd = newTokensToDistribute;
                              } else {
                                  // nextWeek > lastTokenTime by definition.
                                  amountToAdd = (newTokensToDistribute * (nextWeek - lastTokenTime)) /
                                      timeSinceLastCheckpoint;
                              }
                          }
                          if (tokensPerWeek[firstIncompleteWeek].add(amountToAdd) <= type(uint128).max) {
                              tokensPerWeek[firstIncompleteWeek] += amountToAdd;
                              tokenState.cachedBalance += uint128(amountToAdd);
                          }
                          // We've now "checkpointed" up to the beginning of next week so must update timestamps appropriately.
                          lastTokenTime = nextWeek;
                          firstIncompleteWeek = nextWeek;
                      }
                      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 veBPT.
                      // They clearly will not then receive rewards.
                      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;
                          // 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 rewards 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,
                              "Reward 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; ++i) {
                          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 = int128(nextWeekToCheckpoint - currentUserPoint.ts);
                              uint256 userBalance = currentUserPoint.bias >
                                  currentUserPoint.slope * dt
                                  ? uint256(
                                      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 rewards.
                              _userBalanceAtTimestamp[user][
                                  nextWeekToCheckpoint
                              ] = userBalance;
                              nextWeekToCheckpoint += 1 weeks;
                          }
                      }
                      // 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`.
                      // 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; ++i) {
                          if (nextWeekToCheckpoint > weekStart) break;
                          _veSupplyCache[nextWeekToCheckpoint] = _votingEscrow.totalSupply(
                              nextWeekToCheckpoint
                          );
                          // This is safe as we're incrementing a timestamp
                          nextWeekToCheckpoint += 1 weeks;
                      }
                      // 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,
                      IERC20 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 veBPT 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; ++i) {
                          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) {
                      // 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) {
                      // Overflows are impossible here for all realistic inputs.
                      return _roundDownTimestamp(timestamp + 1 weeks - 1);
                  }
                  /**
                   * @notice Adds allowed tokens for the distribution.
                   * @param tokens - An array of ERC20 token addresses to be added for the further reward distribution.
                   */
                  function addAllowedRewardTokens(address[] calldata tokens) external onlyAdmin {
                      for (uint256 i = 0; i < tokens.length; i++) {
                          require(!allowedRewardTokens[tokens[i]], "already exist");
                          allowedRewardTokens[tokens[i]] = true;
                          _rewardTokens.push(tokens[i]);
                          emit TokenAdded(tokens[i]);
                      }
                  }
                  /**
                   * @notice Returns allowed for reward distribution tokens list.
                   * @return An array of ERC20 token addresses which can be used as rewards.
                   */
                  function getAllowedRewardTokens() external view returns (address[] memory) {
                      return _rewardTokens;
                  }
                  /**
                   * @notice Transfers admin rights to new address.
                   * @param newAdmin - The new admin address to set.
                   */
                  function transferAdmin(address newAdmin) external onlyAdmin {
                      require (newAdmin != address(0), "zero address");
                      admin = newAdmin;
                      emit NewAdmin(newAdmin);
                  }
              }
              

              File 4 of 7: Voting Escrow
              # @version 0.3.7
              
              """
              @title Voting Escrow
              @author Curve Finance
              @license MIT
              @notice Votes have a weight depending on time, so that users are
                      committed to the future of (whatever they are voting for)
              @dev Vote weight decays linearly over time. Lock time cannot be
                   more than `MAXTIME` (set by creator).
              """
              
              # Voting escrow to have time-weighted votes
              # Votes have a weight depending on time, so that users are committed
              # to the future of (whatever they are voting for).
              # The weight in this implementation is linear, and lock cannot be more than maxtime:
              # w ^
              # 1 +        /
              #   |      /
              #   |    /
              #   |  /
              #   |/
              # 0 +--------+------> time
              #       maxtime
              
              struct Point:
                  bias: int128
                  slope: int128  # - dweight / dt
                  ts: uint256
                  blk: uint256  # block
              # We cannot really do block numbers per se b/c slope is per time, not per block
              # and per block could be fairly bad b/c Ethereum changes blocktimes.
              # What we can do is to extrapolate ***At functions
              
              struct LockedBalance:
                  amount: int128
                  end: uint256
              
              
              interface ERC20:
                  def decimals() -> uint256: view
                  def name() -> String[64]: view
                  def symbol() -> String[32]: view
                  def balanceOf(account: address) -> uint256: view
                  def transfer(to: address, amount: uint256) -> bool: nonpayable
                  def approve(spender: address, amount: uint256) -> bool: nonpayable
                  def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable
              
              
              # Interface for checking whether address belongs to a whitelisted
              # type of a smart wallet.
              # When new types are added - the whole contract is changed
              # The check() method is modifying to be able to use caching
              # for individual wallet addresses
              interface SmartWalletChecker:
                  def check(addr: address) -> bool: nonpayable
              
              interface BalancerMinter:
                  def mint(gauge: address) -> uint256: nonpayable
              
              interface RewardDistributor:
                  def depositToken(token: address, amount: uint256): nonpayable
              
              DEPOSIT_FOR_TYPE: constant(int128) = 0
              CREATE_LOCK_TYPE: constant(int128) = 1
              INCREASE_LOCK_AMOUNT: constant(int128) = 2
              INCREASE_UNLOCK_TIME: constant(int128) = 3
              
              
              event CommitOwnership:
                  admin: address
              
              event ApplyOwnership:
                  admin: address
              
              event EarlyUnlock:
                  status: bool
              
              event PenaltySpeed:
                  penalty_k: uint256
              
              event PenaltyTreasury:
                  penalty_treasury: address
              
              event TotalUnlock:
                  status: bool
              
              event RewardReceiver:
                  newReceiver: address
              
              event Deposit:
                  provider: indexed(address)
                  value: uint256
                  locktime: indexed(uint256)
                  type: int128
                  ts: uint256
              
              event Withdraw:
                  provider: indexed(address)
                  value: uint256
                  ts: uint256
              
              event WithdrawEarly:
                  provider: indexed(address)
                  penalty: uint256
                  time_left: uint256
              
              event Supply:
                  prevSupply: uint256
                  supply: uint256
              
              
              WEEK: constant(uint256) = 7 * 86400  # all future times are rounded by week
              MAXTIME: public(uint256)
              MULTIPLIER: constant(uint256) = 10**18
              
              TOKEN: public(address)
              
              NAME: String[64]
              SYMBOL: String[32]
              DECIMALS: uint256
              
              supply: public(uint256)
              locked: public(HashMap[address, LockedBalance])
              
              epoch: public(uint256)
              point_history: public(Point[100000000000000000000000000000])  # epoch -> unsigned point
              user_point_history: public(HashMap[address, Point[1000000000]])  # user -> Point[user_epoch]
              user_point_epoch: public(HashMap[address, uint256])
              slope_changes: public(HashMap[uint256, int128])  # time -> signed slope change
              
              # Checker for whitelisted (smart contract) wallets which are allowed to deposit
              # The goal is to prevent tokenizing the escrow
              future_smart_wallet_checker: public(address)
              smart_wallet_checker: public(address)
              
              admin: public(address)
              
              # unlock admins can be set only once. Zero-address means unlock is disabled
              admin_unlock_all: public(address)
              admin_early_unlock: public(address)
              
              future_admin: public(address)
              
              is_initialized: public(bool)
              
              early_unlock: public(bool)
              penalty_k: public(uint256)
              prev_penalty_k: public(uint256)
              penalty_upd_ts: public(uint256)
              PENALTY_COOLDOWN: constant(uint256) = 60 # cooldown to prevent font-run on penalty change
              PENALTY_MULTIPLIER: constant(uint256) = 10
              
              penalty_treasury: public(address)
              
              balMinter: public(address)
              balToken: public(address)
              rewardReceiver: public(address)
              rewardReceiverChangeable: public(bool)
              
              rewardDistributor: public(address)
              
              all_unlock: public(bool)
              
              
              @external
              def initialize(
                  _token_addr: address,
                  _name: String[64],
                  _symbol: String[32],
                  _admin_addr: address,
                  _admin_unlock_all: address,
                  _admin_early_unlock: address,
                  _max_time: uint256,
                  _balToken: address,
                  _balMinter: address,
                  _rewardReceiver: address,
                  _rewardReceiverChangeable: bool,
                  _rewardDistributor: address
              ):
                  """
                  @notice Contract constructor
                  @param _token_addr 80/20 Token-WETH BPT token address
                  @param _name Token name
                  @param _symbol Token symbol
                  @param _admin_addr Contract admin address
                  @param _admin_unlock_all Admin to enable Unlock-All feature (zero-address to disable forever)
                  @param _admin_early_unlock Admin to enable Eraly-Unlock feature (zero-address to disable forever)
                  @param _max_time Locking max time
                  @param _balToken Address of the Balancer token
                  @param _balMinter Address of the Balancer minter
                  @param _rewardReceiver Address of the reward receiver
                  @param _rewardReceiverChangeable Boolean indicating whether the reward receiver is changeable
                  @param _rewardDistributor The RewardDistributor contract address
                  """
              
                  assert(not self.is_initialized), 'only once'
                  self.is_initialized = True
              
                  assert(_admin_addr != empty(address)), '!empty'
                  self.admin = _admin_addr
              
                  self.penalty_k = 10
                  self.prev_penalty_k = 10
                  self.penalty_upd_ts = block.timestamp
                  self.penalty_treasury = _admin_addr
              
                  self.TOKEN = _token_addr
                  self.point_history[0].blk = block.number
                  self.point_history[0].ts = block.timestamp
              
                  _decimals: uint256 = ERC20(_token_addr).decimals()  # also validates token for non-zero
                  assert (_decimals >= 6 and _decimals <= 255), '!decimals'
              
                  self.NAME = _name
                  self.SYMBOL = _symbol
                  self.DECIMALS = _decimals
              
                  assert(_max_time >= WEEK and _max_time <= WEEK * 52 * 5), '!maxlock'
                  self.MAXTIME = _max_time
              
                  self.admin_unlock_all = _admin_unlock_all
                  self.admin_early_unlock = _admin_early_unlock
              
                  self.balToken = _balToken
                  self.balMinter = _balMinter
                  self.rewardReceiver = _rewardReceiver
                  self.rewardReceiverChangeable = _rewardReceiverChangeable
                  self.rewardDistributor = _rewardDistributor
              
              
              @external
              @view
              def token() -> address:
                  return self.TOKEN
              
              @external
              @view
              def name() -> String[64]:
                  return self.NAME
              
              @external
              @view
              def symbol() -> String[32]:
                  return self.SYMBOL
              
              @external
              @view
              def decimals() -> uint256:
                  return self.DECIMALS
              
              @external
              def commit_transfer_ownership(addr: address):
                  """
                  @notice Transfer ownership of VotingEscrow contract to `addr`
                  @param addr Address to have ownership transferred to
                  """
                  assert msg.sender == self.admin  # dev: admin only
                  self.future_admin = addr
                  log CommitOwnership(addr)
              
              
              @external
              def apply_transfer_ownership():
                  """
                  @notice Apply ownership transfer
                  """
                  assert msg.sender == self.admin  # dev: admin only
                  _admin: address = self.future_admin
                  assert _admin != empty(address)  # dev: admin not set
                  self.admin = _admin
                  log ApplyOwnership(_admin)
              
              
              @external
              def commit_smart_wallet_checker(addr: address):
                  """
                  @notice Set an external contract to check for approved smart contract wallets
                  @param addr Address of Smart contract checker
                  """
                  assert msg.sender == self.admin
                  self.future_smart_wallet_checker = addr
              
              @external
              def apply_smart_wallet_checker():
                  """
                  @notice Apply setting external contract to check approved smart contract wallets
                  """
                  assert msg.sender == self.admin
                  self.smart_wallet_checker = self.future_smart_wallet_checker
              
              
              @internal
              def assert_not_contract(addr: address):
                  """
                  @notice Check if the call is from a whitelisted smart contract, revert if not
                  @param addr Address to be checked
                  """
                  if addr != tx.origin:
                      checker: address = self.smart_wallet_checker
                      if checker != empty(address):
                          if SmartWalletChecker(checker).check(addr):
                              return
                      raise "Smart contract depositors not allowed"
              
              
              @external
              def set_early_unlock(_early_unlock: bool):
                  """
                  @notice Sets the availability for users to unlock their locks before lock-end with penalty
                  @dev Only the admin_early_unlock can execute this function.
                  @param _early_unlock A boolean indicating whether early unlock is allowed or not.
                  """
                  assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
                  assert _early_unlock != self.early_unlock, 'already'
                  
                  self.early_unlock = _early_unlock
                  log EarlyUnlock(_early_unlock)
              
              
              @external
              def set_early_unlock_penalty_speed(_penalty_k: uint256):
                  """
                  @notice Sets penalty speed for early unlocking
                  @dev Only the admin can execute this function. To prevent frontrunning we use PENALTY_COOLDOWN period
                  @param _penalty_k Coefficient indicating the penalty speed for early unlock.
                                    Must be between 0 and 50, inclusive. Default 10 - means linear speed.
                  """
                  assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
                  assert _penalty_k <= 50, '!k'
                  assert block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN, 'early' # to avoid frontrun
              
                  self.prev_penalty_k = self.penalty_k
                  self.penalty_k = _penalty_k
                  self.penalty_upd_ts = block.timestamp
              
                  log PenaltySpeed(_penalty_k)
              
              
              @external
              def set_penalty_treasury(_penalty_treasury: address):
                  """
                  @notice Sets penalty treasury address
                  @dev Only the admin_early_unlock can execute this function.
                  @param _penalty_treasury The address to collect early penalty (default admin address)
                  """
                  assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
                  assert _penalty_treasury != empty(address), '!zero'
                 
                  self.penalty_treasury = _penalty_treasury
                  log PenaltyTreasury(_penalty_treasury)
              
              
              @external
              def set_all_unlock():
                  """
                  @notice Deactivates VotingEscrow and allows users to unlock their locks before lock-end. 
                          New deposits will no longer be accepted.
                  @dev Only the admin_unlock_all can execute this function. Make sure there are no rewards for distribution in other contracts.
                  """
                  assert msg.sender == self.admin_unlock_all, '!admin'  # dev: admin_unlock_all only
                  self.all_unlock = True
                  log TotalUnlock(True)
              
              
              @external
              @view
              def get_last_user_slope(addr: address) -> int128:
                  """
                  @notice Get the most recently recorded rate of voting power decrease for `addr`
                  @param addr Address of the user wallet
                  @return Value of the slope
                  """
                  uepoch: uint256 = self.user_point_epoch[addr]
                  return self.user_point_history[addr][uepoch].slope
              
              
              @external
              @view
              def user_point_history__ts(_addr: address, _idx: uint256) -> uint256:
                  """
                  @notice Get the timestamp for checkpoint `_idx` for `_addr`
                  @param _addr User wallet address
                  @param _idx User epoch number
                  @return Epoch time of the checkpoint
                  """
                  return self.user_point_history[_addr][_idx].ts
              
              
              @external
              @view
              def locked__end(_addr: address) -> uint256:
                  """
                  @notice Get timestamp when `_addr`'s lock finishes
                  @param _addr User wallet
                  @return Epoch time of the lock end
                  """
                  return self.locked[_addr].end
              
              
              @internal
              def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance):
                  """
                  @notice Record global and per-user data to checkpoint
                  @param addr User's wallet address. No user checkpoint if 0x0
                  @param old_locked Pevious locked amount / end lock time for the user
                  @param new_locked New locked amount / end lock time for the user
                  """
                  u_old: Point = empty(Point)
                  u_new: Point = empty(Point)
                  old_dslope: int128 = 0
                  new_dslope: int128 = 0
                  _epoch: uint256 = self.epoch
              
                  if addr != empty(address):
                      # Calculate slopes and biases
                      # Kept at zero when they have to
                      if old_locked.end > block.timestamp and old_locked.amount > 0:
                          u_old.slope = old_locked.amount / convert(self.MAXTIME, int128)
                          u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128)
                      if new_locked.end > block.timestamp and new_locked.amount > 0:
                          u_new.slope = new_locked.amount / convert(self.MAXTIME, int128)
                          u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128)
              
              
                      # Read values of scheduled changes in the slope
                      # old_locked.end can be in the past and in the future
                      # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros
                      old_dslope = self.slope_changes[old_locked.end]
                      if new_locked.end != 0:
                          if new_locked.end == old_locked.end:
                              new_dslope = old_dslope
                          else:
                              new_dslope = self.slope_changes[new_locked.end]
              
                  last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number})
                  if _epoch > 0:
                      last_point = self.point_history[_epoch]
                  last_checkpoint: uint256 = last_point.ts
                  # initial_last_point is used for extrapolation to calculate block number
                  # (approximately, for *At methods) and save them
                  # as we cannot figure that out exactly from inside the contract
                  initial_last_point: Point = last_point
                  block_slope: uint256 = 0  # dblock/dt
                  if block.timestamp > last_point.ts:
                      block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts)
                  # If last point is already recorded in this block, slope=0
                  # But that's ok b/c we know the block in such case
              
                  # Go over weeks to fill history and calculate what the current point is
                  t_i: uint256 = (last_checkpoint / WEEK) * WEEK
                  for i in range(255):
                      # Hopefully it won't happen that this won't get used in 5 years!
                      # If it does, users will be able to withdraw but vote weight will be broken
                      t_i += WEEK
                      d_slope: int128 = 0
                      if t_i > block.timestamp:
                          t_i = block.timestamp
                      else:
                          d_slope = self.slope_changes[t_i]
                      last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128)
                      last_point.slope += d_slope
                      if last_point.bias < 0:  # This can happen
                          last_point.bias = 0
                      if last_point.slope < 0:  # This cannot happen - just in case
                          last_point.slope = 0
                      last_checkpoint = t_i
                      last_point.ts = t_i
                      last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER
                      _epoch += 1
                      if t_i == block.timestamp:
                          last_point.blk = block.number
                          break
                      else:
                          self.point_history[_epoch] = last_point
              
                  self.epoch = _epoch
                  # Now point_history is filled until t=now
              
                  if addr != empty(address):
                      # If last point was in this block, the slope change has been applied already
                      # But in such case we have 0 slope(s)
                      last_point.slope += (u_new.slope - u_old.slope)
                      last_point.bias += (u_new.bias - u_old.bias)
                      if last_point.slope < 0:
                          last_point.slope = 0
                      if last_point.bias < 0:
                          last_point.bias = 0
              
                  # Record the changed point into history
                  self.point_history[_epoch] = last_point
              
                  if addr != empty(address):
                      # Schedule the slope changes (slope is going down)
                      # We subtract new_user_slope from [new_locked.end]
                      # and add old_user_slope to [old_locked.end]
                      if old_locked.end > block.timestamp:
                          # old_dslope was <something> - u_old.slope, so we cancel that
                          old_dslope += u_old.slope
                          if new_locked.end == old_locked.end:
                              old_dslope -= u_new.slope  # It was a new deposit, not extension
                          self.slope_changes[old_locked.end] = old_dslope
              
                      if new_locked.end > block.timestamp:
                          if new_locked.end > old_locked.end:
                              new_dslope -= u_new.slope  # old slope disappeared at this point
                              self.slope_changes[new_locked.end] = new_dslope
                          # else: we recorded it already in old_dslope
              
                      # Now handle user history
                      user_epoch: uint256 = self.user_point_epoch[addr] + 1
              
                      self.user_point_epoch[addr] = user_epoch
                      u_new.ts = block.timestamp
                      u_new.blk = block.number
                      self.user_point_history[addr][user_epoch] = u_new
              
              
              @internal
              def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128):
                  """
                  @notice Deposit and lock tokens for a user
                  @param _addr User's wallet address
                  @param _value Amount to deposit
                  @param unlock_time New time when to unlock the tokens, or 0 if unchanged
                  @param locked_balance Previous locked amount / timestamp
                  """
                  # block all new deposits (and extensions) in case of unlocked contract
                  assert (not self.all_unlock), "all unlocked,no sense"
              
                  _locked: LockedBalance = locked_balance
                  supply_before: uint256 = self.supply
              
                  self.supply = supply_before + _value
                  old_locked: LockedBalance = _locked
                  # Adding to existing lock, or if a lock is expired - creating a new one
                  _locked.amount += convert(_value, int128)
                  if unlock_time != 0:
                      _locked.end = unlock_time
                  self.locked[_addr] = _locked
              
                  # Possibilities:
                  # Both old_locked.end could be current or expired (>/< block.timestamp)
                  # value == 0 (extend lock) or value > 0 (add to lock or extend lock)
                  # _locked.end > block.timestamp (always)
                  self._checkpoint(_addr, old_locked, _locked)
              
                  if _value != 0:
                      assert ERC20(self.TOKEN).transferFrom(_addr, self, _value, default_return_value=True)
              
                  log Deposit(_addr, _value, _locked.end, type, block.timestamp)
                  log Supply(supply_before, supply_before + _value)
              
              
              @external
              def checkpoint():
                  """
                  @notice Record global data to checkpoint
                  """
                  self._checkpoint(empty(address), empty(LockedBalance), empty(LockedBalance))
              
              
              @external
              @nonreentrant("lock")
              def deposit_for(_addr: address, _value: uint256):
                  """
                  @notice Deposit `_value` tokens for `_addr` and add to the lock
                  @dev Anyone (even a smart contract) can deposit for someone else, but
                       cannot extend their locktime and deposit for a brand new user
                  @param _addr User's wallet address
                  @param _value Amount to add to user's lock
                  """
                  _locked: LockedBalance = self.locked[_addr]
              
                  assert _value > 0  # dev: need non-zero value
                  assert _locked.amount > 0, "No existing lock found"
                  assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"
              
                  self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE)
              
              
              @external
              @nonreentrant("lock")
              def create_lock(_value: uint256, _unlock_time: uint256):
                  """
                  @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time`
                  @param _value Amount to deposit
                  @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
                  """
                  self.assert_not_contract(msg.sender)
                  unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
                  _locked: LockedBalance = self.locked[msg.sender]
              
                  assert _value > 0  # dev: need non-zero value
                  assert _locked.amount == 0, "Withdraw old tokens first"
                  assert (unlock_time > block.timestamp), "Can only lock until time in the future"
                  assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long"
              
                  self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE)
              
              
              @external
              @nonreentrant("lock")
              def increase_amount(_value: uint256):
                  """
                  @notice Deposit `_value` additional tokens for `msg.sender`
                          without modifying the unlock time
                  @param _value Amount of tokens to deposit and add to the lock
                  """
                  self.assert_not_contract(msg.sender)
                  _locked: LockedBalance = self.locked[msg.sender]
              
                  assert _value > 0  # dev: need non-zero value
                  assert _locked.amount > 0, "No existing lock found"
                  assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"
              
                  self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT)
              
              
              @external
              @nonreentrant("lock")
              def increase_unlock_time(_unlock_time: uint256):
                  """
                  @notice Extend the unlock time for `msg.sender` to `_unlock_time`
                  @param _unlock_time New epoch time for unlocking
                  """
                  self.assert_not_contract(msg.sender)
                  _locked: LockedBalance = self.locked[msg.sender]
                  unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
              
                  assert _locked.end > block.timestamp, "Lock expired"
                  assert _locked.amount > 0, "Nothing is locked"
                  assert unlock_time > _locked.end, "Can only increase lock duration"
                  assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long"
              
                  self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME)
              
              
              @external
              @nonreentrant("lock")
              def withdraw():
                  """
                  @notice Withdraw all tokens for `msg.sender`
                  @dev Only possible if the lock has expired
                  """
                  _locked: LockedBalance = self.locked[msg.sender]
                  assert block.timestamp >= _locked.end or self.all_unlock, "lock !expire or !unlock"
                  value: uint256 = convert(_locked.amount, uint256)
              
                  old_locked: LockedBalance = _locked
                  _locked.end = 0
                  _locked.amount = 0
                  self.locked[msg.sender] = _locked
                  supply_before: uint256 = self.supply
                  self.supply = supply_before - value
              
                  # old_locked can have either expired <= timestamp or zero end
                  # _locked has only 0 end
                  # Both can have >= 0 amount
                  self._checkpoint(msg.sender, old_locked, _locked)
              
                  assert ERC20(self.TOKEN).transfer(msg.sender, value, default_return_value=True)
              
                  log Withdraw(msg.sender, value, block.timestamp)
                  log Supply(supply_before, supply_before - value)
              
              
              @external
              @nonreentrant("lock")
              def withdraw_early():
                  """
                  @notice Withdraws locked tokens for `msg.sender` before lock-end with penalty
                  @dev Only possible if `early_unlock` is enabled (true)
                  By defualt there is linear formula for calculating penalty. 
                  In some cases an admin can configure penalty speed using `set_early_unlock_penalty_speed()`
                  
                  L - lock amount
                  k - penalty coefficient, defined by admin (default 1)
                  Tleft - left time to unlock
                  Tmax - MAXLOCK time
                  Penalty amount = L * k * (Tlast / Tmax)
                  """
                  assert(self.early_unlock == True), "!early unlock"
              
                  _locked: LockedBalance = self.locked[msg.sender]
                  assert block.timestamp < _locked.end, "lock expired"
              
                  value: uint256 = convert(_locked.amount, uint256)
              
                  time_left: uint256 = _locked.end - block.timestamp
                  
                  # to avoid front-run with penalty_k
                  penalty_k_: uint256 = 0
                  if block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN:
                      penalty_k_ = self.penalty_k
                  else:
                      penalty_k_ = self.prev_penalty_k
              
                  penalty_ratio: uint256 = (time_left * MULTIPLIER / self.MAXTIME) * penalty_k_
                  penalty: uint256 = (value * penalty_ratio / MULTIPLIER) / PENALTY_MULTIPLIER    
                  if penalty > value:
                      penalty = value
                  user_amount: uint256 = value - penalty
              
                  old_locked: LockedBalance = _locked
                  _locked.end = 0
                  _locked.amount = 0
                  self.locked[msg.sender] = _locked
                  supply_before: uint256 = self.supply
                  self.supply = supply_before - value
              
                  # old_locked can have either expired <= timestamp or zero end
                  # _locked has only 0 end
                  # Both can have >= 0 amount
                  self._checkpoint(msg.sender, old_locked, _locked)
              
                  if penalty > 0:
                      assert ERC20(self.TOKEN).transfer(self.penalty_treasury, penalty, default_return_value=True)
                  if user_amount > 0:
                      assert ERC20(self.TOKEN).transfer(msg.sender, user_amount, default_return_value=True)
              
                  log Withdraw(msg.sender, value, block.timestamp)
                  log Supply(supply_before, supply_before - value)
                  log WithdrawEarly(msg.sender, penalty, time_left)
              
              
              # The following ERC20/minime-compatible methods are not real balanceOf and supply!
              # They measure the weights for the purpose of voting, so they don't represent
              # real coins.
              
              @internal
              @view
              def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find epoch containing block number
                  @param _block Block to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _block
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.point_history[_mid].blk <= _block:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              @internal
              @view
              def find_timestamp_epoch(_timestamp: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find epoch for timestamp
                  @param _timestamp timestamp to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _timestamp
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.point_history[_mid].ts <= _timestamp:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              
              @internal
              @view
              def find_block_user_epoch(_addr: address, _block: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find epoch for block number
                  @param _addr User for which to find user epoch for
                  @param _block Block to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _block
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.user_point_history[_addr][_mid].blk <= _block:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              
              @internal
              @view
              def find_timestamp_user_epoch(_addr: address, _timestamp: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find user epoch for timestamp
                  @param _addr User for which to find user epoch for
                  @param _timestamp timestamp to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _timestamp
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.user_point_history[_addr][_mid].ts <= _timestamp:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              @external
              @view
              def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256:
                  """
                  @notice Get the current voting power for `msg.sender`
                  @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
                  @param addr User wallet address
                  @param _t Epoch time to return voting power at
                  @return User voting power
                  """
                  _epoch: uint256 = 0
                  if _t == block.timestamp:
                      # No need to do binary search, will always live in current epoch
                      _epoch = self.user_point_epoch[addr]
                  else:
                      _epoch = self.find_timestamp_user_epoch(addr, _t, self.user_point_epoch[addr])
              
                  if _epoch == 0:
                      return 0
                  else:
                      last_point: Point = self.user_point_history[addr][_epoch]
                      last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128)
                      if last_point.bias < 0:
                          last_point.bias = 0
                      return convert(last_point.bias, uint256)
              
              
              @external
              @view
              def balanceOfAt(addr: address, _block: uint256) -> uint256:
                  """
                  @notice Measure voting power of `addr` at block height `_block`
                  @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
                  @param addr User's wallet address
                  @param _block Block to calculate the voting power at
                  @return Voting power
                  """
                  # Copying and pasting totalSupply code because Vyper cannot pass by
                  # reference yet
                  assert _block <= block.number
              
                  _user_epoch: uint256 = self.find_block_user_epoch(addr, _block, self.user_point_epoch[addr])
                  upoint: Point = self.user_point_history[addr][_user_epoch]
              
                  max_epoch: uint256 = self.epoch
                  _epoch: uint256 = self.find_block_epoch(_block, max_epoch)
                  point_0: Point = self.point_history[_epoch]
                  d_block: uint256 = 0
                  d_t: uint256 = 0
                  if _epoch < max_epoch:
                      point_1: Point = self.point_history[_epoch + 1]
                      d_block = point_1.blk - point_0.blk
                      d_t = point_1.ts - point_0.ts
                  else:
                      d_block = block.number - point_0.blk
                      d_t = block.timestamp - point_0.ts
                  block_time: uint256 = point_0.ts
                  if d_block != 0:
                      block_time += d_t * (_block - point_0.blk) / d_block
              
                  upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128)
                  if upoint.bias >= 0:
                      return convert(upoint.bias, uint256)
                  else:
                      return 0
              
              
              @internal
              @view
              def supply_at(point: Point, t: uint256) -> uint256:
                  """
                  @notice Calculate total voting power at some point in the past
                  @param point The point (bias/slope) to start search from
                  @param t Time to calculate the total voting power at
                  @return Total voting power at that time
                  """
                  last_point: Point = point
                  t_i: uint256 = (last_point.ts / WEEK) * WEEK
                  for i in range(255):
                      t_i += WEEK
                      d_slope: int128 = 0
                      if t_i > t:
                          t_i = t
                      else:
                          d_slope = self.slope_changes[t_i]
                      last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128)
                      if t_i == t:
                          break
                      last_point.slope += d_slope
                      last_point.ts = t_i
              
                  if last_point.bias < 0:
                      last_point.bias = 0
                  return convert(last_point.bias, uint256)
              
              
              @external
              @view
              def totalSupply(t: uint256 = block.timestamp) -> uint256:
                  """
                  @notice Calculate total voting power
                  @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
                  @return Total voting power
                  """
                  _epoch: uint256 = 0
                  if t == block.timestamp:
                      # No need to do binary search, will always live in current epoch
                      _epoch = self.epoch
                  else:
                      _epoch = self.find_timestamp_epoch(t, self.epoch)
              
                  if _epoch == 0:
                      return 0
                  else:
                      last_point: Point = self.point_history[_epoch]
                      return self.supply_at(last_point, t)
              
              
              @external
              @view
              def totalSupplyAt(_block: uint256) -> uint256:
                  """
                  @notice Calculate total voting power at some point in the past
                  @param _block Block to calculate the total voting power at
                  @return Total voting power at `_block`
                  """
                  assert _block <= block.number
                  _epoch: uint256 = self.epoch
                  target_epoch: uint256 = self.find_block_epoch(_block, _epoch)
              
                  point: Point = self.point_history[target_epoch]
                  dt: uint256 = 0
                  if target_epoch < _epoch:
                      point_next: Point = self.point_history[target_epoch + 1]
                      if point.blk != point_next.blk:
                          dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk)
                  else:
                      if point.blk != block.number:
                          dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk)
                  # Now dt contains info on how far are we beyond point
              
                  return self.supply_at(point, point.ts + dt)
              
              @external
              @nonreentrant("lock")
              def claimExternalRewards():
                  """
                  @notice Claims BAL rewards
                  @dev Only possible if the TOKEN is Guage contract
                  """
                  BalancerMinter(self.balMinter).mint(self.TOKEN)
                  balBalance: uint256 = ERC20(self.balToken).balanceOf(self)
                  if balBalance > 0:
                      # distributes rewards using rewardDistributor into current week
                      if self.rewardReceiver == self.rewardDistributor:
                          assert ERC20(self.balToken).approve(self.rewardDistributor, balBalance, default_return_value=True)
                          RewardDistributor(self.rewardDistributor).depositToken(self.balToken, balBalance)
                      else:
                          assert ERC20(self.balToken).transfer(self.rewardReceiver, balBalance, default_return_value=True)
              
              
              @external
              def changeRewardReceiver(newReceiver: address):
                  """
                  @notice Changes the reward receiver address
                  @param newReceiver New address to set as the reward receiver
                  """
                  assert msg.sender == self.admin, '!admin'
                  assert (self.rewardReceiverChangeable), '!available'
                  assert newReceiver != empty(address), '!empty'
              
                  self.rewardReceiver = newReceiver
                  log RewardReceiver(newReceiver)

              File 5 of 7: Voting Escrow
              # @version 0.3.7
              
              """
              @title Voting Escrow
              @author Curve Finance
              @license MIT
              @notice Votes have a weight depending on time, so that users are
                      committed to the future of (whatever they are voting for)
              @dev Vote weight decays linearly over time. Lock time cannot be
                   more than `MAXTIME` (set by creator).
              """
              
              # Voting escrow to have time-weighted votes
              # Votes have a weight depending on time, so that users are committed
              # to the future of (whatever they are voting for).
              # The weight in this implementation is linear, and lock cannot be more than maxtime:
              # w ^
              # 1 +        /
              #   |      /
              #   |    /
              #   |  /
              #   |/
              # 0 +--------+------> time
              #       maxtime
              
              struct Point:
                  bias: int128
                  slope: int128  # - dweight / dt
                  ts: uint256
                  blk: uint256  # block
              # We cannot really do block numbers per se b/c slope is per time, not per block
              # and per block could be fairly bad b/c Ethereum changes blocktimes.
              # What we can do is to extrapolate ***At functions
              
              struct LockedBalance:
                  amount: int128
                  end: uint256
              
              
              interface ERC20:
                  def decimals() -> uint256: view
                  def name() -> String[64]: view
                  def symbol() -> String[32]: view
                  def balanceOf(account: address) -> uint256: view
                  def transfer(to: address, amount: uint256) -> bool: nonpayable
                  def approve(spender: address, amount: uint256) -> bool: nonpayable
                  def transferFrom(spender: address, to: address, amount: uint256) -> bool: nonpayable
              
              
              # Interface for checking whether address belongs to a whitelisted
              # type of a smart wallet.
              # When new types are added - the whole contract is changed
              # The check() method is modifying to be able to use caching
              # for individual wallet addresses
              interface SmartWalletChecker:
                  def check(addr: address) -> bool: nonpayable
              
              interface BalancerMinter:
                  def mint(gauge: address) -> uint256: nonpayable
              
              interface RewardDistributor:
                  def depositToken(token: address, amount: uint256): nonpayable
              
              DEPOSIT_FOR_TYPE: constant(int128) = 0
              CREATE_LOCK_TYPE: constant(int128) = 1
              INCREASE_LOCK_AMOUNT: constant(int128) = 2
              INCREASE_UNLOCK_TIME: constant(int128) = 3
              
              
              event CommitOwnership:
                  admin: address
              
              event ApplyOwnership:
                  admin: address
              
              event EarlyUnlock:
                  status: bool
              
              event PenaltySpeed:
                  penalty_k: uint256
              
              event PenaltyTreasury:
                  penalty_treasury: address
              
              event TotalUnlock:
                  status: bool
              
              event RewardReceiver:
                  newReceiver: address
              
              event Deposit:
                  provider: indexed(address)
                  value: uint256
                  locktime: indexed(uint256)
                  type: int128
                  ts: uint256
              
              event Withdraw:
                  provider: indexed(address)
                  value: uint256
                  ts: uint256
              
              event WithdrawEarly:
                  provider: indexed(address)
                  penalty: uint256
                  time_left: uint256
              
              event Supply:
                  prevSupply: uint256
                  supply: uint256
              
              
              WEEK: constant(uint256) = 7 * 86400  # all future times are rounded by week
              MAXTIME: public(uint256)
              MULTIPLIER: constant(uint256) = 10**18
              
              TOKEN: public(address)
              
              NAME: String[64]
              SYMBOL: String[32]
              DECIMALS: uint256
              
              supply: public(uint256)
              locked: public(HashMap[address, LockedBalance])
              
              epoch: public(uint256)
              point_history: public(Point[100000000000000000000000000000])  # epoch -> unsigned point
              user_point_history: public(HashMap[address, Point[1000000000]])  # user -> Point[user_epoch]
              user_point_epoch: public(HashMap[address, uint256])
              slope_changes: public(HashMap[uint256, int128])  # time -> signed slope change
              
              # Checker for whitelisted (smart contract) wallets which are allowed to deposit
              # The goal is to prevent tokenizing the escrow
              future_smart_wallet_checker: public(address)
              smart_wallet_checker: public(address)
              
              admin: public(address)
              
              # unlock admins can be set only once. Zero-address means unlock is disabled
              admin_unlock_all: public(address)
              admin_early_unlock: public(address)
              
              future_admin: public(address)
              
              is_initialized: public(bool)
              
              early_unlock: public(bool)
              penalty_k: public(uint256)
              prev_penalty_k: public(uint256)
              penalty_upd_ts: public(uint256)
              PENALTY_COOLDOWN: constant(uint256) = 60 # cooldown to prevent font-run on penalty change
              PENALTY_MULTIPLIER: constant(uint256) = 10
              
              penalty_treasury: public(address)
              
              balMinter: public(address)
              balToken: public(address)
              rewardReceiver: public(address)
              rewardReceiverChangeable: public(bool)
              
              rewardDistributor: public(address)
              
              all_unlock: public(bool)
              
              
              @external
              def initialize(
                  _token_addr: address,
                  _name: String[64],
                  _symbol: String[32],
                  _admin_addr: address,
                  _admin_unlock_all: address,
                  _admin_early_unlock: address,
                  _max_time: uint256,
                  _balToken: address,
                  _balMinter: address,
                  _rewardReceiver: address,
                  _rewardReceiverChangeable: bool,
                  _rewardDistributor: address
              ):
                  """
                  @notice Contract constructor
                  @param _token_addr 80/20 Token-WETH BPT token address
                  @param _name Token name
                  @param _symbol Token symbol
                  @param _admin_addr Contract admin address
                  @param _admin_unlock_all Admin to enable Unlock-All feature (zero-address to disable forever)
                  @param _admin_early_unlock Admin to enable Eraly-Unlock feature (zero-address to disable forever)
                  @param _max_time Locking max time
                  @param _balToken Address of the Balancer token
                  @param _balMinter Address of the Balancer minter
                  @param _rewardReceiver Address of the reward receiver
                  @param _rewardReceiverChangeable Boolean indicating whether the reward receiver is changeable
                  @param _rewardDistributor The RewardDistributor contract address
                  """
              
                  assert(not self.is_initialized), 'only once'
                  self.is_initialized = True
              
                  assert(_admin_addr != empty(address)), '!empty'
                  self.admin = _admin_addr
              
                  self.penalty_k = 10
                  self.prev_penalty_k = 10
                  self.penalty_upd_ts = block.timestamp
                  self.penalty_treasury = _admin_addr
              
                  self.TOKEN = _token_addr
                  self.point_history[0].blk = block.number
                  self.point_history[0].ts = block.timestamp
              
                  _decimals: uint256 = ERC20(_token_addr).decimals()  # also validates token for non-zero
                  assert (_decimals >= 6 and _decimals <= 255), '!decimals'
              
                  self.NAME = _name
                  self.SYMBOL = _symbol
                  self.DECIMALS = _decimals
              
                  assert(_max_time >= WEEK and _max_time <= WEEK * 52 * 5), '!maxlock'
                  self.MAXTIME = _max_time
              
                  self.admin_unlock_all = _admin_unlock_all
                  self.admin_early_unlock = _admin_early_unlock
              
                  self.balToken = _balToken
                  self.balMinter = _balMinter
                  self.rewardReceiver = _rewardReceiver
                  self.rewardReceiverChangeable = _rewardReceiverChangeable
                  self.rewardDistributor = _rewardDistributor
              
              
              @external
              @view
              def token() -> address:
                  return self.TOKEN
              
              @external
              @view
              def name() -> String[64]:
                  return self.NAME
              
              @external
              @view
              def symbol() -> String[32]:
                  return self.SYMBOL
              
              @external
              @view
              def decimals() -> uint256:
                  return self.DECIMALS
              
              @external
              def commit_transfer_ownership(addr: address):
                  """
                  @notice Transfer ownership of VotingEscrow contract to `addr`
                  @param addr Address to have ownership transferred to
                  """
                  assert msg.sender == self.admin  # dev: admin only
                  self.future_admin = addr
                  log CommitOwnership(addr)
              
              
              @external
              def apply_transfer_ownership():
                  """
                  @notice Apply ownership transfer
                  """
                  assert msg.sender == self.admin  # dev: admin only
                  _admin: address = self.future_admin
                  assert _admin != empty(address)  # dev: admin not set
                  self.admin = _admin
                  log ApplyOwnership(_admin)
              
              
              @external
              def commit_smart_wallet_checker(addr: address):
                  """
                  @notice Set an external contract to check for approved smart contract wallets
                  @param addr Address of Smart contract checker
                  """
                  assert msg.sender == self.admin
                  self.future_smart_wallet_checker = addr
              
              @external
              def apply_smart_wallet_checker():
                  """
                  @notice Apply setting external contract to check approved smart contract wallets
                  """
                  assert msg.sender == self.admin
                  self.smart_wallet_checker = self.future_smart_wallet_checker
              
              
              @internal
              def assert_not_contract(addr: address):
                  """
                  @notice Check if the call is from a whitelisted smart contract, revert if not
                  @param addr Address to be checked
                  """
                  if addr != tx.origin:
                      checker: address = self.smart_wallet_checker
                      if checker != empty(address):
                          if SmartWalletChecker(checker).check(addr):
                              return
                      raise "Smart contract depositors not allowed"
              
              
              @external
              def set_early_unlock(_early_unlock: bool):
                  """
                  @notice Sets the availability for users to unlock their locks before lock-end with penalty
                  @dev Only the admin_early_unlock can execute this function.
                  @param _early_unlock A boolean indicating whether early unlock is allowed or not.
                  """
                  assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
                  assert _early_unlock != self.early_unlock, 'already'
                  
                  self.early_unlock = _early_unlock
                  log EarlyUnlock(_early_unlock)
              
              
              @external
              def set_early_unlock_penalty_speed(_penalty_k: uint256):
                  """
                  @notice Sets penalty speed for early unlocking
                  @dev Only the admin can execute this function. To prevent frontrunning we use PENALTY_COOLDOWN period
                  @param _penalty_k Coefficient indicating the penalty speed for early unlock.
                                    Must be between 0 and 50, inclusive. Default 10 - means linear speed.
                  """
                  assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
                  assert _penalty_k <= 50, '!k'
                  assert block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN, 'early' # to avoid frontrun
              
                  self.prev_penalty_k = self.penalty_k
                  self.penalty_k = _penalty_k
                  self.penalty_upd_ts = block.timestamp
              
                  log PenaltySpeed(_penalty_k)
              
              
              @external
              def set_penalty_treasury(_penalty_treasury: address):
                  """
                  @notice Sets penalty treasury address
                  @dev Only the admin_early_unlock can execute this function.
                  @param _penalty_treasury The address to collect early penalty (default admin address)
                  """
                  assert msg.sender == self.admin_early_unlock, '!admin'  # dev: admin_early_unlock only
                  assert _penalty_treasury != empty(address), '!zero'
                 
                  self.penalty_treasury = _penalty_treasury
                  log PenaltyTreasury(_penalty_treasury)
              
              
              @external
              def set_all_unlock():
                  """
                  @notice Deactivates VotingEscrow and allows users to unlock their locks before lock-end. 
                          New deposits will no longer be accepted.
                  @dev Only the admin_unlock_all can execute this function. Make sure there are no rewards for distribution in other contracts.
                  """
                  assert msg.sender == self.admin_unlock_all, '!admin'  # dev: admin_unlock_all only
                  self.all_unlock = True
                  log TotalUnlock(True)
              
              
              @external
              @view
              def get_last_user_slope(addr: address) -> int128:
                  """
                  @notice Get the most recently recorded rate of voting power decrease for `addr`
                  @param addr Address of the user wallet
                  @return Value of the slope
                  """
                  uepoch: uint256 = self.user_point_epoch[addr]
                  return self.user_point_history[addr][uepoch].slope
              
              
              @external
              @view
              def user_point_history__ts(_addr: address, _idx: uint256) -> uint256:
                  """
                  @notice Get the timestamp for checkpoint `_idx` for `_addr`
                  @param _addr User wallet address
                  @param _idx User epoch number
                  @return Epoch time of the checkpoint
                  """
                  return self.user_point_history[_addr][_idx].ts
              
              
              @external
              @view
              def locked__end(_addr: address) -> uint256:
                  """
                  @notice Get timestamp when `_addr`'s lock finishes
                  @param _addr User wallet
                  @return Epoch time of the lock end
                  """
                  return self.locked[_addr].end
              
              
              @internal
              def _checkpoint(addr: address, old_locked: LockedBalance, new_locked: LockedBalance):
                  """
                  @notice Record global and per-user data to checkpoint
                  @param addr User's wallet address. No user checkpoint if 0x0
                  @param old_locked Pevious locked amount / end lock time for the user
                  @param new_locked New locked amount / end lock time for the user
                  """
                  u_old: Point = empty(Point)
                  u_new: Point = empty(Point)
                  old_dslope: int128 = 0
                  new_dslope: int128 = 0
                  _epoch: uint256 = self.epoch
              
                  if addr != empty(address):
                      # Calculate slopes and biases
                      # Kept at zero when they have to
                      if old_locked.end > block.timestamp and old_locked.amount > 0:
                          u_old.slope = old_locked.amount / convert(self.MAXTIME, int128)
                          u_old.bias = u_old.slope * convert(old_locked.end - block.timestamp, int128)
                      if new_locked.end > block.timestamp and new_locked.amount > 0:
                          u_new.slope = new_locked.amount / convert(self.MAXTIME, int128)
                          u_new.bias = u_new.slope * convert(new_locked.end - block.timestamp, int128)
              
              
                      # Read values of scheduled changes in the slope
                      # old_locked.end can be in the past and in the future
                      # new_locked.end can ONLY by in the FUTURE unless everything expired: than zeros
                      old_dslope = self.slope_changes[old_locked.end]
                      if new_locked.end != 0:
                          if new_locked.end == old_locked.end:
                              new_dslope = old_dslope
                          else:
                              new_dslope = self.slope_changes[new_locked.end]
              
                  last_point: Point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number})
                  if _epoch > 0:
                      last_point = self.point_history[_epoch]
                  last_checkpoint: uint256 = last_point.ts
                  # initial_last_point is used for extrapolation to calculate block number
                  # (approximately, for *At methods) and save them
                  # as we cannot figure that out exactly from inside the contract
                  initial_last_point: Point = last_point
                  block_slope: uint256 = 0  # dblock/dt
                  if block.timestamp > last_point.ts:
                      block_slope = MULTIPLIER * (block.number - last_point.blk) / (block.timestamp - last_point.ts)
                  # If last point is already recorded in this block, slope=0
                  # But that's ok b/c we know the block in such case
              
                  # Go over weeks to fill history and calculate what the current point is
                  t_i: uint256 = (last_checkpoint / WEEK) * WEEK
                  for i in range(255):
                      # Hopefully it won't happen that this won't get used in 5 years!
                      # If it does, users will be able to withdraw but vote weight will be broken
                      t_i += WEEK
                      d_slope: int128 = 0
                      if t_i > block.timestamp:
                          t_i = block.timestamp
                      else:
                          d_slope = self.slope_changes[t_i]
                      last_point.bias -= last_point.slope * convert(t_i - last_checkpoint, int128)
                      last_point.slope += d_slope
                      if last_point.bias < 0:  # This can happen
                          last_point.bias = 0
                      if last_point.slope < 0:  # This cannot happen - just in case
                          last_point.slope = 0
                      last_checkpoint = t_i
                      last_point.ts = t_i
                      last_point.blk = initial_last_point.blk + block_slope * (t_i - initial_last_point.ts) / MULTIPLIER
                      _epoch += 1
                      if t_i == block.timestamp:
                          last_point.blk = block.number
                          break
                      else:
                          self.point_history[_epoch] = last_point
              
                  self.epoch = _epoch
                  # Now point_history is filled until t=now
              
                  if addr != empty(address):
                      # If last point was in this block, the slope change has been applied already
                      # But in such case we have 0 slope(s)
                      last_point.slope += (u_new.slope - u_old.slope)
                      last_point.bias += (u_new.bias - u_old.bias)
                      if last_point.slope < 0:
                          last_point.slope = 0
                      if last_point.bias < 0:
                          last_point.bias = 0
              
                  # Record the changed point into history
                  self.point_history[_epoch] = last_point
              
                  if addr != empty(address):
                      # Schedule the slope changes (slope is going down)
                      # We subtract new_user_slope from [new_locked.end]
                      # and add old_user_slope to [old_locked.end]
                      if old_locked.end > block.timestamp:
                          # old_dslope was <something> - u_old.slope, so we cancel that
                          old_dslope += u_old.slope
                          if new_locked.end == old_locked.end:
                              old_dslope -= u_new.slope  # It was a new deposit, not extension
                          self.slope_changes[old_locked.end] = old_dslope
              
                      if new_locked.end > block.timestamp:
                          if new_locked.end > old_locked.end:
                              new_dslope -= u_new.slope  # old slope disappeared at this point
                              self.slope_changes[new_locked.end] = new_dslope
                          # else: we recorded it already in old_dslope
              
                      # Now handle user history
                      user_epoch: uint256 = self.user_point_epoch[addr] + 1
              
                      self.user_point_epoch[addr] = user_epoch
                      u_new.ts = block.timestamp
                      u_new.blk = block.number
                      self.user_point_history[addr][user_epoch] = u_new
              
              
              @internal
              def _deposit_for(_addr: address, _value: uint256, unlock_time: uint256, locked_balance: LockedBalance, type: int128):
                  """
                  @notice Deposit and lock tokens for a user
                  @param _addr User's wallet address
                  @param _value Amount to deposit
                  @param unlock_time New time when to unlock the tokens, or 0 if unchanged
                  @param locked_balance Previous locked amount / timestamp
                  """
                  # block all new deposits (and extensions) in case of unlocked contract
                  assert (not self.all_unlock), "all unlocked,no sense"
              
                  _locked: LockedBalance = locked_balance
                  supply_before: uint256 = self.supply
              
                  self.supply = supply_before + _value
                  old_locked: LockedBalance = _locked
                  # Adding to existing lock, or if a lock is expired - creating a new one
                  _locked.amount += convert(_value, int128)
                  if unlock_time != 0:
                      _locked.end = unlock_time
                  self.locked[_addr] = _locked
              
                  # Possibilities:
                  # Both old_locked.end could be current or expired (>/< block.timestamp)
                  # value == 0 (extend lock) or value > 0 (add to lock or extend lock)
                  # _locked.end > block.timestamp (always)
                  self._checkpoint(_addr, old_locked, _locked)
              
                  if _value != 0:
                      assert ERC20(self.TOKEN).transferFrom(_addr, self, _value, default_return_value=True)
              
                  log Deposit(_addr, _value, _locked.end, type, block.timestamp)
                  log Supply(supply_before, supply_before + _value)
              
              
              @external
              def checkpoint():
                  """
                  @notice Record global data to checkpoint
                  """
                  self._checkpoint(empty(address), empty(LockedBalance), empty(LockedBalance))
              
              
              @external
              @nonreentrant("lock")
              def deposit_for(_addr: address, _value: uint256):
                  """
                  @notice Deposit `_value` tokens for `_addr` and add to the lock
                  @dev Anyone (even a smart contract) can deposit for someone else, but
                       cannot extend their locktime and deposit for a brand new user
                  @param _addr User's wallet address
                  @param _value Amount to add to user's lock
                  """
                  _locked: LockedBalance = self.locked[_addr]
              
                  assert _value > 0  # dev: need non-zero value
                  assert _locked.amount > 0, "No existing lock found"
                  assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"
              
                  self._deposit_for(_addr, _value, 0, self.locked[_addr], DEPOSIT_FOR_TYPE)
              
              
              @external
              @nonreentrant("lock")
              def create_lock(_value: uint256, _unlock_time: uint256):
                  """
                  @notice Deposit `_value` tokens for `msg.sender` and lock until `_unlock_time`
                  @param _value Amount to deposit
                  @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
                  """
                  self.assert_not_contract(msg.sender)
                  unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
                  _locked: LockedBalance = self.locked[msg.sender]
              
                  assert _value > 0  # dev: need non-zero value
                  assert _locked.amount == 0, "Withdraw old tokens first"
                  assert (unlock_time > block.timestamp), "Can only lock until time in the future"
                  assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long"
              
                  self._deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE)
              
              
              @external
              @nonreentrant("lock")
              def increase_amount(_value: uint256):
                  """
                  @notice Deposit `_value` additional tokens for `msg.sender`
                          without modifying the unlock time
                  @param _value Amount of tokens to deposit and add to the lock
                  """
                  self.assert_not_contract(msg.sender)
                  _locked: LockedBalance = self.locked[msg.sender]
              
                  assert _value > 0  # dev: need non-zero value
                  assert _locked.amount > 0, "No existing lock found"
                  assert _locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"
              
                  self._deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT)
              
              
              @external
              @nonreentrant("lock")
              def increase_unlock_time(_unlock_time: uint256):
                  """
                  @notice Extend the unlock time for `msg.sender` to `_unlock_time`
                  @param _unlock_time New epoch time for unlocking
                  """
                  self.assert_not_contract(msg.sender)
                  _locked: LockedBalance = self.locked[msg.sender]
                  unlock_time: uint256 = (_unlock_time / WEEK) * WEEK  # Locktime is rounded down to weeks
              
                  assert _locked.end > block.timestamp, "Lock expired"
                  assert _locked.amount > 0, "Nothing is locked"
                  assert unlock_time > _locked.end, "Can only increase lock duration"
                  assert (unlock_time <= block.timestamp + self.MAXTIME), "Voting lock too long"
              
                  self._deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME)
              
              
              @external
              @nonreentrant("lock")
              def withdraw():
                  """
                  @notice Withdraw all tokens for `msg.sender`
                  @dev Only possible if the lock has expired
                  """
                  _locked: LockedBalance = self.locked[msg.sender]
                  assert block.timestamp >= _locked.end or self.all_unlock, "lock !expire or !unlock"
                  value: uint256 = convert(_locked.amount, uint256)
              
                  old_locked: LockedBalance = _locked
                  _locked.end = 0
                  _locked.amount = 0
                  self.locked[msg.sender] = _locked
                  supply_before: uint256 = self.supply
                  self.supply = supply_before - value
              
                  # old_locked can have either expired <= timestamp or zero end
                  # _locked has only 0 end
                  # Both can have >= 0 amount
                  self._checkpoint(msg.sender, old_locked, _locked)
              
                  assert ERC20(self.TOKEN).transfer(msg.sender, value, default_return_value=True)
              
                  log Withdraw(msg.sender, value, block.timestamp)
                  log Supply(supply_before, supply_before - value)
              
              
              @external
              @nonreentrant("lock")
              def withdraw_early():
                  """
                  @notice Withdraws locked tokens for `msg.sender` before lock-end with penalty
                  @dev Only possible if `early_unlock` is enabled (true)
                  By defualt there is linear formula for calculating penalty. 
                  In some cases an admin can configure penalty speed using `set_early_unlock_penalty_speed()`
                  
                  L - lock amount
                  k - penalty coefficient, defined by admin (default 1)
                  Tleft - left time to unlock
                  Tmax - MAXLOCK time
                  Penalty amount = L * k * (Tlast / Tmax)
                  """
                  assert(self.early_unlock == True), "!early unlock"
              
                  _locked: LockedBalance = self.locked[msg.sender]
                  assert block.timestamp < _locked.end, "lock expired"
              
                  value: uint256 = convert(_locked.amount, uint256)
              
                  time_left: uint256 = _locked.end - block.timestamp
                  
                  # to avoid front-run with penalty_k
                  penalty_k_: uint256 = 0
                  if block.timestamp > self.penalty_upd_ts + PENALTY_COOLDOWN:
                      penalty_k_ = self.penalty_k
                  else:
                      penalty_k_ = self.prev_penalty_k
              
                  penalty_ratio: uint256 = (time_left * MULTIPLIER / self.MAXTIME) * penalty_k_
                  penalty: uint256 = (value * penalty_ratio / MULTIPLIER) / PENALTY_MULTIPLIER    
                  if penalty > value:
                      penalty = value
                  user_amount: uint256 = value - penalty
              
                  old_locked: LockedBalance = _locked
                  _locked.end = 0
                  _locked.amount = 0
                  self.locked[msg.sender] = _locked
                  supply_before: uint256 = self.supply
                  self.supply = supply_before - value
              
                  # old_locked can have either expired <= timestamp or zero end
                  # _locked has only 0 end
                  # Both can have >= 0 amount
                  self._checkpoint(msg.sender, old_locked, _locked)
              
                  if penalty > 0:
                      assert ERC20(self.TOKEN).transfer(self.penalty_treasury, penalty, default_return_value=True)
                  if user_amount > 0:
                      assert ERC20(self.TOKEN).transfer(msg.sender, user_amount, default_return_value=True)
              
                  log Withdraw(msg.sender, value, block.timestamp)
                  log Supply(supply_before, supply_before - value)
                  log WithdrawEarly(msg.sender, penalty, time_left)
              
              
              # The following ERC20/minime-compatible methods are not real balanceOf and supply!
              # They measure the weights for the purpose of voting, so they don't represent
              # real coins.
              
              @internal
              @view
              def find_block_epoch(_block: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find epoch containing block number
                  @param _block Block to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _block
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.point_history[_mid].blk <= _block:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              @internal
              @view
              def find_timestamp_epoch(_timestamp: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find epoch for timestamp
                  @param _timestamp timestamp to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _timestamp
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.point_history[_mid].ts <= _timestamp:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              
              @internal
              @view
              def find_block_user_epoch(_addr: address, _block: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find epoch for block number
                  @param _addr User for which to find user epoch for
                  @param _block Block to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _block
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.user_point_history[_addr][_mid].blk <= _block:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              
              @internal
              @view
              def find_timestamp_user_epoch(_addr: address, _timestamp: uint256, max_epoch: uint256) -> uint256:
                  """
                  @notice Binary search to find user epoch for timestamp
                  @param _addr User for which to find user epoch for
                  @param _timestamp timestamp to find
                  @param max_epoch Don't go beyond this epoch
                  @return Epoch which contains _timestamp
                  """
                  # Binary search
                  _min: uint256 = 0
                  _max: uint256 = max_epoch
                  for i in range(128):  # Will be always enough for 128-bit numbers
                      if _min >= _max:
                          break
                      _mid: uint256 = (_min + _max + 1) / 2
                      if self.user_point_history[_addr][_mid].ts <= _timestamp:
                          _min = _mid
                      else:
                          _max = _mid - 1
                  return _min
              
              @external
              @view
              def balanceOf(addr: address, _t: uint256 = block.timestamp) -> uint256:
                  """
                  @notice Get the current voting power for `msg.sender`
                  @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
                  @param addr User wallet address
                  @param _t Epoch time to return voting power at
                  @return User voting power
                  """
                  _epoch: uint256 = 0
                  if _t == block.timestamp:
                      # No need to do binary search, will always live in current epoch
                      _epoch = self.user_point_epoch[addr]
                  else:
                      _epoch = self.find_timestamp_user_epoch(addr, _t, self.user_point_epoch[addr])
              
                  if _epoch == 0:
                      return 0
                  else:
                      last_point: Point = self.user_point_history[addr][_epoch]
                      last_point.bias -= last_point.slope * convert(_t - last_point.ts, int128)
                      if last_point.bias < 0:
                          last_point.bias = 0
                      return convert(last_point.bias, uint256)
              
              
              @external
              @view
              def balanceOfAt(addr: address, _block: uint256) -> uint256:
                  """
                  @notice Measure voting power of `addr` at block height `_block`
                  @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
                  @param addr User's wallet address
                  @param _block Block to calculate the voting power at
                  @return Voting power
                  """
                  # Copying and pasting totalSupply code because Vyper cannot pass by
                  # reference yet
                  assert _block <= block.number
              
                  _user_epoch: uint256 = self.find_block_user_epoch(addr, _block, self.user_point_epoch[addr])
                  upoint: Point = self.user_point_history[addr][_user_epoch]
              
                  max_epoch: uint256 = self.epoch
                  _epoch: uint256 = self.find_block_epoch(_block, max_epoch)
                  point_0: Point = self.point_history[_epoch]
                  d_block: uint256 = 0
                  d_t: uint256 = 0
                  if _epoch < max_epoch:
                      point_1: Point = self.point_history[_epoch + 1]
                      d_block = point_1.blk - point_0.blk
                      d_t = point_1.ts - point_0.ts
                  else:
                      d_block = block.number - point_0.blk
                      d_t = block.timestamp - point_0.ts
                  block_time: uint256 = point_0.ts
                  if d_block != 0:
                      block_time += d_t * (_block - point_0.blk) / d_block
              
                  upoint.bias -= upoint.slope * convert(block_time - upoint.ts, int128)
                  if upoint.bias >= 0:
                      return convert(upoint.bias, uint256)
                  else:
                      return 0
              
              
              @internal
              @view
              def supply_at(point: Point, t: uint256) -> uint256:
                  """
                  @notice Calculate total voting power at some point in the past
                  @param point The point (bias/slope) to start search from
                  @param t Time to calculate the total voting power at
                  @return Total voting power at that time
                  """
                  last_point: Point = point
                  t_i: uint256 = (last_point.ts / WEEK) * WEEK
                  for i in range(255):
                      t_i += WEEK
                      d_slope: int128 = 0
                      if t_i > t:
                          t_i = t
                      else:
                          d_slope = self.slope_changes[t_i]
                      last_point.bias -= last_point.slope * convert(t_i - last_point.ts, int128)
                      if t_i == t:
                          break
                      last_point.slope += d_slope
                      last_point.ts = t_i
              
                  if last_point.bias < 0:
                      last_point.bias = 0
                  return convert(last_point.bias, uint256)
              
              
              @external
              @view
              def totalSupply(t: uint256 = block.timestamp) -> uint256:
                  """
                  @notice Calculate total voting power
                  @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
                  @return Total voting power
                  """
                  _epoch: uint256 = 0
                  if t == block.timestamp:
                      # No need to do binary search, will always live in current epoch
                      _epoch = self.epoch
                  else:
                      _epoch = self.find_timestamp_epoch(t, self.epoch)
              
                  if _epoch == 0:
                      return 0
                  else:
                      last_point: Point = self.point_history[_epoch]
                      return self.supply_at(last_point, t)
              
              
              @external
              @view
              def totalSupplyAt(_block: uint256) -> uint256:
                  """
                  @notice Calculate total voting power at some point in the past
                  @param _block Block to calculate the total voting power at
                  @return Total voting power at `_block`
                  """
                  assert _block <= block.number
                  _epoch: uint256 = self.epoch
                  target_epoch: uint256 = self.find_block_epoch(_block, _epoch)
              
                  point: Point = self.point_history[target_epoch]
                  dt: uint256 = 0
                  if target_epoch < _epoch:
                      point_next: Point = self.point_history[target_epoch + 1]
                      if point.blk != point_next.blk:
                          dt = (_block - point.blk) * (point_next.ts - point.ts) / (point_next.blk - point.blk)
                  else:
                      if point.blk != block.number:
                          dt = (_block - point.blk) * (block.timestamp - point.ts) / (block.number - point.blk)
                  # Now dt contains info on how far are we beyond point
              
                  return self.supply_at(point, point.ts + dt)
              
              @external
              @nonreentrant("lock")
              def claimExternalRewards():
                  """
                  @notice Claims BAL rewards
                  @dev Only possible if the TOKEN is Guage contract
                  """
                  BalancerMinter(self.balMinter).mint(self.TOKEN)
                  balBalance: uint256 = ERC20(self.balToken).balanceOf(self)
                  if balBalance > 0:
                      # distributes rewards using rewardDistributor into current week
                      if self.rewardReceiver == self.rewardDistributor:
                          assert ERC20(self.balToken).approve(self.rewardDistributor, balBalance, default_return_value=True)
                          RewardDistributor(self.rewardDistributor).depositToken(self.balToken, balBalance)
                      else:
                          assert ERC20(self.balToken).transfer(self.rewardReceiver, balBalance, default_return_value=True)
              
              
              @external
              def changeRewardReceiver(newReceiver: address):
                  """
                  @notice Changes the reward receiver address
                  @param newReceiver New address to set as the reward receiver
                  """
                  assert msg.sender == self.admin, '!admin'
                  assert (self.rewardReceiverChangeable), '!available'
                  assert newReceiver != empty(address), '!empty'
              
                  self.rewardReceiver = newReceiver
                  log RewardReceiver(newReceiver)

              File 6 of 7: RewardFaucet
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Contract module that helps prevent reentrant calls to a function.
               *
               * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
               * available, which can be applied to functions to make sure there are no nested
               * (reentrant) calls to them.
               *
               * Note that because there is a single `nonReentrant` guard, functions marked as
               * `nonReentrant` may not call one another. This can be worked around by making
               * those functions `private`, and then adding `external` `nonReentrant` entry
               * points to them.
               *
               * TIP: If you would like to learn more about reentrancy and alternative ways
               * to protect against it, check out our blog post
               * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
               */
              abstract contract ReentrancyGuard {
                  // Booleans are more expensive than uint256 or any type that takes up a full
                  // word because each write operation emits an extra SLOAD to first read the
                  // slot's contents, replace the bits taken up by the boolean, and then write
                  // back. This is the compiler's defense against contract upgrades and
                  // pointer aliasing, and it cannot be disabled.
                  // The values being non-zero value makes deployment a bit more expensive,
                  // but in exchange the refund on every call to nonReentrant will be lower in
                  // amount. Since refunds are capped to a percentage of the total
                  // transaction's gas, it is best to keep them low in cases like this one, to
                  // increase the likelihood of the full refund coming into effect.
                  uint256 private constant _NOT_ENTERED = 1;
                  uint256 private constant _ENTERED = 2;
                  uint256 private _status;
                  constructor() {
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Prevents a contract from calling itself, directly or indirectly.
                   * Calling a `nonReentrant` function from another `nonReentrant`
                   * function is not supported. It is possible to prevent this from happening
                   * by making the `nonReentrant` function external, and making it call a
                   * `private` function that does the actual work.
                   */
                  modifier nonReentrant() {
                      _nonReentrantBefore();
                      _;
                      _nonReentrantAfter();
                  }
                  function _nonReentrantBefore() private {
                      // On the first call to nonReentrant, _status will be _NOT_ENTERED
                      require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                      // Any calls to nonReentrant after this point will fail
                      _status = _ENTERED;
                  }
                  function _nonReentrantAfter() private {
                      // By storing the original value once again, a refund is triggered (see
                      // https://eips.ethereum.org/EIPS/eip-2200)
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
                   * `nonReentrant` function in the call stack.
                   */
                  function _reentrancyGuardEntered() internal view returns (bool) {
                      return _status == _ENTERED;
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
               * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
               *
               * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
               * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
               * need to send a transaction, and thus is not required to hold Ether at all.
               */
              interface IERC20Permit {
                  /**
                   * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
                   * given ``owner``'s signed approval.
                   *
                   * IMPORTANT: The same issues {IERC20-approve} has related to transaction
                   * ordering also apply here.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `deadline` must be a timestamp in the future.
                   * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
                   * over the EIP712-formatted function arguments.
                   * - the signature must use ``owner``'s current nonce (see {nonces}).
                   *
                   * For more information on the signature format, see the
                   * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
                   * section].
                   */
                  function permit(
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) external;
                  /**
                   * @dev Returns the current nonce for `owner`. This value must be
                   * included whenever a signature is generated for {permit}.
                   *
                   * Every successful call to {permit} increases ``owner``'s nonce by one. This
                   * prevents a signature from being used multiple times.
                   */
                  function nonces(address owner) external view returns (uint256);
                  /**
                   * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
                   */
                  // solhint-disable-next-line func-name-mixedcase
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `to`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address to, uint256 amount) external returns (bool);
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
                  /**
                   * @dev Moves `amount` tokens from `from` to `to` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(address from, address to, uint256 amount) external returns (bool);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
              pragma solidity ^0.8.0;
              import "../IERC20.sol";
              import "../extensions/IERC20Permit.sol";
              import "../../../utils/Address.sol";
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  using Address for address;
                  /**
                   * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful.
                   */
                  function safeTransfer(IERC20 token, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
                  /**
                   * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
                   * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
                   */
                  function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
                  /**
                   * @dev Deprecated. This function has issues similar to the ones found in
                   * {IERC20-approve}, and its usage is discouraged.
                   *
                   * Whenever possible, use {safeIncreaseAllowance} and
                   * {safeDecreaseAllowance} instead.
                   */
                  function safeApprove(IERC20 token, address spender, uint256 value) internal {
                      // safeApprove should only be called when setting an initial allowance,
                      // or when resetting it to zero. To increase and decrease it, use
                      // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                      require(
                          (value == 0) || (token.allowance(address(this), spender) == 0),
                          "SafeERC20: approve from non-zero to non-zero allowance"
                      );
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                  }
                  /**
                   * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful.
                   */
                  function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
                  }
                  /**
                   * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful.
                   */
                  function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      unchecked {
                          uint256 oldAllowance = token.allowance(address(this), spender);
                          require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
                      }
                  }
                  /**
                   * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
                   * to be set to zero before setting it to a non-zero value, such as USDT.
                   */
                  function forceApprove(IERC20 token, address spender, uint256 value) internal {
                      bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
                      if (!_callOptionalReturnBool(token, approvalCall)) {
                          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
                          _callOptionalReturn(token, approvalCall);
                      }
                  }
                  /**
                   * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
                   * Revert on invalid signature.
                   */
                  function safePermit(
                      IERC20Permit token,
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal {
                      uint256 nonceBefore = token.nonces(owner);
                      token.permit(owner, spender, value, deadline, v, r, s);
                      uint256 nonceAfter = token.nonces(owner);
                      require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   */
                  function _callOptionalReturn(IERC20 token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
                      // the target address contains contract code and also asserts for success in the low-level call.
                      bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                      require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   *
                   * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
                   */
                  function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
                      // and not revert is the subcall reverts.
                      (bool success, bytes memory returndata) = address(token).call(data);
                      return
                          success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
              pragma solidity ^0.8.1;
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   *
                   * Furthermore, `isContract` will also return true if the target contract within
                   * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
                   * which only has an effect at the end of a transaction.
                   * ====
                   *
                   * [IMPORTANT]
                   * ====
                   * You shouldn't rely on `isContract` to protect against flash loan attacks!
                   *
                   * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                   * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                   * constructor.
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies on extcodesize/address.code.length, which returns 0
                      // for contracts in construction, since the code is only stored at the end
                      // of the constructor execution.
                      return account.code.length > 0;
                  }
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      require(address(this).balance >= amount, "Address: insufficient balance");
                      (bool success, ) = recipient.call{value: amount}("");
                      require(success, "Address: unable to send value, recipient may have reverted");
                  }
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain `call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - `target` must be a contract.
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, 0, "Address: low-level call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                   * `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, 0, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but also transferring `value` wei to `target`.
                   *
                   * Requirements:
                   *
                   * - the calling contract must have an ETH balance of at least `value`.
                   * - the called Solidity function must be `payable`.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                   * with `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      require(address(this).balance >= value, "Address: insufficient balance for call");
                      (bool success, bytes memory returndata) = target.call{value: value}(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a static call.
                   *
                   * _Available since v3.3._
                   */
                  function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                      return functionStaticCall(target, data, "Address: low-level static call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                   * but performing a static call.
                   *
                   * _Available since v3.3._
                   */
                  function functionStaticCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal view returns (bytes memory) {
                      (bool success, bytes memory returndata) = target.staticcall(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
                   * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
                   *
                   * _Available since v4.8._
                   */
                  function verifyCallResultFromTarget(
                      address target,
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal view returns (bytes memory) {
                      if (success) {
                          if (returndata.length == 0) {
                              // only check isContract if the call was successful and the return data is empty
                              // otherwise we already know that it was a contract
                              require(isContract(target), "Address: call to non-contract");
                          }
                          return returndata;
                      } else {
                          _revert(returndata, errorMessage);
                      }
                  }
                  /**
                   * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
                   * revert reason or using the provided one.
                   *
                   * _Available since v4.3._
                   */
                  function verifyCallResult(
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal pure returns (bytes memory) {
                      if (success) {
                          return returndata;
                      } else {
                          _revert(returndata, errorMessage);
                      }
                  }
                  function _revert(bytes memory returndata, string memory errorMessage) private pure {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          /// @solidity memory-safe-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // 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.18;
              import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
              import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
              interface IRewardDistributor {
                  function claimToken(address user, address token) external returns (uint256);
                  function faucetDepositToken(address token, uint256 amount) external;
              }
              /**
               * @title RewardFaucet
               * @notice The contract offers users the flexibility to manage and distribute rewards while
               *         ensuring equitable and even distribution of tokens over specified time periods.
               */
              contract RewardFaucet is ReentrancyGuard {
                  using SafeERC20 for IERC20;
                  bool public isInitialized;
                  IRewardDistributor public rewardDistributor;
                  mapping(address token => uint256 rewardAmount) public totalTokenRewards;
                  mapping(address token => mapping(uint256 weekStart => uint256 amount)) public tokenWeekAmounts;
                  event WeeksDistributions(address token, uint256 totalAmount, uint256 weeksCount);
                  event ExactWeekDistribution(address token, uint256 totalAmount, uint256 weeksCount);
                  event DistributePast(address token, uint256 amount, uint256 weekStart);
                  event MovePastRewards(address token, uint256 moveAmount, uint256 pastWeekStart, uint256 nextWeekStart);
                  function initialize(address _rewardDistributor) external {
                      require(!isInitialized, "!twice");
                      require(_rewardDistributor != address(0), '!zero');
                      isInitialized = true;
                      rewardDistributor = IRewardDistributor(_rewardDistributor);
                  }
                  /**
                   * @notice Deposit rewards evenly across a specified period starting from the current week
                   * @dev weekAmount = amount / weeksCount
                   * @param token The address of the token to be deposited as a reward
                   * @param amount The total amount of `token` to be deposited as a reward over the entire period
                   * @param weeksCount The number of weeks, including the current one, over which the rewards will be distributed
                   */
                  function depositEqualWeeksPeriod(
                      address token,
                      uint256 amount,
                      uint256 weeksCount
                  ) nonReentrant external {
                      require(weeksCount > 0 && weeksCount <= 104, '!week');
                      // if some tokens were transferred directly
                      uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token];
                      uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount;
                      uint256 weekAmount = totalAmount / weeksCount;
                      if (weeksCount != 1) {
                          // current week will be distributed now, thus filling map from the next week
                          uint256 weekStart = _roundUpTimestamp(block.timestamp);
                          for (uint256 i = 2; i <= weeksCount; ) {
                              // last iteration with leftovers
                              if (i == weeksCount) {
                                  tokenWeekAmounts[token][weekStart] += (totalAmount - weekAmount * (weeksCount - 1));
                                  break;
                              }
                              tokenWeekAmounts[token][weekStart] += weekAmount;
                              unchecked { i++; }
                              weekStart += 1 weeks;
                          }
                          // first week will be distributed now, thus subtract 1 weekAmount
                          totalTokenRewards[token] += totalAmount - weekAmount;
                      }
                      // don't confuse with 'totalAmount'
                      IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
                      // current week distribution
                      IRewardDistributor rewardDistributor_ = rewardDistributor;
                      IERC20(token).forceApprove(address(rewardDistributor_), weekAmount);
                      rewardDistributor_.faucetDepositToken(token, weekAmount);
                      emit WeeksDistributions(token, totalAmount, weeksCount);
                  }
                  /**
                   * @notice Deposit rewards into a specific week (starting from current)
                   * @dev If a week is separated from previous reward weeks, 
                   *      or rewards were not claimed in previous weeks in the RewardDistributor contract,
                   *      users may need to manually call the `distributePastRewards()` function 
                   *      to ensure that the rewards are added to the RewardDistributor contract.
                   * @param token The address of the token to be deposited as a reward
                   * @param amount The amount of `token` to be deposited as a reward
                   * @param weekTimeStamp The timestamp of the week for which rewards are being distributed
                   */
                  function depositExactWeek(
                      address token,
                      uint256 amount,
                      uint256 weekTimeStamp
                  ) nonReentrant external {
                      require(
                          weekTimeStamp >= _roundDownTimestamp(block.timestamp) && weekTimeStamp <= block.timestamp + 104 weeks,
                          'bad week'
                      );
                      // if some tokens were transferred directly
                      uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token];
                      uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount;
                      IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
                      uint256 weekStart = _roundDownTimestamp(weekTimeStamp);
                      if (weekStart == _roundDownTimestamp(block.timestamp)) {
                          // current week will be distributed now
                          IRewardDistributor rewardDistributor_ = rewardDistributor;
                          IERC20(token).forceApprove(address(rewardDistributor_), totalAmount);
                          rewardDistributor_.faucetDepositToken(token, totalAmount);
                      } else {
                          tokenWeekAmounts[token][weekStart] += totalAmount;
                          totalTokenRewards[token] += totalAmount;
                      }
                      emit ExactWeekDistribution(token, totalAmount, weekStart);
                  }
                  /**
                   * @notice Collects all rewards for 10 past weeks and sends them to RewardDistributor
                   * @param token - the token address to collect rewards
                   */
                  function distributePastRewards(address token) external {
                      if (totalTokenRewards[token] == 0) return;
                      
                      uint256 weekStart = _roundDownTimestamp(block.timestamp);
                      uint256 totalAmount;
                      for (uint256 i = 0; i < 10; ++i) {
                          uint256 amount = tokenWeekAmounts[token][weekStart];
                          if (amount == 0) {
                              weekStart -= 1 weeks;
                              continue;
                          }
                          tokenWeekAmounts[token][weekStart] = 0;
                          totalAmount += amount;
                          weekStart -= 1 weeks;
                      }
                      if (totalAmount > 0) {
                          totalTokenRewards[token] -= totalAmount;
                          IRewardDistributor rewardDistributor_ = rewardDistributor;
                          IERC20(token).forceApprove(address(rewardDistributor_), totalAmount);
                          rewardDistributor_.faucetDepositToken(token, totalAmount);
                          emit DistributePast(token, totalAmount, weekStart);
                      }
                      
                  }
                  /**
                  * @notice Manually moves unclaimed past rewards to the next week to enable distribution
                  * @dev This function can be called by anyone
                  * @param token The reward token address to be moved
                  * @param pastWeekTimestamp The timestamp representing a point in the past week (must be at least 10 weeks ago)
                  */
                  function movePastRewards(address token, uint256 pastWeekTimestamp) external {
                      uint256 pastWeekStart = _roundDownTimestamp(pastWeekTimestamp);
                      require(pastWeekStart < _roundDownTimestamp(block.timestamp) - 9 weeks, '!outdate');
                      
                      uint256 nextWeekStart = _roundUpTimestamp(block.timestamp);
                      
                      uint256 moveAmount = tokenWeekAmounts[token][pastWeekStart];
                      tokenWeekAmounts[token][pastWeekStart] = 0;
                      tokenWeekAmounts[token][nextWeekStart] += moveAmount;
                      emit MovePastRewards(token, moveAmount, pastWeekStart, nextWeekStart);
                  }
                  /**
                  * @notice Returns the reward amount for a specified week (represented by a point within the week)
                  * @dev The `pointOfWeek` parameter is any timestamp within the week: wStart[---p-----]wEnd
                  * @param token The address of the reward token to be distributed
                  * @param pointOfWeek The timestamp representing a specific week
                  * @return The reward amount for the specified week
                  */
                  function getTokenWeekAmounts(address token, uint256 pointOfWeek) external view returns (uint256) {
                      uint256 weekStart = _roundDownTimestamp(pointOfWeek);
                      return tokenWeekAmounts[token][weekStart];
                  }
                  /**
                  * @notice Returns rewards for a specified number of weeks starting from the current week
                  * @param token The address of the reward token to be distributed
                  * @param weeksCount The number of weeks to check rewards for
                  * @return An array containing reward amounts for each week
                  */
                  function getUpcomingRewardsForNWeeks(
                      address token,
                      uint256 weeksCount
                  ) external view returns (uint256[] memory) {
                      uint256 weekStart = _roundDownTimestamp(block.timestamp);
                      uint256[] memory rewards = new uint256[](weeksCount);
                      for (uint256 i = 0; i < weeksCount; i++) {
                          rewards[i] = tokenWeekAmounts[token][weekStart + i * 1 weeks];
                      }
                      return rewards;
                  }
                  /**
                   * @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) {
                      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) {
                      // Overflows are impossible here for all realistic inputs.
                      return _roundDownTimestamp(timestamp + 1 weeks);
                  }
              }
              

              File 7 of 7: RewardFaucet
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Contract module that helps prevent reentrant calls to a function.
               *
               * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
               * available, which can be applied to functions to make sure there are no nested
               * (reentrant) calls to them.
               *
               * Note that because there is a single `nonReentrant` guard, functions marked as
               * `nonReentrant` may not call one another. This can be worked around by making
               * those functions `private`, and then adding `external` `nonReentrant` entry
               * points to them.
               *
               * TIP: If you would like to learn more about reentrancy and alternative ways
               * to protect against it, check out our blog post
               * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
               */
              abstract contract ReentrancyGuard {
                  // Booleans are more expensive than uint256 or any type that takes up a full
                  // word because each write operation emits an extra SLOAD to first read the
                  // slot's contents, replace the bits taken up by the boolean, and then write
                  // back. This is the compiler's defense against contract upgrades and
                  // pointer aliasing, and it cannot be disabled.
                  // The values being non-zero value makes deployment a bit more expensive,
                  // but in exchange the refund on every call to nonReentrant will be lower in
                  // amount. Since refunds are capped to a percentage of the total
                  // transaction's gas, it is best to keep them low in cases like this one, to
                  // increase the likelihood of the full refund coming into effect.
                  uint256 private constant _NOT_ENTERED = 1;
                  uint256 private constant _ENTERED = 2;
                  uint256 private _status;
                  constructor() {
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Prevents a contract from calling itself, directly or indirectly.
                   * Calling a `nonReentrant` function from another `nonReentrant`
                   * function is not supported. It is possible to prevent this from happening
                   * by making the `nonReentrant` function external, and making it call a
                   * `private` function that does the actual work.
                   */
                  modifier nonReentrant() {
                      _nonReentrantBefore();
                      _;
                      _nonReentrantAfter();
                  }
                  function _nonReentrantBefore() private {
                      // On the first call to nonReentrant, _status will be _NOT_ENTERED
                      require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                      // Any calls to nonReentrant after this point will fail
                      _status = _ENTERED;
                  }
                  function _nonReentrantAfter() private {
                      // By storing the original value once again, a refund is triggered (see
                      // https://eips.ethereum.org/EIPS/eip-2200)
                      _status = _NOT_ENTERED;
                  }
                  /**
                   * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
                   * `nonReentrant` function in the call stack.
                   */
                  function _reentrancyGuardEntered() internal view returns (bool) {
                      return _status == _ENTERED;
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
               * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
               *
               * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
               * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
               * need to send a transaction, and thus is not required to hold Ether at all.
               */
              interface IERC20Permit {
                  /**
                   * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
                   * given ``owner``'s signed approval.
                   *
                   * IMPORTANT: The same issues {IERC20-approve} has related to transaction
                   * ordering also apply here.
                   *
                   * Emits an {Approval} event.
                   *
                   * Requirements:
                   *
                   * - `spender` cannot be the zero address.
                   * - `deadline` must be a timestamp in the future.
                   * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
                   * over the EIP712-formatted function arguments.
                   * - the signature must use ``owner``'s current nonce (see {nonces}).
                   *
                   * For more information on the signature format, see the
                   * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
                   * section].
                   */
                  function permit(
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) external;
                  /**
                   * @dev Returns the current nonce for `owner`. This value must be
                   * included whenever a signature is generated for {permit}.
                   *
                   * Every successful call to {permit} increases ``owner``'s nonce by one. This
                   * prevents a signature from being used multiple times.
                   */
                  function nonces(address owner) external view returns (uint256);
                  /**
                   * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
                   */
                  // solhint-disable-next-line func-name-mixedcase
                  function DOMAIN_SEPARATOR() external view returns (bytes32);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
              pragma solidity ^0.8.0;
              /**
               * @dev Interface of the ERC20 standard as defined in the EIP.
               */
              interface IERC20 {
                  /**
                   * @dev Emitted when `value` tokens are moved from one account (`from`) to
                   * another (`to`).
                   *
                   * Note that `value` may be zero.
                   */
                  event Transfer(address indexed from, address indexed to, uint256 value);
                  /**
                   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                   * a call to {approve}. `value` is the new allowance.
                   */
                  event Approval(address indexed owner, address indexed spender, uint256 value);
                  /**
                   * @dev Returns the amount of tokens in existence.
                   */
                  function totalSupply() external view returns (uint256);
                  /**
                   * @dev Returns the amount of tokens owned by `account`.
                   */
                  function balanceOf(address account) external view returns (uint256);
                  /**
                   * @dev Moves `amount` tokens from the caller's account to `to`.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transfer(address to, uint256 amount) external returns (bool);
                  /**
                   * @dev Returns the remaining number of tokens that `spender` will be
                   * allowed to spend on behalf of `owner` through {transferFrom}. This is
                   * zero by default.
                   *
                   * This value changes when {approve} or {transferFrom} are called.
                   */
                  function allowance(address owner, address spender) external view returns (uint256);
                  /**
                   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * IMPORTANT: Beware that changing an allowance with this method brings the risk
                   * that someone may use both the old and the new allowance by unfortunate
                   * transaction ordering. One possible solution to mitigate this race
                   * condition is to first reduce the spender's allowance to 0 and set the
                   * desired value afterwards:
                   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                   *
                   * Emits an {Approval} event.
                   */
                  function approve(address spender, uint256 amount) external returns (bool);
                  /**
                   * @dev Moves `amount` tokens from `from` to `to` using the
                   * allowance mechanism. `amount` is then deducted from the caller's
                   * allowance.
                   *
                   * Returns a boolean value indicating whether the operation succeeded.
                   *
                   * Emits a {Transfer} event.
                   */
                  function transferFrom(address from, address to, uint256 amount) external returns (bool);
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)
              pragma solidity ^0.8.0;
              import "../IERC20.sol";
              import "../extensions/IERC20Permit.sol";
              import "../../../utils/Address.sol";
              /**
               * @title SafeERC20
               * @dev Wrappers around ERC20 operations that throw on failure (when the token
               * contract returns false). Tokens that return no value (and instead revert or
               * throw on failure) are also supported, non-reverting calls are assumed to be
               * successful.
               * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
               * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
               */
              library SafeERC20 {
                  using Address for address;
                  /**
                   * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful.
                   */
                  function safeTransfer(IERC20 token, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                  }
                  /**
                   * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
                   * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
                   */
                  function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                      _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                  }
                  /**
                   * @dev Deprecated. This function has issues similar to the ones found in
                   * {IERC20-approve}, and its usage is discouraged.
                   *
                   * Whenever possible, use {safeIncreaseAllowance} and
                   * {safeDecreaseAllowance} instead.
                   */
                  function safeApprove(IERC20 token, address spender, uint256 value) internal {
                      // safeApprove should only be called when setting an initial allowance,
                      // or when resetting it to zero. To increase and decrease it, use
                      // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                      require(
                          (value == 0) || (token.allowance(address(this), spender) == 0),
                          "SafeERC20: approve from non-zero to non-zero allowance"
                      );
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                  }
                  /**
                   * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful.
                   */
                  function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
                  }
                  /**
                   * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful.
                   */
                  function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                      unchecked {
                          uint256 oldAllowance = token.allowance(address(this), spender);
                          require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
                      }
                  }
                  /**
                   * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
                   * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
                   * to be set to zero before setting it to a non-zero value, such as USDT.
                   */
                  function forceApprove(IERC20 token, address spender, uint256 value) internal {
                      bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
                      if (!_callOptionalReturnBool(token, approvalCall)) {
                          _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
                          _callOptionalReturn(token, approvalCall);
                      }
                  }
                  /**
                   * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
                   * Revert on invalid signature.
                   */
                  function safePermit(
                      IERC20Permit token,
                      address owner,
                      address spender,
                      uint256 value,
                      uint256 deadline,
                      uint8 v,
                      bytes32 r,
                      bytes32 s
                  ) internal {
                      uint256 nonceBefore = token.nonces(owner);
                      token.permit(owner, spender, value, deadline, v, r, s);
                      uint256 nonceAfter = token.nonces(owner);
                      require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   */
                  function _callOptionalReturn(IERC20 token, bytes memory data) private {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
                      // the target address contains contract code and also asserts for success in the low-level call.
                      bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                      require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
                  /**
                   * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                   * on the return value: the return value is optional (but if data is returned, it must not be false).
                   * @param token The token targeted by the call.
                   * @param data The call data (encoded using abi.encode or one of its variants).
                   *
                   * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
                   */
                  function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
                      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                      // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
                      // and not revert is the subcall reverts.
                      (bool success, bytes memory returndata) = address(token).call(data);
                      return
                          success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
                  }
              }
              // SPDX-License-Identifier: MIT
              // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
              pragma solidity ^0.8.1;
              /**
               * @dev Collection of functions related to the address type
               */
              library Address {
                  /**
                   * @dev Returns true if `account` is a contract.
                   *
                   * [IMPORTANT]
                   * ====
                   * It is unsafe to assume that an address for which this function returns
                   * false is an externally-owned account (EOA) and not a contract.
                   *
                   * Among others, `isContract` will return false for the following
                   * types of addresses:
                   *
                   *  - an externally-owned account
                   *  - a contract in construction
                   *  - an address where a contract will be created
                   *  - an address where a contract lived, but was destroyed
                   *
                   * Furthermore, `isContract` will also return true if the target contract within
                   * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
                   * which only has an effect at the end of a transaction.
                   * ====
                   *
                   * [IMPORTANT]
                   * ====
                   * You shouldn't rely on `isContract` to protect against flash loan attacks!
                   *
                   * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
                   * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
                   * constructor.
                   * ====
                   */
                  function isContract(address account) internal view returns (bool) {
                      // This method relies on extcodesize/address.code.length, which returns 0
                      // for contracts in construction, since the code is only stored at the end
                      // of the constructor execution.
                      return account.code.length > 0;
                  }
                  /**
                   * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                   * `recipient`, forwarding all available gas and reverting on errors.
                   *
                   * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                   * of certain opcodes, possibly making contracts go over the 2300 gas limit
                   * imposed by `transfer`, making them unable to receive funds via
                   * `transfer`. {sendValue} removes this limitation.
                   *
                   * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                   *
                   * IMPORTANT: because control is transferred to `recipient`, care must be
                   * taken to not create reentrancy vulnerabilities. Consider using
                   * {ReentrancyGuard} or the
                   * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                   */
                  function sendValue(address payable recipient, uint256 amount) internal {
                      require(address(this).balance >= amount, "Address: insufficient balance");
                      (bool success, ) = recipient.call{value: amount}("");
                      require(success, "Address: unable to send value, recipient may have reverted");
                  }
                  /**
                   * @dev Performs a Solidity function call using a low level `call`. A
                   * plain `call` is an unsafe replacement for a function call: use this
                   * function instead.
                   *
                   * If `target` reverts with a revert reason, it is bubbled up by this
                   * function (like regular Solidity function calls).
                   *
                   * Returns the raw returned data. To convert to the expected return value,
                   * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
                   *
                   * Requirements:
                   *
                   * - `target` must be a contract.
                   * - calling `target` with `data` must not revert.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, 0, "Address: low-level call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
                   * `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, 0, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but also transferring `value` wei to `target`.
                   *
                   * Requirements:
                   *
                   * - the calling contract must have an ETH balance of at least `value`.
                   * - the called Solidity function must be `payable`.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
                      return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
                   * with `errorMessage` as a fallback revert reason when `target` reverts.
                   *
                   * _Available since v3.1._
                   */
                  function functionCallWithValue(
                      address target,
                      bytes memory data,
                      uint256 value,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      require(address(this).balance >= value, "Address: insufficient balance for call");
                      (bool success, bytes memory returndata) = target.call{value: value}(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a static call.
                   *
                   * _Available since v3.3._
                   */
                  function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                      return functionStaticCall(target, data, "Address: low-level static call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                   * but performing a static call.
                   *
                   * _Available since v3.3._
                   */
                  function functionStaticCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal view returns (bytes memory) {
                      (bool success, bytes memory returndata) = target.staticcall(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                      return functionDelegateCall(target, data, "Address: low-level delegate call failed");
                  }
                  /**
                   * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
                   * but performing a delegate call.
                   *
                   * _Available since v3.4._
                   */
                  function functionDelegateCall(
                      address target,
                      bytes memory data,
                      string memory errorMessage
                  ) internal returns (bytes memory) {
                      (bool success, bytes memory returndata) = target.delegatecall(data);
                      return verifyCallResultFromTarget(target, success, returndata, errorMessage);
                  }
                  /**
                   * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
                   * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
                   *
                   * _Available since v4.8._
                   */
                  function verifyCallResultFromTarget(
                      address target,
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal view returns (bytes memory) {
                      if (success) {
                          if (returndata.length == 0) {
                              // only check isContract if the call was successful and the return data is empty
                              // otherwise we already know that it was a contract
                              require(isContract(target), "Address: call to non-contract");
                          }
                          return returndata;
                      } else {
                          _revert(returndata, errorMessage);
                      }
                  }
                  /**
                   * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
                   * revert reason or using the provided one.
                   *
                   * _Available since v4.3._
                   */
                  function verifyCallResult(
                      bool success,
                      bytes memory returndata,
                      string memory errorMessage
                  ) internal pure returns (bytes memory) {
                      if (success) {
                          return returndata;
                      } else {
                          _revert(returndata, errorMessage);
                      }
                  }
                  function _revert(bytes memory returndata, string memory errorMessage) private pure {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          /// @solidity memory-safe-assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
              // SPDX-License-Identifier: MIT
              // 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.18;
              import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
              import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
              interface IRewardDistributor {
                  function claimToken(address user, address token) external returns (uint256);
                  function faucetDepositToken(address token, uint256 amount) external;
              }
              /**
               * @title RewardFaucet
               * @notice The contract offers users the flexibility to manage and distribute rewards while
               *         ensuring equitable and even distribution of tokens over specified time periods.
               */
              contract RewardFaucet is ReentrancyGuard {
                  using SafeERC20 for IERC20;
                  bool public isInitialized;
                  IRewardDistributor public rewardDistributor;
                  mapping(address token => uint256 rewardAmount) public totalTokenRewards;
                  mapping(address token => mapping(uint256 weekStart => uint256 amount)) public tokenWeekAmounts;
                  event WeeksDistributions(address token, uint256 totalAmount, uint256 weeksCount);
                  event ExactWeekDistribution(address token, uint256 totalAmount, uint256 weeksCount);
                  event DistributePast(address token, uint256 amount, uint256 weekStart);
                  event MovePastRewards(address token, uint256 moveAmount, uint256 pastWeekStart, uint256 nextWeekStart);
                  function initialize(address _rewardDistributor) external {
                      require(!isInitialized, "!twice");
                      require(_rewardDistributor != address(0), '!zero');
                      isInitialized = true;
                      rewardDistributor = IRewardDistributor(_rewardDistributor);
                  }
                  /**
                   * @notice Deposit rewards evenly across a specified period starting from the current week
                   * @dev weekAmount = amount / weeksCount
                   * @param token The address of the token to be deposited as a reward
                   * @param amount The total amount of `token` to be deposited as a reward over the entire period
                   * @param weeksCount The number of weeks, including the current one, over which the rewards will be distributed
                   */
                  function depositEqualWeeksPeriod(
                      address token,
                      uint256 amount,
                      uint256 weeksCount
                  ) nonReentrant external {
                      require(weeksCount > 0 && weeksCount <= 104, '!week');
                      // if some tokens were transferred directly
                      uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token];
                      uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount;
                      uint256 weekAmount = totalAmount / weeksCount;
                      if (weeksCount != 1) {
                          // current week will be distributed now, thus filling map from the next week
                          uint256 weekStart = _roundUpTimestamp(block.timestamp);
                          for (uint256 i = 2; i <= weeksCount; ) {
                              // last iteration with leftovers
                              if (i == weeksCount) {
                                  tokenWeekAmounts[token][weekStart] += (totalAmount - weekAmount * (weeksCount - 1));
                                  break;
                              }
                              tokenWeekAmounts[token][weekStart] += weekAmount;
                              unchecked { i++; }
                              weekStart += 1 weeks;
                          }
                          // first week will be distributed now, thus subtract 1 weekAmount
                          totalTokenRewards[token] += totalAmount - weekAmount;
                      }
                      // don't confuse with 'totalAmount'
                      IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
                      // current week distribution
                      IRewardDistributor rewardDistributor_ = rewardDistributor;
                      IERC20(token).forceApprove(address(rewardDistributor_), weekAmount);
                      rewardDistributor_.faucetDepositToken(token, weekAmount);
                      emit WeeksDistributions(token, totalAmount, weeksCount);
                  }
                  /**
                   * @notice Deposit rewards into a specific week (starting from current)
                   * @dev If a week is separated from previous reward weeks, 
                   *      or rewards were not claimed in previous weeks in the RewardDistributor contract,
                   *      users may need to manually call the `distributePastRewards()` function 
                   *      to ensure that the rewards are added to the RewardDistributor contract.
                   * @param token The address of the token to be deposited as a reward
                   * @param amount The amount of `token` to be deposited as a reward
                   * @param weekTimeStamp The timestamp of the week for which rewards are being distributed
                   */
                  function depositExactWeek(
                      address token,
                      uint256 amount,
                      uint256 weekTimeStamp
                  ) nonReentrant external {
                      require(
                          weekTimeStamp >= _roundDownTimestamp(block.timestamp) && weekTimeStamp <= block.timestamp + 104 weeks,
                          'bad week'
                      );
                      // if some tokens were transferred directly
                      uint256 currentDiff = IERC20(token).balanceOf(address(this)) - totalTokenRewards[token];
                      uint256 totalAmount = currentDiff > 0 ? amount + currentDiff : amount;
                      IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
                      uint256 weekStart = _roundDownTimestamp(weekTimeStamp);
                      if (weekStart == _roundDownTimestamp(block.timestamp)) {
                          // current week will be distributed now
                          IRewardDistributor rewardDistributor_ = rewardDistributor;
                          IERC20(token).forceApprove(address(rewardDistributor_), totalAmount);
                          rewardDistributor_.faucetDepositToken(token, totalAmount);
                      } else {
                          tokenWeekAmounts[token][weekStart] += totalAmount;
                          totalTokenRewards[token] += totalAmount;
                      }
                      emit ExactWeekDistribution(token, totalAmount, weekStart);
                  }
                  /**
                   * @notice Collects all rewards for 10 past weeks and sends them to RewardDistributor
                   * @param token - the token address to collect rewards
                   */
                  function distributePastRewards(address token) external {
                      if (totalTokenRewards[token] == 0) return;
                      
                      uint256 weekStart = _roundDownTimestamp(block.timestamp);
                      uint256 totalAmount;
                      for (uint256 i = 0; i < 10; ++i) {
                          uint256 amount = tokenWeekAmounts[token][weekStart];
                          if (amount == 0) {
                              weekStart -= 1 weeks;
                              continue;
                          }
                          tokenWeekAmounts[token][weekStart] = 0;
                          totalAmount += amount;
                          weekStart -= 1 weeks;
                      }
                      if (totalAmount > 0) {
                          totalTokenRewards[token] -= totalAmount;
                          IRewardDistributor rewardDistributor_ = rewardDistributor;
                          IERC20(token).forceApprove(address(rewardDistributor_), totalAmount);
                          rewardDistributor_.faucetDepositToken(token, totalAmount);
                          emit DistributePast(token, totalAmount, weekStart);
                      }
                      
                  }
                  /**
                  * @notice Manually moves unclaimed past rewards to the next week to enable distribution
                  * @dev This function can be called by anyone
                  * @param token The reward token address to be moved
                  * @param pastWeekTimestamp The timestamp representing a point in the past week (must be at least 10 weeks ago)
                  */
                  function movePastRewards(address token, uint256 pastWeekTimestamp) external {
                      uint256 pastWeekStart = _roundDownTimestamp(pastWeekTimestamp);
                      require(pastWeekStart < _roundDownTimestamp(block.timestamp) - 9 weeks, '!outdate');
                      
                      uint256 nextWeekStart = _roundUpTimestamp(block.timestamp);
                      
                      uint256 moveAmount = tokenWeekAmounts[token][pastWeekStart];
                      tokenWeekAmounts[token][pastWeekStart] = 0;
                      tokenWeekAmounts[token][nextWeekStart] += moveAmount;
                      emit MovePastRewards(token, moveAmount, pastWeekStart, nextWeekStart);
                  }
                  /**
                  * @notice Returns the reward amount for a specified week (represented by a point within the week)
                  * @dev The `pointOfWeek` parameter is any timestamp within the week: wStart[---p-----]wEnd
                  * @param token The address of the reward token to be distributed
                  * @param pointOfWeek The timestamp representing a specific week
                  * @return The reward amount for the specified week
                  */
                  function getTokenWeekAmounts(address token, uint256 pointOfWeek) external view returns (uint256) {
                      uint256 weekStart = _roundDownTimestamp(pointOfWeek);
                      return tokenWeekAmounts[token][weekStart];
                  }
                  /**
                  * @notice Returns rewards for a specified number of weeks starting from the current week
                  * @param token The address of the reward token to be distributed
                  * @param weeksCount The number of weeks to check rewards for
                  * @return An array containing reward amounts for each week
                  */
                  function getUpcomingRewardsForNWeeks(
                      address token,
                      uint256 weeksCount
                  ) external view returns (uint256[] memory) {
                      uint256 weekStart = _roundDownTimestamp(block.timestamp);
                      uint256[] memory rewards = new uint256[](weeksCount);
                      for (uint256 i = 0; i < weeksCount; i++) {
                          rewards[i] = tokenWeekAmounts[token][weekStart + i * 1 weeks];
                      }
                      return rewards;
                  }
                  /**
                   * @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) {
                      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) {
                      // Overflows are impossible here for all realistic inputs.
                      return _roundDownTimestamp(timestamp + 1 weeks);
                  }
              }