Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add ERC7540DelayedRedeem / ERC7540DelayedDeposit: concrete delayed-fulfillment extensions for ERC-7540 #6445

@ernestognw

Description

@ernestognw

Part of #4761 / #6399.

Summary

Add two concrete, non-abstract extensions on top of the ERC7540Redeem and ERC7540Deposit abstract base contracts that implement delayed fulfillment: fulfillment becomes permissionless after a configurable waiting period elapses since the request was submitted.

  • ERC7540DelayedRedeem extends ERC7540Redeem, Ownable
  • ERC7540DelayedDeposit extends ERC7540Deposit, Ownable

Motivation

The most common reason for using an async vault is that redemptions (and sometimes deposits) cannot be settled immediately — the vault must first unwind a position (unstaking, RWA liquidation, cross-chain bridge). In the simplest cases the unwinding takes a known, fixed duration. After that duration any actor (the controller, a keeper, a bot) can trigger settlement without requiring a privileged transaction.

Real-world examples: BeefySonic (protocol-dictated SFC unbonding period), MagmaV2 (owner-configurable delay), Tangle.

Proposed interface

contract ERC7540DelayedRedeem is ERC7540Redeem, Ownable {
    error ERC7540DelayNotElapsed(uint256 requestedAt, uint256 delay, uint256 currentTime);
    error ERC7540DelayTooLong(uint256 delay, uint256 maxDelay);

    event DelayUpdated(uint256 oldDelay, uint256 newDelay);
    event RedeemFulfilled(address indexed controller, uint256 shares, uint256 assets);

    uint256 public delay;
    uint256 public constant MAX_DELAY = 365 days;

    constructor(IERC20 asset_, address initialOwner, uint256 initialDelay);

    function setDelay(uint256 newDelay) external onlyOwner;
    function requestRedeem(uint256 shares, address controller, address owner) public override returns (uint256);
    function fulfillRedeem(address controller) external returns (uint256 assets);
}

ERC7540DelayedDeposit is symmetric: same storage, same constructor shape, requestDeposit override, and fulfillDeposit(address controller).

Behavior

  • On requestRedeem / requestDeposit, block.timestamp is recorded for the controller.
  • fulfillRedeem(controller) / fulfillDeposit(controller) is permissionless — reverts with ERC7540DelayNotElapsed if block.timestamp < _requestedAt[controller] + delay. On success, calls _fulfillRedeem / _fulfillDeposit with the full pending balance at the live exchange rate.
  • The delay is owner-configurable but capped at MAX_DELAY to prevent locking funds.
  • Pending shares are held in the vault during the wait (default _lockSharesIn_completeSharesIn flow), so they continue to accrue yield until burned at fulfillment.
  • Pending deposit assets are excluded from totalAssets() during the wait (handled by ERC7540Deposit), preventing dilution of existing holders.

Open questions

  • MAX_DELAY value. 365 days covers liquid staking unbonding periods; 28 days may be safer for general-purpose use.
  • Timestamp accumulation policy. When a controller adds to an existing pending request, should the timestamp be kept at the earliest (first-in wins, recommended) or reset to the latest?

Composability

A vault that delays only redeems uses ERC7540DelayedRedeem alone. A fully delayed vault inherits from both extensions plus ERC7540:

contract MyFullyDelayedVault is ERC7540, ERC7540DelayedRedeem, ERC7540DelayedDeposit, Ownable { ... }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions