ETH Price: $2,069.40 (+2.30%)

Transaction Decoder

Block:
14804010 at May-19-2022 08:42:32 AM +UTC
Transaction Fee:
0.010520031248258574 ETH $21.77
Gas Used:
363,783 Gas / 28.918424578 Gwei

Emitted Events:

188 ve.Transfer( from=0x00000000...000000000, to=0x87dFd4cE...B9f32c663, tokenId=224 )
189 MultichainToken.Transfer( from=[Sender] 0xd1e4a32679216f4a4dd38e45dab9bc4b8a45e592, to=[Receiver] ve, value=20000000000000000000 )
190 MultichainToken.Approval( owner=[Sender] 0xd1e4a32679216f4a4dd38e45dab9bc4b8a45e592, spender=[Receiver] ve, value=115792089237316195423570985008687907853269984665640564038277584007913129639935 )
191 ve.Deposit( provider=[Sender] 0xd1e4a32679216f4a4dd38e45dab9bc4b8a45e592, tokenId=224, value=20000000000000000000, locktime=1778716800, deposit_type=1, ts=1652949752 )
192 ve.Supply( prevSupply=3005630569417865803672809, supply=3005650569417865803672809 )

Account State Difference:

  Address   Before After State Difference Code
0x65Ef703f...548492df4
0xbbA4115e...E2748ceBa
0xD1E4a326...B8a45e592
147.163586209212565884 Eth
Nonce: 851
147.15306617796430731 Eth
Nonce: 852
0.010520031248258574
(Ethermine)
1,308.445665926989318382 Eth1,308.446029709989318382 Eth0.000363783

Execution Trace

ve.create_lock_for( _value=20000000000000000000, _lock_duration=126144000, _to=0x87dFd4cE2001a8B0Ba0A7E4b3bDDEaeB9f32c663 ) => ( 224 )
  • MultichainToken.transferFrom( sender=0xD1E4a32679216F4A4DD38e45DAb9Bc4B8a45e592, recipient=0xbbA4115ecB1F811061eCb5A8DC8FcdEE2748ceBa, amount=20000000000000000000 ) => ( True )
    create_lock_for[ve (ln:1045)]
    File 1 of 2: 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 <brecht@loopring.org>
    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 2 of 2: MultichainToken
    // SPDX-License-Identifier: GPL-3.0-or-later
    // Sources flattened with hardhat v2.8.0 https://hardhat.org
    
    // File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.4.1
    
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `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 @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.4.1
    
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Interface for the optional metadata functions from the ERC20 standard.
     *
     * _Available since v4.1._
     */
    interface IERC20Metadata is IERC20 {
        /**
         * @dev Returns the name of the token.
         */
        function name() external view returns (string memory);
    
        /**
         * @dev Returns the symbol of the token.
         */
        function symbol() external view returns (string memory);
    
        /**
         * @dev Returns the decimals places of the token.
         */
        function decimals() external view returns (uint8);
    }
    
    
    // File @openzeppelin/contracts/utils/Context.sol@v4.4.1
    
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    
    pragma solidity ^0.8.0;
    
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    
    
    // File @openzeppelin/contracts/token/ERC20/ERC20.sol@v4.4.1
    
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)
    
    pragma solidity ^0.8.0;
    
    
    
    /**
     * @dev Implementation of the {IERC20} interface.
     *
     * This implementation is agnostic to the way tokens are created. This means
     * that a supply mechanism has to be added in a derived contract using {_mint}.
     * For a generic mechanism see {ERC20PresetMinterPauser}.
     *
     * TIP: For a detailed writeup see our guide
     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
     * to implement supply mechanisms].
     *
     * We have followed general OpenZeppelin Contracts guidelines: functions revert
     * instead returning `false` on failure. This behavior is nonetheless
     * conventional and does not conflict with the expectations of ERC20
     * applications.
     *
     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
     * This allows applications to reconstruct the allowance for all accounts just
     * by listening to said events. Other implementations of the EIP may not emit
     * these events, as it isn't required by the specification.
     *
     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
     * functions have been added to mitigate the well-known issues around setting
     * allowances. See {IERC20-approve}.
     */
    contract ERC20 is Context, IERC20, IERC20Metadata {
        mapping(address => uint256) private _balances;
    
        mapping(address => mapping(address => uint256)) private _allowances;
    
        uint256 private _totalSupply;
    
        string private _name;
        string private _symbol;
    
        /**
         * @dev Sets the values for {name} and {symbol}.
         *
         * The default value of {decimals} is 18. To select a different value for
         * {decimals} you should overload it.
         *
         * All two of these values are immutable: they can only be set once during
         * construction.
         */
        constructor(string memory name_, string memory symbol_) {
            _name = name_;
            _symbol = symbol_;
        }
    
        /**
         * @dev Returns the name of the token.
         */
        function name() public view virtual override returns (string memory) {
            return _name;
        }
    
        /**
         * @dev Returns the symbol of the token, usually a shorter version of the
         * name.
         */
        function symbol() public view virtual override returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev Returns the number of decimals used to get its user representation.
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
         * be displayed to a user as `5.05` (`505 / 10 ** 2`).
         *
         * Tokens usually opt for a value of 18, imitating the relationship between
         * Ether and Wei. This is the value {ERC20} uses, unless this function is
         * overridden;
         *
         * NOTE: This information is only used for _display_ purposes: it in
         * no way affects any of the arithmetic of the contract, including
         * {IERC20-balanceOf} and {IERC20-transfer}.
         */
        function decimals() public view virtual override returns (uint8) {
            return 18;
        }
    
        /**
         * @dev See {IERC20-totalSupply}.
         */
        function totalSupply() public view virtual override returns (uint256) {
            return _totalSupply;
        }
    
        /**
         * @dev See {IERC20-balanceOf}.
         */
        function balanceOf(address account) public view virtual override returns (uint256) {
            return _balances[account];
        }
    
        /**
         * @dev See {IERC20-transfer}.
         *
         * Requirements:
         *
         * - `recipient` cannot be the zero address.
         * - the caller must have a balance of at least `amount`.
         */
        function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-allowance}.
         */
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
            return _allowances[owner][spender];
        }
    
        /**
         * @dev See {IERC20-approve}.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-transferFrom}.
         *
         * Emits an {Approval} event indicating the updated allowance. This is not
         * required by the EIP. See the note at the beginning of {ERC20}.
         *
         * Requirements:
         *
         * - `sender` and `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         * - the caller must have allowance for ``sender``'s tokens of at least
         * `amount`.
         */
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) public virtual override returns (bool) {
            _transfer(sender, recipient, amount);
    
            uint256 currentAllowance = _allowances[sender][_msgSender()];
            require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
            unchecked {
                _approve(sender, _msgSender(), currentAllowance - amount);
            }
    
            return true;
        }
    
        /**
         * @dev Atomically increases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
            return true;
        }
    
        /**
         * @dev Atomically decreases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `spender` must have allowance for the caller of at least
         * `subtractedValue`.
         */
        function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
            uint256 currentAllowance = _allowances[_msgSender()][spender];
            require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
            unchecked {
                _approve(_msgSender(), spender, currentAllowance - subtractedValue);
            }
    
            return true;
        }
    
        /**
         * @dev Moves `amount` of tokens from `sender` to `recipient`.
         *
         * This internal function is equivalent to {transfer}, and can be used to
         * e.g. implement automatic token fees, slashing mechanisms, etc.
         *
         * Emits a {Transfer} event.
         *
         * Requirements:
         *
         * - `sender` cannot be the zero address.
         * - `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         */
        function _transfer(
            address sender,
            address recipient,
            uint256 amount
        ) internal virtual {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            _beforeTokenTransfer(sender, recipient, amount);
    
            uint256 senderBalance = _balances[sender];
            require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
            unchecked {
                _balances[sender] = senderBalance - amount;
            }
            _balances[recipient] += amount;
    
            emit Transfer(sender, recipient, amount);
    
            _afterTokenTransfer(sender, recipient, amount);
        }
    
        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
         * the total supply.
         *
         * Emits a {Transfer} event with `from` set to the zero address.
         *
         * Requirements:
         *
         * - `account` cannot be the zero address.
         */
        function _mint(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _beforeTokenTransfer(address(0), account, amount);
    
            _totalSupply += amount;
            _balances[account] += amount;
            emit Transfer(address(0), account, amount);
    
            _afterTokenTransfer(address(0), account, amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`, reducing the
         * total supply.
         *
         * Emits a {Transfer} event with `to` set to the zero address.
         *
         * Requirements:
         *
         * - `account` cannot be the zero address.
         * - `account` must have at least `amount` tokens.
         */
        function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _beforeTokenTransfer(account, address(0), amount);
    
            uint256 accountBalance = _balances[account];
            require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
            unchecked {
                _balances[account] = accountBalance - amount;
            }
            _totalSupply -= amount;
    
            emit Transfer(account, address(0), amount);
    
            _afterTokenTransfer(account, address(0), amount);
        }
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
         *
         * This internal function is equivalent to `approve`, and can be used to
         * e.g. set automatic allowances for certain subsystems, etc.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `owner` cannot be the zero address.
         * - `spender` cannot be the zero address.
         */
        function _approve(
            address owner,
            address spender,
            uint256 amount
        ) internal virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    
        /**
         * @dev Hook that is called before any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * will be transferred to `to`.
         * - when `from` is zero, `amount` tokens will be minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 amount
        ) internal virtual {}
    
        /**
         * @dev Hook that is called after any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * has been transferred to `to`.
         * - when `from` is zero, `amount` tokens have been minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _afterTokenTransfer(
            address from,
            address to,
            uint256 amount
        ) internal virtual {}
    }
    
    
    // File internal/MultichainToken.sol
    
    pragma solidity ^0.8.6;
    
    contract MultichainToken is ERC20 {
        constructor() ERC20("Multichain", "MULTI") {
            _mint(msg.sender, 1e8 * 10**uint(decimals()));
        }
    }