Enumerate state-changing entrypoints in Solidity contracts, trace user-supplied parameters into state mutations and external calls, and flag unsanitized inputs. Use when auditing attack surface, reviewing access control, checking input validation, or scoping a security review.
How this skill is triggered — by the user, by Claude, or both
Slash command
/solidity-language-server:entrypoint-analysis <file-path> [function-name] [--scope state-changing|all]<file-path> [function-name] [--scope state-changing|all]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Security-focused analysis of how user-controlled inputs flow through state-changing entrypoints into sensitive sinks (storage writes, external calls, token transfers, access control decisions).
Security-focused analysis of how user-controlled inputs flow through state-changing entrypoints into sensitive sinks (storage writes, external calls, token transfers, access control decisions).
$ARGUMENTS — Parse the arguments:
external and public functions.state-changing (default) or all (includes view/pure).Use LSP documentSymbol on the file to get all function symbols with their line numbers, visibility, and mutability.
For each external or public function, read the function signature (2-3 lines) and classify:
external, publicpayable, default) vs read-only (view, pure)onlyOwner, onlyPoolManager, custom require/if-revert on msg.sender in the first 5 lines of the body)Filter to state-changing functions unless --scope all was specified.
For each entrypoint, list every parameter including implicit ones:
msg.sender — who is callingmsg.value — ETH attached (if payable)calldata/hookData — opaque bytes that get decoded insideOutput a table:
## Entrypoints
| Function | Line | Visibility | Payable | Access Control | Parameters |
|---|---|---|---|---|---|
| swap() | 266 | external | yes | none (permissionless) | key, zeroForOne, amountIn, tick, minAmountOut, deadline, msg.value |
| fill() | 758 | external | yes | none (permissionless) | order, zeroForOne, fillAmount, msg.value |
For each entrypoint, use LSP outgoingCalls (depth 2) to identify all internal functions called. Then grep the entrypoint body AND each called internal function for state mutation patterns:
Patterns to grep in the target file (restrict to function line ranges):
\w+\[.*\]\s*= — mapping/array write
\w+\[.*\]\s*\+= — mapping/array increment
\w+\[.*\]\s*\-= — mapping/array decrement
delete\s+\w+ — storage deletion
\w+\s*=\s*(?!.*==) — state variable assignment (exclude comparisons)
\.push\( — array push
\.pop\( — array pop
emit\s+\w+ — events (not mutations, but mark state-change intent)
For each mutation found:
Output per entrypoint:
### swap() — State Mutations
| State Variable | Operation | Line | Derived From | Validated? |
|---|---|---|---|---|
| orderDeadline[orderId][zeroForOne] | write | 287 | deadline (param) | yes — checked > block.timestamp |
| balancesIn[orderId][zeroForOne] | += | 713 | amountIn (param) - feeAmount | yes — amountIn > 0 |
| balancesOut[orderId][zeroForOne] | += | 718 | _computeAmountOut(netInput, tick) | no — tick unchecked (by design) |
For each entrypoint, combine LSP outgoing calls with grep for low-level calls (.call{, .call(, .staticcall(, .delegatecall(). For each external call:
Output per entrypoint:
### fill() — External Calls
| Call | Line | Target Source | Data Source | Value Source | Checked | Re-entrant? |
|---|---|---|---|---|---|---|
| token.call(transferFrom) | 740 | Currency.unwrap(outputCurrency) — from storage | filler (msg.sender), recipient (from order), amount (from state) | none | yes + balance | YES |
| POOL_MANAGER.transfer() | 832 | immutable POOL_MANAGER | order.swapper, inputCurrency.toId(), preview.userShare | none | trusted | no |
For each user-supplied parameter in each permissionless entrypoint, trace forward through the function body and called internal functions to answer:
Classification for each parameter→sink path:
require/if-revert or bounded by trusted state before reaching the sink.Output a taint flow table per entrypoint:
### swap() — Parameter Taint Flows
| Parameter | Sink | Path | Validation | Classification |
|---|---|---|---|---|
| tick | balancesOut (via _computeAmountOut) | swap → _processOrder → _computeAmountOut | minAmountOut slippage check | BOUNDED — user picks price, minAmountOut is backstop |
| key | pools[key.toId()] lookup | swap → router → beforeSwap → _processOrder | POOL_MISMATCH revert | SANITIZED — invalid pool reverts entire tx |
| amountIn | balancesIn write | swap → router → beforeSwap → _processOrder | require(amountIn > 0) | SANITIZED |
| msg.value | ETH settlement | swap → router → _settleExactInput | msg.value == amountIn | SANITIZED |
Check whether state written by one entrypoint is read by another without re-validation:
swap() (balancesIn, balancesOut, orderDeadline, feeRemaining)fill() and cancelOrder() — are the reads safe given any possible write values?Flag cases where:
Present the full analysis in this order:
Produce a summary table of all findings, ordered by severity:
## Findings
| # | Severity | Entrypoint | Parameter/State | Finding | Classification |
|---|---|---|---|---|---|
| 1 | HIGH | fill() | outputCurrency (token.call) | Arbitrary ERC-20 transferFrom after state mutation — reentrancy possible | UNCHECKED target |
| 2 | MED | swap() | tick | User-chosen price with no oracle validation at swap time | BOUNDED (minAmountOut) |
| 3 | INFO | cancelOrder() | order.swapper | Controlled by caller but content-addressed — forgery produces revert | BOUNDED |
Severity classification:
After all analysis phases are complete, produce a structured security review report that consolidates everything into an auditor-readable document. The report should be self-contained — a reader who has not seen the intermediate tables should understand the contract's attack surface.
Format:
# Security Review: <ContractName>
**File**: <file-path>
**Solidity**: <pragma version>
**Inherits**: <parent contracts>
**Date**: <today>
## 1. Executive Summary
<2-3 sentence overview: what the contract does, how many state-changing entrypoints it exposes, the most significant risks found.>
## 2. Attack Surface Overview
<Total entrypoints, how many are permissionless vs access-controlled, whether the contract is payable, whether it holds tokens/ETH, whether it has callback re-entry points.>
### 2.1 Entrypoints
<The entrypoints table from Phase 1.>
### 2.2 Trust Boundaries
<List each trust boundary: who can call what, what external contracts are trusted (PoolManager, oracles, tokens), what addresses are user-controlled.>
## 3. Input Validation Analysis
For each permissionless entrypoint, present:
### 3.N <functionName>()
**Parameters**: <list with types>
**Access**: <permissionless | role-gated>
#### State Mutations
<State mutations table from Phase 2, Step 4>
#### External Calls
<External calls table from Phase 2, Step 5>
#### Parameter Taint Flows
<Taint flow table from Phase 3, Step 6>
#### Assessment
<1-3 sentences: is this entrypoint safe? What is the worst-case scenario for a malicious caller?>
## 4. Cross-Entrypoint State Coupling
<Cross-entrypoint dependency analysis from Phase 3, Step 7. Focus on: can a permissionless caller manipulate state that affects another user's execution path?>
## 5. Findings
<Findings table from Phase 4, Step 9, sorted by severity.>
For each HIGH or CRITICAL finding, add a detailed section:
### Finding N: <title>
- **Severity**: <CRITICAL|HIGH|MEDIUM|LOW|INFO>
- **Entrypoint**: <function>
- **Parameter/State**: <what is unsanitized>
- **Sink**: <where it ends up>
- **Impact**: <what an attacker can achieve>
- **Proof sketch**: <step-by-step attack scenario, or why exploitation is infeasible>
- **Recommendation**: <fix suggestion, if applicable>
## 6. Appendix: Raw Data
<Link back to the intermediate tables (state mutations, external calls, taint flows) for reference. Include them inline if the report is standalone.>
Important formatting rules:
When this skill is invoked as an agent by the security-review orchestrator, also append structured FINDING and LEAD blocks after the normal output. These blocks enable deduplication across agents.
For each UNCHECKED or PARTIAL taint flow with HIGH/CRITICAL severity:
FINDING | contract: Name | function: func | line: N | bug_class: kebab-tag | group_key: Contract | function | bug-class
path: parameter → transformation → sink (state write or external call)
proof: concrete parameter values that reach the sink, with validation gaps identified
description: one sentence
fix: one-sentence suggestion
For each BOUNDED taint flow or MEDIUM/LOW severity:
LEAD | contract: Name | function: func | line: N | bug_class: kebab-tag | group_key: Contract | function | bug-class
code_smells: what you found (e.g., unchecked tick, zero minAmountOut allowed)
description: one sentence explaining the risk and what defense exists
npx claudepluginhub asyncswap/skillsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.