Smart contract security review methodology. Use when auditing Solidity code, analyzing vulnerabilities, or verifying security findings.
Run these skills in order, then verify findings before reporting:
Step 1: entry-point-analyzer Map all public/external entry points and their parameters.
Step 2: audit-context-building Deep code analysis. Apply 5 Whys to every conditional branch involving user-controlled parameters.
Step 3: guidelines-advisor Best practices and common pitfalls check.
Step 4: Verification For each raw finding, verify by tracing exact execution path. Filter findings with confidence < 8/10. Never report without verificationβfalse positives waste developer time.
After completing all steps, output ONLY a findings table.
Include ONLY findings that could lead to: loss of funds, unauthorized access, reward manipulation, or denial of service.
Exclude: documentation gaps, gas optimizations, code style, theoretical issues with unsupported token types.
| ID | Finding | Severity | File:Line | Evidence |
|----|---------|----------|-----------|----------|
| 1 | [Description] | CRITICAL/HIGH/MEDIUM/LOW | `File.sol:XX` | [Proof or attack path] |
Every user-provided input must be validated. Apply 5 Whys to understand why each branch exists and whether an attacker can exploit it:
Observation: Token transfer is skipped when owner == address(this)
Why #1: Why is the transfer skipped?
β The code assumes tokens are already in the contract
Why #2: Why would tokens already be there?
β Internal flow deposits tokens before calling
Why #3: Why is there no check that internal flow actually called?
β The code trusts only internal flows use owner == address(this)
Why #4: Why would only internal flows use that value?
β No reason - external callers can pass any owner value
Why #5: Why is this dangerous?
β Attacker creates unbacked stake, drains rewards
Special values to test for each user-controlled parameter:
address(0) - zero addressaddress(this) - the contract itselfmsg.sender - caller addresstype(uintX).max - maximum valuesValidation questions:
Example vulnerability:
// VULNERABLE - allows arbitrary owner without validation:
function stakeOnBehalf(..., address owner, ...) external {
// Missing: if (owner == address(this)) revert InvalidOwner();
depositId = _stake(amount, delegatee, beneficiary, owner, false);
}
// In _stake():
if (owner != address(this)) {
ds.staking.stakeToken.safeTransferFrom(msg.sender, proxy, amount);
} else {
// NO TRANSFER - assumes internal use only
// But stakeOnBehalf allows external callers to reach this path!
}
Always trace: (1) where tokens come FROM, (2) where tokens go TO, (3) who gets ownership/benefits
// Tokens: FROM msg.sender, TO proxy
ds.staking.stakeToken.safeTransferFrom(msg.sender, proxy, amount);
// Questions to answer:
// - Who pays the tokens? (msg.sender vs arbitrary parameter)
// - Who receives the tokens/deposit?
// - Who gets the benefits (rewards, ownership)?
// - What happens if transfer is skipped?
Both regular function calls and low-level calls create new call frames. The difference is how reverts are handled:
// Regular function call - revert propagates automatically:
function foo() external {
bar(); // If bar() reverts, foo() reverts too, all state rolled back
}
// Low-level call - revert is captured, caller decides:
function foo() external {
storage.value = 1; // This persists even if inner call reverts!
(bool success, ) = address(this).call(
abi.encodeCall(IContract.method, (arg))
);
if (!success) {
// Inner call reverted - but we continue execution
// State changes INSIDE the .call() are rolled back
// State changes BEFORE the .call() persist!
}
}
Key points:
address.call(): captures revert, returns (false, errorData), caller decides to bubble up or continuetry-catch: syntactic sugar that also captures reverts, state before try persists on catch.call() or try persist even if inner call reverts| Claim | Reality Check |
|---|---|
| "State persists after revert" | Only if low-level call captures the revert |
| "Overflow possible" | Check if token supply constrains values |
| "No validation on read" | Check if write-side is protected |
| "Silent failure is bug" | May be intentional for batch protection |
| "Anyone can call" | Check if caller pays tokens (gift vs theft) |
Recognize when "vulnerabilities" are intentional:
| Severity | Criteria |
|---|---|
| CRITICAL | Direct fund loss, unauthorized access to funds |
| HIGH | Significant economic impact, DoS on critical functions |
| MEDIUM | Limited impact, requires specific conditions |
| LOW | Best practice violations, design concerns |