An analysis of the `TrustBonding` contract yields two critical design vulnerabilities stemming from how rewards are allocated based on single point-in-time snapshots. ### 1. Zero-balance reward eclipse when lock expiration strictly equals the epoch's end timestamp **Severity**: High **Location**: `src/protocol/emissions/TrustBonding.sol` -> `userBondedBalanceAtEpochEnd()` and `_userEligibleRewardsForEpoch()` **Root cause**: Epoch rewards are calculated via `_userEligibleRewardsForEpoch()` using the exact `_balanceOf()` of the user at `_epochTimestampEnd(epoch)`. In Curve's `VotingEscrow` logic, an account's lock balance strictly decays to `0` exactly at `unlock_time`. Because `VotingEscrow` locks are purposefully aligned to multiple block periods matching `_epochLength`, a user's final lock expiration will almost certainly land *exactly* on an epoch's end timestamp. At that exact block/timestamp, their evaluated balance evaluates to mathematically `0`. Consequently, `userBondedBalanceAtEpochEnd` returns `0`, causing the user to forfeit 100% of their rewards for their entire final epoch—even though their tokens were fully locked and exposed to risk over the entire duration of that epoch. **Impact**: Users systematically lose the final epoch of yield for their locks. For a minimum lock duration (1 epoch), the user will lock funds but receive absolutely 0 rewards. This represents a deterministic loss of user funds and unrecoverable protocol yield. **Preconditions**: Permissionless. Happens organically at the conclusion of any naturally aligned lock. **PoC sketch**: 1. Global `epochLength` = 1 week. Alice calls `create_lock()` for exactly 1 week at the beginning of Epoch 1. 2. Curve's logic sets her lock `end` at the exact boundary aligned with `_epochTimestampEnd(1)`. 3. Alice participates for the entirety of Epoch 1 with her tokens functionally locked. 4. Epoch 1 completes. In Epoch 2, Alice calls `claimRewards()` requesting Epoch 1 rewards. 5. The contract calls `userBondedBalanceAtEpochEnd(Alice, 1)` at `_epochTimestampEnd(1)`. At this exact timestamp, `_balanceOf` decays to 0. 6. Alice receives `0` user eligible rewards and gets nothing for her lock. **Remediation**: Do not evaluate the balance strictly at the end of the epoch. Because the lock held weight for the actual duration, querying `_balanceOf(account, _epochTimestampEnd(epoch) - epochLength)` (or calculating a time-weighted integral `cumulative_ve`) will properly respect the duration. To stick with a snapshot, snapshotting at `- 1` second from the epoch bounds prevents decaying to an absolute zero entirely. ### 2. Single point-in-time snapshot enables zero-duration JIT (Just-In-Time) reward extraction **Severity**: High **Location**: `src/protocol/emissions/TrustBonding.sol` -> `_userEligibleRewardsForEpoch()` **Root cause**: The protocol uses a single end-of-epoch snapshot `userBondedBalanceAtEpochEnd(account, epoch)` and `totalBondedBalanceAtEpochEnd(epoch)` to determine a user's proportional share of the emissions for the ENTIRE epoch. It does not integrate a time-weighted balance over the epoch's lifecycle (unlike Curve's gauge distributions which use `integrate_fraction`). **Impact**: An attacker can completely bypass the economic risk and opportunity cost of locking tokens during the epoch. They can monitor the mempool or simply wait until the very last block before `_epochTimestampEnd` triggers, deposit massive capital to create a lock, and instantly be entitled to the full proportional share of the *entire preceding epoch's* rewards. While their funds must remain locked for the minimum duration going forward, they have stolen a near-complete epoch's emissions from honest long-term lockers who held through the whole period. **Preconditions**: Permissionless. **PoC sketch**: 1. Epoch 1 lasts 7 days. Honest users create locks on Day 1. 2. At Day 6, 23 hours, 59 minutes, an attacker executes `create_lock()` with a massive amount of TRUST, setting minimum unlock time. 3. Because evaluating `userBondedBalanceAtEpochEnd` strictly reads the balance exactly at Day 7, the attacker commands an overwhelming percentage of `totalBalance`. 4. In Epoch 2, the attacker immediately claims the majority of Epoch 1's rewards despite only locking capital for 1 block of active epoch duration. **Remediation**: Transition strictly to a time-weighted balance integration (like standard Curve `Gauge` metrics checking `user_point_history`) or issue block-by-block checkpoints mapped to epoch rewards, so that eligible rewards are directly proportional to the amount *and* duration of time spent holding in the epoch. ## Slither False Positives - **`divide-before-multiply` in `VotingEscrow._checkpoint` and `_create_lock`**: False positive. Standard Curve veCRV rounding algorithm. It intentionally leverages floor division to align timestamps perfectly with fixed `WEEK` / `_epochLength` boundaries (e.g., `(t / WEEK) * WEEK`). - **`timestamp` comparisons & dangerous strict equalities**: False positive. Timestamps are structurally necessary to compute linear rate decay accurately inside ve-systems, and `block.timestamp == t` indices track discrete boundaries. - **`erc20-interface` warning on `ISatelliteEmissionsController.transfer(address,uint256)`**: False positive. Slither expects `boolean` return value for ERC-20 `transfer()`. The Satellite Emissions Controller is not a strict ERC-20 but rather a protocol treasury contract with a specialized method signature. ## High-confidence observations - **Safe Zero-Utilization Bounds:** System utilization logic inherently protects against catastrophic division-by-zero during empty/idle epochs; `_getSystemUtilizationRatio` successfully intercepts instances where `rawUtilizationDelta == 0` prior to parsing a `target == 0`. - **Claim Stability via Time Windows:** `_getSystemUtilizationRatio(N)` cleanly evaluates `totalClaimedRewardsForEpoch[N-1]`. Because rewards for `N` can only be calculated and paid out in `N+1` (where claims for `N-1` are structurally locked out), the parameters affecting `userRewards` remain immutable at payout time, preventing APY drift vulnerabilities during the claim process.