DepositQueue
Overview
The DepositQueue
contract enables asynchronous asset deposits into vaults using a time-delayed, oracle-priced queuing mechanism. Deposits are not processed immediately. Instead, users submit requests that are later fulfilled when an external price oracle submits a valid report. This enables batching (based on Fenwick Tree data structure), mitigates front-running, and facilitates accurate share pricing.
Deposit Lifecycle
Step 1: User Deposits
A user submits a deposit request via
deposit(assets, referral, merkleProof)
.The deposited amount is stored as a
(timestamp, value)
checkpoint underrequestOf[msg.sender]
.Each account can have only one pending request at a time.
Deposits are validated via optional Merkle whitelist logic (using
merkleProof
) or onchain mapping (ifflags.hasWhitelist()
returns true).If a previous request exists, it must be claimed or canceled before creating a new one.
Step 2: Oracle Report
Oracle submits a report via the
handleReport(priceD18, timestamp)
method.The queue validates the report:
It must be called by the vault.
The timestamp must be in the past.
Price must be non-zero.
The queue handles deposit requests that are pending for longer than
depositInterval
seconds (the interval is specified in the oracle’s security params).The contract stores the price in
prices
and uses it to convert accumulated assets to shares.Converted shares are allocated but not minted yet.
Events:
ReportHandled
is emitted.
Step 3: User Claims
A user calls
claim(account)
to mint and receive previously allocated shares.The number of shares is computed as:
uint256 shares = (request.assets * reducedByDepositFeePriceD18) / 1e18;
Shares are minted to the user via
mintAllocatedShares
.
Cancellation
A user may cancel a pending request using
cancelDepositRequest()
.Cancellation reverts if the request has already become claimable (i.e., processed by an oracle report).
Refund is issued in the original asset amount.
Event:
DepositRequestCanceled
is emitted.
Query Methods
claimableOf(address account)
: Returns how many shares are currently claimable for a given user.requestOf(address account)
: Returns the(timestamp, amount)
tuple of a user’s current pending request.
Internal Mechanics
The system tracks all deposit requests and prices using the following structures:
Checkpoints.Trace224 prices
: Stores historical oracle-reported prices keyed by timestamp.mapping(address => Checkpoints.Checkpoint224) requestOf
: Maps users to their active deposit requests.FenwickTreeLibrary.Tree requests
: A prefix-sum data structure tracking asset totals across compressed timestamps.uint256 handledIndices
: Tracks the last fully processed request index, ensuring each oracle report progresses the queue.
Scalability Challenge
Vaults may face thousands of deposit requests daily. Processing each one individually leads to significant gas costs or even OOG. To optimize:
A Fenwick Tree (Binary Indexed Tree) is used to efficiently manage aggregate deposit data by timestamp.
Fenwick Tree Mechanics
On Deposit:
When a user deposits an amount
A
at timeT
, the system performs:fenwickTree[T] += A
On Cancellation:
If the user cancels the request:
fenwickTree[T] -= A
On Oracle Report:
During report at
reportTimestamp
, the system calculates:fenwickTree.getSum(latestHandledTimestamp + 1, reportTimestamp - depositInterval)
This determines the total amount eligible for conversion into vault shares at the reported price.
Lazy Propagation of Shares
Rather than eagerly updating each user balance during handleReport
, the vault employs lazy propagation:
Each user’s claimable shares are finalized only during subsequent calling
claim()
.This significantly reduces processing cost during batch report execution.
Timestamp Compression
To minimize storage writes and reads used by FenwickTree.sol
the system uses coordinate compression, storing only timestamps where actual deposit requests occurred.
This compression strategy ensures that the Fenwick Tree remains compact, even with high-frequency usage.
Key Invariants
Single Active Request
Each user may have at most one unprocessed deposit request, user can not do new deposit till previous one is not claimed.
Delayed Execution
Deposit processing requires an oracle report submitted after a configured
depositInterval
.Lazy Claiming
Deposits are converted to shares during oracle processing, but users must call
claim()
to receive them.Whitelist Enforcement
Deposits may require Merkle proof for depositor whitelisting.
Events
DepositRequested(address account, address referral, uint224 assets, uint32 timestamp)
: Emitted on new deposit submission.DepositRequestCanceled(address account, uint256 assets, uint32 timestamp)
: Emitted when a request is canceled and assets refunded.DepositRequestClaimed(address account, uint256 shares, uint32 timestamp)
: Emitted when deposit shares are successfully claimed.ReportHandled(uint224 priceD18, uint32 timestamp)
: Emitted when an oracle report is processed.
Errors
DepositNotAllowed()
: Depositor not whitelisted.PendingRequestExists()
: Existing request not yet processed or claimed.ClaimableRequestExists()
: Attempting to cancel after request has become claimable.NoPendingRequest()
: No existing request to cancel.ZeroValue()
: Input value is zero.InvalidReport()
: Oracle report failed validation.Forbidden()
: Unauthorized caller.QueuePaused()
: Deposits disabled via vault pause mechanism.