Agent skill
ptb-composability
Trigger Pattern PTB flag (always for Sui -- Programmable Transaction Blocks are the Sui transaction model) - Inject Into Breadth agents, depth-external, depth-state-trace
Install this agent skill to your Project
npx add-skill https://github.com/PlamenTSV/plamen/tree/main/agents/skills/sui/ptb-composability
SKILL.md
Skill: PTB_COMPOSABILITY (Sui)
Trigger Pattern: PTB flag (always for Sui -- Programmable Transaction Blocks are the Sui transaction model) Inject Into: Breadth agents, depth-external, depth-state-trace Finding prefix:
[PTB-N]Rules referenced: R4, R5, R8, R10, R15
Programmable Transaction Blocks (PTBs) are Sui's transaction composition primitive. A single PTB can contain up to 1024 commands, each calling a different function, with return values routed between commands. This enables atomic multi-step sequences that are fundamentally different from EVM's single-entry-point model. Every public function is a potential PTB command -- there is no "internal only" visibility equivalent to Solidity's internal.
STEP PRIORITY: Steps 1 (Single-Call Assumption Audit) and 4 (Atomic Read-Modify-Write) are where HIGH/CRITICAL severity findings most commonly hide. Do NOT rush these steps. If constrained, skip conditional sections (6, 7) before skipping 1, 3, or 4.
1. Entry Point Inventory
For EVERY public and entry function in the protocol, classify composability:
| # | Function | Module | Visibility | Returns Value? | Takes Shared Obj? | Composable in PTB? | Notes |
|---|---|---|---|---|---|---|---|
| 1 | {func} | {mod} | public / entry / public(package) | YES / NO | YES / NO | YES / NO | {context} |
Sui visibility semantics:
entryfunctions: callable from PTB but return values CANNOT be used by subsequent commands (consumed or discarded within the command). Objects can only be transferred, not returned.publicfunctions: fully composable -- return values pass between commands. This is the primary composability surface.public(package): callable only by other modules in the same package. NOT callable from PTB. Safe from external composition.fun(private): Not callable from outside the module. Safe.
Key observation: Any function that is public (not entry, not public(package)) is fully composable. Its return values can be routed to ANY other function in the same PTB.
1b. Single-Call Assumption Audit
For EVERY public and entry function, check whether its security model implicitly assumes it is the ONLY function called in the transaction:
| # | Function | Assumes Single-Call? | What Assumption? | Breakable via PTB? | Impact |
|---|---|---|---|---|---|
| 1 | {func} | YES/NO | {describe assumption} | YES/NO | {impact} |
Common single-call assumptions that PTBs break:
- Post-call state check: Function reads state, performs action, then a SEPARATE function checks the result. Attacker inserts commands between action and check.
- Balance snapshot: Function reads a balance at start, assumes balance changed only due to its work. Another PTB command could have changed the balance.
- One-operation-per-transaction: Protocol assumes a user can only deposit OR withdraw in a single transaction. PTB allows deposit + borrow + withdraw atomically.
- Temporal separation: Protocol assumes time must pass between action A and action B (cooldown). If both functions are callable, PTB executes them in the same transaction with zero time delta.
- Reentrancy-like patterns: Function A updates state partially, function B reads partial state. PTBs naturally enable "call A then call B" -- state IS updated between commands.
Key question for each function: "If an attacker calls this function as command N in a PTB, and can execute arbitrary commands 1..N-1 before it and N+1..1024 after it, what can go wrong?"
2. Multi-Step Composition Analysis
For each public function that returns objects:
| # | Function | Input Objects | Output Objects | Can Output Be Routed To? | Can Be Called Multiple Times? |
|---|---|---|---|---|---|
| 1 | {func} | {owned/shared/immutable} | {Coin<T> / Object / HotPotato} | {any function accepting this type} | YES/NO |
Return value routing checks:
- Coin routing: If function A returns
Coin<T>, can it be routed to function C (external) instead of intended function B? - Coin splitting: PTB has native
SplitCoinscommand. ReturnedCoin<T>can be split. Does protocol assume full amount passed? - Coin merging: PTB has native
MergeCoinscommand. Can attacker merge extra coins to inflate amounts? - Mutable reference routing:
&mut Objectreferences are borrowed for a command and released after. Same object can be passed to multiple commands sequentially. Does command N assume the object wasn't modified by command N-1? - Recipient mismatch: If function returns a value-bearing object, the final
TransferObjectscommand determines the recipient.
2b. Value Interception Pattern
Model this specific attack for each function returning value:
1. Attacker calls protocol_function_A() -> returns Coin<USDC> (intended for pool)
2. Attacker routes Coin<USDC> to their own address via TransferObjects
3. Protocol expects the Coin was consumed by the next step but it was intercepted
Check: Does the protocol rely on PTB command ordering to ensure returned values reach the right destination? If YES -> the user controls the ordering, not the protocol.
3. Flash Loan via PTB
Without explicit flash loan protocols, PTBs enable flash-loan-like patterns:
1. [Command 1] Split large Coin<SUI> from attacker's balance
2. [Command 2] Deposit into protocol (inflates TVL/balance)
3. [Command 3] Trigger reward distribution (calculated on inflated balance)
4. [Command 4] Withdraw from protocol
5. [Command 5] Join coins back (net zero capital, captured rewards)
Can protocol state be manipulated between PTB commands?
| Function Pair | Shared State | Deposit-Action-Withdraw Possible? | Defense? |
|---|---|---|---|
| {deposit, claim} | {pool balance} | YES/NO | {cooldown? same-epoch check? minimum lock?} |
Hot potato flash loan pattern:
1. borrow(pool, amount) -> (Coin<T>, FlashReceipt) // Creates hot potato
2. [attacker does arbitrary operations with Coin<T>]
3. repay(pool, coin, receipt) // Consumes hot potato
Checks for hot potato flash loans:
- Does
repay()verify returned amount >= borrowed amount + fee? - Can attacker call OTHER protocol functions between borrow and repay that benefit from temporarily inflated balance?
- Is
FlashReceipttype-parameterized to prevent cross-pool receipt reuse? - Verify hot potato struct has NO abilities (no
key,store,drop,copy)
4. Shared Object Mutation Ordering
Within a PTB touching shared objects:
4a. Oracle Manipulation Within PTB
1. [Command 1] Call DEX swap to move on-chain price
2. [Command 2] Call protocol function that reads manipulated price
3. [Command 3] Reverse DEX swap to restore price
4. Net effect: Protocol acted on manipulated price, attacker profited
Check: Does protocol read spot prices (manipulable) or TWAP/oracle prices (resistant)?
4b. Balance Manipulation Within PTB
1. [Command 1] Deposit large amount (inflate balance/shares)
2. [Command 2] Trigger reward distribution (on inflated balance)
3. [Command 3] Withdraw deposited amount
4. Net effect: Captured rewards with zero time commitment
4c. State Toggling Within PTB
1. [Command 1] Set state to value A (e.g., via AdminCap function)
2. [Command 2] Perform action requiring state A
3. [Command 3] Reset state back to original value B
4. Net effect: Privileged action performed, state appears unchanged
Check: Possible if attacker controls an AdminCap? (Rule 6: semi-trusted role analysis)
4d. Shared Object Reorder Sensitivity
| Shared Object | Invariant | Commands That Mutate It | Sequence-Dependent? | Exploitable? |
|---|---|---|---|---|
| {obj} | {invariant description} | {cmd1, cmd2, cmd3} | YES/NO | {if YES, describe exploit} |
Check for each shared object: Write down the invariant. Can a sequence of 2-3 valid individual operations (each maintaining the invariant) produce a combined state that violates the invariant?
5. Hot Potato Enforcement
For each zero-ability struct (hot potato):
| # | Hot Potato Type | Created By | Consumed By | Abilities | Bypass Possible? |
|---|---|---|---|---|---|
| 1 | {Receipt} | {borrow()} | {repay()} | NONE (confirmed) | {analysis} |
Checks:
- Verify struct has NO abilities. If it has
drop-> NOT a hot potato. - Is the consuming function the ONLY function accepting this type?
- Can multiple hot potatoes be created in a single PTB? Does consumption order matter?
- Does consuming function validate the hot potato matches the creating context?
- Can attacker cause consumption precondition to fail AFTER hot potato created? (entire PTB aborts -- griefing vector)
- Can a wrapper with
storeability store the hot potato? (bypass via external module -- check for public generic wrappers)
6. Object Wrapping/Unwrapping in PTB
Can objects be wrapped, manipulated, and unwrapped within a single PTB to bypass checks?
| Wrap Operation | Unwrap Operation | What's Inside | Check Bypassed? |
|---|---|---|---|
| {wrap_func} | {unwrap_func} | {object with restrictions} | {describe bypass or NONE} |
Pattern: Object has transfer restrictions (key only, no store). Attacker wraps it inside a store-capable struct, transfers the wrapper, then unwraps on the other side. If wrap and unwrap are both public functions -> transfer restriction bypassed within a single PTB.
7. Gas Budget Manipulation
PTB gas budget is shared across all commands.
| Attack Vector | Description | Check |
|---|---|---|
| Many-small-operations | 1000+ tiny operations to circumvent aggregate limits | Do aggregate limits track across PTB commands? |
| Object creation spam | PTB creates many objects (up to 1024 per PTB) | Unbounded object creation paths? |
| Gas exhaustion griefing | Craft PTB that consumes max gas on revert | Gas charged on abort -- attacker pays |
Typically lower severity: Sui's gas model charges the sender, so resource attacks are self-penalizing. Focus on aggregate limit bypass.
Output Schema
## Finding [PTB-N]: Title
**Verdict**: CONFIRMED / PARTIAL / REFUTED / CONTESTED
**Step Execution**: check1,2,3,4,5,6,7 | skip(reason) | uncertain
**Rules Applied**: [R4:___, R5:___, R8:___, R10:___, R15:___]
**Severity**: Critical/High/Medium/Low/Info
**Location**: sources/{module}.move:LineN
**PTB Attack Type**: SINGLE_CALL_ASSUMPTION / RETURN_VALUE_ROUTING / FLASH_LOAN_VIA_PTB / ATOMIC_MANIPULATION / HOT_POTATO_BYPASS / WRAP_UNWRAP_BYPASS / AGGREGATE_LIMIT
**Attack Sequence**:
1. [Command 1]: {what attacker does}
2. [Command 2]: {what attacker does}
3. [Command N]: {what attacker does}
**Net Effect**: {what the attacker gained}
**Description**: What is wrong
**Impact**: What can happen (fund loss, unfair value capture, invariant violation)
**Evidence**: Code showing vulnerability + PTB command sequence
Step Execution Checklist (MANDATORY)
| Step | Required | Completed? | Notes |
|---|---|---|---|
| 1. Entry Point Inventory | YES | All public/entry functions classified | |
| 1b. Single-Call Assumption Audit | YES | HIGH PRIORITY -- every public function checked | |
| 2. Multi-Step Composition Analysis | YES | All return values checked | |
| 2b. Value Interception Pattern | YES | Value-returning functions modeled | |
| 3. Flash Loan via PTB | YES | Deposit-action-withdraw patterns | |
| 4. Shared Object Mutation Ordering | YES | HIGH PRIORITY -- oracle, balance, state toggle | |
| 4d. Shared Object Reorder Sensitivity | YES | Invariant + multi-command sequences | |
| 5. Hot Potato Enforcement | IF hot potatoes exist | Zero-ability verified | |
| 6. Object Wrapping/Unwrapping | IF wrap/unwrap functions exist | Transfer restriction bypass | |
| 7. Gas Budget Manipulation | IF aggregate limits exist |
Cross-Reference Markers
After Step 1b: If single-call assumption found on fund-critical function -> immediate HIGH finding.
After Step 3: Cross-reference with SHARE_ALLOCATION_FAIRNESS for deposit-action-withdraw reward capture.
After Step 4a: Cross-reference with ORACLE_ANALYSIS if on-chain DEX prices are used.
After Step 5: Cross-reference with ABILITY_ANALYSIS Section 5 (Hot Potato Enforcement).
If any step skipped, document valid reason (N/A, no hot potatoes, no external calls, no aggregate limits).
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?