Analyze incoming and outgoing calls for Solidity functions using LSP call hierarchy with recursive depth tracing and manual grep for low-level calls. Use when analyzing call graphs, tracing call chains, auditing external calls, or checking reentrancy surfaces in Solidity contracts.
How this skill is triggered — by the user, by Claude, or both
Slash command
/solidity-language-server:call-analysis <file-path> [function-name] [--depth N] [--direction both|incoming|outgoing]<file-path> [function-name] [--depth N] [--direction both|incoming|outgoing]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
Perform a comprehensive call graph analysis for Solidity functions using LSP call hierarchy plus manual grep for calls the LSP cannot resolve.
Perform a comprehensive call graph analysis for Solidity functions using LSP call hierarchy plus manual grep for calls the LSP cannot resolve.
$ARGUMENTS — Parse the arguments:
external and public functions in the file.incoming, outgoing, or incoming,outgoing (default: incoming,outgoing).For each target function, recursively trace calls using LSP.
Use LSP documentSymbol on the file to get all function symbols with their line numbers. If a specific function name was given, filter to that function. Otherwise, select all external and public functions (skip view/pure where not interesting — but include them if they call external contracts).
For each target function, perform recursive outgoing/incoming call tracing:
function traceOutgoing(filePath, line, character, currentDepth, maxDepth, visited):
if currentDepth > maxDepth: return "... (depth limit reached, may have deeper calls)"
key = filePath + ":" + line
if key in visited: return "(circular — already visited)"
visited.add(key)
results = LSP outgoingCalls(filePath, line, character)
for each call in results:
record: call.name, call.filePath, call.line, currentDepth
traceOutgoing(call.filePath, call.line, call.character, currentDepth + 1, maxDepth, visited)
Apply the same pattern for incomingCalls when direction includes incoming.
Important rules:
filePath:line to prevent infinite loops on circular/recursive calls.maxDepth (default 3, max 7) and note: "... recursive calls may continue beyond depth limit".v4-core, openzeppelin), continue tracing but mark them as [external-dep].Tag every call node with one of:
[internal] — same contract, private/internal function[inherited] — parent contract function[external-dep] — call into a dependency (OpenZeppelin, Uniswap, etc.)[external-untrusted] — call to an address that could be user-supplied or arbitrary (ERC-20 tokens, oracles, callbacks)[interface] — call through an interface (resolve to concrete type if possible)[leaf] — function has no further outgoing callsLSP cannot resolve dynamic/low-level calls. Grep the target file and all internal contract files for these patterns:
Search the target file and all files discovered in Phase 1 that are in src/:
Patterns to grep (in .sol files under src/):
\.call\{ — low-level call with value
\.call\( — low-level call
\.staticcall\( — static call
\.delegatecall\( — delegate call
abi\.encodeWithSelector\( — often paired with .call()
abi\.encodeWithSignature\( — often paired with .call()
abi\.encodeCall\( — typed call encoding
For each match:
POOL_MANAGER, address(rewardToken)) — tag [resolved][unresolved-dynamic]Currency.unwrap() or similar pattern indicating a token address — tag [unresolved-token]Search for patterns that indicate the contract can be called back:
Patterns to grep:
receive\(\) — ETH receive callback
fallback\(\) — fallback function
onERC721Received — NFT callback
unlockCallback — Uniswap V4 callback
on.*Callback — generic callback pattern
Combine the LSP call tree with the grep-discovered calls. Insert grep-discovered calls at the correct nesting level (the function where the .call() was found).
Format the output as an indented tree with annotations:
functionName() [ContractName:line]
├── childCall() [internal] [ContractName:line]
│ ├── deeperCall() [external-dep] [Dependency:line]
│ │ └── LEAF (pure math)
│ └── .call{value}(transferFrom) [unresolved-token] [ContractName:line]
│ Target: Currency.unwrap(inputCurrency) — arbitrary ERC-20
│ Selector: IERC20.transferFrom.selector
│ Return: checked (reverts on failure)
├── externalCall() [external-untrusted] [OracleInterface:line]
│ └── ... (depth limit reached, may have deeper calls)
└── POOL_MANAGER.unlock() [external-dep] [IPoolManager:line]
└── unlockCallback() [inherited] [ContractName:line] <- callback re-entry
├── POOL_MANAGER.burn() [external-dep] [IPoolManager:line] -> LEAF
└── POOL_MANAGER.take() [external-dep] [IPoolManager:line] -> LEAF
After the tree, produce a summary table:
| External Call | Depth | Parent Function | Target Type | Checked | Risk |
|---|---|---|---|---|---|
| IERC20.transferFrom() | 2 | _deliverOutput | [unresolved-token] | yes | HIGH — reentrancy |
| oracle.getPrice() | 3 | previewSurplus | [external-untrusted] | try/catch | MED — manipulation |
| .call{value}("") | 1 | withdrawNative | [resolved: param] | yes | MED — recipient |
Risk classification:
If any branch hit the depth limit, add a section:
## Branches Truncated at Depth Limit
The following call chains were cut off at depth {N} and may have deeper calls:
- `functionA() -> functionB() -> functionC() -> ...` (last seen in DependencyFile.sol:123)
Recommendation: increase --depth or manually inspect DependencyFile.sol:functionC()
To trace deeper, re-run with: /call-analysis <file> <function> --depth 7
Present the full analysis in this order:
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 HIGH or CRITICAL risk in the External Call Surface table:
FINDING | contract: Name | function: func | line: N | bug_class: kebab-tag | group_key: Contract | function | bug-class
path: caller → function → state change → external call → impact
proof: concrete call chain with node classifications
description: one sentence
fix: one-sentence suggestion
For each MEDIUM or LOW risk:
LEAD | contract: Name | function: func | line: N | bug_class: kebab-tag | group_key: Contract | function | bug-class
code_smells: what you found (e.g., unresolved-token call, missing reentrancy guard)
description: one sentence explaining the risk and what remains unverified
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.