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
docs: fix per-wallet APY walkthrough and align README with post-audit contract
- Fix walkthrough: use net unstake amount (495) not gross (550), correcting
netProfit from 155 to 100 and APY from 19% to 12.3%
- Clarify SQL schema: annotate rnbw_amount as gross, exit_fee for unstakes
- Split event docs: per-wallet events vs PoolTotalsUpdated (global only)
- Add DustSharesRemaining to custom errors table
- Add MIN_SHARES_THRESHOLD (1e14) to security features
- Add dust guard to unstaking flow step 2
- Note TWAC stale-rate approximation for integrators
5. Net amount -- if ceil-rounded fee consumes everything (dust), `netAmount = 0` and shares are burned with no transfer (lets users clear dust positions).
#### Deriving Exchange Rate From Events (No Extra RPC Calls)
340
+
For unstake events: `rnbw_amount` is the gross value before fee (`rnbwValue` from the event), `exit_fee` is the fee deducted. Net received = `rnbw_amount - exit_fee`. The APY formula uses the net amount (see walkthrough below).
341
341
342
-
The exchange rate is implicit in every event — no need to emit it separately:
**Global pool event** — for tracking the global exchange rate over time without reconstructing it from per-user events:
355
+
356
+
| Event | Fields | Exchange Rate |
357
+
|-------|--------|---------------|
358
+
|`PoolTotalsUpdated(totalPooledRnbw, totalShares)`| post-operation pool totals (no user address) |`totalPooledRnbw / totalShares`|
359
+
360
+
`PoolTotalsUpdated` fires after every stake, unstake, and cashback allocation. It is not useful for per-wallet APY because it has no `user` field. Use it when you only need the global rate history (e.g., charting exchange rate over time, computing global APY without the 2-block RPC approach).
349
361
350
362
> Edge case: the very first stake mints 1000 dead shares, so `sharesMinted = amount - 1000`. The derived rate is still correct but slightly above 1.0.
Step 1 — Build capital periods (what Alice had, for how long):
363
375
@@ -367,32 +379,34 @@ Step 1 — Build capital periods (what Alice had, for how long):
367
379
| Mar 1 → Jul 1 | 1050 | 1.0 | 1050 | 122 |
368
380
| Jul 1 → Dec 31 | 550 | 1.1 | 605 | 183 |
369
381
370
-
Each period starts when an event changes the wallet's share balance. The capital is `running_shares × exchange_rate` at that point.
382
+
Each period starts when an event changes the wallet's share balance. The capital is `running_shares × exchange_rate` at that point. The exchange rate changes continuously between events (drip), so this is an approximation — accuracy improves with more frequent user activity.
`totalUnstaked` is the **net** amount after exit fee (matches `totalRnbwUnstaked` in the contract / `net_received` in the indexer), not the gross value.
`totalUnstaked` must use the **net** amount (after exit fee), not the gross. The contract stores this as `totalRnbwUnstaked`; in the indexer compute it as `SUM(rnbw_amount - exit_fee)` for unstake events.
427
+
410
428
> Most users have 2-10 events, so per-wallet queries are trivially fast even at 500 tx/day.
411
429
412
430
---
@@ -425,6 +443,7 @@ Where:
425
443
- Dust unstake handling: when ceil-rounded exit fee consumes 100% of a dust unstake, shares are burned with no transfer (clears dust positions without reverting)
426
444
- 2-step safe transfer: `proposeSafe()` + `acceptSafe()` prevents transfer to wrong address
- Partial unstake dust guard: when partial unstake is enabled, `_unstake` reverts with `DustSharesRemaining` if the user's remaining shares would be > 0 but < `MIN_SHARES_THRESHOLD` (1e14). Prevents attackers from leaving dust shares to bypass the dead-share sweep and inflate the exchange rate.
428
447
- Preview dust guard: `previewStake(user, amount)` returns 0 instead of reverting for dust amounts and for first-time stakers below `minStakeAmount`
429
448
- Recipient guards: `stakeFor` and `stakeForWithSignature` reject `address(0)`, `address(this)`, and `DEAD_ADDRESS` to prevent token locking and dead-share corruption
430
449
- Batch size limit: `batchAllocateCashbackWithSignature` capped at 50 entries with upfront reserve check
@@ -442,6 +461,7 @@ All user-facing errors include contextual parameters for off-chain debugging. Ad
0 commit comments