ETH Price: $3,064.22 (-1.68%)
Gas: 2 Gwei

Transaction Decoder

Block:
17637451 at Jul-06-2023 09:23:35 PM +UTC
Transaction Fee:
0.006188803404637858 ETH $18.96
Gas Used:
210,362 Gas / 29.419778309 Gwei

Emitted Events:

244 FiatTokenProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000044529a37a43bab8af2336698e31f2e4585ad7db6, 0x000000000000000000000000cab7106222c1c34ef335ae3d41e58b4623ba1634, 0000000000000000000000000000000000000000000000000000000003e9e372 )
245 Reward.LogClaimReward( tokenId=201, reward=65659762 )

Account State Difference:

  Address   Before After State Difference Code
0x44529A37...585ad7db6
(Multichain: veMULTI Reward)
(builder0x69)
1.082670604175854917 Eth1.082691640375854917 Eth0.0000210362
0xA0b86991...E3606eB48
0xCaB71062...623BA1634
0.291743511989091211 Eth
Nonce: 183
0.285554708584453353 Eth
Nonce: 184
0.006188803404637858

Execution Trace

Reward.claimReward( tokenId=201, intervals= ) => ( reward=65659762 )
  • ve.ownerOf( _tokenId=201 ) => ( 0xCaB7106222C1C34ef335aE3d41E58b4623BA1634 )
  • ve.balanceOfAtNFT( _tokenId=201, _block=17581846 ) => ( 7519990529473608152500 )
  • ve.balanceOfAtNFT( _tokenId=201, _block=17631694 ) => ( 7487478930051165536875 )
  • ve.ownerOf( _tokenId=201 ) => ( 0xCaB7106222C1C34ef335aE3d41E58b4623BA1634 )
  • FiatTokenProxy.a9059cbb( )
    • FiatTokenV2_1.transfer( to=0xCaB7106222C1C34ef335aE3d41E58b4623BA1634, value=65659762 ) => ( True )
      claimReward[Reward (ln:290)]
      File 1 of 4: Reward
      // SPDX-License-Identifier: MIT
      pragma solidity 0.8.11;
      
      interface IERC20 {
          function transfer(address recipient, uint256 amount) external returns (bool);
          function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
      }
      
      library Address {
          function isContract(address account) internal view returns (bool) {
              bytes32 codehash;
              bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
              // solhint-disable-next-line no-inline-assembly
              assembly { codehash := extcodehash(account) }
              return (codehash != 0x0 && codehash != accountHash);
          }
      }
      
      library SafeERC20 {
          using Address for address;
      
          function safeTransfer(IERC20 token, address to, uint value) internal {
              callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
          }
      
          function safeTransferFrom(IERC20 token, address from, address to, uint value) internal {
              callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
          }
      
          function callOptionalReturn(IERC20 token, bytes memory data) private {
              require(address(token).isContract(), "SafeERC20: call to non-contract");
      
              // solhint-disable-next-line avoid-low-level-calls
              (bool success, bytes memory returndata) = address(token).call(data);
              require(success, "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");
              }
          }
      }
      
      interface ve {
          function balanceOfAtNFT(uint _tokenId, uint _block) external view returns (uint);
          function balanceOfNFTAt(uint _tokenId, uint _t) external view returns (uint);
          function totalSupplyAt(uint _block) external view returns (uint);
          function totalSupplyAtT(uint t) external view returns (uint);
          function ownerOf(uint) external view returns (address);
          function create_lock(uint _value, uint _lock_duration) external returns (uint);
      }
      
      contract Reward {
          using SafeERC20 for IERC20;
      
          struct EpochInfo {
              uint startTime;
              uint endTime;
              uint rewardPerSecond; // totalReward * RewardMultiplier / (endBlock - startBlock)
              uint totalPower;
              uint startBlock;
          }
      
          /// @dev Ve nft
          address public immutable _ve;
          /// @dev reward erc20 token, USDT
          address public immutable rewardToken;
          /// @dev RewardMultiplier
          uint immutable RewardMultiplier = 10000000;
          /// @dev BlockMultiplier
          uint immutable BlockMultiplier = 1000000000000000000;
      
          /// @dev reward epochs.
          EpochInfo[] public epochInfo;
      
          /// @dev user's last claim time.
          mapping(uint => mapping(uint => uint)) public userLastClaimTime; // tokenId -> epoch id -> last claim timestamp\
      
          address public admin;
          address public pendingAdmin;
      
          modifier onlyAdmin() {
              require(msg.sender == admin);
              _;
          }
      
          event LogClaimReward(uint tokenId, uint reward);
          event LogAddEpoch(uint epochId, EpochInfo epochInfo);
          event LogAddEpoch(uint startTime, uint epochLength, uint epochCount, uint startEpochId);
          event LogTransferAdmin(address pendingAdmin);
          event LogAcceptAdmin(address admin);
      
          constructor (
              address _ve_,
              address rewardToken_
          ) {
              admin = msg.sender;
              _ve = _ve_;
              rewardToken = rewardToken_;
              // add init point
              addCheckpoint();
          }
          
          struct Point {
              uint256 ts;
              uint256 blk; // block
          }
      
          /// @dev list of checkpoints, used in getBlockByTime
          Point[] public point_history;
         
          /// @notice add checkpoint to point_history
          /// called in constructor, addEpoch, addEpochBatch and claimReward
          /// point_history increments without repetition, length always >= 1
          function addCheckpoint() internal {
              point_history.push(Point(block.timestamp, block.number));
          }
          
          /// @notice estimate last block number before given time
          /// @return blockNumber
          function getBlockByTime(uint _time) public view returns (uint) {
              // Binary search
              uint _min = 0;
              uint _max = point_history.length - 1; // asserting length >= 2
              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].ts <= _time) {
                      _min = _mid;
                  } else {
                      _max = _mid - 1;
                  }
              }
      
              Point memory point0 = point_history[_min];
              Point memory point1 = point_history[_min + 1];
              if (_time == point0.ts) {
                  return point0.blk;
              }
              // asserting point0.blk < point1.blk, point0.ts < point1.ts
              uint block_slope; // dblock/dt
              block_slope = (BlockMultiplier * (point1.blk - point0.blk)) / (point1.ts - point0.ts);
              uint dblock = (block_slope * (_time - point0.ts)) / BlockMultiplier;
              return point0.blk + dblock;
          }
      
          function withdrawFee(uint amount) external onlyAdmin {
              IERC20(rewardToken).safeTransfer(admin, amount);
          }
      
          function transferAdmin(address _admin) external onlyAdmin {
              pendingAdmin = _admin;
              emit LogTransferAdmin(pendingAdmin);
          }
      
          function acceptAdmin() external {
              require(msg.sender == pendingAdmin);
              admin = pendingAdmin;
              pendingAdmin = address(0);
              emit LogAcceptAdmin(admin);
          }
      
          /// @notice add one epoch
          /// @return epochId
          /// @return accurateTotalReward
          function addEpoch(uint startTime, uint endTime, uint totalReward) external onlyAdmin returns(uint, uint) {
              assert(block.timestamp < endTime && startTime < endTime);
              if (epochInfo.length > 0) {
                  require(epochInfo[epochInfo.length - 1].endTime <= startTime);
              }
              (uint epochId, uint accurateTotalReward) = _addEpoch(startTime, endTime, totalReward);
              uint lastPointTime = point_history[point_history.length - 1].ts;
              if (lastPointTime < block.timestamp) {
                  addCheckpoint();
              }
              emit LogAddEpoch(epochId, epochInfo[epochId]);
              return (epochId, accurateTotalReward);
          }
      
          /// @notice add a batch of continuous epochs
          /// @return firstEpochId
          /// @return lastEpochId
          /// @return accurateTotalReward
          function addEpochBatch(uint startTime, uint epochLength, uint epochCount, uint totalReward) external onlyAdmin returns(uint, uint, uint) {
              require(block.timestamp < startTime + epochLength);
              if (epochInfo.length > 0) {
                  require(epochInfo[epochInfo.length - 1].endTime <= startTime);
              }
              uint _reward = totalReward / epochCount;
              uint _epochId;
              uint accurateTR;
              uint _start = startTime;
              uint _end = _start + epochLength;
              for (uint i = 0; i < epochCount; i++) {
                  (_epochId, accurateTR) = _addEpoch(_start, _end, _reward);
                  _start = _end;
                  _end = _start + epochLength;
              }
              uint lastPointTime = point_history[point_history.length - 1].ts;
              if (lastPointTime < block.timestamp) {
                  addCheckpoint();
              }
              emit LogAddEpoch(startTime, epochLength, epochCount, _epochId + 1 - epochCount);
              return (_epochId + 1 - epochCount, _epochId, accurateTR * epochCount);
          }
      
          /// @notice add one epoch
          /// @return epochId
          /// @return accurateTotalReward
          function _addEpoch(uint startTime, uint endTime, uint totalReward) internal returns(uint, uint) {
              uint rewardPerSecond = totalReward * RewardMultiplier / (endTime - startTime);
              uint epochId = epochInfo.length;
              epochInfo.push(EpochInfo(startTime, endTime, rewardPerSecond, 1, 1));
              uint accurateTotalReward = (endTime - startTime) * rewardPerSecond / RewardMultiplier;
              return (epochId, accurateTotalReward);
          }
      
          /// @notice set epoch reward
          function updateEpochReward(uint epochId, uint totalReward) external onlyAdmin {
              require(block.timestamp < epochInfo[epochId].startTime);
              epochInfo[epochId].rewardPerSecond = totalReward * RewardMultiplier / (epochInfo[epochId].endTime - epochInfo[epochId].startTime);
          }
      
          /// @notice query pending reward by epoch
          /// @return pendingReward
          /// @return finished
          /// panic when block.timestamp < epoch.startTime
          function _pendingRewardSingle(uint tokenId, uint lastClaimTime, EpochInfo memory epoch) internal view returns (uint, bool) {
              uint last = lastClaimTime >= epoch.startTime ? lastClaimTime : epoch.startTime;
              if (last >= epoch.endTime) {
                  return (0, true);
              }
              if (epoch.totalPower == 0) {
                  return (0, true);
              }
              
              uint end = block.timestamp;
              bool finished = false;
              if (end > epoch.endTime) {
                  end = epoch.endTime;
                  finished = true;
              }
      
              uint power = ve(_ve).balanceOfAtNFT(tokenId, epoch.startBlock);
              
              uint reward = epoch.rewardPerSecond * (end - last) * power / (epoch.totalPower * RewardMultiplier);
              return (reward, finished);
          }
      
          function checkpointAndCheckEpoch(uint epochId) public {
              uint lastPointTime = point_history[point_history.length - 1].ts;
              if (lastPointTime < block.timestamp) {
                  addCheckpoint();
              }
              checkEpoch(epochId);
          }
      
          function checkEpoch(uint epochId) internal {
              if (epochInfo[epochId].startBlock == 1) {
                  epochInfo[epochId].startBlock = getBlockByTime(epochInfo[epochId].startTime);
              }
              if (epochInfo[epochId].totalPower == 1) {
                  epochInfo[epochId].totalPower = ve(_ve).totalSupplyAt(epochInfo[epochId].startBlock);
              }
          }
      
          struct Interval {
              uint startEpoch;
              uint endEpoch;
          }
      
          struct IntervalReward {
              uint startEpoch;
              uint endEpoch;
              uint reward;
          }
      
          function claimRewardMany(uint[] calldata tokenIds, Interval[][] calldata intervals) public returns (uint[] memory rewards) {
              require(tokenIds.length == intervals.length, "length not equal");
              rewards = new uint[] (tokenIds.length);
              for (uint i = 0; i < tokenIds.length; i++) {
                  rewards[i] = claimReward(tokenIds[i], intervals[i]);
              }
              return rewards;
          }
      
          function claimReward(uint tokenId, Interval[] calldata intervals) public returns (uint reward) {
              for (uint i = 0; i < intervals.length; i++) {
                  reward += claimReward(tokenId, intervals[i].startEpoch, intervals[i].endEpoch);
              }
              return reward;
          }
      
          /// @notice claim reward in range
          function claimReward(uint tokenId, uint startEpoch, uint endEpoch) public returns (uint reward) {
              require(msg.sender == ve(_ve).ownerOf(tokenId));
              require(endEpoch < epochInfo.length, "claim out of range");
              EpochInfo memory epoch;
              uint lastPointTime = point_history[point_history.length - 1].ts;
              for (uint i = startEpoch; i <= endEpoch; i++) {
                  epoch = epochInfo[i];
                  if (block.timestamp < epoch.startTime) {
                      break;
                  }
                  if (lastPointTime < epoch.startTime) {
                      // this branch runs 0 or 1 time
                      lastPointTime = block.timestamp;
                      addCheckpoint();
                  }
                  checkEpoch(i);
                  (uint reward_i, bool finished) = _pendingRewardSingle(tokenId, userLastClaimTime[tokenId][i], epochInfo[i]);
                  if (reward_i > 0) {
                      reward += reward_i;
                      userLastClaimTime[tokenId][i] = block.timestamp;
                  }
                  if (!finished) {
                      break;
                  }
              }
              IERC20(rewardToken).safeTransfer(ve(_ve).ownerOf(tokenId), reward);
              emit LogClaimReward(tokenId, reward);
              return reward;
          }
      
          /// @notice get epoch by time
          function getEpochIdByTime(uint _time) view public returns (uint) {
              assert(epochInfo[0].startTime <= _time);
              if (_time > epochInfo[epochInfo.length - 1].startTime) {
                  return epochInfo.length - 1;
              }
              // Binary search
              uint _min = 0;
              uint _max = epochInfo.length - 1; // asserting length >= 2
              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 (epochInfo[_mid].startTime <= _time) {
                      _min = _mid;
                  } else {
                      _max = _mid - 1;
                  }
              }
              return _min;
          }
      
          /**
          External read functions
           */
          struct RewardInfo {
              uint epochId;
              uint reward;
          }
      
          uint constant MaxQueryLength = 50;
      
          /// @notice get epoch info
          /// @return startTime
          /// @return endTime
          /// @return totalReward
          function getEpochInfo(uint epochId) public view returns (uint, uint, uint) {
              if (epochId >= epochInfo.length) {
                  return (0,0,0);
              }
              EpochInfo memory epoch = epochInfo[epochId];
              uint totalReward = (epoch.endTime - epoch.startTime) * epoch.rewardPerSecond / RewardMultiplier;
              return (epoch.startTime, epoch.endTime, totalReward);
          }
      
          function getCurrentEpochId() public view returns (uint) {
              uint currentEpochId = getEpochIdByTime(block.timestamp);
              return currentEpochId;
          }
      
          /// @notice only for external view functions
          /// Time beyond last checkpoint resulting in inconsistent estimated block number.
          function getBlockByTimeWithoutLastCheckpoint(uint _time) public view returns (uint) {
              if (point_history[point_history.length - 1].ts >= _time) {
                  return getBlockByTime(_time);
              }
              Point memory point0 = point_history[point_history.length - 1];
              if (_time == point0.ts) {
                  return point0.blk;
              }
              uint block_slope;
              block_slope = (BlockMultiplier * (block.number - point0.blk)) / (block.timestamp - point0.ts);
              uint dblock = (block_slope * (_time - point0.ts)) / BlockMultiplier;
              return point0.blk + dblock;
          }
      
          function getEpochStartBlock(uint epochId) public view returns (uint) {
              if (epochInfo[epochId].startBlock == 1) {
                  return getBlockByTimeWithoutLastCheckpoint(epochInfo[epochId].startTime);
              }
              return epochInfo[epochId].startBlock;
          }
      
          function getEpochTotalPower(uint epochId) public view returns (uint) {
              if (epochInfo[epochId].totalPower == 1) {
                  uint blk = getEpochStartBlock(epochId);
                  if (blk > block.number) {
                      return ve(_ve).totalSupplyAtT(epochInfo[epochId].startTime);
                  }
                  return ve(_ve).totalSupplyAt(blk);
              }
              return epochInfo[epochId].totalPower;
          }
      
          /// @notice get user's power at epochId
          function getUserPower(uint tokenId, uint epochId) view public returns (uint) {
              EpochInfo memory epoch = epochInfo[epochId];
              uint blk = getBlockByTimeWithoutLastCheckpoint(epoch.startTime);
              if (blk < block.number) {
                  return ve(_ve).balanceOfAtNFT(tokenId, blk);
              }
              return ve(_ve).balanceOfNFTAt(tokenId, epochInfo[epochId].startTime);
          }
      
          /// @notice
          /// Current epoch reward is inaccurate
          /// because the checkpoint may not have been added.
          function getPendingRewardSingle(uint tokenId, uint epochId) public view returns (uint reward, bool finished) {
              if (epochId > getCurrentEpochId()) {
                  return (0, false);
              }
              EpochInfo memory epoch = epochInfo[epochId];
              uint startBlock = getEpochStartBlock(epochId);
              uint totalPower = getEpochTotalPower(epochId);
              if (totalPower == 0) {
                  return (0, true);
              }
              uint power = ve(_ve).balanceOfAtNFT(tokenId, startBlock);
      
              uint last = userLastClaimTime[tokenId][epochId];
              last = last >= epoch.startTime ? last : epoch.startTime;
              if (last >= epoch.endTime) {
                  return (0, true);
              }
              
              uint end = block.timestamp;
              finished = false;
              if (end > epoch.endTime) {
                  end = epoch.endTime;
                  finished = true;
              }
              
              reward = epoch.rewardPerSecond * (end - last) * power / (totalPower * RewardMultiplier);
              return (reward, finished);
          }
      
          /// @notice get claimable reward
          function pendingReward(uint tokenId, uint start, uint end) public view returns (IntervalReward[] memory intervalRewards) {
              uint current = getCurrentEpochId();
              require(start <= end);
              if (end > current) {
                  end = current;
              }
              RewardInfo[] memory rewards = new RewardInfo[](end - start + 1);
              for (uint i = start; i <= end; i++) {
                  if (block.timestamp < epochInfo[i].startTime) {
                      break;
                  }
                  (uint reward_i,) = getPendingRewardSingle(tokenId, i);
                  rewards[i-start] = RewardInfo(i, reward_i);
              }
      
              // omit zero rewards and convert epoch list to intervals
              IntervalReward[] memory intervalRewards_0 = new IntervalReward[] (rewards.length);
              uint intv = 0;
              uint intvCursor = 0;
              uint sum = 0;
              for (uint i = 0; i < rewards.length; i++) {
                  if (rewards[i].reward == 0) {
                      if (i != intvCursor) {
                          intervalRewards_0[intv] = IntervalReward(rewards[intvCursor].epochId, rewards[i-1].epochId, sum);
                          intv++;
                          sum = 0;
                      }
                      intvCursor = i + 1;
                      continue;
                  }
                  sum += rewards[i].reward;
              }
              if (sum > 0) {
                  intervalRewards_0[intv] = IntervalReward(rewards[intvCursor].epochId, rewards[rewards.length-1].epochId, sum);
                  intervalRewards = new IntervalReward[] (intv+1);
                  // Copy interval array
                  for (uint i = 0; i < intv+1; i++) {
                      intervalRewards[i] = intervalRewards_0[i];
                  }
              } else {
                  intervalRewards = new IntervalReward[] (intv);
                  // Copy interval array
                  for (uint i = 0; i < intv; i++) {
                      intervalRewards[i] = intervalRewards_0[i];
                  }
              }
              
              return intervalRewards;
          }
      }

      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: ve
      // SPDX-License-Identifier: MIT
      pragma solidity 0.8.11;
      
      /**
      @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` (4 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 (4 years?)
      */
      
      /// [MIT License]
      /// @title Base64
      /// @notice Provides a function for encoding some bytes in base64
      /// @author Brecht Devos <[email protected]>
      library Base64 {
          bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
      
          /// @notice Encodes some bytes to the base64 representation
          function encode(bytes memory data) internal pure returns (string memory) {
              uint len = data.length;
              if (len == 0) return "";
      
              // multiply by 4/3 rounded up
              uint encodedLen = 4 * ((len + 2) / 3);
      
              // Add some extra buffer at the end
              bytes memory result = new bytes(encodedLen + 32);
      
              bytes memory table = TABLE;
      
              assembly {
                  let tablePtr := add(table, 1)
                  let resultPtr := add(result, 32)
      
                  for {
                      let i := 0
                  } lt(i, len) {
      
                  } {
                      i := add(i, 3)
                      let input := and(mload(add(data, i)), 0xffffff)
      
                      let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
                      out := shl(8, out)
                      out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF))
                      out := shl(8, out)
                      out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF))
                      out := shl(8, out)
                      out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
                      out := shl(224, out)
      
                      mstore(resultPtr, out)
      
                      resultPtr := add(resultPtr, 4)
                  }
      
                  switch mod(len, 3)
                  case 1 {
                      mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
                  }
                  case 2 {
                      mstore(sub(resultPtr, 1), shl(248, 0x3d))
                  }
      
                  mstore(result, encodedLen)
              }
      
              return string(result);
          }
      }
      
      /**
      * @dev Interface of the ERC165 standard, as defined in the
      * https://eips.ethereum.org/EIPS/eip-165[EIP].
      *
      * Implementers can declare support of contract interfaces, which can then be
      * queried by others ({ERC165Checker}).
      *
      * For an implementation, see {ERC165}.
      */
      interface IERC165 {
          /**
          * @dev Returns true if this contract implements the interface defined by
          * `interfaceId`. See the corresponding
          * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
          * to learn more about how these ids are created.
          *
          * This function call must use less than 30 000 gas.
          */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }
      
      /**
      * @dev Required interface of an ERC721 compliant contract.
      */
      interface IERC721 is IERC165 {
          /**
          * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
          */
          event Transfer(address indexed from, address indexed to, uint indexed tokenId);
      
          /**
          * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
          */
          event Approval(address indexed owner, address indexed approved, uint indexed tokenId);
      
          /**
          * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
          */
          event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
      
          /**
          * @dev Returns the number of tokens in ``owner``'s account.
          */
          function balanceOf(address owner) external view returns (uint balance);
      
          /**
          * @dev Returns the owner of the `tokenId` token.
          *
          * Requirements:
          *
          * - `tokenId` must exist.
          */
          function ownerOf(uint tokenId) external view returns (address owner);
      
          /**
          * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
          * are aware of the ERC721 protocol to prevent tokens from being forever locked.
          *
          * Requirements:
          *
          * - `from` cannot be the zero address.
          * - `to` cannot be the zero address.
          * - `tokenId` token must exist and be owned by `from`.
          * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
          * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
          *
          * Emits a {Transfer} event.
          */
          function safeTransferFrom(
              address from,
              address to,
              uint tokenId
          ) external;
      
          /**
          * @dev Transfers `tokenId` token from `from` to `to`.
          *
          * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
          *
          * Requirements:
          *
          * - `from` cannot be the zero address.
          * - `to` cannot be the zero address.
          * - `tokenId` token must be owned by `from`.
          * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
          *
          * Emits a {Transfer} event.
          */
          function transferFrom(
              address from,
              address to,
              uint tokenId
          ) external;
      
          /**
          * @dev Gives permission to `to` to transfer `tokenId` token to another account.
          * The approval is cleared when the token is transferred.
          *
          * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
          *
          * Requirements:
          *
          * - The caller must own the token or be an approved operator.
          * - `tokenId` must exist.
          *
          * Emits an {Approval} event.
          */
          function approve(address to, uint tokenId) external;
      
          /**
          * @dev Returns the account approved for `tokenId` token.
          *
          * Requirements:
          *
          * - `tokenId` must exist.
          */
          function getApproved(uint tokenId) external view returns (address operator);
      
          /**
          * @dev Approve or remove `operator` as an operator for the caller.
          * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
          *
          * Requirements:
          *
          * - The `operator` cannot be the caller.
          *
          * Emits an {ApprovalForAll} event.
          */
          function setApprovalForAll(address operator, bool _approved) external;
      
          /**
          * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
          *
          * See {setApprovalForAll}
          */
          function isApprovedForAll(address owner, address operator) external view returns (bool);
      
          /**
          * @dev Safely transfers `tokenId` token from `from` to `to`.
          *
          * Requirements:
          *
          * - `from` cannot be the zero address.
          * - `to` cannot be the zero address.
          * - `tokenId` token must exist and be owned by `from`.
          * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
          * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
          *
          * Emits a {Transfer} event.
          */
          function safeTransferFrom(
              address from,
              address to,
              uint tokenId,
              bytes calldata data
          ) external;
      }
      
      /**
      * @title ERC721 token receiver interface
      * @dev Interface for any contract that wants to support safeTransfers
      * from ERC721 asset contracts.
      */
      interface IERC721Receiver {
          /**
          * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
          * by `operator` from `from`, this function is called.
          *
          * It must return its Solidity selector to confirm the token transfer.
          * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
          *
          * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
          */
          function onERC721Received(
              address operator,
              address from,
              uint tokenId,
              bytes calldata data
          ) external returns (bytes4);
      }
      
      /**
      * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
      * @dev See https://eips.ethereum.org/EIPS/eip-721
      */
      interface IERC721Metadata is IERC721 {
          /**
          * @dev Returns the token collection name.
          */
          function name() external view returns (string memory);
      
          /**
          * @dev Returns the token collection symbol.
          */
          function symbol() external view returns (string memory);
      
          /**
          * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
          */
          function tokenURI(uint tokenId) external view returns (string memory);
      }
      
      /**
      * @dev Interface of the ERC20 standard as defined in the EIP.
      */
      interface IERC20 {
          /**
          * @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, uint 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,
              uint amount
          ) external returns (bool);
      }
      
      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 ve is IERC721, IERC721Metadata {
          enum DepositType {
              DEPOSIT_FOR_TYPE,
              CREATE_LOCK_TYPE,
              INCREASE_LOCK_AMOUNT,
              INCREASE_UNLOCK_TIME,
              MERGE_TYPE
          }
      
          event Deposit(
              address indexed provider,
              uint tokenId,
              uint value,
              uint indexed locktime,
              DepositType deposit_type,
              uint ts
          );
          event Withdraw(address indexed provider, uint tokenId, uint value, uint ts);
          event Supply(uint prevSupply, uint supply);
      
          uint internal constant WEEK = 1 weeks;
          uint internal constant MAXTIME = 4 * 365 * 86400;
          int128 internal constant iMAXTIME = 4 * 365 * 86400;
          uint internal constant MULTIPLIER = 1 ether;
      
          address immutable public token;
          uint public supply;
          uint public nftSupply;
          mapping(uint => LockedBalance) public locked;
      
          mapping(uint => uint) public ownership_change;
      
          uint public epoch;
          mapping(uint => Point) public point_history; // epoch -> unsigned point
          mapping(uint => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
      
          mapping(uint => uint) public user_point_epoch;
          mapping(uint => int128) public slope_changes; // time -> signed slope change
      
          mapping(uint => uint) public attachments;
          mapping(uint => bool) public voted;
          address public voter;
      
          string constant public name = "veMULTI NFT";
          string constant public symbol = "veMULTI";
          string constant public version = "1.0.0";
          uint8 constant public decimals = 18;
      
          /// @dev Current count of token
          uint internal tokenId;
      
          /// @dev Mapping from NFT ID to the address that owns it.
          mapping(uint => address) internal idToOwner;
      
          /// @dev Mapping from NFT ID to approved address.
          mapping(uint => address) internal idToApprovals;
      
          /// @dev Mapping from owner address to count of his tokens.
          mapping(address => uint) internal ownerToNFTokenCount;
      
          /// @dev Mapping from owner address to mapping of index to tokenIds
          mapping(address => mapping(uint => uint)) internal ownerToNFTokenIdList;
      
          /// @dev Mapping from NFT ID to index of owner
          mapping(uint => uint) internal tokenToOwnerIndex;
      
          /// @dev Mapping from owner address to mapping of operator addresses.
          mapping(address => mapping(address => bool)) internal ownerToOperators;
      
          /// @dev Mapping of interface id to bool about whether or not it's supported
          mapping(bytes4 => bool) internal supportedInterfaces;
      
          /// @dev ERC165 interface ID of ERC165
          bytes4 internal constant ERC165_INTERFACE_ID = 0x01ffc9a7;
      
          /// @dev ERC165 interface ID of ERC721
          bytes4 internal constant ERC721_INTERFACE_ID = 0x80ac58cd;
      
          /// @dev ERC165 interface ID of ERC721Metadata
          bytes4 internal constant ERC721_METADATA_INTERFACE_ID = 0x5b5e139f;
      
          /// @dev reentrancy guard
          uint8 internal constant _not_entered = 1;
          uint8 internal constant _entered = 2;
          uint8 internal _entered_state = 1;
          modifier nonreentrant() {
              require(_entered_state == _not_entered);
              _entered_state = _entered;
              _;
              _entered_state = _not_entered;
          }
      
          /// @notice Contract constructor
          /// @param token_addr `ERC20CRV` token address
          constructor(
              address token_addr
          ) {
              token = token_addr;
              voter = msg.sender;
              point_history[0].blk = block.number;
              point_history[0].ts = block.timestamp;
      
              supportedInterfaces[ERC165_INTERFACE_ID] = true;
              supportedInterfaces[ERC721_INTERFACE_ID] = true;
              supportedInterfaces[ERC721_METADATA_INTERFACE_ID] = true;
      
              // mint-ish
              emit Transfer(address(0), address(this), tokenId);
              // burn-ish
              emit Transfer(address(this), address(0), tokenId);
          }
      
          /// @dev Interface identification is specified in ERC-165.
          /// @param _interfaceID Id of the interface
          function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
              return supportedInterfaces[_interfaceID];
          }
      
          /// @notice Get the most recently recorded rate of voting power decrease for `_tokenId`
          /// @param _tokenId token of the NFT
          /// @return Value of the slope
          function get_last_user_slope(uint _tokenId) external view returns (int128) {
              uint uepoch = user_point_epoch[_tokenId];
              return user_point_history[_tokenId][uepoch].slope;
          }
      
          /// @notice Get the timestamp for checkpoint `_idx` for `_tokenId`
          /// @param _tokenId token of the NFT
          /// @param _idx User epoch number
          /// @return Epoch time of the checkpoint
          function user_point_history__ts(uint _tokenId, uint _idx) external view returns (uint) {
              return user_point_history[_tokenId][_idx].ts;
          }
      
          /// @notice Get timestamp when `_tokenId`'s lock finishes
          /// @param _tokenId User NFT
          /// @return Epoch time of the lock end
          function locked__end(uint _tokenId) external view returns (uint) {
              return locked[_tokenId].end;
          }
      
          /// @dev Returns the number of NFTs owned by `_owner`.
          ///      Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
          /// @param _owner Address for whom to query the balance.
          function _balance(address _owner) internal view returns (uint) {
              return ownerToNFTokenCount[_owner];
          }
      
          /// @dev Returns the number of NFTs owned by `_owner`.
          ///      Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
          /// @param _owner Address for whom to query the balance.
          function balanceOf(address _owner) external view returns (uint) {
              return _balance(_owner);
          }
      
          function totalNFTSupply() external view returns (uint) {
              return nftSupply;
          }
      
          /// @dev Returns the address of the owner of the NFT.
          /// @param _tokenId The identifier for an NFT.
          function ownerOf(uint _tokenId) public view returns (address) {
              address owner = idToOwner[_tokenId];
              require(owner != address(0), "VE NFT: owner query for nonexistent token");
              return owner;
          }
      
          /// @dev Get the approved address for a single NFT.
          /// @param _tokenId ID of the NFT to query the approval of.
          function getApproved(uint _tokenId) external view returns (address) {
              return idToApprovals[_tokenId];
          }
      
          /// @dev Checks if `_operator` is an approved operator for `_owner`.
          /// @param _owner The address that owns the NFTs.
          /// @param _operator The address that acts on behalf of the owner.
          function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
              return (ownerToOperators[_owner])[_operator];
          }
      
          /// @dev  Get token by index
          function tokenOfOwnerByIndex(address _owner, uint _tokenIndex) external view returns (uint) {
              return ownerToNFTokenIdList[_owner][_tokenIndex];
          }
      
          /// @dev Returns whether the given spender can transfer a given token ID
          /// @param _spender address of the spender to query
          /// @param _tokenId uint ID of the token to be transferred
          /// @return bool whether the msg.sender is approved for the given token ID, is an operator of the owner, or is the owner of the token
          function _isApprovedOrOwner(address _spender, uint _tokenId) internal view returns (bool) {
              address owner = idToOwner[_tokenId];
              bool spenderIsOwner = owner == _spender;
              bool spenderIsApproved = _spender == idToApprovals[_tokenId];
              bool spenderIsApprovedForAll = (ownerToOperators[owner])[_spender];
              return spenderIsOwner || spenderIsApproved || spenderIsApprovedForAll;
          }
      
          function isApprovedOrOwner(address _spender, uint _tokenId) external view returns (bool) {
              return _isApprovedOrOwner(_spender, _tokenId);
          }
      
          /// @dev Add a NFT to an index mapping to a given address
          /// @param _to address of the receiver
          /// @param _tokenId uint ID Of the token to be added
          function _addTokenToOwnerList(address _to, uint _tokenId) internal {
              uint current_count = _balance(_to);
      
              ownerToNFTokenIdList[_to][current_count] = _tokenId;
              tokenToOwnerIndex[_tokenId] = current_count;
          }
      
          /// @dev Remove a NFT from an index mapping to a given address
          /// @param _from address of the sender
          /// @param _tokenId uint ID Of the token to be removed
          function _removeTokenFromOwnerList(address _from, uint _tokenId) internal {
              // Delete
              uint current_count = _balance(_from)-1;
              uint current_index = tokenToOwnerIndex[_tokenId];
      
              if (current_count == current_index) {
                  // update ownerToNFTokenIdList
                  ownerToNFTokenIdList[_from][current_count] = 0;
                  // update tokenToOwnerIndex
                  tokenToOwnerIndex[_tokenId] = 0;
              } else {
                  uint lastTokenId = ownerToNFTokenIdList[_from][current_count];
      
                  // Add
                  // update ownerToNFTokenIdList
                  ownerToNFTokenIdList[_from][current_index] = lastTokenId;
                  // update tokenToOwnerIndex
                  tokenToOwnerIndex[lastTokenId] = current_index;
      
                  // Delete
                  // update ownerToNFTokenIdList
                  ownerToNFTokenIdList[_from][current_count] = 0;
                  // update tokenToOwnerIndex
                  tokenToOwnerIndex[_tokenId] = 0;
              }
          }
      
          /// @dev Add a NFT to a given address
          ///      Throws if `_tokenId` is owned by someone.
          function _addTokenTo(address _to, uint _tokenId) internal {
              // Throws if `_tokenId` is owned by someone
              assert(idToOwner[_tokenId] == address(0));
              // Change the owner
              idToOwner[_tokenId] = _to;
              // Update owner token index tracking
              _addTokenToOwnerList(_to, _tokenId);
              // Change count tracking
              ownerToNFTokenCount[_to] += 1;
          }
      
          /// @dev Remove a NFT from a given address
          ///      Throws if `_from` is not the current owner.
          function _removeTokenFrom(address _from, uint _tokenId) internal {
              // Throws if `_from` is not the current owner
              assert(idToOwner[_tokenId] == _from);
              // Change the owner
              idToOwner[_tokenId] = address(0);
              // Update owner token index tracking
              _removeTokenFromOwnerList(_from, _tokenId);
              // Change count tracking
              ownerToNFTokenCount[_from] -= 1;
          }
      
          /// @dev Clear an approval of a given address
          ///      Throws if `_owner` is not the current owner.
          function _clearApproval(address _owner, uint _tokenId) internal {
              // Throws if `_owner` is not the current owner
              assert(idToOwner[_tokenId] == _owner);
              if (idToApprovals[_tokenId] != address(0)) {
                  // Reset approvals
                  idToApprovals[_tokenId] = address(0);
              }
          }
      
          /// @dev Exeute transfer of a NFT.
          ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
          ///      address for this NFT. (NOTE: `msg.sender` not allowed in internal function so pass `_sender`.)
          ///      Throws if `_to` is the zero address.
          ///      Throws if `_from` is not the current owner.
          ///      Throws if `_tokenId` is not a valid NFT.
          function _transferFrom(
              address _from,
              address _to,
              uint _tokenId,
              address _sender
          ) internal {
              require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
              // Check requirements
              require(_isApprovedOrOwner(_sender, _tokenId));
              // Clear approval. Throws if `_from` is not the current owner
              _clearApproval(_from, _tokenId);
              // Remove NFT. Throws if `_tokenId` is not a valid NFT
              _removeTokenFrom(_from, _tokenId);
              // Add NFT
              _addTokenTo(_to, _tokenId);
              // Set the block of ownership transfer (for Flash NFT protection)
              ownership_change[_tokenId] = block.number;
              // Log the transfer
              emit Transfer(_from, _to, _tokenId);
          }
      
          /* TRANSFER FUNCTIONS */
          /// @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT.
          ///      Throws if `_from` is not the current owner.
          ///      Throws if `_to` is the zero address.
          ///      Throws if `_tokenId` is not a valid NFT.
          /// @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
          ///        they maybe be permanently lost.
          /// @param _from The current owner of the NFT.
          /// @param _to The new owner.
          /// @param _tokenId The NFT to transfer.
          function transferFrom(
              address _from,
              address _to,
              uint _tokenId
          ) external {
              _transferFrom(_from, _to, _tokenId, msg.sender);
          }
      
          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.
              uint size;
              assembly {
                  size := extcodesize(account)
              }
              return size > 0;
          }
      
          /// @dev Transfers the ownership of an NFT from one address to another address.
          ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the
          ///      approved address for this NFT.
          ///      Throws if `_from` is not the current owner.
          ///      Throws if `_to` is the zero address.
          ///      Throws if `_tokenId` is not a valid NFT.
          ///      If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
          ///      the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
          /// @param _from The current owner of the NFT.
          /// @param _to The new owner.
          /// @param _tokenId The NFT to transfer.
          /// @param _data Additional data with no specified format, sent in call to `_to`.
          function safeTransferFrom(
              address _from,
              address _to,
              uint _tokenId,
              bytes memory _data
          ) public {
              _transferFrom(_from, _to, _tokenId, msg.sender);
      
              if (_isContract(_to)) {
                  // Throws if transfer destination is a contract which does not implement 'onERC721Received'
                  try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 retval) {
                      require(retval == IERC721Receiver.onERC721Received.selector, "ERC721: transfer to non ERC721Receiver implementer");
                  } catch (
                      bytes memory reason
                  ) {
                      if (reason.length == 0) {
                          revert('ERC721: transfer to non ERC721Receiver implementer');
                      } else {
                          assembly {
                              revert(add(32, reason), mload(reason))
                          }
                      }
                  }
              }
          }
      
          /// @dev Transfers the ownership of an NFT from one address to another address.
          ///      Throws unless `msg.sender` is the current owner, an authorized operator, or the
          ///      approved address for this NFT.
          ///      Throws if `_from` is not the current owner.
          ///      Throws if `_to` is the zero address.
          ///      Throws if `_tokenId` is not a valid NFT.
          ///      If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
          ///      the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`.
          /// @param _from The current owner of the NFT.
          /// @param _to The new owner.
          /// @param _tokenId The NFT to transfer.
          function safeTransferFrom(
              address _from,
              address _to,
              uint _tokenId
          ) external {
              safeTransferFrom(_from, _to, _tokenId, '');
          }
      
          /// @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.
          ///      Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
          ///      Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
          ///      Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
          /// @param _approved Address to be approved for the given NFT ID.
          /// @param _tokenId ID of the token to be approved.
          function approve(address _approved, uint _tokenId) public {
              address owner = idToOwner[_tokenId];
              // Throws if `_tokenId` is not a valid NFT
              require(owner != address(0));
              // Throws if `_approved` is the current owner
              require(_approved != owner);
              // Check requirements
              bool senderIsOwner = (idToOwner[_tokenId] == msg.sender);
              bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender];
              require(senderIsOwner || senderIsApprovedForAll);
              // Set the approval
              idToApprovals[_tokenId] = _approved;
              emit Approval(owner, _approved, _tokenId);
          }
      
          /// @dev Enables or disables approval for a third party ("operator") to manage all of
          ///      `msg.sender`'s assets. It also emits the ApprovalForAll event.
          ///      Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
          /// @notice This works even if sender doesn't own any tokens at the time.
          /// @param _operator Address to add to the set of authorized operators.
          /// @param _approved True if the operators is approved, false to revoke approval.
          function setApprovalForAll(address _operator, bool _approved) external {
              // Throws if `_operator` is the `msg.sender`
              assert(_operator != msg.sender);
              ownerToOperators[msg.sender][_operator] = _approved;
              emit ApprovalForAll(msg.sender, _operator, _approved);
          }
      
          /// @dev Function to mint tokens
          ///      Throws if `_to` is zero address.
          ///      Throws if `_tokenId` is owned by someone.
          /// @param _to The address that will receive the minted tokens.
          /// @param _tokenId The token id to mint.
          /// @return A boolean that indicates if the operation was successful.
          function _mint(address _to, uint _tokenId) internal returns (bool) {
              // Throws if `_to` is zero address
              assert(_to != address(0));
              // Add NFT. Throws if `_tokenId` is owned by someone
              _addTokenTo(_to, _tokenId);
              nftSupply++;
              emit Transfer(address(0), _to, _tokenId);
              return true;
          }
      
          /// @notice Record global and per-user data to checkpoint
          /// @param _tokenId NFT token ID. No user checkpoint if 0
          /// @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(
              uint _tokenId,
              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 (_tokenId != 0) {
                  // 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(int256(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(int256(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
              Point memory initial_last_point = last_point;
              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(int256(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 (_tokenId != 0) {
                  // 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 (_tokenId != 0) {
                  // 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
                  uint user_epoch = user_point_epoch[_tokenId] + 1;
      
                  user_point_epoch[_tokenId] = user_epoch;
                  u_new.ts = block.timestamp;
                  u_new.blk = block.number;
                  user_point_history[_tokenId][user_epoch] = u_new;
              }
          }
      
          /// @notice Deposit and lock tokens for a user
          /// @param _tokenId NFT that holds lock
          /// @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(
              uint _tokenId,
              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(int256(_value));
              if (unlock_time != 0) {
                  _locked.end = unlock_time;
              }
              locked[_tokenId] = _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(_tokenId, old_locked, _locked);
      
              address from = msg.sender;
              if (_value != 0 && deposit_type != DepositType.MERGE_TYPE) {
                  assert(IERC20(token).transferFrom(from, address(this), _value));
              }
      
              emit Deposit(from, _tokenId, _value, _locked.end, deposit_type, block.timestamp);
              emit Supply(supply_before, supply_before + _value);
          }
      
          function setVoter(address _voter) external {
              require(msg.sender == voter);
              voter = _voter;
          }
      
          function voting(uint _tokenId) external {
              require(msg.sender == voter);
              voted[_tokenId] = true;
          }
      
          function abstain(uint _tokenId) external {
              require(msg.sender == voter);
              voted[_tokenId] = false;
          }
      
          function attach(uint _tokenId) external {
              require(msg.sender == voter);
              attachments[_tokenId] = attachments[_tokenId]+1;
          }
      
          function detach(uint _tokenId) external {
              require(msg.sender == voter);
              attachments[_tokenId] = attachments[_tokenId]-1;
          }
      
          function merge(uint _from, uint _to) external {
              require(attachments[_from] == 0 && !voted[_from], "attached");
              require(_from != _to);
              require(_isApprovedOrOwner(msg.sender, _from));
              require(_isApprovedOrOwner(msg.sender, _to));
      
              LockedBalance memory _locked0 = locked[_from];
              LockedBalance memory _locked1 = locked[_to];
              uint value0 = uint(int256(_locked0.amount));
              uint end = _locked0.end >= _locked1.end ? _locked0.end : _locked1.end;
      
              locked[_from] = LockedBalance(0, 0);
              _checkpoint(_from, _locked0, LockedBalance(0, 0));
              _burn(_from);
              _deposit_for(_to, value0, end, _locked1, DepositType.MERGE_TYPE);
          }
      
          function block_number() external view returns (uint) {
              return block.number;
          }
      
          /// @notice Record global data to checkpoint
          function checkpoint() external {
              _checkpoint(0, LockedBalance(0, 0), LockedBalance(0, 0));
          }
      
          /// @notice Deposit `_value` tokens for `_tokenId` 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 _tokenId lock NFT
          /// @param _value Amount to add to user's lock
          function deposit_for(uint _tokenId, uint _value) external nonreentrant {
              LockedBalance memory _locked = locked[_tokenId];
      
              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(_tokenId, _value, 0, _locked, DepositType.DEPOSIT_FOR_TYPE);
          }
      
          /// @notice Deposit `_value` tokens for `_to` and lock for `_lock_duration`
          /// @param _value Amount to deposit
          /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
          /// @param _to Address to deposit
          function _create_lock(uint _value, uint _lock_duration, address _to) internal returns (uint) {
              uint unlock_time = (block.timestamp + _lock_duration) / WEEK * WEEK; // Locktime is rounded down to weeks
      
              require(_value > 0); // dev: need non-zero value
              require(unlock_time > block.timestamp, 'Can only lock until time in the future');
              require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');
      
              ++tokenId;
              uint _tokenId = tokenId;
              _mint(_to, _tokenId);
      
              _deposit_for(_tokenId, _value, unlock_time, locked[_tokenId], DepositType.CREATE_LOCK_TYPE);
              return _tokenId;
          }
      
          /// @notice Deposit `_value` tokens for `_to` and lock for `_lock_duration`
          /// @param _value Amount to deposit
          /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
          /// @param _to Address to deposit
          function create_lock_for(uint _value, uint _lock_duration, address _to) external nonreentrant returns (uint) {
              return _create_lock(_value, _lock_duration, _to);
          }
      
          /// @notice Deposit `_value` tokens for `msg.sender` and lock for `_lock_duration`
          /// @param _value Amount to deposit
          /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week)
          function create_lock(uint _value, uint _lock_duration) external nonreentrant returns (uint) {
              return _create_lock(_value, _lock_duration, msg.sender);
          }
      
          /// @notice Deposit `_value` additional tokens for `_tokenId` without modifying the unlock time
          /// @param _value Amount of tokens to deposit and add to the lock
          function increase_amount(uint _tokenId, uint _value) external nonreentrant {
              assert(_isApprovedOrOwner(msg.sender, _tokenId));
      
              LockedBalance memory _locked = locked[_tokenId];
      
              assert(_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(_tokenId, _value, 0, _locked, DepositType.INCREASE_LOCK_AMOUNT);
          }
      
          /// @notice Extend the unlock time for `_tokenId`
          /// @param _lock_duration New number of seconds until tokens unlock
          function increase_unlock_time(uint _tokenId, uint _lock_duration) external nonreentrant {
              assert(_isApprovedOrOwner(msg.sender, _tokenId));
      
              LockedBalance memory _locked = locked[_tokenId];
              uint unlock_time = (block.timestamp + _lock_duration) / 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 4 years max');
      
              _deposit_for(_tokenId, 0, unlock_time, _locked, DepositType.INCREASE_UNLOCK_TIME);
          }
      
          /// @notice Withdraw all tokens for `_tokenId`
          /// @dev Only possible if the lock has expired
          function withdraw(uint _tokenId) external nonreentrant {
              assert(_isApprovedOrOwner(msg.sender, _tokenId));
              require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
      
              LockedBalance memory _locked = locked[_tokenId];
              require(block.timestamp >= _locked.end, "The lock didn't expire");
              uint value = uint(int256(_locked.amount));
      
              locked[_tokenId] = 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(_tokenId, _locked, LockedBalance(0,0));
      
              address owner = ownerOf(_tokenId);
              // Burn the NFT
              _burn(_tokenId);
      
              assert(IERC20(token).transfer(owner, value));
      
              emit Withdraw(msg.sender, _tokenId, value, block.timestamp);
              emit Supply(supply_before, supply_before - value);
          }
      
          // 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 `_tokenId`
          /// @dev Adheres to the ERC20 `balanceOf` interface for Aragon compatibility
          /// @param _tokenId NFT for lock
          /// @param _t Epoch time to return voting power at
          /// @return User voting power
          function _balanceOfNFT(uint _tokenId, uint _t) internal view returns (uint) {
              uint _epoch = user_point_epoch[_tokenId];
              if (_epoch == 0) {
                  return 0;
              } else {
                  Point memory last_point = user_point_history[_tokenId][_epoch];
                  last_point.bias -= last_point.slope * int128(int256(_t) - int256(last_point.ts));
                  if (last_point.bias < 0) {
                      last_point.bias = 0;
                  }
                  return uint(int256(last_point.bias));
              }
          }
      
          /// @dev Returns current token URI metadata
          /// @param _tokenId Token ID to fetch URI for.
          function tokenURI(uint _tokenId) external view returns (string memory) {
              require(idToOwner[_tokenId] != address(0), "Query for nonexistent token");
              LockedBalance memory _locked = locked[_tokenId];
              return
              _tokenURI(
                  _tokenId,
                  _balanceOfNFT(_tokenId, block.timestamp),
                  _locked.end,
                  uint(int256(_locked.amount))
              );
          }
      
          function balanceOfNFT(uint _tokenId) external view returns (uint) {
              if (ownership_change[_tokenId] == block.number) return 0;
              return _balanceOfNFT(_tokenId, block.timestamp);
          }
      
          function balanceOfNFTAt(uint _tokenId, uint _t) external view returns (uint) {
              return _balanceOfNFT(_tokenId, _t);
          }
      
          /// @notice Measure voting power of `_tokenId` at block height `_block`
          /// @dev Adheres to MiniMe `balanceOfAt` interface: https://github.com/Giveth/minime
          /// @param _tokenId User's wallet NFT
          /// @param _block Block to calculate the voting power at
          /// @return Voting power
          function _balanceOfAtNFT(uint _tokenId, uint _block) internal view returns (uint) {
              // Copying and pasting totalSupply code because Vyper cannot pass by
              // reference yet
              assert(_block <= block.number);
      
              // Binary search
              uint _min = 0;
              uint _max = user_point_epoch[_tokenId];
              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[_tokenId][_mid].blk <= _block) {
                      _min = _mid;
                  } else {
                      _max = _mid - 1;
                  }
              }
      
              Point memory upoint = user_point_history[_tokenId][_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(int256(block_time - upoint.ts));
              if (upoint.bias >= 0) {
                  return uint(uint128(upoint.bias));
              } else {
                  return 0;
              }
          }
      
          function balanceOfAtNFT(uint _tokenId, uint _block) external view returns (uint) {
              return _balanceOfAtNFT(_tokenId, _block);
          }
      
          /// @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(int256(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 totalSupplyAtT(uint t) public view returns (uint) {
              uint _epoch = epoch;
              Point memory last_point = point_history[_epoch];
              return _supply_at(last_point, t);
          }
      
          function totalSupply() external view returns (uint) {
              return totalSupplyAtT(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) {
              assert(_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);
          }
      
          function _tokenURI(uint _tokenId, uint _balanceOf, uint _locked_end, uint _value) internal pure returns (string memory output) {
              output = '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350"><style>.base { fill: white; font-family: serif; font-size: 14px; }</style><rect width="100%" height="100%" fill="black" /><text x="10" y="20" class="base">';
              output = string(abi.encodePacked(output, "token ", toString(_tokenId), '</text><text x="10" y="40" class="base">'));
              output = string(abi.encodePacked(output, "balanceOf ", toString(_balanceOf), '</text><text x="10" y="60" class="base">'));
              output = string(abi.encodePacked(output, "locked_end ", toString(_locked_end), '</text><text x="10" y="80" class="base">'));
              output = string(abi.encodePacked(output, "value ", toString(_value), '</text></svg>'));
      
              string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "lock #', toString(_tokenId), '", "description": "veMULTI NFT", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(output)), '"}'))));
              output = string(abi.encodePacked('data:application/json;base64,', json));
          }
      
          function toString(uint value) internal pure returns (string memory) {
              // Inspired by OraclizeAPI's implementation - MIT license
              // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
      
              if (value == 0) {
                  return "0";
              }
              uint temp = value;
              uint digits;
              while (temp != 0) {
                  digits++;
                  temp /= 10;
              }
              bytes memory buffer = new bytes(digits);
              while (value != 0) {
                  digits -= 1;
                  buffer[digits] = bytes1(uint8(48 + uint(value % 10)));
                  value /= 10;
              }
              return string(buffer);
          }
      
          function _burn(uint _tokenId) internal {
              require(_isApprovedOrOwner(msg.sender, _tokenId), "caller is not owner nor approved");
      
              address owner = ownerOf(_tokenId);
      
              // Clear approval
              _clearApproval(owner, _tokenId);
              // Remove token
              _removeTokenFrom(owner, _tokenId);
              nftSupply--;
              emit Transfer(owner, address(0), _tokenId);
          }
      }

      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";
          }
      }