This specification explains a tokenized reserve mechanism standard. Current smart contracts record transactions and are made public. The reserve will implement added functionality allowing stakeholders proactively to audit a contract. Using ERC-4626, stakeholders can create shares to show support for actions in the contract.
Motivation
Tokenized vaults store ERC-20 tokens that are represented by shares within vault contracts. Implementations can follow the ERC-4626 standard to provide basic functionality for depositing, withdrawing, and reading balances for a vault. As tokenization becomes increasingly popular, applications should use a form of tokenized vaults to store assets and allow all parties to track performance.
This specification introduces a standard for an on-chain reserve that uses tokenized vaults to represent reserve stakeholders. Core functionality, which is an extension of ERC-4626, will provide stakeholders representation by depositing and withdrawing from the vault. The record of transactions for other ERC-20 assets should be easily accessible to any party for auditing.
In a tokenized reserve, stakeholders are represented by minting shares from the vault. The goal is to create a reserve similar to a real-world reserve fund used as a contingency for an entity. In most cases, an entity would follow criteria like running low on regular funds, to utilize the reserve fund. In a decentralized environment, an entity should incorporate stakeholders as criteria. Assets associated with the reserve as well as its origin will vary in decentralized environments, so transparent auditing is needed.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Definitions:
- owner: The creator of the reserve
- user: Stakeholders participating in policies
- reserve: The assets held on the contract other than underlying token
- policies: Created by reserve owners to encourage stakeholder participation
- rAuth: Authorized user, for cases utilizing more than one owner/ limiting owner withdrawals
- rOwner: Owner of the Reserve
Interface
// SPDX-License-Identifier: CC0-1.0
import"./ERC4626.sol";interfaceTokenReserveisERC4626{/**
* @dev Event emitted after a new policy is created
*/eventpolicies(addressindexedtoken,uint256indexedpolicyNum,uint256indexedamount,addressrecipient);/**
* @dev Event emitted after a new deposit is made by the owner
*/eventdepositR(addressindexedtoken,uint256indexedamount,uint256indexedtime,uint256count);/**
* @dev Get time a deposit/withdrawal was made by the owner
* @param count Number for deposit count
* @return block.timestamp format
*/functionownerTime(uint256count)externalviewreturns(uint256)/**
* @dev Get amount deposited to reserve by owner
* @param count Number for deposit count
* @param policy The policy number to deposit to
* @return uint256 Amount of an asset that was deposited
*/functionownerDeposit(uint256count,uint256policy)externalviewreturns(uint256)/**
* @dev Amount withdrawn for a opened policy by the owner
* @param policy The policy number
* @return Amount of ERC20
*/functionownerWithdrawals(uint256policy)externalviewreturns(uint256)/**
* @dev Token type deposited to reserve by owner
* - MUST be an address of ERC20 token
* @param count Number of deposit count
* @return address Address of ERC20 token
*/functiontokenDeposit(uint256count)externalviewreturns(address)/**
* @dev Amount deposited to a policy for shares
* - MUST be an ERC20 token
* @param user Address of user
* @param policy The policy number the user deposited to
* @return uint256 Amount of ERC20 deposited
*/functionuserDeposit(addressuser,uint256policy)externalviewreturns(uint256)/**
* @dev Amount withdrawn from a policy by the user
* @param user The address of user
* @param policy The policy number for user withdrawal
* @param uint256 Amount of ERC20
*/functionuserWithdrawals(addressuser,uint256policy)publicviewreturns(uint256)/**
* @dev Token type withdrawn for an opened policy by the owner
* - MUST be ERC20 address
* @param policy The policy number for the token used
* @return Token ERC20 address
*/functionpolicyToken(uint256policy)externalviewreturns(address)/**
* @dev Make a deposit to a policy creating new shares using deposit function from ERC4626
* - MUST be opened policy
* - MUST NOT be opened policy that was closed
* - SHOULD be only method to deposit to ERC4626 vault
* NOTE: using the deposit() will cause assets to not be accounted for in a policy (see Security Considerations section)
* @param assets Amount being deposited
* @param receiver Address of depositor
* @param policy The number associated policy
* @return Amount of shares minted
*/functionpolicyDeposit(uint256assets,addressreceiver,uint256policy)externalvirtualreturns(uint256)/**
* @dev Burn shares, receive 1 to 1 value of shares using withdraw function from ERC4626
* - MUST have userDeposit greater than or equal to userWithdrawal
* - SHOULD be only method for withdrawing from ERC4626 vault
* @param assets Amount being deposited
* @param receiver Address of receiver
* @param owner Address of token owner
* @param policy Number associated policy
* @return Amount of the asset
*/functionwithdrawPolicy(uint256assets,addressreceiver,addressowner,uint256policy)externalvirtualreturns(uint256)/**
* @dev Issue new policy
* - MUST create new policy number
* - MUST account for amount withdrawn
* - MUST be only method to withdraw ERC20 tokens (excluding underlying ERC4626 token)
* - MUST be owner
* - SHOULD emit policies event
* @param token Address of ERC-20 token
* @param amount Token amount being withdrawn
* @param receiver Address of token recipient
* @return The policy number
*/functionopenPolicy(addresstoken,uint256amount,addressreceiver)externalvirtualreturns(uint256)/**
* @dev Make a deposit and/or close an opened policy
* - MUST be owner
* - MUST account for amount received
* - SHOULD emit policies event
* @param token Address of ERC-20 token
* @param policy Number of the desired policy
* @param amount Token amount being deposited to the reserve
* @param close Choose to close the policy
* @return True for closed policy
*/functionclosePolicy(addresstoken,uint256policy,uint256amount,boolclose)externalvirtualreturns(bool)/**
* @dev Accounting for tokens deposited by owner
* - MUST be reserve owner
* - SHOULD emit depositR event
* NOTE: No shares are issued, funds can not be redeemed and no policy is opened. Withdrawnal made with openPolicy function.
* @param token Address of ERC-20 token being deposited
* @param sender Address of token sender
* @param amount Token amount being deposited
*/functiondepositReserve(addresstoken,addresssender,uint256amount)externalvirtual}
Rationale
This proposed standard is designed to be a core implementation of a tokenized reserve interface. Other non-specified conditions should be addressed on a case-by-case basis. Each reserve uses ERC-20 standard for shares, and ERC-4626 for the creation of shares. The reserve token SHOULD be considered as either the underlying asset for the ERC-4626 vault or the shares that are created when depositing to the vault.
ERC-4626 is used to create a transparent creation of stakeholders of the reserve. There MUST be a representation of interested parties in the reserve. The implementer can decide how to treat representation based on users entering and leaving the vault. For example, a user could be forced not to use the same tokens in multiple policies to allow shares to be distributed fairly.
Backwards Compatibility
Tokenized reserves are made compatible with ERC-20 and ERC-4626.
Security Considerations
Tokenized reserves share the same security considerations as ERC-20 and ERC-4626.
Assests withdrawn by owner are not secured by vaults.
Stakeholders SHOULD be aware that the underlying asset can be withdrawn by the owner with no restrictions or authorizing party, like requiring an rAuth. Depending on the authorizing implementation, asset may still be withdrawn by the owner.
A RECOMMENDED implementation:
The openPolicy MUST explictly restrict the transfer of the underlying asset.
If the underlying asset is apart of the reserve and not the vault,
the reserve MUST provide a method to avoid user asset loss.