skills/solidity-checklist/SKILL.md
[AUTO-INVOKE] MUST be invoked BEFORE any on-chain operation (cast send, forge script --broadcast). Systematic 6-layer verification checklist: permissions, dependencies, parameters, security, testing, and knowledge capture. Trigger: any task involving sending transactions, deploying contracts, or interacting with on-chain state.
npx skillsauth add 0xlayerghost/solidity-agent-kit solidity-checklistInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Most on-chain operation failures come from skipping systematic verification. Instead of the reactive loop:
deploy -> fail -> paste error -> ask AI -> retry -> fail again -> ask again
This skill enforces a proactive verification flow:
check permissions -> check dependencies -> check params -> check security -> test locally -> execute -> capture knowledge
Every on-chain operation MUST pass through these 6 layers in order before execution.
Layer 1: PERMISSIONS — Do I have the right to do this?
Layer 2: DEPENDENCIES — Do I understand what this affects?
Layer 3: PARAMETERS — Are my inputs correct?
Layer 4: SECURITY — Is this safe to execute?
Layer 5: TESTING — Have I verified locally first?
Layer 6: EXECUTE & CAPTURE — Do it, then record what I learned.
Before any on-chain call, verify the caller has the required access.
| Check | Command | Pass Condition |
|-------|---------|----------------|
| Contract owner | cast call <CONTRACT> "owner()" --rpc-url <RPC> | Returns expected deployer/admin address |
| Role-based access | cast call <CONTRACT> "hasRole(bytes32,address)" <ROLE_HASH> <CALLER> --rpc-url <RPC> | Returns true |
| Whitelist status | cast call <CONTRACT> "isWhitelisted(address)" <CALLER> --rpc-url <RPC> | Returns true (if applicable) |
| Token allowance | cast call <TOKEN> "allowance(address,address)" <OWNER> <SPENDER> --rpc-url <RPC> | Returns >= required amount |
| Paused state | cast call <CONTRACT> "paused()" --rpc-url <RPC> | Returns false |
| Result | Action |
|--------|--------|
| All checks pass | Proceed to Layer 2 |
| Missing permission | Fix permission first (grant role / approve / unpause), then re-check |
| Unsure which permissions are needed | Read contract source — find all onlyOwner, onlyRole, require in target function |
Understand the blast radius — what contracts and state does this operation touch?
| Check | How |
|-------|-----|
| Direct dependencies | Read target function source — list all external calls (IERC20(token).transfer(...), etc.) |
| Upstream contracts | Which contracts hold a reference to this contract's address? |
| Downstream effects | Will this state change trigger callbacks, events, or off-chain indexers? |
| Initialization state | cast call <CONTRACT> "initialized()" --rpc-url <RPC> — is the contract fully set up? |
| Linked addresses | Verify all addresses stored in contract state are correct and not zero |
Before operating on a multi-contract system, build a mental (or written) map:
ContractA (owner: deployer)
├── references: TokenB (ERC20), RouterC
├── authorized callers: deployer, ContractD
└── state deps: must be initialized, TokenB must be approved
ContractD (owner: deployer)
├── references: ContractA, OracleE
└── state deps: OracleE must have price feed set
| Result | Action | |--------|--------| | Dependencies clear | Proceed to Layer 3 | | Unknown dependency | Read contract source or ask — do NOT proceed blindly | | Redeployment needed | Trace the full dependency graph to identify ALL contracts needing address updates |
Verify every input before sending.
| Check | Common Pitfall |
|-------|---------------|
| Address format | Wrong network address, zero address, checksum mismatch |
| Token decimals | Using 18 decimals for a 6-decimal token (USDC/USDT) |
| Amount precision | 1e18 vs 1e6 — off by 12 orders of magnitude |
| Function selector | Calling wrong function or wrong overload |
| Enum values | Passing invalid enum index |
| Array length | Mismatched array lengths in batch operations |
| Deadline/timestamp | Expired deadline, wrong timezone, block.timestamp vs unix |
# Check token decimals before calculating amounts
cast call <TOKEN> "decimals()" --rpc-url <RPC>
# Verify address is a contract (not EOA)
cast code <ADDRESS> --rpc-url <RPC>
# Empty (0x) = EOA, non-empty = contract
# Decode your own calldata to double-check
cast calldata-decode "functionName(type1,type2)" <CALLDATA>
# Estimate gas before sending (catches obvious reverts)
cast estimate <CONTRACT> "functionName(args)" --from <CALLER> --rpc-url <RPC>
| Result | Action | |--------|--------| | All params verified | Proceed to Layer 4 | | Decimal mismatch found | Recalculate with correct decimals | | Address is EOA, expected contract | Wrong address — verify deployment |
Scan for common security risks before execution.
| Risk | Check | Red Flag |
|------|-------|----------|
| Reentrancy | Does the function make external calls before updating state? | External call before state update |
| Front-running | Is this a price-sensitive operation (swap, liquidation)? | No slippage protection or deadline |
| Access control | Does the function restrict callers appropriately? | Missing onlyOwner / onlyRole on sensitive function |
| Value handling | Does the function handle msg.value correctly? | Accepts ETH but doesn't use it, or vice versa |
| Approval amount | Am I approving more than necessary? | approve(spender, type(uint256).max) on unknown contract |
| Private key exposure | Am I using keystore, not raw private key? | --private-key flag in command |
# NEVER do this
cast send ... --private-key 0xdead...
# ALWAYS do this
cast send ... --account <KEYSTORE_NAME>
# Set up keystore if not done
cast wallet import <NAME> --interactive
| Result | Action | |--------|--------| | No red flags | Proceed to Layer 5 | | Reentrancy risk found | Add ReentrancyGuard or fix CEI pattern before proceeding | | Front-running risk | Add slippage/deadline params | | Private key exposed | STOP — rotate the key, use keystore |
Never send a transaction that hasn't been verified locally or on a fork.
| Method | Command | When to Use |
|--------|---------|-------------|
| Unit test | forge test --match-test <testName> -vvvv | New/modified contract functions |
| Fork test | forge test --fork-url <RPC> --match-test <testName> -vvvv | Testing against live state |
| Dry-run script | forge script <Script> --rpc-url <RPC> -vvvv (no --broadcast) | Deployment or complex operations |
| Gas estimation | cast estimate <CONTRACT> "func(args)" --from <CALLER> --rpc-url <RPC> | Before any cast send |
| Simulation | cast call <CONTRACT> "func(args)" --from <CALLER> --rpc-url <RPC> | Quick function call test |
Is this a new or modified contract?
├── YES → Write forge test → Run test → Pass? → Proceed
│ → Fail? → Fix and re-test
└── NO (calling existing deployed contract)
├── Simple read? → cast call (Layer 3 already covers this)
└── State-changing? → cast estimate first
├── Estimate succeeds → Proceed to Layer 6
└── Estimate reverts → Debug with cast call + revert reason → Fix → Re-estimate
| Result | Action | |--------|--------| | Test passes / estimate succeeds | Proceed to Layer 6 | | Test fails | Fix the issue — do NOT deploy broken code | | Estimate reverts | Likely a Layer 1 (permissions) or Layer 3 (params) issue — go back |
Execute the operation, verify success, and capture knowledge.
# Send transaction using keystore
cast send <CONTRACT> "functionName(type1,type2)" <arg1> <arg2> \
--account <KEYSTORE_NAME> \
--rpc-url <RPC>
# Deploy using forge script
forge script <Script> \
--rpc-url <RPC> \
--account <KEYSTORE_NAME> \
--broadcast \
--gas-limit <LIMIT> \
-vvvv
| Check | Command |
|-------|---------|
| TX status | Check status field in receipt: 1 = success, 0 = fail |
| State change | cast call to verify the expected state change happened |
| Events emitted | Check logs in receipt for expected events |
| Balance change | cast balance or cast call balanceOf to verify token movements |
After every successful (or failed) operation, capture what you learned:
| What to Record | Where |
|----------------|-------|
| Successful command with parameters | Personal command handbook (.md file) |
| New error encountered + solution | Debug notes |
| Contract address and its role | Project architecture doc |
| Permission/role requirements discovered | Contract dependency map |
| Gas cost of common operations | Gas reference table |
The biggest leverage: turn every AI interaction into your own knowledge, not a one-time consumption.
If you asked AI for a command and it worked:
| Operation | Critical Layers | Most Common Failure |
|-----------|----------------|---------------------|
| approve | L1 (owner), L3 (amount/decimals) | Wrong decimals |
| transfer | L1 (balance), L3 (amount/decimals) | Insufficient balance |
| addLiquidity | L1 (approve both tokens), L3 (amounts/ratio), L5 (estimate) | Missing approval |
| removeLiquidity | L1 (LP approve), L3 (minAmounts), L4 (slippage) | Slippage too tight |
| stake | L1 (approve + whitelist), L2 (staking contract state), L3 (amount) | Staking not active / not whitelisted |
| unstake | L1 (staker status), L2 (lock period), L3 (amount) | Lock period not expired |
| deploy | L3 (constructor args), L4 (full security check), L5 (fork test mandatory) | Constructor arg mismatch |
| upgrade | L1 (proxy admin), L2 (storage layout), L4 (storage collision), L5 (fork test mandatory) | Storage layout incompatible |
Print this mental checklist before EVERY on-chain operation:
[ ] L1 PERMISSIONS — Can I call this? (owner/role/approve/whitelist/paused)
[ ] L2 DEPENDENCIES — What does this touch? (contracts/state/events)
[ ] L3 PARAMETERS — Are inputs correct? (address/decimals/amount/selector)
[ ] L4 SECURITY — Is this safe? (reentrancy/frontrun/key exposure)
[ ] L5 TESTING — Did I test locally? (forge test/estimate/dry-run)
[ ] L6 EXECUTE — Send it, verify it, record what I learned.
6 layers, 2 minutes. Saves hours of debugging and wasted gas.
testing
[AUTO-INVOKE] MUST be invoked when designing or reviewing ERC20 token contracts that need flash loan protection. Covers token-level defense design patterns: cost tracking, same-block cooldown, progressive sell tax, minimum balance retention, EIP-7702 aware address checks, front-run protection, and referral binding. Trigger: any ERC20 token with anti-flash-loan, anti-bot, or tokenomics security design requirements.
development
[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any Solidity contract (.sol files). Covers pragma version, naming conventions, project layout, OpenZeppelin library selection standards, oracle integration, and anti-patterns. Trigger: any task involving creating, editing, or reviewing .sol source files.
development
[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any test files (*.t.sol). Covers test structure, naming conventions, coverage requirements, fuzz testing, and Foundry cheatcodes. Trigger: any task involving creating, editing, or running Solidity tests.
testing
[AUTO-INVOKE] MUST be invoked BEFORE writing or modifying any Solidity contract (.sol files). Covers private key handling, access control, reentrancy prevention, gas safety, and pre-audit checklists. Trigger: any task involving creating, editing, or reviewing .sol source files.