Agent skill
share-allocation-fairness
Trigger Pattern SHARE_ALLOCATION flag detected in pattern scan - Inject Into Breadth agents, depth-edge-case
Install this agent skill to your Project
npx add-skill https://github.com/PlamenTSV/plamen/tree/main/agents/skills/sui/share-allocation-fairness
SKILL.md
Skill: Share Allocation Fairness (Sui)
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|
balance::join|balance::split|coin::mint|reward_index|cumulative|epoch.*reward
Purpose
Analyze fairness of share/token allocation mechanisms on Sui where users receive Coin<T> shares or Balance<T> proportional to deposits, contributions, or participation -- checking for late-entry advantages, PTB-based timing exploitation, queue-position gaming, and time-weighting omissions.
Sui-specific share representations:
Coin<ShareToken>: Fungible, freely transferable (haskey + store). User holds as owned object.Balance<ShareToken>inside a position object: Non-transferable accounting. Locked within the protocol's object model.u64field in a shared/owned struct: Simple numerical tracking, no token representation.- Check which representation is used -- it affects transferability, composability, and gaming vectors.
Methodology
STEP 1: Classify Allocation Mechanism
Identify which pattern the protocol uses:
| Type | Sui Pattern | Key Risk |
|---|---|---|
| Pro-rata snapshot | Shares minted at fixed ratio via balance::split/coin::mint_balance at deposit time |
Late depositors dilute early depositors' accrued value |
| Time-weighted | Per-user owned object tracks reward_per_share_paid and accrued_rewards with clock::timestamp_ms() |
Checkpoint manipulation, discrete vs continuous accrual |
| Queue-based | Table/VecMap in shared object stores pending deposits | Queue position gaming, PTB-based front-running |
| Epoch-based | Shares valued per Sui epoch boundary via tx_context::epoch() |
Cross-epoch timing arbitrage at epoch transition |
STEP 2: Late Entry Attack Model
For each allocation entry function:
- Identify accrual source: What generates value for existing share holders? (yield from external DeFi, fees collected in shared pool, token emissions via TreasuryCap)
- Trace timing: When does accrued value become claimable vs when can new shares enter? Is there a separate crank/update function?
- Check for time-weighting: Does allocation account for HOW LONG shares were held, or only THAT shares are held at checkpoint time?
- Model attack: Can a depositor enter AFTER value accrues but BEFORE distribution, capturing value they did not earn?
| Entry Function | Accrual Source | Time-Weighted? | Late Entry Possible? | Impact |
|---|
Sui timing model:
clock::timestamp_ms()provides millisecond-precision timestamps (read from theClockshared object)- Timestamps advance per checkpoint (~0.5-2s), NOT per transaction
- Multiple transactions within the same checkpoint see the SAME timestamp
- Implication: time-weighted calculations based on
clock::timestamp_ms()have ~0.5-2s granularity. An attacker can deposit and withdraw within the same checkpoint and see zero time elapsed, potentially capturing rewards with zero time commitment.
PTB-specific timing: With PTB composability, an attacker can compose multiple function calls in a single atomic transaction: [deposit] -> [trigger_distribution] -> [withdraw] within one PTB. This is more powerful than EVM flash loans for timing attacks because PTBs execute atomically with no inter-step cost.
STEP 2c: Cross-Address Deposit Model
For each entry function accepting a recipient address parameter:
| Entry Function | Accepts Recipient? | Default State for New Recipient | Exploitable? | Impact |
|---|
Check: When a new position object or dynamic field is created for a recipient:
- What is the DEFAULT state? (
reward_per_share_paid = 0?last_deposit_epoch = 0?) - If
reward_per_share_paidstarts at 0 while the global index is at N, the new position holder captures ALL historical rewards on their deposit -- FINDING - Can
deposit(recipient, coin)whererecipient != sendercreate a position that captures historical rewards the recipient did not earn? - On Sui, this may manifest as a new owned object created for the recipient (clean state -- typically safe) or a new dynamic field added to a shared object keyed by address (check default values).
STEP 2d: Pre-Setter Timing Model
For each admin-settable reward/rate parameter:
| Parameter Setter | Cap Required | Staked-Before-Set? | Retroactive Rewards? | Fair? |
|---|
Model: user deposits (position created with current index) -> admin sets reward rate -> rewards accrue.
- Does the user receive retroactive rewards for the period BEFORE the rate was set?
- Is the global reward index updated atomically with the rate change in the same function call?
2e. Pre-Configuration State Analysis
For the allocation mechanism identified in Step 1:
| Configuration Step | Parameter Set | Functions Available Before Set | Exploitable Default? |
|---|
- What is the deployment/initialization sequence? In Sui,
initruns once at package publish. What configuration happens ininitvs subsequent admin transactions? - For each step: what functions are callable BEFORE this configuration completes?
- Are there reward/share calculations that use unconfigured (zero/default) values in shared objects?
- Can a user deposit/stake before full configuration and receive outsized rewards/shares?
- Is there a version flag or
is_initializedcheck that gates user interactions?
Sui-specific: init() runs atomically at publish. If configuration requires MULTIPLE transactions (init -> configure_pool -> set_rates), there are windows between these transactions where the protocol is partially configured.
If users can interact during partial configuration AND default values create unfair advantage -> FINDING (minimum Medium, Rule 13: design gap).
STEP 3: Queue Position and Batch Processing
For protocols with batch/queue processing:
- Ordering fairness: Is queue order FIFO (Table insertion order), arbitrary (admin-chosen), or manipulable (PTB composition order)?
- Partial processing: Can admin process some deposits but not others within a batch? Does the batch function iterate with a limit?
- Cross-batch state: Does processing order within a batch affect allocation ratios?
- Deposit splitting: Can a user split one large
Coin<T>into many small deposits (viacoin::splitin a PTB) for queue advantage or per-deposit limit bypass?
Sui-specific ordering:
- Transactions touching only owned objects are processed without consensus (fast path) -- no ordering manipulation
- Transactions touching shared objects go through consensus -- validator-influenced ordering within checkpoint
- PTB atomicity: all commands execute atomically, batch processing within a single PTB is all-or-nothing
- An attacker can use PTB to atomically: read queue state -> deposit at favorable position -> trigger processing -> claim
STEP 4: Share Redemption Symmetry
Check that entry and exit use consistent valuation:
- Mint vs burn ratio: Are shares minted at the same exchange rate they can be burned? (check share price calculation in both deposit and withdraw)
- Pending claims: Can unclaimed reward Balance<T> dilute active shares' value? (rewards already owed but counted in TVL)
- Withdrawal queue: Does withdrawal ordering create unfair priority?
Sui-specific redemption:
- If shares are
Coin<ShareToken>, user burns them via protocol function. Check: can user transfer shares to another address and redeem there to bypass cooldowns? - If shares are
Balance<ShareToken>inside position object, redemption requires the position object. Check: can position object be transferred (hasstore?) to bypass restrictions? - First-depositor / last-withdrawer edge cases: what happens when
total_supply == 0and someone deposits? (division by zero in share calculation?)
TreasuryCap authority risks:
- Can TreasuryCap be used outside of deposit logic to inflate share supply?
- Is TreasuryCap stored in a shared object with access control? If
storeallows extraction from the wrapper -> unauthorized minting. - If freeze authority pattern exists (rare on Sui): who controls it?
STEP 4b: Aggregate Constraint Coherence (Rule 14)
For independently-settable allocation rates/shares (e.g., per-pool weights, fee splits, distribution percentages):
| Rate/Weight Setter | Aggregate Constraint | Enforced On-Chain? | What if Sum Exceeds/Falls Short? |
|---|
Sui-specific: If weights are stored as dynamic fields on a shared object (one field per pool), the setter function may not iterate all fields to validate the sum. Check: does the setter read all weight dynamic fields and validate the total?
If aggregate constraint NOT enforced and rates independently settable -> FINDING (Rule 14).
Output
For each finding, specify:
- Allocation mechanism type (pro-rata, time-weighted, queue, epoch)
- Whether time-weighting is present or missing
- Concrete attack sequence with numerical example (SUI/token amounts)
- Who benefits and who is harmed
- Whether the attack requires PTB composition or is achievable with single function calls
- Sui-specific timing factors (
clock::timestamp_ms()granularity, checkpoint ordering)
Finding Template
**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**: sources/{module}.move:LineN
**Title**: {fairness violation type}
**Description**: {specific issue with numerical example}
**Impact**: {quantified at worst-state parameters -- who loses how much}
Step Execution Checklist (MANDATORY)
| Step | Required | Completed? | Notes |
|---|---|---|---|
| 1. Classify Allocation Mechanism | YES | ||
| 2. Late Entry Attack Model | YES | PTB composition timing check | |
| 2c. Cross-Address Deposit Model | YES | Check recipient != sender patterns | |
| 2d. Pre-Setter Timing Model | YES | Model deposit-before-rate-set sequence | |
| 2e. Pre-Configuration State Analysis | YES | Post-init() window + unconfigured defaults | |
| 3. Queue Position and Batch Processing | IF queue/batch detected | Include PTB deposit splitting | |
| 4. Share Redemption Symmetry | YES | Include TreasuryCap access 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).
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
integration-hazard-research
Protocol Type Trigger NAMED_EXTERNAL_PROTOCOL (detected when recon finds import/interface for an identifiable external protocol — not standard libraries). Researches known integration hazards of the target protocol.
outcome-determinism
Protocol Type Trigger outcome_determinism - detected when EITHER of these code patterns are present - - Selection from finite depletable pool with fallback behavior (while(full)...
governance-attack-vectors
Protocol Type Trigger governance (detected when Governor, Timelock, voting, proposal, quorum, delegate patterns found) - Inject Into Breadth agents, depth-external, depth-edge-case
vault-accounting
Protocol Type Trigger vault (detected in recon TASK 0 Step 1) - Inject Into Core state agent OR economic design agent (merge via M4 hierarchy)
lending-protocol-security
Protocol Type Trigger lending (detected when recon finds liquidate|borrow|repay|collateral|lend|loan|LTV|healthFactor|interestRate|debtToken) - Inject Into Breadth agents, depth...
dex-integration-security
Protocol Type Trigger dex_integration (detected when recon finds swap|addLiquidity|removeLiquidity|IUniswapV2Router|ISwapRouter|amountOutMin|amountOutMinimum|slippage - AND the...
Didn't find tool you were looking for?