You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tests: v2 routing, single-vault withdraw, totalAssets view, transfer re-attribution
- Deposit routes via affiliates(user) and records underlying shares
- Withdraw resolves to a single vault and reverts when insufficient
- totalAssets sums convertToAssets across wrapper-held positions in tracked vaults
- Transfers proportionally re-attribute underlying shares (no external calls)
Test plan:
- Run: bunx hardhat test test/rewards/SendEarnRewards.erc4626.spec.ts
Copy file name to clipboardExpand all lines: docs/rewards-aggregator-erc4626.md
+25Lines changed: 25 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,6 +9,11 @@ Status: Spec only (docs-first). Implementation and tests will follow in separate
9
9
- Shares are transferable (standard ERC20 semantics). No non-transferable override.
10
10
- No streaming dependencies in this spec. Streaming is deferred.
11
11
12
+
## Data model and asset/conversions
13
+
- Per-user per-vault underlying shares: the wrapper attributes underlying SendEarn vault shares to users in `_userUnderlyingShares[user][vault]`.
14
+
- Active vaults (wrapper-wide): first time a deposit is made into a vault, it’s added to `_activeVaults` for view-only `totalAssets()`.
15
+
- Active vaults (per-user): first time a user receives underlying shares for a vault, it’s added to that user’s active set for proportional re‑attribution on ERC20 transfers (no external calls).
16
+
12
17
## Asset and conversions
13
18
- The aggregator’s `asset()` equals the underlying asset used by all routed SendEarn vaults (e.g., USDC).
14
19
- Follow standard ERC4626 math for conversions; do not assume 1:1 shares/assets. `totalAssets()` reflects the current value of all held SendEarn vault shares converted via each vault’s `convertToAssets`.
@@ -20,6 +25,10 @@ sum over all held SendEarn vaults v:
20
25
```
21
26
22
27
## Deposit routing (selection policy)
28
+
Resolution order for each action (deposit/withdraw):
29
+
- Use `affiliates(account)` if non-zero; else `SEND_EARN()`.
30
+
- Note: Changing your affiliate does not migrate legacy underlying shares. It only affects future actions’ resolution.
31
+
- Example: if you deposited into default vault and later set an affiliate to a different vault, withdraw resolves to the new affiliate vault and will revert unless you hold underlying shares there.
23
32
Resolution order for deposits by caller:
24
33
1) If `factory.affiliates(caller) != address(0)`, route deposit to that SendEarn vault.
25
34
2) Else, let `d = factory.SEND_EARN()` (the default SendEarn vault). If `IERC20(d).balanceOf(caller) > 0` (caller already holds default shares), prefer `d`.
@@ -31,9 +40,25 @@ All routed targets MUST satisfy:
31
40
32
41
## Withdraw policy (gas‑efficient; no loops)
33
42
- Withdraw uses a single vault only: resolve the vault via `affiliates(owner)`; if empty, use `SEND_EARN()`.
43
+
- Redeem from that resolved vault exclusively; do not call multiple vaults.
44
+
- If the resolved vault position is insufficient to satisfy the requested assets/shares, revert.
45
+
- A non‑standard helper such as `withdrawFrom(vault, assets)` may be introduced later for finer control.
46
+
47
+
## Transfers (re‑attribute underlying shares, no external calls)
48
+
- Wrapper shares are transferable.
49
+
- On transfer, the wrapper proportionally re‑attributes the sender’s underlying shares across the sender’s active vaults to the receiver in proportion to the transferred wrapper shares over the sender’s pre‑transfer wrapper balance.
50
+
- This is an in‑memory loop over the sender’s active vault list; no vault external calls are made.
51
+
- Practical effect: the receiver can withdraw from any vaults the sender had underlying shares in (subject to the receiver’s affiliate resolution at withdraw time).
52
+
- Withdraw uses a single vault only: resolve the vault via `affiliates(owner)`; if empty, use `SEND_EARN()`.
34
53
- Redeem from that resolved vault exclusively; do not loop across multiple vaults.
35
54
- If the resolved vault position is insufficient to satisfy the requested assets/shares, revert. A non‑standard helper such as `withdrawFrom(vault, assets)` may be introduced later if finer control is desired.
36
55
56
+
## Total assets (view-only)
57
+
-`totalAssets()` sums across wrapper-held positions:
0 commit comments