Trigger Pattern SHARE_ALLOCATION flag detected in pattern scan - Inject Into Breadth agents, depth-edge-case
Trigger Pattern: SHARE_ALLOCATION flag detected in pattern scan Inject Into: Breadth agents, depth-edge-case Finding prefix:
[SAF-N]Rules referenced: R5, R10, R13, R14
shares|allocation|distribute|pro_rata|proportional|vest|reward_per_share|
mint|burn|reward_index|cumulative|checkpoint|epoch_reward
Analyze fairness of share/token allocation mechanisms on Soroban where users receive shares proportional to deposits, contributions, or participation — checking for late-entry advantages, storage-default manipulation, authorization bypass, and time-weighting omissions.
Soroban arithmetic constraint: ALL share math uses i128 (signed 128-bit integer). There is no floating-point arithmetic. All pro-rata and time-weighted calculations must use multiply-then-divide integer patterns. Division truncates toward zero. Overflow with unchecked on wraps silently in Rust release mode — use .
*i128checked_mulIdentify which pattern the protocol uses:
| Type | Soroban Pattern | Key Risk |
|---|---|---|
| Pro-rata snapshot | Shares minted at fixed ratio via mint() on custom SEP-41 token at deposit time | Late depositors dilute early depositors' accrued value |
| Time-weighted | Per-user Persistent entry tracks reward_per_share_paid: i128 and accrued_rewards: i128 | Checkpoint manipulation, stale reward index |
| Epoch-based | Shares valued per ledger-sequence epoch; epoch tracked in Instance storage | Cross-epoch timing arbitrage at epoch transition ledgers |
| Queue-based | Ordered list in Instance/Persistent storage (Vec or linked entries) | Queue position gaming, partial processing fairness |
For each allocation entry function:
| Entry Function | Accrual Source | Time-Weighted? | Late Entry Possible? | Impact |
|---|
Soroban timing: With ~5s ledger close time, timing attacks require multi-ledger execution. However, in high-value situations an attacker can monitor on-chain state and submit a transaction to land in the specific ledger just before a known distribution event.
For each entry function accepting a recipient: Address parameter that is NOT verified to be the function caller:
| Entry Function | Accepts Recipient != Caller? | Default State for New Persistent Entry | Exploitable? | Impact |
|---|
Check: When a new Persistent entry is created for a recipient address:
reward_per_share_paid: i128 = 0? last_deposit_ledger: u32 = 0?)reward_per_share_paid starts at 0 while the global index is at N, the new entry holder is entitled to ALL historical rewards on their deposit — FINDING (late-entry variant)deposit(recipient, amount) where recipient != caller be used to create a new Persistent entry that captures historical rewards the recipient did not earn?For each admin-settable reward/rate parameter stored in Instance storage:
| Parameter Setter | Staked-Before-Set? | Retroactive Rewards? | Fair? |
|---|
Model the sequence: user deposits (Persistent entry created with current index) → admin sets reward rate → rewards accrue.
For the allocation mechanism identified in Step 1:
| Configuration Step | Storage Key Initialized | Functions Available Before Init | Exploitable Default? |
|---|
initialize_* functions in order.None)?None → unwrap_or(0) defaults?is_initialized flag in Instance storage or an admin check that prevents interaction before configuration completes?If users can interact during partial initialization AND default storage values create unfair advantage → FINDING (minimum Medium, Rule 13: design gap).
For protocols with queue-based or batch processing:
Resource-aware batching: If batch processing iterates over a Vec stored in Instance storage:
Check that entry and exit use consistent valuation:
i128 math: does rounding in deposit vs withdraw consistently favor one party?floor(amount * total_shares / total_value) shares; withdraw gives floor(shares * total_value / total_shares) tokens — rounding may always favor the protocolToken admin risks in Soroban:
For independently-settable allocation rates/shares (e.g., per-pool weights, fee splits, distribution percentages stored as separate Instance storage keys):
| Rate/Weight Setter | Aggregate Constraint | Enforced On-Chain? | What if Sum Exceeds/Falls Short? |
|---|
Soroban-specific: If weights are stored as separate Instance storage keys (one per pool), the setter function may update ONE key without reading and summing ALL keys to validate the aggregate. This is especially problematic when each setter only receives its own key as context.
If aggregate constraint NOT enforced and rates independently settable → FINDING (Rule 14).
Also check: Can admin set a weight to 0 for an active pool? What happens to users with deposits in that pool? (Rule 14 setter regression — setting weight below accumulated state causes division-by-zero or zero-allocation for existing depositors)
For each finding, specify:
i128 example (token amounts in smallest unit, e.g., stroops for XLM)**ID**: [SAF-N]
**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: (see checklist below)
**Rules Applied**: [R5:___, R10:___, R13:___, R14:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: src/{file}.rs:LineN
**Title**: {fairness violation type}
**Description**: {specific issue with i128 numerical example}
**Impact**: {quantified at worst-state parameters — who loses how much, in stroops or token units}
| Step | Required | Completed? | Notes |
|---|---|---|---|
| 1. Classify Allocation Mechanism | YES | ||
| 2. Late Entry Attack Model | YES | ||
| 2c. Cross-Address Deposit Model | YES | Check recipient != caller patterns | |
| 2d. Pre-Setter Timing Model | YES | Model deposit-before-rate-set sequence | |
| 2e. Pre-Configuration State Analysis | YES | Deployment window + uninitialized storage defaults | |
| 3. Queue Position and Batch Processing | IF queue/batch detected | Include resource-aware batch analysis | |
| 4. Share Redemption Symmetry | YES | Include custom token mint + SAC freeze check | |
| 4b. Aggregate Constraint Coherence | IF multiple settable weights | Rule 14 enforcement check |
If any step skipped, document valid reason (N/A, no queue, single pool, no settable weights).