Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-6229: Tokenized Vaults with Lock-in Period

ERC-4626 Tokenized Vaults with Lock-in Period.

Authors Anderson Chen (@Ankarrr), Martinet Lee <martinetlee@gmail.com>, Anton Cheng <antonassocareer@gmail.com>
Created 2022-12-21
Discussion Link https://ethereum-magicians.org/t/eip-tokenized-vaults-with-lock-in-period/12298
Requires EIP-4626

Abstract

This standard extends EIP-4626 to support lock-in periods.

Motivation

The EIP-4626 standard defines a tokenized vault allowing users (contracts or EOAs) to deposit and withdraw underlying tokens at any time. However, there exist cases where the vault needs to lock the underlying tokens (perhaps to execute certain strategies). During the lock-in period, neither withdrawals nor deposits should be allowed. This standard extends the EIP-4626 to support lock-in periods and handle scheduled deposits and withdrawals during them.

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.

All vaults that follow this EIP MUST implement EIP-4626 to provide basic vault functions and EIP-20 to represent shares.

Definitions

  • asset: The underlying EIP-20 token that the vault accepts and manages.
  • share: The EIP-20 token that the vault issued.
  • locked: A status of the vault. When the vault is locked, user can’t withdraw or deposit assets from the vault.
  • unlocked: A status of the vault. When the vault is unlocked, user can withdraw or deposit assets from the vault.
  • round: The period that the vault is locked.

View Methods

isLocked

The current state of the vault.

true represents a vault is in the locked state, and false represents a vault is in the unlocked state.

- name: isLocked
  type: bool
  stateMutability: view

  inputs: []

  outputs:
    - name: isLocked
      type: bool

vaultRound

The current round of the vault.

MUST start with 0.

MUST add 1 each time a new round starts, that is, when the isLocked becomes true. MUST NOT be modified in any other circumstances.

- name: vaultRound
  type: uint256
  stateMutability: view

  inputs: []

  outputs:
    - name: vaultRound
      type: uint256

Methods

scheduleDeposit

Schedule the intent to deposit assets when the isLocked is true.

MUST only be callable when the isLocked is true.

MUST transfer the assets from the caller to the vault. MUST not issue new shares.

MUST revert if assets cannot be deposited.

MUST revert if the isLocked is false.

- name: scheduleDeposit
  type: function
  stateMutability: nonpayable

  inputs:
    - name: assets
      type: uint256

scheduleRedeem

Schedule the intent to redeem shares from the vault when the isLocked is true.

MUST only be callable when the isLocked is true.

MUST transfer the shares from the caller to the vault. MUST not transfer assets to caller.

MUST revert if shares cannot be redeemed.

MUST revert if the isLocked is false.

- name: scheduleRedeem
  type: function
  stateMutability: nonpayable

  inputs:
    - name: shares
      type: uint256

settleDeposits

Process all scheduled deposits for depositor and minting newShares.

MUST only be callable when the isLocked is false.

MUST issue newShares according to the current share price for the scheduled depositor.

MUST revert if there is no scheduled deposit for depositor.

- name: settleDeposits
  type: function
  stateMutability: nonpayable

  inputs:
    - name: depositor
    - type: address

  outputs:
    - name: newShares
    - type: uint256

settleRedemptions

Process all scheduled redemptions for redeemer by burning burnShares and transferring redeemAssets to the redeemer.

MUST only be callable when the isLocked is false.

MUST burn the burnShares and transfer redeemAssets back to the redeemer according to the current share price.

MUST revert if no scheduled redemption for redeemer.

- name: settleRedemptions
  type: function
  stateMutability: nonpayable

  inputs:
    - name: redeemer
    - type: address

  outputs:
    - name: burnShares
    - type: uint256
    - name: redeemAssets
    - type: uint256

getScheduledDeposits

Get the totalAssets of scheduled deposits for depositor.

MUST NOT revert.

- name: getScheduledDeposits
  type: function
  stateMutability: view

  inputs:
    - name: depositor
    - type: address

  outputs:
    - name: totalAssets
    - type: uint256

getScheduledRedemptions

Get the totalShares of scheduled redemptions for redeemer.

MUST NOT revert.

- name: getScheduledRedemptions
  type: function
  stateMutability: view

  inputs:
    - name: redeemer
    - type: address

  outputs:
    - name: totalShares
    - type: uint256

Events

ScheduleDeposit

sender schedules a deposit with assets in this round.

MUST be emitted via scheduleDeposit method.

- name: ScheduleDeposit
  type: event

  inputs:
    - name: sender
      indexed: true
      type: address
    - name: assets
      indexed: false
      type: uint256
    - name: round
      indexed: false
      type: uint256

ScheduleRedeem

sender schedules a redemption with shares in this round.

MUST be emitted via scheduleRedeem method.

- name: ScheduleRedeem
  type: event

  inputs:
    - name: sender
      indexed: true
      type: address
    - name: shares
      indexed: false
      type: uint256
    - name: round
      indexed: false
      type: uint2

SettleDeposits

Settle scheduled deposits for depositor in this round. Issue newShares and transfer them to the depositor.

MUST be emitted via settleDeposits method.

- name: SettleDeposits
  type: event

  inputs:
    - name: depositor
      indexed: true
      type: address
    - name: newShares
      type: uint256
    - name: round
      type: uint256

SettleRedemptions

Settle scheduled redemptions for redeemer in this round. Burn burnShares and transfer redeemAssets back to the redeemer.

MUST be emitted via settleRedemptions method.

- name: SettleRedemptions
  type: event

  inputs:
    - name: redeemer
      indexed: true
      type: address
    - name: burnShares
      type: uint256
    - name: redeemAssets
      type: uint256
    - name: round
      type: uint256

Rationale

The standard is designed to be a minimal interface. Details such as the start and end of a lock-in period, and how the underlying tokens are being used during the lock-in period are not specified.

There is no function for scheduling a withdrawal, since during the lock-in period, the share price is undetermined, so it is impossible to determine how many underlying tokens can be withdrawn.

Backwards Compatibility

The deposit, mint, withdraw, redeem methods for EIP-4626 should revert when the isLocked is true to prevent issuing or burning shares with an undefined share price.

Security Considerations

Implementors need to be aware of unsettled scheduled deposits and redemptions. If a user has scheduled a deposit or redemption but does not settle when the isLocked is false, and then settles it after several rounds, the vault will process it with an incorrect share price. We didn’t specify the solution in the standard since there are many possible ways to solve this issue and we think implementors should decide the solution according to their use cases. For example:

  • Not allow the isLocked to become true if there is any unsettled scheduled deposit or redemption
  • Force settling the scheduled deposits or redemptions when the isLocked becomes true
  • Memorize the ending share price for each round and let the users settle according to the share prices

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Anderson Chen (@Ankarrr), Martinet Lee <martinetlee@gmail.com>, Anton Cheng <antonassocareer@gmail.com>, "ERC-6229: Tokenized Vaults with Lock-in Period [DRAFT]," Ethereum Improvement Proposals, no. 6229, December 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6229.