TokenVault

This contract is where all the ERC20 tokens that were locked into FNFTs are stored, this handles value tracking, and interest-bearing tokens.

Code

Events

CreateFNFT

event CreateFNFT(uint indexed fnftId, address indexed from);

Emitted on each creation of a new FNFT.

RedeemFNFT

event RedeemFNFT(uint indexed fnftId, address indexed from);

Emitted on each withdraw or split of an FNFT.

Read-Only Functions

getFNFTCurrentValue

function getFNFTCurrentValue(uint fnftId) external view override returns (uint) {
    if(fnfts[fnftId].pipeToContract.supportsInterface(OUTPUT_RECEIVER_INTERFACE_ID)) {
        return IOutputReceiver(fnfts[fnftId].pipeToContract).getValue((fnftId));
    }

    uint currentAmount = 0;
    if(fnfts[fnftId].asset != address(0)) {
        currentAmount = IERC20(fnfts[fnftId].asset).balanceOf(address(this));
        IRevest.TokenTracker memory tracker = tokenTrackers[fnfts[fnftId].asset];
        uint lastMul = tracker.lastBalance == 0 ? multiplierPrecision : multiplierPrecision * currentAmount / tracker.lastBalance;
        return fnfts[fnftId].depositAmount * lastMul / fnfts[fnftId].depositMul;
    }
    //Default fall-through
    return currentAmount;
}

Gets the current real value of the FNFT, including interest that is owed on it/

getFNFT

function getFNFT(uint fnftId) external view override returns (IRevest.FNFTConfig memory) {
    return fnfts[fnftId];
}

Gets the config associated with a given FNFT id.

getNontransferable

function getNontransferable(uint fnftId) external view override returns (bool) {
    return fnfts[fnftId].nontransferrable;
}

Gets whether the requested FNFT is nontransferrable and subject to whitelisting.

getSplitsRemaining

function getSplitsRemaining(uint fnftId) external view override returns (uint) {
    IRevest.FNFTConfig storage fnft = fnfts[fnftId];
    return fnft.split;
}

Gets the remaining value of splits for an splittable FNFT.

cloneFNFTConfig

function cloneFNFTConfig(IRevest.FNFTConfig memory old) public pure override returns (IRevest.FNFTConfig memory) {
    return IRevest.FNFTConfig({
        asset: old.asset,
        depositAmount: old.depositAmount,
        depositMul: old.depositMul,
        split: old.split,
        maturityExtension: old.maturityExtension,
        pipeToContract: old.pipeToContract,
        isMulti : old.isMulti,
        depositStopTime: old.depositStopTime,
        nontransferrable: old.nontransferrable
    });
}

Clones a given FNFT config into a new instance of that object with the same values.

State-Changing Functions

createFNFT

function createFNFT(uint fnftId, IRevest.FNFTConfig memory fnftConfig, uint quantity, address from) external override {
    mapFNFTToToken(fnftId, fnftConfig); // Costs 72K gas
    depositToken(fnftId, fnftConfig.depositAmount, quantity); //Costs 100K gas
    emit CreateFNFT(fnftId, from);
}
  • This method receives the deposited tokens from IRevest, maps the FNFT to the token amounts, initializes interest trackers, and emits a “CreateFNFT” event

  • fnftId - the FNFT id to map to

  • fnftConfig - the config to map to this id

  • Quantity - the number of FNFTs being created within this series

  • From - the address who initiated the fnft creation process (and by extension, the deposit of tokens)

  • Emit CreateFNFT event.

updateBalance

function updateBalance(uint fnftId, uint incomingDeposit) internal {
    IRevest.FNFTConfig storage fnft = fnfts[fnftId];
    address asset = fnft.asset;
    IRevest.TokenTracker storage tracker = tokenTrackers[asset];

    uint currentAmount;
    uint lastBal = tracker.lastBalance;

    if(asset != address(0)){
        currentAmount = IERC20(asset).balanceOf(address(this));
    } else {
        //Keep us from zeroing out zero assets
        currentAmount = lastBal;
    }
    tracker.lastMul = lastBal == 0 ? multiplierPrecision : multiplierPrecision * currentAmount / lastBal;
    tracker.lastBalance = currentAmount + incomingDeposit;
}

Updates internal trackers to the current balance of tokens within the contract, calls update methods of IInterestHandler methods on this fnftId if they exist.

depositToken

function depositToken(
    uint fnftId,
    uint transferAmount,
    uint quantity
) public override onlyRevestController {
    //Updates in advance, to handle rebasing tokens
    updateBalance(fnftId, quantity * transferAmount);

    IRevest.FNFTConfig storage fnft = fnfts[fnftId];

    //If there is no interest handler, use TokenTracker
    fnft.depositMul = tokenTrackers[fnft.asset].lastMul;
}

Receives deposited tokens and initializes trackers, updates staking information if relevant.

withdrawToken

function withdrawToken(
    uint fnftId,
    uint quantity,
    address user
) external override onlyRevestController {
    IRevest.FNFTConfig storage fnft = fnfts[fnftId];
    IRevest.TokenTracker storage tracker = tokenTrackers[fnft.asset];
    address asset = fnft.asset;
    // Modify balances first, then send
    // Will do nothing special to interest-bearing assets

    //Will take care of things for both IInterestHandler and TokenTracker
    updateBalance(fnftId, 0);

    uint withdrawAmount = fnft.depositAmount * quantity * tracker.lastMul / fnft.depositMul;
    tracker.lastBalance -= withdrawAmount;

    address pipeTo = fnft.pipeToContract;
    if (pipeTo == address(0)) {
        if(asset != address(0)) {
            IERC20(asset).safeTransfer(user, withdrawAmount);
        }
    }
    else {
        if(fnft.depositAmount > 0 && asset != address(0)) {
            //Only transfer value if there is value to transfer
            IERC20(asset).safeTransfer(fnft.pipeToContract, withdrawAmount);
        }
        if(pipeTo.supportsInterface(OUTPUT_RECEIVER_INTERFACE_ID)) {
            IOutputReceiver(pipeTo).receiveRevestOutput(fnftId, asset, payable(user), quantity);
        }
    }

    if(getFNFTHandler().getSupply(fnftId) == 0) {
        removeFNFT(fnftId);
    }
    emit RedeemFNFT(fnftId, _msgSender());
}
  • Withdraws tokens from the token vault, updates their trackers, and updates the staking system.

  • Emit RedeemFNFT event.

mapFNFTToToken

function mapFNFTToToken(
    uint fnftId,
    IRevest.FNFTConfig memory fnftConfig
) public override onlyRevestController {
    fnfts[fnftId].asset =  fnftConfig.asset;
    fnfts[fnftId].depositAmount =  fnftConfig.depositAmount;
    if(fnftConfig.depositMul > 0) {
        fnfts[fnftId].depositMul = fnftConfig.depositMul;
    }
    if(fnftConfig.split > 0) {
        fnfts[fnftId].split = fnftConfig.split;
    }
    if(fnftConfig.maturityExtension) {
        fnfts[fnftId].maturityExtension = fnftConfig.maturityExtension;
    }
    if(fnftConfig.pipeToContract != address(0)) {
        fnfts[fnftId].pipeToContract = fnftConfig.pipeToContract;
    }
    if(fnftConfig.isMulti) {
        fnfts[fnftId].isMulti = fnftConfig.isMulti;
        fnfts[fnftId].depositStopTime = fnftConfig.depositStopTime;
    }
    if(fnftConfig.nontransferrable){
        fnfts[fnftId].nontransferrable = fnftConfig.nontransferrable;
    }
}

Helper function to map FNFTs to their configs.

splitFNFT

function splitFNFT(
    uint fnftId,
    uint[] memory newFNFTIds,
    uint[] memory proportions,
    uint quantity
) external override {
    IRevest.FNFTConfig storage fnft = fnfts[fnftId];
    updateBalance(fnftId, 0);
    // Burn the original FNFT but keep its lock

    // Create new FNFTs with the same config, only thing changed is the depositAmount
    uint denominator = 1000; // proportions should add up to this
    uint runningTotal = 0;
    for(uint i = 0; i < proportions.length; i++) {
        runningTotal += proportions[i];
        uint amount = fnft.depositAmount * proportions[i] / denominator;
        IRevest.FNFTConfig memory newFNFT = cloneFNFTConfig(fnft);
        newFNFT.depositAmount = amount;
        newFNFT.split -= 1;
        mapFNFTToToken(newFNFTIds[i], newFNFT);
        emit CreateFNFT(newFNFTIds[i], _msgSender());
    }
    require(runningTotal == denominator, 'E054'); // This is really a precondition but more efficient to place here
    if(quantity == getFNFTHandler().getSupply(fnftId)) {
        removeFNFT(fnftId); // We should also burn it, send ownership to 0x0000 - but should that happen here?
    }
    emit RedeemFNFT(fnftId, _msgSender());
}

Helper function for the split method in IRevest instances

  • fnftId - the original FNFT id

  • newFNFTIds - an array of the new IDs that need mappings

  • Proportions - the previously mentioned array describing split amounts

  • Quantity - how many FNFTs in the original series to effect

  • Emit RedeemFNFT event.

removeFNFT

function removeFNFT(uint fnftId) internal {
    delete fnfts[fnftId];
}

Internal function to remove a mapping.

handleMultipleDeposits

function handleMultipleDeposits(
    uint fnftId,
    uint newFNFTId,
    uint amount
) external override onlyRevestController {
    require(amount >= fnfts[fnftId].depositAmount, 'E003');
    IRevest.FNFTConfig storage config = fnfts[fnftId];
    config.depositAmount = amount;
    mapFNFTToToken(fnftId, config);
    if(newFNFTId != 0) {
        mapFNFTToToken(newFNFTId, config);
    }
}

Helper function for multiple deposits from IRevest instances

Interface

// SPDX-License-Identifier: GNU-GPL v3.0 or later

pragma solidity >=0.8.0;

/**
 * @title Mintable interface for Revest FNFTs
 * @dev ...
 */
interface ITokenVault {

    function createFNFT(
        uint fnftId,
        IRevest.FNFTConfig memory fnftConfig,
        uint quantity,
        address from
    ) external;

    function withdrawToken(
        uint fnftId,
        uint quantity,
        address user
    ) external;

    function depositToken(
        uint fnftId,
        uint amount,
        uint quantity
    ) external;

    function cloneFNFTConfig(IRevest.FNFTConfig memory old) external returns (IRevest.FNFTConfig memory);

    function mapFNFTToToken(
        uint fnftId,
        IRevest.FNFTConfig memory fnftConfig
    ) external;

    function handleMultipleDeposits(
        uint fnftId,
        uint newFNFTId,
        uint amount
    ) external;

    function splitFNFT(
        uint fnftId,
        uint[] memory newFNFTIds,
        uint[] memory proportions,
        uint quantity
    ) external;

    function getFNFT(uint fnftId) external view returns (IRevest.FNFTConfig memory);
    function getFNFTCurrentValue(uint fnftId) external view returns (uint);
    function getNontransferable(uint fnftId) external view returns (bool);
    function getSplitsRemaining(uint fnftId) external view returns (uint);
}

ABI

Last updated