ETH Price: $2,408.63 (+8.08%)

Transaction Decoder

Block:
18333368 at Oct-12-2023 09:13:23 AM +UTC
Transaction Fee:
0.002398690682852312 ETH $5.78
Gas Used:
320,744 Gas / 7.478520823 Gwei

Emitted Events:

385 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x000000000000000000000000af667811a7edcd5b0066cd4ca0da51637db76d09, 0x000000000000000000000000f872ab92e0ee2879dd07fb0d9f5aeed135068eae, 000000000000000000000000000000000000000000000000000000006377bf6b )
386 FeeDistributor.TokensClaimed( user=[Sender] 0xf872ab92e0ee2879dd07fb0d9f5aeed135068eae, token=[Receiver] FiatTokenProxy, amount=1668792171, userTokenTimeCursor=1697068800 )

Account State Difference:

  Address   Before After State Difference Code
(beaverbuild)
18.730722168024600065 Eth18.730754242424600065 Eth0.0000320744
0xA0b86991...E3606eB48
0xAF667811...37DB76D09
0xF872ab92...135068eae
0.010449265122777574 Eth
Nonce: 14
0.008050574439925262 Eth
Nonce: 15
0.002398690682852312

Execution Trace

FeeDistributor.claimTokens( user=0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae, tokens=[0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48] ) => ( [1668792171] )
  • VotingEscrow.user_point_epoch( 0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae ) => ( 4 )
  • VotingEscrow.user_point_history( 0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae, 3 ) => ( bias=61780333238331045181003, slope=666623814053779, ts=1689064343, blk=17669206 )
  • VotingEscrow.user_point_history( 0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae, 4 ) => ( bias=62873070993813768363151, slope=666623814053779, ts=1697101931, blk=18333362 )
  • VotingEscrow.user_point_history( 0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae, 3 ) => ( bias=61780333238331045181003, slope=666623814053779, ts=1689064343, blk=17669206 )
  • VotingEscrow.user_point_history( 0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae, 4 ) => ( bias=62873070993813768363151, slope=666623814053779, ts=1697101931, blk=18333362 )
  • FiatTokenProxy.a9059cbb( )
    • FiatTokenV2_1.transfer( to=0xF872ab92E0eE2879dd07fB0d9f5aeeD135068eae, value=1668792171 ) => ( True )
      claimTokens[FeeDistributor (ln:978)]
      File 1 of 4: FeeDistributor
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <0.8.0;
      import "../utils/Context.sol";
      /**
       * @dev Contract module which provides a basic access control mechanism, where
       * there is an account (an owner) that can be granted exclusive access to
       * specific functions.
       *
       * By default, the owner account will be the one that deploys the contract. This
       * can later be changed with {transferOwnership}.
       *
       * This module is used through inheritance. It will make available the modifier
       * `onlyOwner`, which can be applied to your functions to restrict their use to
       * the owner.
       */
      abstract contract Ownable is Context {
          address private _owner;
          event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
          /**
           * @dev Initializes the contract setting the deployer as the initial owner.
           */
          constructor () internal {
              address msgSender = _msgSender();
              _owner = msgSender;
              emit OwnershipTransferred(address(0), msgSender);
          }
          /**
           * @dev Returns the address of the current owner.
           */
          function owner() public view virtual returns (address) {
              return _owner;
          }
          /**
           * @dev Throws if called by any account other than the owner.
           */
          modifier onlyOwner() {
              require(owner() == _msgSender(), "Ownable: caller is not the owner");
              _;
          }
          /**
           * @dev Leaves the contract without owner. It will not be possible to call
           * `onlyOwner` functions anymore. Can only be called by the current owner.
           *
           * NOTE: Renouncing ownership will leave the contract without an owner,
           * thereby removing any functionality that is only available to the owner.
           */
          function renounceOwnership() public virtual onlyOwner {
              emit OwnershipTransferred(_owner, address(0));
              _owner = address(0);
          }
          /**
           * @dev Transfers ownership of the contract to a new account (`newOwner`).
           * Can only be called by the current owner.
           */
          function transferOwnership(address newOwner) public virtual onlyOwner {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              emit OwnershipTransferred(_owner, newOwner);
              _owner = newOwner;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <0.8.0;
      /**
       * @dev Standard math utilities missing in the Solidity language.
       */
      library Math {
          /**
           * @dev Returns the largest of two numbers.
           */
          function max(uint256 a, uint256 b) internal pure returns (uint256) {
              return a >= b ? a : b;
          }
          /**
           * @dev Returns the smallest of two numbers.
           */
          function min(uint256 a, uint256 b) internal pure returns (uint256) {
              return a < b ? a : b;
          }
          /**
           * @dev Returns the average of two numbers. The result is rounded towards
           * zero.
           */
          function average(uint256 a, uint256 b) internal pure returns (uint256) {
              // (a + b) / 2 can overflow, so we distribute
              return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <0.8.0;
      /**
       * @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, with an overflow flag.
           *
           * _Available since v3.4._
           */
          function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
              uint256 c = a + b;
              if (c < a) return (false, 0);
              return (true, c);
          }
          /**
           * @dev Returns the substraction of two unsigned integers, with an overflow flag.
           *
           * _Available since v3.4._
           */
          function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
              if (b > a) return (false, 0);
              return (true, a - b);
          }
          /**
           * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
           *
           * _Available since v3.4._
           */
          function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
              // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
              // benefit is lost if 'b' is also tested.
              // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
              if (a == 0) return (true, 0);
              uint256 c = a * b;
              if (c / a != b) return (false, 0);
              return (true, c);
          }
          /**
           * @dev Returns the division of two unsigned integers, with a division by zero flag.
           *
           * _Available since v3.4._
           */
          function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
              if (b == 0) return (false, 0);
              return (true, a / b);
          }
          /**
           * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
           *
           * _Available since v3.4._
           */
          function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
              if (b == 0) return (false, 0);
              return (true, a % b);
          }
          /**
           * @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, "SafeMath: addition 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) {
              require(b <= a, "SafeMath: subtraction overflow");
              return a - b;
          }
          /**
           * @dev Returns the multiplication of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `*` operator.
           *
           * Requirements:
           *
           * - Multiplication cannot overflow.
           */
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              if (a == 0) return 0;
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
              return c;
          }
          /**
           * @dev Returns the integer division of two unsigned integers, reverting on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b > 0, "SafeMath: division by zero");
              return a / b;
          }
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * reverting when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b > 0, "SafeMath: modulo by zero");
              return a % b;
          }
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
           * overflow (when the result is negative).
           *
           * CAUTION: This function is deprecated because it requires allocating memory for the error
           * message unnecessarily. For custom revert reasons use {trySub}.
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           *
           * - Subtraction cannot overflow.
           */
          function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              return a - b;
          }
          /**
           * @dev Returns the integer division of two unsigned integers, reverting with custom message on
           * division by zero. The result is rounded towards zero.
           *
           * CAUTION: This function is deprecated because it requires allocating memory for the error
           * message unnecessarily. For custom revert reasons use {tryDiv}.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b > 0, errorMessage);
              return a / b;
          }
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * reverting with custom message when dividing by zero.
           *
           * CAUTION: This function is deprecated because it requires allocating memory for the error
           * message unnecessarily. For custom revert reasons use {tryMod}.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
              require(b > 0, errorMessage);
              return a % b;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <0.8.0;
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
          /**
           * @dev Moves `amount` tokens from the caller's account to `recipient`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address recipient, uint256 amount) external returns (bool);
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender) external view returns (uint256);
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
          /**
           * @dev Moves `amount` tokens from `sender` to `recipient` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(address indexed owner, address indexed spender, uint256 value);
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <0.8.0;
      import "./IERC20.sol";
      import "../../math/SafeMath.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 SafeMath for uint256;
          using Address for address;
          function safeTransfer(IERC20 token, address to, uint256 value) internal {
              _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
          }
          function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
              _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
          }
          /**
           * @dev Deprecated. This function has issues similar to the ones found in
           * {IERC20-approve}, and its usage is discouraged.
           *
           * Whenever possible, use {safeIncreaseAllowance} and
           * {safeDecreaseAllowance} instead.
           */
          function safeApprove(IERC20 token, address spender, uint256 value) internal {
              // safeApprove should only be called when setting an initial allowance,
              // or when resetting it to zero. To increase and decrease it, use
              // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
              // solhint-disable-next-line max-line-length
              require((value == 0) || (token.allowance(address(this), spender) == 0),
                  "SafeERC20: approve from non-zero to non-zero allowance"
              );
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
          }
          function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 newAllowance = token.allowance(address(this), spender).add(value);
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
          function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
              uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
          /**
           * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
           * on the return value: the return value is optional (but if data is returned, it must not be false).
           * @param token The token targeted by the call.
           * @param data The call data (encoded using abi.encode or one of its variants).
           */
          function _callOptionalReturn(IERC20 token, bytes memory data) private {
              // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
              // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
              // the target address contains contract code and also asserts for success in the low-level call.
              bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
              if (returndata.length > 0) { // Return data is optional
                  // solhint-disable-next-line max-line-length
                  require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.2 <0.8.0;
      /**
       * @dev Collection of functions related to the address type
       */
      library Address {
          /**
           * @dev Returns true if `account` is a contract.
           *
           * [IMPORTANT]
           * ====
           * It is unsafe to assume that an address for which this function returns
           * false is an externally-owned account (EOA) and not a contract.
           *
           * Among others, `isContract` will return false for the following
           * types of addresses:
           *
           *  - an externally-owned account
           *  - a contract in construction
           *  - an address where a contract will be created
           *  - an address where a contract lived, but was destroyed
           * ====
           */
          function isContract(address account) internal view returns (bool) {
              // This method relies on extcodesize, which returns 0 for contracts in
              // construction, since the code is only stored at the end of the
              // constructor execution.
              uint256 size;
              // solhint-disable-next-line no-inline-assembly
              assembly { size := extcodesize(account) }
              return size > 0;
          }
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
              // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
              (bool success, ) = recipient.call{ value: amount }("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain`call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCall(target, data, "Address: low-level call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
              return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
          }
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
              require(address(this).balance >= value, "Address: insufficient balance for call");
              require(isContract(target), "Address: call to non-contract");
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.call{ value: value }(data);
              return _verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
              return functionStaticCall(target, data, "Address: low-level static call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data, string memory errorMessage) internal view returns (bytes memory) {
              require(isContract(target), "Address: static call to non-contract");
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.staticcall(data);
              return _verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionDelegateCall(target, data, "Address: low-level delegate call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
              require(isContract(target), "Address: delegate call to non-contract");
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.delegatecall(data);
              return _verifyCallResult(success, returndata, errorMessage);
          }
          function _verifyCallResult(bool success, bytes memory returndata, string memory errorMessage) private 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(errorMessage);
                  }
              }
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <0.8.0;
      /*
       * @dev Provides information about the current execution context, including the
       * sender of the transaction and its data. While these are generally available
       * via msg.sender and msg.data, they should not be accessed in such a direct
       * manner, since when dealing with GSN meta-transactions the account sending and
       * paying for execution may not be the actual sender (as far as an application
       * is concerned).
       *
       * This contract is only required for intermediate, library-like contracts.
       */
      abstract contract Context {
          function _msgSender() internal view virtual returns (address payable) {
              return msg.sender;
          }
          function _msgData() internal view virtual returns (bytes memory) {
              this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
              return msg.data;
          }
      }
      // SPDX-License-Identifier: MIT
      pragma solidity >=0.6.0 <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 () internal {
              _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() {
              // On the first call to nonReentrant, _notEntered will be true
              require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
              // Any calls to nonReentrant after this point will fail
              _status = _ENTERED;
              _;
              // 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: GPL-3.0-or-later
      pragma solidity ^0.7.0;
      pragma experimental ABIEncoderV2;
      import "@openzeppelin-solc-0.7/contracts/utils/ReentrancyGuard.sol";
      import "@openzeppelin-solc-0.7/contracts/math/Math.sol";
      import "@openzeppelin-solc-0.7/contracts/math/SafeMath.sol";
      import "@openzeppelin-solc-0.7/contracts/token/ERC20/SafeERC20.sol";
      import "@openzeppelin-solc-0.7/contracts/token/ERC20/IERC20.sol";
      import "@openzeppelin-solc-0.7/contracts/access/Ownable.sol";
      import "./interfaces/IFeeDistributor.sol";
      import "./interfaces/IVotingEscrow.sol";
      // solhint-disable not-rely-on-time
      /**
       * @title Fee Distributor
       * @author Balancer Labs. Original version https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/fee-distribution/FeeDistributor.sol
       * @notice Distributes any tokens transferred to the contract (e.g. Protocol fees) among veSTG
       * holders proportionally based on a snapshot of the week at which the tokens are sent to the FeeDistributor contract.
       * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veSTG holders call `depositToken`.
       */
      contract FeeDistributor is IFeeDistributor, Ownable, ReentrancyGuard {
          using SafeMath for uint256;
          using SafeERC20 for IERC20;
          // gas optimization
          uint256 private constant WEEK_MINUS_SECOND = 1 weeks - 1;
          IVotingEscrow private immutable _votingEscrow;
          uint256 private immutable _startTime;
          // Global State
          uint256 private _timeCursor;
          mapping(uint256 => uint256) private _veSupplyCache;
          // Token State
          // `startTime` and `timeCursor` are both timestamps so comfortably fit in a uint64.
          // `cachedBalance` will comfortably fit the total supply of any meaningful token.
          // Should more than 2^128 tokens be sent to this contract then checkpointing this token will fail until enough
          // tokens have been claimed to bring the total balance back below 2^128.
          struct TokenState {
              uint64 startTime;
              uint64 timeCursor;
              uint128 cachedBalance;
          }
          mapping(IERC20 => TokenState) private _tokenState;
          mapping(IERC20 => mapping(uint256 => uint256)) private _tokensPerWeek;
          mapping(IERC20 => bool) private _tokenClaimingEnabled;
          // 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;
          mapping(address => bool) private _onlyVeHolderClaimingEnabled;
          /**
           * @dev Reverts if only the VotingEscrow holder can claim their rewards and the given address is a third-party caller.
           * @param user - The address to validate as the only allowed caller.
           */
          modifier userAllowedToClaim(address user) {
              if (_onlyVeHolderClaimingEnabled[user]) {
                  require(msg.sender == user, "Claiming is not allowed");
              }
              _;
          }
          /**
           * @dev Reverts if the given token cannot be claimed.
           * @param token - The token to check.
           */
          modifier tokenCanBeClaimed(IERC20 token) {
              _checkIfClaimingEnabled(token);
              _;
          }
          /**
           * @dev Reverts if the given tokens cannot be claimed.
           * @param tokens - The tokens to check.
           */
          modifier tokensCanBeClaimed(IERC20[] calldata tokens) {
              uint256 tokensLength = tokens.length;
              for (uint256 i = 0; i < tokensLength; ++i) {
                  _checkIfClaimingEnabled(tokens[i]);
              }
              _;
          }
          constructor(IVotingEscrow votingEscrow, uint256 startTime) {
              _votingEscrow = votingEscrow;
              startTime = _roundDownTimestamp(startTime);
              uint256 currentWeek = _roundDownTimestamp(block.timestamp);
              require(startTime >= currentWeek, "Cannot start before current week");
              IVotingEscrow.Point memory pt = votingEscrow.point_history(0);
              require(startTime > pt.ts, "Cannot start before VotingEscrow first epoch");
              _startTime = startTime;
              _timeCursor = startTime;
          }
          /**
           * @notice Returns the VotingEscrow (veSTG) token contract
           */
          function getVotingEscrow() external view override returns (IVotingEscrow) {
              return _votingEscrow;
          }
          /**
           * @notice Returns the time when fee distribution starts.
           */
          function getStartTime() external view override returns (uint256) {
              return _startTime;
          }
          /**
           * @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 start time representing the first week they're eligible to claim tokens.
           * @param user - The address of the user to query.
           */
          function getUserStartTime(address user) external view override returns (uint256) {
              return _userState[user].startTime;
          }
          /**
           * @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 user-level last checkpointed epoch.
           * @param user - The address of the user to query.
           */
          function getUserLastEpochCheckpointed(address user) external view override returns (uint256) {
              return _userState[user].lastEpochCheckpointed;
          }
          /**
           * @notice True if the given token can be claimed, false otherwise.
           * @param token - The ERC20 token address to query.
           */
          function canTokenBeClaimed(IERC20 token) external view override returns (bool) {
              return _tokenClaimingEnabled[token];
          }
          /**
           * @notice Returns the token-level start time representing the timestamp users could start claiming this token
           * @param token - The ERC20 token address to query.
           */
          function getTokenStartTime(IERC20 token) external view override returns (uint256) {
              return _tokenState[token].startTime;
          }
          /**
           * @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 token-level cached balance.
           * @param token - The ERC20 token address to query.
           */
          function getTokenCachedBalance(IERC20 token) external view override returns (uint256) {
              return _tokenState[token].cachedBalance;
          }
          /**
           * @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 veSTG 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 veSTG as of the provided timestamp.
           * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values.
           * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached.
           * @param timestamp - The timestamp at which to read the cached total supply at.
           */
          function getTotalSupplyAtTimestamp(uint256 timestamp) external view override returns (uint256) {
              return _veSupplyCache[timestamp];
          }
          /**
           * @notice Returns the FeeDistributor's cached balance of `token`.
           */
          function getTokenLastBalance(IERC20 token) external view override returns (uint256) {
              return _tokenState[token].cachedBalance;
          }
          /**
           * @notice Returns the amount of `token` which the FeeDistributor received in the week beginning at `timestamp`.
           * @param token - The ERC20 token address to query.
           * @param timestamp - The timestamp corresponding to the beginning of the week of interest.
           */
          function getTokensDistributedInWeek(IERC20 token, uint256 timestamp) external view override returns (uint256) {
              return _tokensPerWeek[token][timestamp];
          }
          // Preventing third-party claiming
          /**
           * @notice Enables / disables rewards claiming only by the VotingEscrow holder for the message sender.
           * @param enabled - True if only the VotingEscrow holder can claim their rewards, false otherwise.
           */
          function enableOnlyVeHolderClaiming(bool enabled) external override {
              _onlyVeHolderClaimingEnabled[msg.sender] = enabled;
              emit OnlyVeHolderClaimingEnabled(msg.sender, enabled);
          }
          /**
           * @notice Returns true if only the VotingEscrow holder can claim their rewards, false otherwise.
           */
          function onlyVeHolderClaimingEnabled(address user) external view override returns (bool) {
              return _onlyVeHolderClaimingEnabled[user];
          }
          // Depositing
          /**
           * @notice Deposits tokens to be distributed in the current week.
           * @dev Sending tokens directly to the FeeDistributor instead of using `depositToken` may result in tokens being
           * retroactively distributed to past weeks, or for the distribution to carry over to future weeks.
           *
           * If for some reason `depositToken` cannot be called, in order to ensure that all tokens are correctly distributed
           * manually call `checkpointToken` before and after the token transfer.
           * @param token - The ERC20 token address to distribute.
           * @param amount - The amount of tokens to deposit.
           */
          function depositToken(IERC20 token, uint256 amount) external override nonReentrant tokenCanBeClaimed(token) {
              _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 {
              require(tokens.length == amounts.length, "Input length mismatch");
              uint256 length = tokens.length;
              for (uint256 i = 0; i < length; ++i) {
                  _checkIfClaimingEnabled(tokens[i]);
                  _checkpointToken(tokens[i], false);
                  tokens[i].safeTransferFrom(msg.sender, address(this), amounts[i]);
                  _checkpointToken(tokens[i], true);
              }
          }
          // Checkpointing
          /**
           * @notice Caches the total supply of veSTG 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 veSTG at the beginning of each week.
           * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
           * @param user - The address of the user to be checkpointed.
           */
          function checkpointUser(address user) external override nonReentrant {
              _checkpointUserBalance(user);
          }
          /**
           * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
           * @dev Any `token` balance held by the FeeDistributor above that which is returned by `getTokenLastBalance`
           * will be distributed evenly across the time period since `token` was last checkpointed.
           *
           * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
           * @param token - The ERC20 token address to be checkpointed.
           */
          function checkpointToken(IERC20 token) external override nonReentrant tokenCanBeClaimed(token) {
              _checkpointToken(token, true);
          }
          /**
           * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
           * @dev A version of `checkpointToken` which supports checkpointing multiple tokens.
           * See `checkpointToken` for more details.
           * @param tokens - An array of ERC20 token addresses to be checkpointed.
           */
          function checkpointTokens(IERC20[] calldata tokens) external override nonReentrant {
              uint256 tokensLength = tokens.length;
              for (uint256 i = 0; i < tokensLength; ++i) {
                  _checkIfClaimingEnabled(tokens[i]);
                  _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 FeeDistributor
           * is up to date before calculating the amount of tokens to be claimed.
           * @param user - The user on behalf of which to claim.
           * @param token - The ERC20 token address to be claimed.
           * @return The amount of `token` sent to `user` as a result of claiming.
           */
          function claimToken(address user, IERC20 token) external override nonReentrant userAllowedToClaim(user) tokenCanBeClaimed(token) returns (uint256) {
              _checkpointTotalSupply();
              _checkpointUserBalance(user);
              _checkpointToken(token, false);
              return _claimToken(user, token);
          }
          /**
           * @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 userAllowedToClaim(user) tokensCanBeClaimed(tokens) returns (uint256[] memory) {
              _checkpointTotalSupply();
              _checkpointUserBalance(user);
              uint256 tokensLength = tokens.length;
              uint256[] memory amounts = new uint256[](tokensLength);
              for (uint256 i = 0; i < tokensLength; ++i) {
                  _checkpointToken(tokens[i], false);
                  amounts[i] = _claimToken(user, tokens[i]);
              }
              return amounts;
          }
          // Governance
          /**
           * @notice Withdraws the specified `amount` of the `token` from the contract to the `recipient`. Can be called only by Stargate DAO.
           * @param token - The token to withdraw.
           * @param amount - The amount to withdraw.
           * @param recipient - The address to transfer the tokens to.
           */
          function withdrawToken(IERC20 token, uint256 amount, address recipient) external override onlyOwner {
              token.safeTransfer(recipient, amount);
              emit TokenWithdrawn(token, amount, recipient);
          }
          /**
           * @notice Enables or disables claiming of the given token. Can be called only by Stargate DAO.
           * @param token - The token to enable or disable claiming.
           * @param enable - True if the token can be claimed, false otherwise.
           */
          function enableTokenClaiming(IERC20 token, bool enable) external override onlyOwner {
              _tokenClaimingEnabled[token] = enable;
              emit TokenClaimingEnabled(token, enable);
          }
          // 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;
                  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) {
                  // Prevent someone from assigning tokens to an inaccessible week.
                  require(block.timestamp > _startTime, "Fee distribution has not started yet");
                  // 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));
              } 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 fees are assigned to the current week without
                      // overspilling into the next week. To mitigate this, we checkpoint if we're near the end of the week.
                      bool nearingEndOfWeek = _roundUpTimestamp(block.timestamp) - block.timestamp < 1 days;
                      // This ensures that we checkpoint once at the beginning of the week and again for each user interaction
                      // towards the end of the week to give an accurate final reading of the balance.
                      if (alreadyCheckpointedThisWeek && !nearingEndOfWeek) {
                          return;
                      }
                  }
              }
              tokenState.timeCursor = uint64(block.timestamp);
              uint256 tokenBalance = token.balanceOf(address(this));
              uint256 newTokensToDistribute = tokenBalance.sub(tokenState.cachedBalance);
              if (newTokensToDistribute == 0) return;
              require(tokenBalance <= type(uint128).max, "Maximum token balance exceeded");
              tokenState.cachedBalance = uint128(tokenBalance);
              uint256 firstIncompleteWeek = _roundDownTimestamp(lastTokenTime);
              uint256 nextWeek = 0;
              // Distribute `newTokensToDistribute` evenly across the time period from `lastTokenTime` to now.
              // These tokens are assigned to weeks proportionally to how much of this period falls into each week.
              mapping(uint256 => uint256) storage tokensPerWeek = _tokensPerWeek[token];
              for (uint256 i = 0; i < 20; ++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) {
                          tokensPerWeek[firstIncompleteWeek] += newTokensToDistribute;
                      } else {
                          // block.timestamp >= lastTokenTime by definition.
                          tokensPerWeek[firstIncompleteWeek] += (newTokensToDistribute * (block.timestamp - lastTokenTime)) / timeSinceLastCheckpoint;
                      }
                      // As we've caught up to the present then we should now break.
                      break;
                  } else {
                      // We've gone a full week or more without checkpointing so need to distribute tokens to previous weeks.
                      if (timeSinceLastCheckpoint == 0 && nextWeek == lastTokenTime) {
                          // It shouldn't be possible to enter this block
                          tokensPerWeek[firstIncompleteWeek] += newTokensToDistribute;
                      } else {
                          // nextWeek > lastTokenTime by definition.
                          tokensPerWeek[firstIncompleteWeek] += (newTokensToDistribute * (nextWeek - lastTokenTime)) / timeSinceLastCheckpoint;
                      }
                  }
                  // We've now "checkpointed" up to the beginning of next week so must update timestamps appropriately.
                  lastTokenTime = nextWeek;
                  firstIncompleteWeek = nextWeek;
              }
              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 STG.
              // They clearly will not then receive fees.
              require(maxUserEpoch > 0, "veSTG balance is zero");
              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 fees every week, and so we only perform
                  // a binary search here rather than integrating it into the main search algorithm, effectively skipping
                  // most of the week's irrelevant checkpoints.
                  // The slight tradeoff is that users who have multiple infrequent `VotingEscrow` interactions and also don't
                  // claim frequently will also perform the binary search, despite it not leading to gas savings.
                  if (maxUserEpoch - userEpoch > 20) {
                      userEpoch = _findTimestampUserEpoch(user, nextWeekToCheckpoint, userEpoch, maxUserEpoch);
                  }
              }
              // Epoch 0 is always empty so bump onto the next one so that we start on a valid epoch.
              if (userEpoch == 0) {
                  userEpoch = 1;
              }
              IVotingEscrow.Point memory nextUserPoint = _votingEscrow.user_point_history(user, userEpoch);
              // If this is the first checkpoint for the user, calculate the first week they're eligible for.
              // i.e. the timestamp of the first Thursday after they locked.
              // If this is earlier then the first distribution then fast forward to then.
              if (nextWeekToCheckpoint == 0) {
                  // Disallow checkpointing before `startTime`.
                  require(block.timestamp > _startTime, "Fee distribution has not started yet");
                  nextWeekToCheckpoint = Math.max(_startTime, _roundUpTimestamp(nextUserPoint.ts));
                  userState.startTime = uint64(nextWeekToCheckpoint);
              }
              // It's safe to increment `userEpoch` and `nextWeekToCheckpoint` in this loop as epochs and timestamps
              // are always much smaller than 2^256 and are being incremented by small values.
              IVotingEscrow.Point memory currentUserPoint;
              for (uint256 i = 0; i < 50; ++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 fees.
                      _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;
                  // NOTE: Replaced Balancer's logic with Solidly/Velodrome implementation due to the differences in the VotingEscrow totalSupply function
                  // See https://github.com/velodrome-finance/v1/blob/master/contracts/RewardsDistributor.sol#L143
                  uint256 epoch = _findTimestampEpoch(nextWeekToCheckpoint);
                  IVotingEscrow.Point memory pt = _votingEscrow.point_history(epoch);
                  int128 dt = nextWeekToCheckpoint > pt.ts ? int128(nextWeekToCheckpoint - pt.ts) : 0;
                  int128 supply = pt.bias - pt.slope * dt;
                  _veSupplyCache[nextWeekToCheckpoint] = supply > 0 ? uint256(supply) : 0;
                  // 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 veSTG 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 Return the global epoch number corresponding to the provided `timestamp`
           */
          function _findTimestampEpoch(uint256 timestamp) internal view returns (uint256) {
              uint256 min = 0;
              uint256 max = _votingEscrow.epoch();
              // 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.point_history(mid);
                  if (pt.ts <= timestamp) {
                      min = mid;
                  } else {
                      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 + WEEK_MINUS_SECOND);
          }
          /**
           * @dev Reverts if the provided token cannot be claimed.
           */
          function _checkIfClaimingEnabled(IERC20 token) private view {
              require(_tokenClaimingEnabled[token], "Token is not allowed");
          }
      }
      // SPDX-License-Identifier: GPL-3.0-or-later
      pragma solidity >=0.7.0 <0.9.0;
      pragma experimental ABIEncoderV2;
      import "@openzeppelin-solc-0.7/contracts/token/ERC20/IERC20.sol";
      import "./IVotingEscrow.sol";
      /**
       * @title Fee Distributor
       * @notice Distributes any tokens transferred to the contract (e.g. Protocol fees) among veSTG
       * holders proportionally based on a snapshot of the week at which the tokens are sent to the FeeDistributor contract.
       * @dev Supports distributing arbitrarily many different tokens. In order to start distributing a new token to veSTG
       * holders simply transfer the tokens to the `FeeDistributor` contract and then call `checkpointToken`.
       */
      interface IFeeDistributor {
          event TokenCheckpointed(IERC20 token, uint256 amount, uint256 lastCheckpointTimestamp);
          event TokensClaimed(address user, IERC20 token, uint256 amount, uint256 userTokenTimeCursor);
          event TokenWithdrawn(IERC20 token, uint256 amount, address recipient);
          event TokenClaimingEnabled(IERC20 token, bool enabled);
          event OnlyVeHolderClaimingEnabled(address user, bool enabled);
          /**
           * @notice Returns the VotingEscrow (veSTG) token contract
           */
          function getVotingEscrow() external view returns (IVotingEscrow);
          /**
           * @notice Returns the time when fee distribution starts.
           */
          function getStartTime() external view returns (uint256);
          /**
           * @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 user-level start time representing the first week they're eligible to claim tokens.
           * @param user - The address of the user to query.
           */
          function getUserStartTime(address user) external view returns (uint256);
          /**
           * @notice True if the given token can be claimed, false otherwise.
           * @param token - The ERC20 token address to query.
           */
          function canTokenBeClaimed(IERC20 token) external view returns (bool);
          /**
           * @notice Returns the token-level start time representing the timestamp users could start claiming this token
           * @param token - The ERC20 token address to query.
           */
          function getTokenStartTime(IERC20 token) 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 token-level cached balance.
           * @param token - The ERC20 token address to query.
           */
          function getTokenCachedBalance(IERC20 token) external view returns (uint256);
          /**
           * @notice Returns the user-level last checkpointed epoch.
           * @param user - The address of the user to query.
           */
          function getUserLastEpochCheckpointed(address user) 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 veSTG 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 veSTG as of the provided timestamp.
           * @dev Only timestamps which fall on Thursdays 00:00:00 UTC will return correct values.
           * This function requires the contract to have been checkpointed past `timestamp` so that the supply is cached.
           * @param timestamp - The timestamp at which to read the cached total supply at.
           */
          function getTotalSupplyAtTimestamp(uint256 timestamp) external view returns (uint256);
          /**
           * @notice Returns the FeeDistributor's cached balance of `token`.
           */
          function getTokenLastBalance(IERC20 token) external view returns (uint256);
          /**
           * @notice Returns the amount of `token` which the FeeDistributor received in the week beginning at `timestamp`.
           * @param token - The ERC20 token address to query.
           * @param timestamp - The timestamp corresponding to the beginning of the week of interest.
           */
          function getTokensDistributedInWeek(IERC20 token, uint256 timestamp) external view returns (uint256);
          // Preventing third-party claiming
          /**
           * @notice Enables / disables rewards claiming only by the VotingEscrow holder for the message sender.
           * @param enabled - True if only the VotingEscrow holder can claim their rewards, false otherwise.
           */
          function enableOnlyVeHolderClaiming(bool enabled) external;
          /**
           * @notice Returns true if only the VotingEscrow holder can claim their rewards, false otherwise.
           */
          function onlyVeHolderClaimingEnabled(address user) external view returns (bool);
          // Depositing
          /**
           * @notice Deposits tokens to be distributed in the current week.
           * @dev Sending tokens directly to the FeeDistributor instead of using `depositTokens` may result in tokens being
           * retroactively distributed to past weeks, or for the distribution to carry over to future weeks.
           *
           * If for some reason `depositTokens` cannot be called, in order to ensure that all tokens are correctly distributed
           * manually call `checkpointToken` before and after the token transfer.
           * @param token - The ERC20 token address to distribute.
           * @param amount - The amount of tokens to deposit.
           */
          function depositToken(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 veSTG 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 veSTG at the beginning of each week.
           * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
           * @param user - The address of the user to be checkpointed.
           */
          function checkpointUser(address user) external;
          /**
           * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
           * @dev Any `token` balance held by the FeeDistributor above that which is returned by `getTokenLastBalance`
           * will be distributed evenly across the time period since `token` was last checkpointed.
           *
           * This function will be called automatically before claiming tokens to ensure the contract is properly updated.
           * @param token - The ERC20 token address to be checkpointed.
           */
          function checkpointToken(IERC20 token) external;
          /**
           * @notice Assigns any newly-received tokens held by the FeeDistributor to weekly distributions.
           * @dev A version of `checkpointToken` which supports checkpointing multiple tokens.
           * See `checkpointToken` for more details.
           * @param tokens - An array of ERC20 token addresses to be checkpointed.
           */
          function checkpointTokens(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 FeeDistributor
           * is up to date before calculating the amount of tokens to be claimed.
           * @param user - The user on behalf of which to claim.
           * @param token - The ERC20 token address to be claimed.
           * @return The amount of `token` sent to `user` as a result of claiming.
           */
          function claimToken(address user, 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);
          // Governance
          /**
           * @notice Withdraws the specified `amount` of the `token` from the contract to the `recipient`. Can be called only by Stargate DAO.
           * @param token - The token to withdraw.
           * @param amount - The amount to withdraw.
           * @param recipient - The address to transfer the tokens to.
           */
          function withdrawToken(IERC20 token, uint256 amount, address recipient) external;
          /**
           * @notice Enables or disables claiming of the given token. Can be called only by Stargate DAO.
           * @param token - The token to enable or disable claiming.
           * @param enable - True if the token can be claimed, false otherwise.
           */
          function enableTokenClaiming(IERC20 token, bool enable) external;
      }
      // SPDX-License-Identifier: GPL-3.0-or-later
      pragma solidity >=0.7.0 <0.9.0;
      pragma experimental ABIEncoderV2;
      // For compatibility, we're keeping the same function names as in the original Curve code, including the mixed-case
      // naming convention.
      // solhint-disable func-name-mixedcase
      interface IVotingEscrow {
          struct Point {
              int128 bias;
              int128 slope; // - dweight / dt
              uint256 ts;
              uint256 blk; // block
          }
          function epoch() external view returns (uint256);
          function balanceOfAtT(address user, uint256 timestamp) external view returns (uint256);
          function totalSupplyAtT(uint256 timestamp) external view returns (uint256);
          function user_point_epoch(address user) external view returns (uint256);
          function point_history(uint256 timestamp) external view returns (Point memory);
          function user_point_history(address user, uint256 timestamp) external view returns (Point memory);
          function checkpoint() external;
          function locked__end(address user) external view returns (uint256);
      }

      File 2 of 4: FiatTokenProxy
      pragma solidity ^0.4.24;
      
      // File: zos-lib/contracts/upgradeability/Proxy.sol
      
      /**
       * @title Proxy
       * @dev Implements delegation of calls to other contracts, with proper
       * forwarding of return values and bubbling of failures.
       * It defines a fallback function that delegates all calls to the address
       * returned by the abstract _implementation() internal function.
       */
      contract Proxy {
        /**
         * @dev Fallback function.
         * Implemented entirely in `_fallback`.
         */
        function () payable external {
          _fallback();
        }
      
        /**
         * @return The Address of the implementation.
         */
        function _implementation() internal view returns (address);
      
        /**
         * @dev Delegates execution to an implementation contract.
         * This is a low level function that doesn't return to its internal call site.
         * It will return to the external caller whatever the implementation returns.
         * @param implementation Address to delegate.
         */
        function _delegate(address implementation) internal {
          assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize)
      
            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
      
            // Copy the returned data.
            returndatacopy(0, 0, returndatasize)
      
            switch result
            // delegatecall returns 0 on error.
            case 0 { revert(0, returndatasize) }
            default { return(0, returndatasize) }
          }
        }
      
        /**
         * @dev Function that is run as the first thing in the fallback function.
         * Can be redefined in derived contracts to add functionality.
         * Redefinitions must call super._willFallback().
         */
        function _willFallback() internal {
        }
      
        /**
         * @dev fallback implementation.
         * Extracted to enable manual triggering.
         */
        function _fallback() internal {
          _willFallback();
          _delegate(_implementation());
        }
      }
      
      // File: openzeppelin-solidity/contracts/AddressUtils.sol
      
      /**
       * Utility library of inline functions on addresses
       */
      library AddressUtils {
      
        /**
         * Returns whether the target address is a contract
         * @dev This function will return false if invoked during the constructor of a contract,
         * as the code is not actually created until after the constructor finishes.
         * @param addr address to check
         * @return whether the target address is a contract
         */
        function isContract(address addr) internal view returns (bool) {
          uint256 size;
          // XXX Currently there is no better way to check if there is a contract in an address
          // than to check the size of the code at that address.
          // See https://ethereum.stackexchange.com/a/14016/36603
          // for more details about how this works.
          // TODO Check this again before the Serenity release, because all addresses will be
          // contracts then.
          // solium-disable-next-line security/no-inline-assembly
          assembly { size := extcodesize(addr) }
          return size > 0;
        }
      
      }
      
      // File: zos-lib/contracts/upgradeability/UpgradeabilityProxy.sol
      
      /**
       * @title UpgradeabilityProxy
       * @dev This contract implements a proxy that allows to change the
       * implementation address to which it will delegate.
       * Such a change is called an implementation upgrade.
       */
      contract UpgradeabilityProxy is Proxy {
        /**
         * @dev Emitted when the implementation is upgraded.
         * @param implementation Address of the new implementation.
         */
        event Upgraded(address implementation);
      
        /**
         * @dev Storage slot with the address of the current implementation.
         * This is the keccak-256 hash of "org.zeppelinos.proxy.implementation", and is
         * validated in the constructor.
         */
        bytes32 private constant IMPLEMENTATION_SLOT = 0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3;
      
        /**
         * @dev Contract constructor.
         * @param _implementation Address of the initial implementation.
         */
        constructor(address _implementation) public {
          assert(IMPLEMENTATION_SLOT == keccak256("org.zeppelinos.proxy.implementation"));
      
          _setImplementation(_implementation);
        }
      
        /**
         * @dev Returns the current implementation.
         * @return Address of the current implementation
         */
        function _implementation() internal view returns (address impl) {
          bytes32 slot = IMPLEMENTATION_SLOT;
          assembly {
            impl := sload(slot)
          }
        }
      
        /**
         * @dev Upgrades the proxy to a new implementation.
         * @param newImplementation Address of the new implementation.
         */
        function _upgradeTo(address newImplementation) internal {
          _setImplementation(newImplementation);
          emit Upgraded(newImplementation);
        }
      
        /**
         * @dev Sets the implementation address of the proxy.
         * @param newImplementation Address of the new implementation.
         */
        function _setImplementation(address newImplementation) private {
          require(AddressUtils.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
      
          bytes32 slot = IMPLEMENTATION_SLOT;
      
          assembly {
            sstore(slot, newImplementation)
          }
        }
      }
      
      // File: zos-lib/contracts/upgradeability/AdminUpgradeabilityProxy.sol
      
      /**
       * @title AdminUpgradeabilityProxy
       * @dev This contract combines an upgradeability proxy with an authorization
       * mechanism for administrative tasks.
       * All external functions in this contract must be guarded by the
       * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
       * feature proposal that would enable this to be done automatically.
       */
      contract AdminUpgradeabilityProxy is UpgradeabilityProxy {
        /**
         * @dev Emitted when the administration has been transferred.
         * @param previousAdmin Address of the previous admin.
         * @param newAdmin Address of the new admin.
         */
        event AdminChanged(address previousAdmin, address newAdmin);
      
        /**
         * @dev Storage slot with the admin of the contract.
         * This is the keccak-256 hash of "org.zeppelinos.proxy.admin", and is
         * validated in the constructor.
         */
        bytes32 private constant ADMIN_SLOT = 0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b;
      
        /**
         * @dev Modifier to check whether the `msg.sender` is the admin.
         * If it is, it will run the function. Otherwise, it will delegate the call
         * to the implementation.
         */
        modifier ifAdmin() {
          if (msg.sender == _admin()) {
            _;
          } else {
            _fallback();
          }
        }
      
        /**
         * Contract constructor.
         * It sets the `msg.sender` as the proxy administrator.
         * @param _implementation address of the initial implementation.
         */
        constructor(address _implementation) UpgradeabilityProxy(_implementation) public {
          assert(ADMIN_SLOT == keccak256("org.zeppelinos.proxy.admin"));
      
          _setAdmin(msg.sender);
        }
      
        /**
         * @return The address of the proxy admin.
         */
        function admin() external view ifAdmin returns (address) {
          return _admin();
        }
      
        /**
         * @return The address of the implementation.
         */
        function implementation() external view ifAdmin returns (address) {
          return _implementation();
        }
      
        /**
         * @dev Changes the admin of the proxy.
         * Only the current admin can call this function.
         * @param newAdmin Address to transfer proxy administration to.
         */
        function changeAdmin(address newAdmin) external ifAdmin {
          require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
          emit AdminChanged(_admin(), newAdmin);
          _setAdmin(newAdmin);
        }
      
        /**
         * @dev Upgrade the backing implementation of the proxy.
         * Only the admin can call this function.
         * @param newImplementation Address of the new implementation.
         */
        function upgradeTo(address newImplementation) external ifAdmin {
          _upgradeTo(newImplementation);
        }
      
        /**
         * @dev Upgrade the backing implementation of the proxy and call a function
         * on the new implementation.
         * This is useful to initialize the proxied contract.
         * @param newImplementation Address of the new implementation.
         * @param data Data to send as msg.data in the low level call.
         * It should include the signature and the parameters of the function to be
         * called, as described in
         * https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.
         */
        function upgradeToAndCall(address newImplementation, bytes data) payable external ifAdmin {
          _upgradeTo(newImplementation);
          require(address(this).call.value(msg.value)(data));
        }
      
        /**
         * @return The admin slot.
         */
        function _admin() internal view returns (address adm) {
          bytes32 slot = ADMIN_SLOT;
          assembly {
            adm := sload(slot)
          }
        }
      
        /**
         * @dev Sets the address of the proxy admin.
         * @param newAdmin Address of the new proxy admin.
         */
        function _setAdmin(address newAdmin) internal {
          bytes32 slot = ADMIN_SLOT;
      
          assembly {
            sstore(slot, newAdmin)
          }
        }
      
        /**
         * @dev Only fall back when the sender is not the admin.
         */
        function _willFallback() internal {
          require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
          super._willFallback();
        }
      }
      
      // File: contracts/FiatTokenProxy.sol
      
      /**
      * Copyright CENTRE SECZ 2018
      *
      * Permission is hereby granted, free of charge, to any person obtaining a copy 
      * of this software and associated documentation files (the "Software"), to deal 
      * in the Software without restriction, including without limitation the rights 
      * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
      * copies of the Software, and to permit persons to whom the Software is furnished to 
      * do so, subject to the following conditions:
      *
      * The above copyright notice and this permission notice shall be included in all 
      * copies or substantial portions of the Software.
      *
      * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
      * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
      * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
      * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
      * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
      * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
      */
      
      pragma solidity ^0.4.24;
      
      
      /**
       * @title FiatTokenProxy
       * @dev This contract proxies FiatToken calls and enables FiatToken upgrades
      */ 
      contract FiatTokenProxy is AdminUpgradeabilityProxy {
          constructor(address _implementation) public AdminUpgradeabilityProxy(_implementation) {
          }
      }

      File 3 of 4: VotingEscrow
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.4;
      /**
      @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` (3 years).
      # 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 (3 years?)
      */
      import "@openzeppelin/contracts/access/Ownable.sol";
      import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
      import "@openzeppelin/contracts/interfaces/IERC20.sol";
      import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
      struct Point {
          int128 bias;
          int128 slope; // # -dweight / dt
          uint ts;
          uint blk; // 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 {
          int128 amount;
          uint end;
      }
      contract VotingEscrow is Ownable, ReentrancyGuard {
          using SafeERC20 for IERC20;
          enum DepositType {
              DEPOSIT_FOR_TYPE,
              CREATE_LOCK_TYPE,
              INCREASE_LOCK_AMOUNT,
              INCREASE_UNLOCK_TIME
          }
          event Deposit(address indexed provider, uint value, uint indexed locktime, DepositType deposit_type, uint ts);
          event Withdraw(address indexed provider, uint value, uint ts);
          event Supply(uint prevSupply, uint supply);
          uint internal constant WEEK = 1 weeks;
          uint public constant MAXTIME = 3 * 365 * 86400;
          int128 internal constant iMAXTIME = 3 * 365 * 86400;
          uint internal constant MULTIPLIER = 1 ether;
          uint public immutable MINTIME;
          address public immutable token;
          uint public supply;
          bool public unlocked;
          mapping(address => LockedBalance) public locked;
          uint public epoch;
          mapping(uint => Point) public point_history; // epoch -> unsigned point
          mapping(address => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
          mapping(address => uint) public user_point_epoch;
          mapping(uint => int128) public slope_changes; // time -> signed slope change
          // Aragon's view methods for compatibility
          address public controller;
          bool public transfersEnabled;
          string public constant name = "veSTG";
          string public constant symbol = "veSTG";
          string public constant version = "1.0.0";
          uint8 public constant decimals = 18;
          // Whitelisted (smart contract) wallets which are allowed to deposit
          // The goal is to prevent tokenizing the escrow
          mapping(address => bool) public contracts_whitelist;
          /// @notice Contract constructor
          /// @param token_addr `ERC20CRV` token address
          constructor(address token_addr, uint min_time) {
              token = token_addr;
              point_history[0].blk = block.number;
              point_history[0].ts = block.timestamp;
              controller = msg.sender;
              transfersEnabled = true;
              MINTIME = min_time;
          }
          modifier onlyUserOrWhitelist() {
              if (msg.sender != tx.origin) {
                  require(contracts_whitelist[msg.sender], "Smart contract not allowed");
              }
              _;
          }
          modifier notUnlocked() {
              require(!unlocked, "unlocked globally");
              _;
          }
          /// @notice Add address to whitelist smart contract depositors `addr`
          /// @param addr Address to be whitelisted
          function add_to_whitelist(address addr) external onlyOwner {
              contracts_whitelist[addr] = true;
          }
          /// @notice Remove a smart contract address from whitelist
          /// @param addr Address to be removed from whitelist
          function remove_from_whitelist(address addr) external onlyOwner {
              contracts_whitelist[addr] = false;
          }
          /// @notice Unlock all locked balances
          function unlock() external onlyOwner {
              unlocked = true;
          }
          /// @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
          function get_last_user_slope(address addr) external view returns (int128) {
              uint uepoch = user_point_epoch[addr];
              return user_point_history[addr][uepoch].slope;
          }
          /// @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
          function user_point_history__ts(address _addr, uint _idx) external view returns (uint) {
              return user_point_history[_addr][_idx].ts;
          }
          /// @notice Get timestamp when `_addr`'s lock finishes
          /// @param _addr User wallet address
          /// @return Epoch time of the lock end
          function locked__end(address _addr) external view returns (uint) {
              return locked[_addr].end;
          }
          /// @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
          function _checkpoint(address _addr, LockedBalance memory old_locked, LockedBalance memory new_locked) internal {
              Point memory u_old;
              Point memory u_new;
              int128 old_dslope = 0;
              int128 new_dslope = 0;
              uint _epoch = epoch;
              if (_addr != address(0x0)) {
                  // Calculate slopes and biases
                  // Kept at zero when they have to
                  if (old_locked.end > block.timestamp && old_locked.amount > 0) {
                      u_old.slope = old_locked.amount / iMAXTIME;
                      u_old.bias = u_old.slope * int128(int(old_locked.end - block.timestamp));
                  }
                  if (new_locked.end > block.timestamp && new_locked.amount > 0) {
                      u_new.slope = new_locked.amount / iMAXTIME;
                      u_new.bias = u_new.slope * int128(int(new_locked.end - block.timestamp));
                  }
                  // 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 = slope_changes[old_locked.end];
                  if (new_locked.end != 0) {
                      if (new_locked.end == old_locked.end) {
                          new_dslope = old_dslope;
                      } else {
                          new_dslope = slope_changes[new_locked.end];
                      }
                  }
              }
              Point memory last_point = Point({bias: 0, slope: 0, ts: block.timestamp, blk: block.number});
              if (_epoch > 0) {
                  last_point = point_history[_epoch];
              }
              uint last_checkpoint = 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
              uint initial_last_point_ts = last_point.ts;
              uint initial_last_point_blk = last_point.blk;
              uint block_slope = 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
              uint t_i = (last_checkpoint / WEEK) * WEEK;
              for (uint i = 0; i < 255; ++i) {
                  // 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;
                  int128 d_slope = 0;
                  if (t_i > block.timestamp) {
                      t_i = block.timestamp;
                  } else {
                      d_slope = slope_changes[t_i];
                  }
                  last_point.bias -= last_point.slope * int128(int(t_i - last_checkpoint));
                  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 {
                      point_history[_epoch] = last_point;
                  }
              }
              epoch = _epoch;
              // Now point_history is filled until t=now
              if (_addr != address(0x0)) {
                  // 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
              point_history[_epoch] = last_point;
              if (_addr != address(0x0)) {
                  // 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
                      }
                      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
                          slope_changes[new_locked.end] = new_dslope;
                      }
                      // else: we recorded it already in old_dslope
                  }
                  // Now handle user history
                  address addr = _addr;
                  uint user_epoch = user_point_epoch[addr] + 1;
                  user_point_epoch[addr] = user_epoch;
                  u_new.ts = block.timestamp;
                  u_new.blk = block.number;
                  user_point_history[addr][user_epoch] = u_new;
              }
          }
          /// @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
          /// @param deposit_type The type of deposit
          function _deposit_for(address _addr, uint _value, uint unlock_time, LockedBalance memory locked_balance, DepositType deposit_type) internal {
              LockedBalance memory _locked = locked_balance;
              uint supply_before = supply;
              supply = supply_before + _value;
              LockedBalance memory old_locked;
              (old_locked.amount, old_locked.end) = (_locked.amount, _locked.end);
              // Adding to existing lock, or if a lock is expired - creating a new one
              _locked.amount += int128(int(_value));
              if (unlock_time != 0) {
                  _locked.end = unlock_time;
              }
              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)
              _checkpoint(_addr, old_locked, _locked);
              if (_value != 0) {
                  IERC20(token).safeTransferFrom(_addr, address(this), _value);
              }
              emit Deposit(_addr, _value, _locked.end, deposit_type, block.timestamp);
              emit Supply(supply_before, supply_before + _value);
          }
          /// @notice Record global data to checkpoint
          function checkpoint() external notUnlocked {
              _checkpoint(address(0x0), LockedBalance(0, 0), LockedBalance(0, 0));
          }
          /// @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
          function deposit_for(address _addr, uint _value) external nonReentrant notUnlocked {
              LockedBalance memory _locked = locked[_addr];
              require(_value > 0); // dev: need non-zero value
              require(_locked.amount > 0, "No existing lock found");
              require(_locked.end > block.timestamp, "Cannot add to expired lock. Withdraw");
              _deposit_for(_addr, _value, 0, _locked, DepositType.DEPOSIT_FOR_TYPE);
          }
          /// @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
          function _create_lock(uint _value, uint _unlock_time) internal {
              require(_value > 0); // dev: need non-zero value
              LockedBalance memory _locked = locked[msg.sender];
              require(_locked.amount == 0, "Withdraw old tokens first");
              uint unlock_time = (_unlock_time / WEEK) * WEEK; // Locktime is rounded down to weeks
              require(unlock_time >= block.timestamp + MINTIME, "Voting lock must be at least MINTIME");
              require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 3 years max");
              _deposit_for(msg.sender, _value, unlock_time, _locked, DepositType.CREATE_LOCK_TYPE);
          }
          /// @notice External function for _create_lock
          /// @param _value Amount to deposit
          /// @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks
          function create_lock(uint _value, uint _unlock_time) external nonReentrant onlyUserOrWhitelist notUnlocked {
              _create_lock(_value, _unlock_time);
          }
          /// @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
          function increase_amount(uint _value) external nonReentrant onlyUserOrWhitelist notUnlocked {
              _increase_amount(_value);
          }
          function _increase_amount(uint _value) internal {
              LockedBalance memory _locked = locked[msg.sender];
              require(_value > 0); // dev: need non-zero value
              require(_locked.amount > 0, "No existing lock found");
              require(_locked.end > block.timestamp, "Cannot add to expired lock. Withdraw");
              _deposit_for(msg.sender, _value, 0, _locked, DepositType.INCREASE_LOCK_AMOUNT);
          }
          /// @notice Extend the unlock time for `msg.sender` to `_unlock_time`
          /// @param _unlock_time New epoch time for unlocking
          function increase_unlock_time(uint _unlock_time) external nonReentrant onlyUserOrWhitelist notUnlocked {
              _increase_unlock_time(_unlock_time);
          }
          function _increase_unlock_time(uint _unlock_time) internal {
              LockedBalance memory _locked = locked[msg.sender];
              uint unlock_time = (_unlock_time / WEEK) * WEEK; // Locktime is rounded down to weeks
              require(_locked.end > block.timestamp, "Lock expired");
              require(_locked.amount > 0, "Nothing is locked");
              require(unlock_time > _locked.end, "Can only increase lock duration");
              require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 3 years max");
              _deposit_for(msg.sender, 0, unlock_time, _locked, DepositType.INCREASE_UNLOCK_TIME);
          }
          /// @notice Extend the unlock time and/or for `msg.sender` to `_unlock_time`
          /// @param _unlock_time New epoch time for unlocking
          function increase_amount_and_time(uint _value, uint _unlock_time) external nonReentrant onlyUserOrWhitelist notUnlocked {
              require(_value > 0 || _unlock_time > 0, "Value and Unlock cannot both be 0");
              if (_value > 0 && _unlock_time > 0) {
                  _increase_amount(_value);
                  _increase_unlock_time(_unlock_time);
              } else if (_value > 0 && _unlock_time == 0) {
                  _increase_amount(_value);
              } else {
                  _increase_unlock_time(_unlock_time);
              }
          }
          /// @notice Withdraw all tokens for `msg.sender`
          /// @dev Only possible if the lock has expired
          function _withdraw() internal {
              LockedBalance memory _locked = locked[msg.sender];
              uint value = uint(int(_locked.amount));
              if (!unlocked) {
                  require(block.timestamp >= _locked.end, "The lock didn't expire");
              }
              locked[msg.sender] = LockedBalance(0, 0);
              uint supply_before = supply;
              supply = supply_before - value;
              // old_locked can have either expired <= timestamp or zero end
              // _locked has only 0 end
              // Both can have >= 0 amount
              _checkpoint(msg.sender, _locked, LockedBalance(0, 0));
              IERC20(token).safeTransfer(msg.sender, value);
              emit Withdraw(msg.sender, value, block.timestamp);
              emit Supply(supply_before, supply_before - value);
          }
          function withdraw() external nonReentrant {
              _withdraw();
          }
          /// @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
          function withdraw_and_create_lock(uint _value, uint _unlock_time) external nonReentrant onlyUserOrWhitelist notUnlocked {
              _withdraw();
              _create_lock(_value, _unlock_time);
          }
          // 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.
          /// @notice Binary search to estimate timestamp for block number
          /// @param _block Block to find
          /// @param max_epoch Don't go beyond this epoch
          /// @return Approximate timestamp for block
          function _find_block_epoch(uint _block, uint max_epoch) internal view returns (uint) {
              // Binary search
              uint _min = 0;
              uint _max = max_epoch;
              for (uint i = 0; i < 128; ++i) {
                  // Will be always enough for 128-bit numbers
                  if (_min >= _max) {
                      break;
                  }
                  uint _mid = (_min + _max + 1) / 2;
                  if (point_history[_mid].blk <= _block) {
                      _min = _mid;
                  } else {
                      _max = _mid - 1;
                  }
              }
              return _min;
          }
          /// @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
          function _balanceOf(address addr, uint _t) internal view returns (uint) {
              uint _epoch = user_point_epoch[addr];
              if (_epoch == 0) {
                  return 0;
              } else {
                  Point memory last_point = user_point_history[addr][_epoch];
                  last_point.bias -= last_point.slope * int128(int(_t) - int(last_point.ts));
                  if (last_point.bias < 0) {
                      last_point.bias = 0;
                  }
                  return uint(int(last_point.bias));
              }
          }
          function balanceOfAtT(address addr, uint _t) external view returns (uint) {
              return _balanceOf(addr, _t);
          }
          function balanceOf(address addr) external view returns (uint) {
              return _balanceOf(addr, block.timestamp);
          }
          /// @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
          function balanceOfAt(address addr, uint _block) external view returns (uint) {
              // Copying and pasting totalSupply code because Vyper cannot pass by
              // reference yet
              require(_block <= block.number);
              // Binary search
              uint _min = 0;
              uint _max = user_point_epoch[addr];
              for (uint i = 0; i < 128; ++i) {
                  // Will be always enough for 128-bit numbers
                  if (_min >= _max) {
                      break;
                  }
                  uint _mid = (_min + _max + 1) / 2;
                  if (user_point_history[addr][_mid].blk <= _block) {
                      _min = _mid;
                  } else {
                      _max = _mid - 1;
                  }
              }
              Point memory upoint = user_point_history[addr][_min];
              uint max_epoch = epoch;
              uint _epoch = _find_block_epoch(_block, max_epoch);
              Point memory point_0 = point_history[_epoch];
              uint d_block = 0;
              uint d_t = 0;
              if (_epoch < max_epoch) {
                  Point memory point_1 = 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;
              }
              uint block_time = point_0.ts;
              if (d_block != 0) {
                  block_time += (d_t * (_block - point_0.blk)) / d_block;
              }
              upoint.bias -= upoint.slope * int128(int(block_time - upoint.ts));
              if (upoint.bias >= 0) {
                  return uint(uint128(upoint.bias));
              } else {
                  return 0;
              }
          }
          /// @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
          function _supply_at(Point memory point, uint t) internal view returns (uint) {
              Point memory last_point = point;
              uint t_i = (last_point.ts / WEEK) * WEEK;
              for (uint i = 0; i < 255; ++i) {
                  t_i += WEEK;
                  int128 d_slope = 0;
                  if (t_i > t) {
                      t_i = t;
                  } else {
                      d_slope = slope_changes[t_i];
                  }
                  last_point.bias -= last_point.slope * int128(int(t_i - last_point.ts));
                  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 uint(uint128(last_point.bias));
          }
          /// @notice Calculate total voting power
          /// @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility
          /// @return Total voting power
          function _totalSupply(uint t) internal view returns (uint) {
              uint _epoch = epoch;
              Point memory last_point = point_history[_epoch];
              return _supply_at(last_point, t);
          }
          function totalSupplyAtT(uint t) external view returns (uint) {
              return _totalSupply(t);
          }
          function totalSupply() external view returns (uint) {
              return _totalSupply(block.timestamp);
          }
          /// @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`
          function totalSupplyAt(uint _block) external view returns (uint) {
              require(_block <= block.number);
              uint _epoch = epoch;
              uint target_epoch = _find_block_epoch(_block, _epoch);
              Point memory point = point_history[target_epoch];
              uint dt = 0;
              if (target_epoch < _epoch) {
                  Point memory point_next = 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 _supply_at(point, point.ts + dt);
          }
          // Dummy methods for compatibility with Aragon
          function changeController(address _newController) external {
              require(msg.sender == controller);
              controller = _newController;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
      pragma solidity ^0.8.0;
      import "../utils/Context.sol";
      /**
       * @dev Contract module which provides a basic access control mechanism, where
       * there is an account (an owner) that can be granted exclusive access to
       * specific functions.
       *
       * By default, the owner account will be the one that deploys the contract. This
       * can later be changed with {transferOwnership}.
       *
       * This module is used through inheritance. It will make available the modifier
       * `onlyOwner`, which can be applied to your functions to restrict their use to
       * the owner.
       */
      abstract contract Ownable is Context {
          address private _owner;
          event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
          /**
           * @dev Initializes the contract setting the deployer as the initial owner.
           */
          constructor() {
              _transferOwnership(_msgSender());
          }
          /**
           * @dev Returns the address of the current owner.
           */
          function owner() public view virtual returns (address) {
              return _owner;
          }
          /**
           * @dev Throws if called by any account other than the owner.
           */
          modifier onlyOwner() {
              require(owner() == _msgSender(), "Ownable: caller is not the owner");
              _;
          }
          /**
           * @dev Leaves the contract without owner. It will not be possible to call
           * `onlyOwner` functions anymore. Can only be called by the current owner.
           *
           * NOTE: Renouncing ownership will leave the contract without an owner,
           * thereby removing any functionality that is only available to the owner.
           */
          function renounceOwnership() public virtual onlyOwner {
              _transferOwnership(address(0));
          }
          /**
           * @dev Transfers ownership of the contract to a new account (`newOwner`).
           * Can only be called by the current owner.
           */
          function transferOwnership(address newOwner) public virtual onlyOwner {
              require(newOwner != address(0), "Ownable: new owner is the zero address");
              _transferOwnership(newOwner);
          }
          /**
           * @dev Transfers ownership of the contract to a new account (`newOwner`).
           * Internal function without access restriction.
           */
          function _transferOwnership(address newOwner) internal virtual {
              address oldOwner = _owner;
              _owner = newOwner;
              emit OwnershipTransferred(oldOwner, newOwner);
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (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() {
              // On the first call to nonReentrant, _notEntered will be true
              require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
              // Any calls to nonReentrant after this point will fail
              _status = _ENTERED;
              _;
              // 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
      // OpenZeppelin Contracts v4.4.1 (interfaces/IERC20.sol)
      pragma solidity ^0.8.0;
      import "../token/ERC20/IERC20.sol";
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
      pragma solidity ^0.8.0;
      import "../IERC20.sol";
      import "../../../utils/Address.sol";
      /**
       * @title SafeERC20
       * @dev Wrappers around ERC20 operations that throw on failure (when the token
       * contract returns false). Tokens that return no value (and instead revert or
       * throw on failure) are also supported, non-reverting calls are assumed to be
       * successful.
       * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
       * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
       */
      library SafeERC20 {
          using Address for address;
          function safeTransfer(
              IERC20 token,
              address to,
              uint256 value
          ) internal {
              _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
          }
          function safeTransferFrom(
              IERC20 token,
              address from,
              address to,
              uint256 value
          ) internal {
              _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
          }
          /**
           * @dev Deprecated. This function has issues similar to the ones found in
           * {IERC20-approve}, and its usage is discouraged.
           *
           * Whenever possible, use {safeIncreaseAllowance} and
           * {safeDecreaseAllowance} instead.
           */
          function safeApprove(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              // safeApprove should only be called when setting an initial allowance,
              // or when resetting it to zero. To increase and decrease it, use
              // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
              require(
                  (value == 0) || (token.allowance(address(this), spender) == 0),
                  "SafeERC20: approve from non-zero to non-zero allowance"
              );
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
          }
          function safeIncreaseAllowance(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              uint256 newAllowance = token.allowance(address(this), spender) + value;
              _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
          }
          function safeDecreaseAllowance(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              unchecked {
                  uint256 oldAllowance = token.allowance(address(this), spender);
                  require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                  uint256 newAllowance = oldAllowance - value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
          }
          /**
           * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
           * on the return value: the return value is optional (but if data is returned, it must not be false).
           * @param token The token targeted by the call.
           * @param data The call data (encoded using abi.encode or one of its variants).
           */
          function _callOptionalReturn(IERC20 token, bytes memory data) private {
              // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
              // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
              // the target address contains contract code and also asserts for success in the low-level call.
              bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
              if (returndata.length > 0) {
                  // Return data is optional
                  require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
              }
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Provides information about the current execution context, including the
       * sender of the transaction and its data. While these are generally available
       * via msg.sender and msg.data, they should not be accessed in such a direct
       * manner, since when dealing with meta-transactions the account sending and
       * paying for execution may not be the actual sender (as far as an application
       * is concerned).
       *
       * This contract is only required for intermediate, library-like contracts.
       */
      abstract contract Context {
          function _msgSender() internal view virtual returns (address) {
              return msg.sender;
          }
          function _msgData() internal view virtual returns (bytes calldata) {
              return msg.data;
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
          /**
           * @dev Moves `amount` tokens from the caller's account to `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);
          /**
           * @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: MIT
      // OpenZeppelin Contracts (last updated v4.5.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
           * ====
           *
           * [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://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(address(this).balance >= amount, "Address: insufficient balance");
              (bool success, ) = recipient.call{value: amount}("");
              require(success, "Address: unable to send value, recipient may have reverted");
          }
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain `call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionCall(target, data, "Address: low-level call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              return functionCallWithValue(target, data, 0, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value
          ) internal returns (bytes memory) {
              return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
          }
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value,
              string memory errorMessage
          ) internal returns (bytes memory) {
              require(address(this).balance >= value, "Address: insufficient balance for call");
              require(isContract(target), "Address: call to non-contract");
              (bool success, bytes memory returndata) = target.call{value: value}(data);
              return verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
              return functionStaticCall(target, data, "Address: low-level static call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a static call.
           *
           * _Available since v3.3._
           */
          function functionStaticCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal view returns (bytes memory) {
              require(isContract(target), "Address: static call to non-contract");
              (bool success, bytes memory returndata) = target.staticcall(data);
              return verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
              return functionDelegateCall(target, data, "Address: low-level delegate call failed");
          }
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
           * but performing a delegate call.
           *
           * _Available since v3.4._
           */
          function functionDelegateCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              require(isContract(target), "Address: delegate call to non-contract");
              (bool success, bytes memory returndata) = target.delegatecall(data);
              return verifyCallResult(success, returndata, errorMessage);
          }
          /**
           * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
           * revert reason using the provided one.
           *
           * _Available since v4.3._
           */
          function verifyCallResult(
              bool success,
              bytes memory returndata,
              string memory errorMessage
          ) internal pure returns (bytes memory) {
              if (success) {
                  return returndata;
              } else {
                  // Look for revert reason and bubble it up if present
                  if (returndata.length > 0) {
                      // The easiest way to bubble the revert reason is using memory via assembly
                      assembly {
                          let returndata_size := mload(returndata)
                          revert(add(32, returndata), returndata_size)
                      }
                  } else {
                      revert(errorMessage);
                  }
              }
          }
      }
      

      File 4 of 4: FiatTokenV2_1
      // File: @openzeppelin/contracts/math/SafeMath.sol
      
      // SPDX-License-Identifier: MIT
      
      pragma solidity ^0.6.0;
      
      /**
       * @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, "SafeMath: addition 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, "SafeMath: subtraction 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,
              string memory errorMessage
          ) internal pure returns (uint256) {
              require(b <= a, errorMessage);
              uint256 c = a - b;
      
              return c;
          }
      
          /**
           * @dev Returns the multiplication of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `*` operator.
           *
           * Requirements:
           *
           * - Multiplication cannot overflow.
           */
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
              // benefit is lost if 'b' is also tested.
              // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              return div(a, b, "SafeMath: division by zero");
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function div(
              uint256 a,
              uint256 b,
              string memory errorMessage
          ) internal pure returns (uint256) {
              require(b > 0, errorMessage);
              uint256 c = a / b;
              // assert(a == b * c + a % b); // There is no case in which this doesn't hold
      
              return c;
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function mod(uint256 a, uint256 b) internal pure returns (uint256) {
              return mod(a, b, "SafeMath: modulo by zero");
          }
      
          /**
           * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
           * Reverts with custom message when dividing by zero.
           *
           * Counterpart to Solidity's `%` operator. This function uses a `revert`
           * opcode (which leaves remaining gas untouched) while Solidity uses an
           * invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           *
           * - The divisor cannot be zero.
           */
          function mod(
              uint256 a,
              uint256 b,
              string memory errorMessage
          ) internal pure returns (uint256) {
              require(b != 0, errorMessage);
              return a % b;
          }
      }
      
      // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
      
      pragma solidity ^0.6.0;
      
      /**
       * @dev Interface of the ERC20 standard as defined in the EIP.
       */
      interface IERC20 {
          /**
           * @dev Returns the amount of tokens in existence.
           */
          function totalSupply() external view returns (uint256);
      
          /**
           * @dev Returns the amount of tokens owned by `account`.
           */
          function balanceOf(address account) external view returns (uint256);
      
          /**
           * @dev Moves `amount` tokens from the caller's account to `recipient`.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transfer(address recipient, uint256 amount)
              external
              returns (bool);
      
          /**
           * @dev Returns the remaining number of tokens that `spender` will be
           * allowed to spend on behalf of `owner` through {transferFrom}. This is
           * zero by default.
           *
           * This value changes when {approve} or {transferFrom} are called.
           */
          function allowance(address owner, address spender)
              external
              view
              returns (uint256);
      
          /**
           * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * IMPORTANT: Beware that changing an allowance with this method brings the risk
           * that someone may use both the old and the new allowance by unfortunate
           * transaction ordering. One possible solution to mitigate this race
           * condition is to first reduce the spender's allowance to 0 and set the
           * desired value afterwards:
           * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
           *
           * Emits an {Approval} event.
           */
          function approve(address spender, uint256 amount) external returns (bool);
      
          /**
           * @dev Moves `amount` tokens from `sender` to `recipient` using the
           * allowance mechanism. `amount` is then deducted from the caller's
           * allowance.
           *
           * Returns a boolean value indicating whether the operation succeeded.
           *
           * Emits a {Transfer} event.
           */
          function transferFrom(
              address sender,
              address recipient,
              uint256 amount
          ) external returns (bool);
      
          /**
           * @dev Emitted when `value` tokens are moved from one account (`from`) to
           * another (`to`).
           *
           * Note that `value` may be zero.
           */
          event Transfer(address indexed from, address indexed to, uint256 value);
      
          /**
           * @dev Emitted when the allowance of a `spender` for an `owner` is set by
           * a call to {approve}. `value` is the new allowance.
           */
          event Approval(
              address indexed owner,
              address indexed spender,
              uint256 value
          );
      }
      
      // File: contracts/v1/AbstractFiatTokenV1.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      abstract contract AbstractFiatTokenV1 is IERC20 {
          function _approve(
              address owner,
              address spender,
              uint256 value
          ) internal virtual;
      
          function _transfer(
              address from,
              address to,
              uint256 value
          ) internal virtual;
      }
      
      // File: contracts/v1/Ownable.sol
      
      /**
       * Copyright (c) 2018 zOS Global Limited.
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      pragma solidity 0.6.12;
      
      /**
       * @notice The Ownable contract has an owner address, and provides basic
       * authorization control functions
       * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-labs/blob/3887ab77b8adafba4a26ace002f3a684c1a3388b/upgradeability_ownership/contracts/ownership/Ownable.sol
       * Modifications:
       * 1. Consolidate OwnableStorage into this contract (7/13/18)
       * 2. Reformat, conform to Solidity 0.6 syntax, and add error messages (5/13/20)
       * 3. Make public functions external (5/27/20)
       */
      contract Ownable {
          // Owner of the contract
          address private _owner;
      
          /**
           * @dev Event to show ownership has been transferred
           * @param previousOwner representing the address of the previous owner
           * @param newOwner representing the address of the new owner
           */
          event OwnershipTransferred(address previousOwner, address newOwner);
      
          /**
           * @dev The constructor sets the original owner of the contract to the sender account.
           */
          constructor() public {
              setOwner(msg.sender);
          }
      
          /**
           * @dev Tells the address of the owner
           * @return the address of the owner
           */
          function owner() external view returns (address) {
              return _owner;
          }
      
          /**
           * @dev Sets a new owner address
           */
          function setOwner(address newOwner) internal {
              _owner = newOwner;
          }
      
          /**
           * @dev Throws if called by any account other than the owner.
           */
          modifier onlyOwner() {
              require(msg.sender == _owner, "Ownable: caller is not the owner");
              _;
          }
      
          /**
           * @dev Allows the current owner to transfer control of the contract to a newOwner.
           * @param newOwner The address to transfer ownership to.
           */
          function transferOwnership(address newOwner) external onlyOwner {
              require(
                  newOwner != address(0),
                  "Ownable: new owner is the zero address"
              );
              emit OwnershipTransferred(_owner, newOwner);
              setOwner(newOwner);
          }
      }
      
      // File: contracts/v1/Pausable.sol
      
      /**
       * Copyright (c) 2016 Smart Contract Solutions, Inc.
       * Copyright (c) 2018-2020 CENTRE SECZ0
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @notice Base contract which allows children to implement an emergency stop
       * mechanism
       * @dev Forked from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/feb665136c0dae9912e08397c1a21c4af3651ef3/contracts/lifecycle/Pausable.sol
       * Modifications:
       * 1. Added pauser role, switched pause/unpause to be onlyPauser (6/14/2018)
       * 2. Removed whenNotPause/whenPaused from pause/unpause (6/14/2018)
       * 3. Removed whenPaused (6/14/2018)
       * 4. Switches ownable library to use ZeppelinOS (7/12/18)
       * 5. Remove constructor (7/13/18)
       * 6. Reformat, conform to Solidity 0.6 syntax and add error messages (5/13/20)
       * 7. Make public functions external (5/27/20)
       */
      contract Pausable is Ownable {
          event Pause();
          event Unpause();
          event PauserChanged(address indexed newAddress);
      
          address public pauser;
          bool public paused = false;
      
          /**
           * @dev Modifier to make a function callable only when the contract is not paused.
           */
          modifier whenNotPaused() {
              require(!paused, "Pausable: paused");
              _;
          }
      
          /**
           * @dev throws if called by any account other than the pauser
           */
          modifier onlyPauser() {
              require(msg.sender == pauser, "Pausable: caller is not the pauser");
              _;
          }
      
          /**
           * @dev called by the owner to pause, triggers stopped state
           */
          function pause() external onlyPauser {
              paused = true;
              emit Pause();
          }
      
          /**
           * @dev called by the owner to unpause, returns to normal state
           */
          function unpause() external onlyPauser {
              paused = false;
              emit Unpause();
          }
      
          /**
           * @dev update the pauser role
           */
          function updatePauser(address _newPauser) external onlyOwner {
              require(
                  _newPauser != address(0),
                  "Pausable: new pauser is the zero address"
              );
              pauser = _newPauser;
              emit PauserChanged(pauser);
          }
      }
      
      // File: contracts/v1/Blacklistable.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title Blacklistable Token
       * @dev Allows accounts to be blacklisted by a "blacklister" role
       */
      contract Blacklistable is Ownable {
          address public blacklister;
          mapping(address => bool) internal blacklisted;
      
          event Blacklisted(address indexed _account);
          event UnBlacklisted(address indexed _account);
          event BlacklisterChanged(address indexed newBlacklister);
      
          /**
           * @dev Throws if called by any account other than the blacklister
           */
          modifier onlyBlacklister() {
              require(
                  msg.sender == blacklister,
                  "Blacklistable: caller is not the blacklister"
              );
              _;
          }
      
          /**
           * @dev Throws if argument account is blacklisted
           * @param _account The address to check
           */
          modifier notBlacklisted(address _account) {
              require(
                  !blacklisted[_account],
                  "Blacklistable: account is blacklisted"
              );
              _;
          }
      
          /**
           * @dev Checks if account is blacklisted
           * @param _account The address to check
           */
          function isBlacklisted(address _account) external view returns (bool) {
              return blacklisted[_account];
          }
      
          /**
           * @dev Adds account to blacklist
           * @param _account The address to blacklist
           */
          function blacklist(address _account) external onlyBlacklister {
              blacklisted[_account] = true;
              emit Blacklisted(_account);
          }
      
          /**
           * @dev Removes account from blacklist
           * @param _account The address to remove from the blacklist
           */
          function unBlacklist(address _account) external onlyBlacklister {
              blacklisted[_account] = false;
              emit UnBlacklisted(_account);
          }
      
          function updateBlacklister(address _newBlacklister) external onlyOwner {
              require(
                  _newBlacklister != address(0),
                  "Blacklistable: new blacklister is the zero address"
              );
              blacklister = _newBlacklister;
              emit BlacklisterChanged(blacklister);
          }
      }
      
      // File: contracts/v1/FiatTokenV1.sol
      
      /**
       *
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title FiatToken
       * @dev ERC20 Token backed by fiat reserves
       */
      contract FiatTokenV1 is AbstractFiatTokenV1, Ownable, Pausable, Blacklistable {
          using SafeMath for uint256;
      
          string public name;
          string public symbol;
          uint8 public decimals;
          string public currency;
          address public masterMinter;
          bool internal initialized;
      
          mapping(address => uint256) internal balances;
          mapping(address => mapping(address => uint256)) internal allowed;
          uint256 internal totalSupply_ = 0;
          mapping(address => bool) internal minters;
          mapping(address => uint256) internal minterAllowed;
      
          event Mint(address indexed minter, address indexed to, uint256 amount);
          event Burn(address indexed burner, uint256 amount);
          event MinterConfigured(address indexed minter, uint256 minterAllowedAmount);
          event MinterRemoved(address indexed oldMinter);
          event MasterMinterChanged(address indexed newMasterMinter);
      
          function initialize(
              string memory tokenName,
              string memory tokenSymbol,
              string memory tokenCurrency,
              uint8 tokenDecimals,
              address newMasterMinter,
              address newPauser,
              address newBlacklister,
              address newOwner
          ) public {
              require(!initialized, "FiatToken: contract is already initialized");
              require(
                  newMasterMinter != address(0),
                  "FiatToken: new masterMinter is the zero address"
              );
              require(
                  newPauser != address(0),
                  "FiatToken: new pauser is the zero address"
              );
              require(
                  newBlacklister != address(0),
                  "FiatToken: new blacklister is the zero address"
              );
              require(
                  newOwner != address(0),
                  "FiatToken: new owner is the zero address"
              );
      
              name = tokenName;
              symbol = tokenSymbol;
              currency = tokenCurrency;
              decimals = tokenDecimals;
              masterMinter = newMasterMinter;
              pauser = newPauser;
              blacklister = newBlacklister;
              setOwner(newOwner);
              initialized = true;
          }
      
          /**
           * @dev Throws if called by any account other than a minter
           */
          modifier onlyMinters() {
              require(minters[msg.sender], "FiatToken: caller is not a minter");
              _;
          }
      
          /**
           * @dev Function to mint tokens
           * @param _to The address that will receive the minted tokens.
           * @param _amount The amount of tokens to mint. Must be less than or equal
           * to the minterAllowance of the caller.
           * @return A boolean that indicates if the operation was successful.
           */
          function mint(address _to, uint256 _amount)
              external
              whenNotPaused
              onlyMinters
              notBlacklisted(msg.sender)
              notBlacklisted(_to)
              returns (bool)
          {
              require(_to != address(0), "FiatToken: mint to the zero address");
              require(_amount > 0, "FiatToken: mint amount not greater than 0");
      
              uint256 mintingAllowedAmount = minterAllowed[msg.sender];
              require(
                  _amount <= mintingAllowedAmount,
                  "FiatToken: mint amount exceeds minterAllowance"
              );
      
              totalSupply_ = totalSupply_.add(_amount);
              balances[_to] = balances[_to].add(_amount);
              minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
              emit Mint(msg.sender, _to, _amount);
              emit Transfer(address(0), _to, _amount);
              return true;
          }
      
          /**
           * @dev Throws if called by any account other than the masterMinter
           */
          modifier onlyMasterMinter() {
              require(
                  msg.sender == masterMinter,
                  "FiatToken: caller is not the masterMinter"
              );
              _;
          }
      
          /**
           * @dev Get minter allowance for an account
           * @param minter The address of the minter
           */
          function minterAllowance(address minter) external view returns (uint256) {
              return minterAllowed[minter];
          }
      
          /**
           * @dev Checks if account is a minter
           * @param account The address to check
           */
          function isMinter(address account) external view returns (bool) {
              return minters[account];
          }
      
          /**
           * @notice Amount of remaining tokens spender is allowed to transfer on
           * behalf of the token owner
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @return Allowance amount
           */
          function allowance(address owner, address spender)
              external
              override
              view
              returns (uint256)
          {
              return allowed[owner][spender];
          }
      
          /**
           * @dev Get totalSupply of token
           */
          function totalSupply() external override view returns (uint256) {
              return totalSupply_;
          }
      
          /**
           * @dev Get token balance of an account
           * @param account address The account
           */
          function balanceOf(address account)
              external
              override
              view
              returns (uint256)
          {
              return balances[account];
          }
      
          /**
           * @notice Set spender's allowance over the caller's tokens to be a given
           * value.
           * @param spender   Spender's address
           * @param value     Allowance amount
           * @return True if successful
           */
          function approve(address spender, uint256 value)
              external
              override
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(spender)
              returns (bool)
          {
              _approve(msg.sender, spender, value);
              return true;
          }
      
          /**
           * @dev Internal function to set allowance
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @param value     Allowance amount
           */
          function _approve(
              address owner,
              address spender,
              uint256 value
          ) internal override {
              require(owner != address(0), "ERC20: approve from the zero address");
              require(spender != address(0), "ERC20: approve to the zero address");
              allowed[owner][spender] = value;
              emit Approval(owner, spender, value);
          }
      
          /**
           * @notice Transfer tokens by spending allowance
           * @param from  Payer's address
           * @param to    Payee's address
           * @param value Transfer amount
           * @return True if successful
           */
          function transferFrom(
              address from,
              address to,
              uint256 value
          )
              external
              override
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(from)
              notBlacklisted(to)
              returns (bool)
          {
              require(
                  value <= allowed[from][msg.sender],
                  "ERC20: transfer amount exceeds allowance"
              );
              _transfer(from, to, value);
              allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
              return true;
          }
      
          /**
           * @notice Transfer tokens from the caller
           * @param to    Payee's address
           * @param value Transfer amount
           * @return True if successful
           */
          function transfer(address to, uint256 value)
              external
              override
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(to)
              returns (bool)
          {
              _transfer(msg.sender, to, value);
              return true;
          }
      
          /**
           * @notice Internal function to process transfers
           * @param from  Payer's address
           * @param to    Payee's address
           * @param value Transfer amount
           */
          function _transfer(
              address from,
              address to,
              uint256 value
          ) internal override {
              require(from != address(0), "ERC20: transfer from the zero address");
              require(to != address(0), "ERC20: transfer to the zero address");
              require(
                  value <= balances[from],
                  "ERC20: transfer amount exceeds balance"
              );
      
              balances[from] = balances[from].sub(value);
              balances[to] = balances[to].add(value);
              emit Transfer(from, to, value);
          }
      
          /**
           * @dev Function to add/update a new minter
           * @param minter The address of the minter
           * @param minterAllowedAmount The minting amount allowed for the minter
           * @return True if the operation was successful.
           */
          function configureMinter(address minter, uint256 minterAllowedAmount)
              external
              whenNotPaused
              onlyMasterMinter
              returns (bool)
          {
              minters[minter] = true;
              minterAllowed[minter] = minterAllowedAmount;
              emit MinterConfigured(minter, minterAllowedAmount);
              return true;
          }
      
          /**
           * @dev Function to remove a minter
           * @param minter The address of the minter to remove
           * @return True if the operation was successful.
           */
          function removeMinter(address minter)
              external
              onlyMasterMinter
              returns (bool)
          {
              minters[minter] = false;
              minterAllowed[minter] = 0;
              emit MinterRemoved(minter);
              return true;
          }
      
          /**
           * @dev allows a minter to burn some of its own tokens
           * Validates that caller is a minter and that sender is not blacklisted
           * amount is less than or equal to the minter's account balance
           * @param _amount uint256 the amount of tokens to be burned
           */
          function burn(uint256 _amount)
              external
              whenNotPaused
              onlyMinters
              notBlacklisted(msg.sender)
          {
              uint256 balance = balances[msg.sender];
              require(_amount > 0, "FiatToken: burn amount not greater than 0");
              require(balance >= _amount, "FiatToken: burn amount exceeds balance");
      
              totalSupply_ = totalSupply_.sub(_amount);
              balances[msg.sender] = balance.sub(_amount);
              emit Burn(msg.sender, _amount);
              emit Transfer(msg.sender, address(0), _amount);
          }
      
          function updateMasterMinter(address _newMasterMinter) external onlyOwner {
              require(
                  _newMasterMinter != address(0),
                  "FiatToken: new masterMinter is the zero address"
              );
              masterMinter = _newMasterMinter;
              emit MasterMinterChanged(masterMinter);
          }
      }
      
      // File: @openzeppelin/contracts/utils/Address.sol
      
      pragma solidity ^0.6.2;
      
      /**
       * @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) {
              // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
              // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
              // for accounts without code, i.e. `keccak256('')`
              bytes32 codehash;
      
                  bytes32 accountHash
               = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
              // solhint-disable-next-line no-inline-assembly
              assembly {
                  codehash := extcodehash(account)
              }
              return (codehash != accountHash && codehash != 0x0);
          }
      
          /**
           * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
           * `recipient`, forwarding all available gas and reverting on errors.
           *
           * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
           * of certain opcodes, possibly making contracts go over the 2300 gas limit
           * imposed by `transfer`, making them unable to receive funds via
           * `transfer`. {sendValue} removes this limitation.
           *
           * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
           *
           * IMPORTANT: because control is transferred to `recipient`, care must be
           * taken to not create reentrancy vulnerabilities. Consider using
           * {ReentrancyGuard} or the
           * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
           */
          function sendValue(address payable recipient, uint256 amount) internal {
              require(
                  address(this).balance >= amount,
                  "Address: insufficient balance"
              );
      
              // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
              (bool success, ) = recipient.call{ value: amount }("");
              require(
                  success,
                  "Address: unable to send value, recipient may have reverted"
              );
          }
      
          /**
           * @dev Performs a Solidity function call using a low level `call`. A
           * plain`call` is an unsafe replacement for a function call: use this
           * function instead.
           *
           * If `target` reverts with a revert reason, it is bubbled up by this
           * function (like regular Solidity function calls).
           *
           * Returns the raw returned data. To convert to the expected return value,
           * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
           *
           * Requirements:
           *
           * - `target` must be a contract.
           * - calling `target` with `data` must not revert.
           *
           * _Available since v3.1._
           */
          function functionCall(address target, bytes memory data)
              internal
              returns (bytes memory)
          {
              return functionCall(target, data, "Address: low-level call failed");
          }
      
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
           * `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCall(
              address target,
              bytes memory data,
              string memory errorMessage
          ) internal returns (bytes memory) {
              return _functionCallWithValue(target, data, 0, errorMessage);
          }
      
          /**
           * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
           * but also transferring `value` wei to `target`.
           *
           * Requirements:
           *
           * - the calling contract must have an ETH balance of at least `value`.
           * - the called Solidity function must be `payable`.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value
          ) internal returns (bytes memory) {
              return
                  functionCallWithValue(
                      target,
                      data,
                      value,
                      "Address: low-level call with value failed"
                  );
          }
      
          /**
           * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
           * with `errorMessage` as a fallback revert reason when `target` reverts.
           *
           * _Available since v3.1._
           */
          function functionCallWithValue(
              address target,
              bytes memory data,
              uint256 value,
              string memory errorMessage
          ) internal returns (bytes memory) {
              require(
                  address(this).balance >= value,
                  "Address: insufficient balance for call"
              );
              return _functionCallWithValue(target, data, value, errorMessage);
          }
      
          function _functionCallWithValue(
              address target,
              bytes memory data,
              uint256 weiValue,
              string memory errorMessage
          ) private returns (bytes memory) {
              require(isContract(target), "Address: call to non-contract");
      
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = target.call{
                  value: weiValue
              }(data);
              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(errorMessage);
                  }
              }
          }
      }
      
      // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
      
      pragma solidity ^0.6.0;
      
      /**
       * @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 SafeMath for uint256;
          using Address for address;
      
          function safeTransfer(
              IERC20 token,
              address to,
              uint256 value
          ) internal {
              _callOptionalReturn(
                  token,
                  abi.encodeWithSelector(token.transfer.selector, to, value)
              );
          }
      
          function safeTransferFrom(
              IERC20 token,
              address from,
              address to,
              uint256 value
          ) internal {
              _callOptionalReturn(
                  token,
                  abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
              );
          }
      
          /**
           * @dev Deprecated. This function has issues similar to the ones found in
           * {IERC20-approve}, and its usage is discouraged.
           *
           * Whenever possible, use {safeIncreaseAllowance} and
           * {safeDecreaseAllowance} instead.
           */
          function safeApprove(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              // safeApprove should only be called when setting an initial allowance,
              // or when resetting it to zero. To increase and decrease it, use
              // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
              // solhint-disable-next-line max-line-length
              require(
                  (value == 0) || (token.allowance(address(this), spender) == 0),
                  "SafeERC20: approve from non-zero to non-zero allowance"
              );
              _callOptionalReturn(
                  token,
                  abi.encodeWithSelector(token.approve.selector, spender, value)
              );
          }
      
          function safeIncreaseAllowance(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              uint256 newAllowance = token.allowance(address(this), spender).add(
                  value
              );
              _callOptionalReturn(
                  token,
                  abi.encodeWithSelector(
                      token.approve.selector,
                      spender,
                      newAllowance
                  )
              );
          }
      
          function safeDecreaseAllowance(
              IERC20 token,
              address spender,
              uint256 value
          ) internal {
              uint256 newAllowance = token.allowance(address(this), spender).sub(
                  value,
                  "SafeERC20: decreased allowance below zero"
              );
              _callOptionalReturn(
                  token,
                  abi.encodeWithSelector(
                      token.approve.selector,
                      spender,
                      newAllowance
                  )
              );
          }
      
          /**
           * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
           * on the return value: the return value is optional (but if data is returned, it must not be false).
           * @param token The token targeted by the call.
           * @param data The call data (encoded using abi.encode or one of its variants).
           */
          function _callOptionalReturn(IERC20 token, bytes memory data) private {
              // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
              // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
              // the target address contains contract code and also asserts for success in the low-level call.
      
              bytes memory returndata = address(token).functionCall(
                  data,
                  "SafeERC20: low-level call failed"
              );
              if (returndata.length > 0) {
                  // Return data is optional
                  // solhint-disable-next-line max-line-length
                  require(
                      abi.decode(returndata, (bool)),
                      "SafeERC20: ERC20 operation did not succeed"
                  );
              }
          }
      }
      
      // File: contracts/v1.1/Rescuable.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      contract Rescuable is Ownable {
          using SafeERC20 for IERC20;
      
          address private _rescuer;
      
          event RescuerChanged(address indexed newRescuer);
      
          /**
           * @notice Returns current rescuer
           * @return Rescuer's address
           */
          function rescuer() external view returns (address) {
              return _rescuer;
          }
      
          /**
           * @notice Revert if called by any account other than the rescuer.
           */
          modifier onlyRescuer() {
              require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
              _;
          }
      
          /**
           * @notice Rescue ERC20 tokens locked up in this contract.
           * @param tokenContract ERC20 token contract address
           * @param to        Recipient address
           * @param amount    Amount to withdraw
           */
          function rescueERC20(
              IERC20 tokenContract,
              address to,
              uint256 amount
          ) external onlyRescuer {
              tokenContract.safeTransfer(to, amount);
          }
      
          /**
           * @notice Assign the rescuer role to a given address.
           * @param newRescuer New rescuer's address
           */
          function updateRescuer(address newRescuer) external onlyOwner {
              require(
                  newRescuer != address(0),
                  "Rescuable: new rescuer is the zero address"
              );
              _rescuer = newRescuer;
              emit RescuerChanged(newRescuer);
          }
      }
      
      // File: contracts/v1.1/FiatTokenV1_1.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title FiatTokenV1_1
       * @dev ERC20 Token backed by fiat reserves
       */
      contract FiatTokenV1_1 is FiatTokenV1, Rescuable {
      
      }
      
      // File: contracts/v2/AbstractFiatTokenV2.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      abstract contract AbstractFiatTokenV2 is AbstractFiatTokenV1 {
          function _increaseAllowance(
              address owner,
              address spender,
              uint256 increment
          ) internal virtual;
      
          function _decreaseAllowance(
              address owner,
              address spender,
              uint256 decrement
          ) internal virtual;
      }
      
      // File: contracts/util/ECRecover.sol
      
      /**
       * Copyright (c) 2016-2019 zOS Global Limited
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title ECRecover
       * @notice A library that provides a safe ECDSA recovery function
       */
      library ECRecover {
          /**
           * @notice Recover signer's address from a signed message
           * @dev Adapted from: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/65e4ffde586ec89af3b7e9140bdc9235d1254853/contracts/cryptography/ECDSA.sol
           * Modifications: Accept v, r, and s as separate arguments
           * @param digest    Keccak-256 hash digest of the signed message
           * @param v         v of the signature
           * @param r         r of the signature
           * @param s         s of the signature
           * @return Signer address
           */
          function recover(
              bytes32 digest,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal pure returns (address) {
              // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
              // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
              // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
              // signatures from current libraries generate a unique signature with an s-value in the lower half order.
              //
              // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
              // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
              // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
              // these malleable signatures as well.
              if (
                  uint256(s) >
                  0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
              ) {
                  revert("ECRecover: invalid signature 's' value");
              }
      
              if (v != 27 && v != 28) {
                  revert("ECRecover: invalid signature 'v' value");
              }
      
              // If the signature is valid (and not malleable), return the signer address
              address signer = ecrecover(digest, v, r, s);
              require(signer != address(0), "ECRecover: invalid signature");
      
              return signer;
          }
      }
      
      // File: contracts/util/EIP712.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP712
       * @notice A library that provides EIP712 helper functions
       */
      library EIP712 {
          /**
           * @notice Make EIP712 domain separator
           * @param name      Contract name
           * @param version   Contract version
           * @return Domain separator
           */
          function makeDomainSeparator(string memory name, string memory version)
              internal
              view
              returns (bytes32)
          {
              uint256 chainId;
              assembly {
                  chainId := chainid()
              }
              return
                  keccak256(
                      abi.encode(
                          // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
                          0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                          keccak256(bytes(name)),
                          keccak256(bytes(version)),
                          chainId,
                          address(this)
                      )
                  );
          }
      
          /**
           * @notice Recover signer's address from a EIP712 signature
           * @param domainSeparator   Domain separator
           * @param v                 v of the signature
           * @param r                 r of the signature
           * @param s                 s of the signature
           * @param typeHashAndData   Type hash concatenated with data
           * @return Signer's address
           */
          function recover(
              bytes32 domainSeparator,
              uint8 v,
              bytes32 r,
              bytes32 s,
              bytes memory typeHashAndData
          ) internal pure returns (address) {
              bytes32 digest = keccak256(
                  abi.encodePacked(
                      "\x19\x01",
                      domainSeparator,
                      keccak256(typeHashAndData)
                  )
              );
              return ECRecover.recover(digest, v, r, s);
          }
      }
      
      // File: contracts/v2/EIP712Domain.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP712 Domain
       */
      contract EIP712Domain {
          /**
           * @dev EIP712 Domain Separator
           */
          bytes32 public DOMAIN_SEPARATOR;
      }
      
      // File: contracts/v2/EIP3009.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP-3009
       * @notice Provide internal implementation for gas-abstracted transfers
       * @dev Contracts that inherit from this must wrap these with publicly
       * accessible functions, optionally adding modifiers where necessary
       */
      abstract contract EIP3009 is AbstractFiatTokenV2, EIP712Domain {
          // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
          bytes32
              public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267;
      
          // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)")
          bytes32
              public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8;
      
          // keccak256("CancelAuthorization(address authorizer,bytes32 nonce)")
          bytes32
              public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429;
      
          /**
           * @dev authorizer address => nonce => bool (true if nonce is used)
           */
          mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
      
          event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
          event AuthorizationCanceled(
              address indexed authorizer,
              bytes32 indexed nonce
          );
      
          /**
           * @notice Returns the state of an authorization
           * @dev Nonces are randomly generated 32-byte data unique to the
           * authorizer's address
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @return True if the nonce is used
           */
          function authorizationState(address authorizer, bytes32 nonce)
              external
              view
              returns (bool)
          {
              return _authorizationStates[authorizer][nonce];
          }
      
          /**
           * @notice Execute a transfer with a signed authorization
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function _transferWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              _requireValidAuthorization(from, nonce, validAfter, validBefore);
      
              bytes memory data = abi.encode(
                  TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
                  "FiatTokenV2: invalid signature"
              );
      
              _markAuthorizationAsUsed(from, nonce);
              _transfer(from, to, value);
          }
      
          /**
           * @notice Receive a transfer with a signed authorization from the payer
           * @dev This has an additional check to ensure that the payee's address
           * matches the caller of this function to prevent front-running attacks.
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function _receiveWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              require(to == msg.sender, "FiatTokenV2: caller must be the payee");
              _requireValidAuthorization(from, nonce, validAfter, validBefore);
      
              bytes memory data = abi.encode(
                  RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from,
                  "FiatTokenV2: invalid signature"
              );
      
              _markAuthorizationAsUsed(from, nonce);
              _transfer(from, to, value);
          }
      
          /**
           * @notice Attempt to cancel an authorization
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function _cancelAuthorization(
              address authorizer,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              _requireUnusedAuthorization(authorizer, nonce);
      
              bytes memory data = abi.encode(
                  CANCEL_AUTHORIZATION_TYPEHASH,
                  authorizer,
                  nonce
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == authorizer,
                  "FiatTokenV2: invalid signature"
              );
      
              _authorizationStates[authorizer][nonce] = true;
              emit AuthorizationCanceled(authorizer, nonce);
          }
      
          /**
           * @notice Check that an authorization is unused
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           */
          function _requireUnusedAuthorization(address authorizer, bytes32 nonce)
              private
              view
          {
              require(
                  !_authorizationStates[authorizer][nonce],
                  "FiatTokenV2: authorization is used or canceled"
              );
          }
      
          /**
           * @notice Check that authorization is valid
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           */
          function _requireValidAuthorization(
              address authorizer,
              bytes32 nonce,
              uint256 validAfter,
              uint256 validBefore
          ) private view {
              require(
                  now > validAfter,
                  "FiatTokenV2: authorization is not yet valid"
              );
              require(now < validBefore, "FiatTokenV2: authorization is expired");
              _requireUnusedAuthorization(authorizer, nonce);
          }
      
          /**
           * @notice Mark an authorization as used
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           */
          function _markAuthorizationAsUsed(address authorizer, bytes32 nonce)
              private
          {
              _authorizationStates[authorizer][nonce] = true;
              emit AuthorizationUsed(authorizer, nonce);
          }
      }
      
      // File: contracts/v2/EIP2612.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title EIP-2612
       * @notice Provide internal implementation for gas-abstracted approvals
       */
      abstract contract EIP2612 is AbstractFiatTokenV2, EIP712Domain {
          // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
          bytes32
              public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
      
          mapping(address => uint256) private _permitNonces;
      
          /**
           * @notice Nonces for permit
           * @param owner Token owner's address (Authorizer)
           * @return Next nonce
           */
          function nonces(address owner) external view returns (uint256) {
              return _permitNonces[owner];
          }
      
          /**
           * @notice Verify a signed approval permit and execute if valid
           * @param owner     Token owner's address (Authorizer)
           * @param spender   Spender's address
           * @param value     Amount of allowance
           * @param deadline  The time at which this expires (unix time)
           * @param v         v of the signature
           * @param r         r of the signature
           * @param s         s of the signature
           */
          function _permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) internal {
              require(deadline >= now, "FiatTokenV2: permit is expired");
      
              bytes memory data = abi.encode(
                  PERMIT_TYPEHASH,
                  owner,
                  spender,
                  value,
                  _permitNonces[owner]++,
                  deadline
              );
              require(
                  EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == owner,
                  "EIP2612: invalid signature"
              );
      
              _approve(owner, spender, value);
          }
      }
      
      // File: contracts/v2/FiatTokenV2.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      /**
       * @title FiatToken V2
       * @notice ERC20 Token backed by fiat reserves, version 2
       */
      contract FiatTokenV2 is FiatTokenV1_1, EIP3009, EIP2612 {
          uint8 internal _initializedVersion;
      
          /**
           * @notice Initialize v2
           * @param newName   New token name
           */
          function initializeV2(string calldata newName) external {
              // solhint-disable-next-line reason-string
              require(initialized && _initializedVersion == 0);
              name = newName;
              DOMAIN_SEPARATOR = EIP712.makeDomainSeparator(newName, "2");
              _initializedVersion = 1;
          }
      
          /**
           * @notice Increase the allowance by a given increment
           * @param spender   Spender's address
           * @param increment Amount of increase in allowance
           * @return True if successful
           */
          function increaseAllowance(address spender, uint256 increment)
              external
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(spender)
              returns (bool)
          {
              _increaseAllowance(msg.sender, spender, increment);
              return true;
          }
      
          /**
           * @notice Decrease the allowance by a given decrement
           * @param spender   Spender's address
           * @param decrement Amount of decrease in allowance
           * @return True if successful
           */
          function decreaseAllowance(address spender, uint256 decrement)
              external
              whenNotPaused
              notBlacklisted(msg.sender)
              notBlacklisted(spender)
              returns (bool)
          {
              _decreaseAllowance(msg.sender, spender, decrement);
              return true;
          }
      
          /**
           * @notice Execute a transfer with a signed authorization
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function transferWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
              _transferWithAuthorization(
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce,
                  v,
                  r,
                  s
              );
          }
      
          /**
           * @notice Receive a transfer with a signed authorization from the payer
           * @dev This has an additional check to ensure that the payee's address
           * matches the caller of this function to prevent front-running attacks.
           * @param from          Payer's address (Authorizer)
           * @param to            Payee's address
           * @param value         Amount to be transferred
           * @param validAfter    The time after which this is valid (unix time)
           * @param validBefore   The time before which this is valid (unix time)
           * @param nonce         Unique nonce
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function receiveWithAuthorization(
              address from,
              address to,
              uint256 value,
              uint256 validAfter,
              uint256 validBefore,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused notBlacklisted(from) notBlacklisted(to) {
              _receiveWithAuthorization(
                  from,
                  to,
                  value,
                  validAfter,
                  validBefore,
                  nonce,
                  v,
                  r,
                  s
              );
          }
      
          /**
           * @notice Attempt to cancel an authorization
           * @dev Works only if the authorization is not yet used.
           * @param authorizer    Authorizer's address
           * @param nonce         Nonce of the authorization
           * @param v             v of the signature
           * @param r             r of the signature
           * @param s             s of the signature
           */
          function cancelAuthorization(
              address authorizer,
              bytes32 nonce,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused {
              _cancelAuthorization(authorizer, nonce, v, r, s);
          }
      
          /**
           * @notice Update allowance with a signed permit
           * @param owner       Token owner's address (Authorizer)
           * @param spender     Spender's address
           * @param value       Amount of allowance
           * @param deadline    Expiration time, seconds since the epoch
           * @param v           v of the signature
           * @param r           r of the signature
           * @param s           s of the signature
           */
          function permit(
              address owner,
              address spender,
              uint256 value,
              uint256 deadline,
              uint8 v,
              bytes32 r,
              bytes32 s
          ) external whenNotPaused notBlacklisted(owner) notBlacklisted(spender) {
              _permit(owner, spender, value, deadline, v, r, s);
          }
      
          /**
           * @notice Internal function to increase the allowance by a given increment
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @param increment Amount of increase
           */
          function _increaseAllowance(
              address owner,
              address spender,
              uint256 increment
          ) internal override {
              _approve(owner, spender, allowed[owner][spender].add(increment));
          }
      
          /**
           * @notice Internal function to decrease the allowance by a given decrement
           * @param owner     Token owner's address
           * @param spender   Spender's address
           * @param decrement Amount of decrease
           */
          function _decreaseAllowance(
              address owner,
              address spender,
              uint256 decrement
          ) internal override {
              _approve(
                  owner,
                  spender,
                  allowed[owner][spender].sub(
                      decrement,
                      "ERC20: decreased allowance below zero"
                  )
              );
          }
      }
      
      // File: contracts/v2/FiatTokenV2_1.sol
      
      /**
       * Copyright (c) 2018-2020 CENTRE SECZ
       *
       * Permission is hereby granted, free of charge, to any person obtaining a copy
       * of this software and associated documentation files (the "Software"), to deal
       * in the Software without restriction, including without limitation the rights
       * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       * copies of the Software, and to permit persons to whom the Software is
       * furnished to do so, subject to the following conditions:
       *
       * The above copyright notice and this permission notice shall be included in
       * copies or substantial portions of the Software.
       *
       * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       * SOFTWARE.
       */
      
      pragma solidity 0.6.12;
      
      // solhint-disable func-name-mixedcase
      
      /**
       * @title FiatToken V2.1
       * @notice ERC20 Token backed by fiat reserves, version 2.1
       */
      contract FiatTokenV2_1 is FiatTokenV2 {
          /**
           * @notice Initialize v2.1
           * @param lostAndFound  The address to which the locked funds are sent
           */
          function initializeV2_1(address lostAndFound) external {
              // solhint-disable-next-line reason-string
              require(_initializedVersion == 1);
      
              uint256 lockedAmount = balances[address(this)];
              if (lockedAmount > 0) {
                  _transfer(address(this), lostAndFound, lockedAmount);
              }
              blacklisted[address(this)] = true;
      
              _initializedVersion = 2;
          }
      
          /**
           * @notice Version string for the EIP712 domain separator
           * @return Version string
           */
          function version() external view returns (string memory) {
              return "2";
          }
      }