# TokenVault

## Code

* [TokenVault.sol](https://github.com/Revest-Finance/RevestContracts/blob/master/hardhat/contracts/TokenVault.sol)

## 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](broken://pages/-MkDTCax6F61PEFYnAkE) 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](broken://pages/-MkDTCax6F61PEFYnAkE) 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](broken://pages/-MkDTCax6F61PEFYnAkE) 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://dev.revest.finance/smart-contracts/tokenvault.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
