Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions .cursor/commands/analyze-unverified-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! Imo its worth to add as a fallback for unverified contracts in analyze-tx.md command. wdyt?

name: analyze-unverified-contract
description: Analyze an unverified contract — validate input, resolve RPC, disassemble with Heimdall, extract and enrich selectors (known/local/cast/4byte), and produce a single well-structured report file with all insights and no duplicate information
usage: /analyze-unverified-contract <address> <network> | /analyze-unverified-contract <block_explorer_contract_url>
---

# Analyze Unverified Contract Command

> **Usage**: `/analyze-unverified-contract <address> <network>`
> **Or**: `/analyze-unverified-contract <block_explorer_contract_url>`
>
> Examples: `/analyze-unverified-contract 0x36d3CBD83961868398d056EfBf50f5CE15528c0D base` | `/analyze-unverified-contract https://basescan.org/address/0x...`

Execute the workflow below in order. This file contains all context needed for a new context window.

---

## 1. Input parsing and validation

- **Two arguments** `ADDRESS` and `NETWORK`: use as contract address and network name.
- **Single argument = URL**: block explorer contract URL. Extract **address** (hex after `/address/` or `/contract/`). Map domain to **network**: etherscan.io→mainnet, basescan.org→base, arbiscan.io→arbitrum, polygonscan.com→polygon, snowtrace.io→avalanche, ftmscan.com→fantom, bscscan.com→bsc, optimistic.etherscan.io→optimism, blockscout.com→(chain-specific). If unknown, ask user for network.
- **Validate address**: Ensure address is 0x + 40 hex (e.g. repo helper `isValidEvmAddress` from `script/helperFunctions.sh`, or `[[ "$ADDRESS" =~ ^0x[0-9a-fA-F]{40}$ ]]`). Reject or ask for correction if invalid.

---

## 2. RPC URL resolution

- **Preferred**: From `config/networks.json` — key = network name, field `rpcUrl`. Standalone: `jq -r --arg n "base" '.[$n].rpcUrl // empty' config/networks.json`. With helpers: source `script/helperFunctions.sh` and use `getRpcUrlFromNetworksJson NETWORK` (reads networks.json).
- **Alternative**: `getRPCUrl NETWORK` from `script/helperFunctions.sh` (reads `ETH_NODE_URI_<NETWORK>` from env).
- **Fallback**: If network not in config and env not set, ask user for RPC URL.

---

## 3. Bytecode (optional)

- **Skip if** only disassemble/decompile: Heimdall accepts `<TARGET>` = address and fetches via RPC; no need to run `cast code` first.
- **When needed**: To save bytecode to a file (e.g. for other tools or offline use): `cast code "<ADDRESS>" --rpc-url "<RPC_URL>" > bytecode-<short_addr>.bin`

---

## 4. Heimdall installation check

- Run `command -v heimdall` (or `which heimdall`).
- If **not found**: Tell the user: "Heimdall is not installed. Install with: `curl -L https://get.heimdall.rs | bash`, then in a new terminal run `bifrost`." Offer to continue after they install (reference the same HTTPS install URL if they ask how to install). Do **not** proceed with disassemble/decompile until heimdall is available.

---
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add here proxy check to make sure we are not analyzing proxy contract but the implementation contracts. Something like this:

- Before decompiling, check if the contract is an EIP-1967 proxy by reading its implementation slot:
  `cast storage <ADDRESS> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc --rpc-url <RPC_URL>`
- If the result is a non-zero address, **this is a proxy**. Use this new implementation address for the Heimdall target in the steps below to extract the actual logic.


## 5. Heimdall usage (disassemble / decompile)

- **Docs**: https://github.com/Jon-Becker/heimdall-rs/wiki/modules
- **Shared options**: `-r` / `--rpc-url` = RPC URL; `-o` = output path or `print`; `-d` / `--default` = non-interactive (choose defaults when prompted).
- **Disassemble** (bytecode → opcodes): `heimdall disassemble <TARGET> -r <RPC_URL> -o <OUTPUT>` — `<TARGET>` = address, ENS, or bytecode file path. Optional: `-d` for decimal program counter.
- **Decompile** (bytecode → pseudo-Solidity + ABI): `heimdall decompile <TARGET> -r <RPC_URL> -o <OUTPUT> -d`. Use `--include-sol` and/or `--include-yul` for full output; `--skip-resolving` to skip selector resolution.
- **Outputs**: e.g. `opcodes-<network>-<short_addr>.txt/` (Heimdall may create a directory; opcodes are in `disassembled.asm` inside) or `opcodes.txt`; optional `decompiled-<short_addr>/`.
- **More insight**: `heimdall dump <TARGET> -r <RPC_URL>` (storage slots); `heimdall cfg <TARGET> -r <RPC_URL> -o <OUTPUT>` (control-flow graph). Use when summarizing structure or proxy/storage patterns.

---

## 6. Extract function selectors from opcodes

- **Pattern**: `PUSH4 <8 hex digits>` (e.g. `PUSH4 8388464e`).
- **Exclude** (selector → signature): `0xffffffff` (sentinel), `0x4e487b71` (Panic(uint256)), `0x08c379a0` (Error(string)).
- **Extract**: All unique PUSH4 + 8 hex, normalize to `0x` + 8 hex, remove duplicates and excluded.
- **Example**:
```bash
grep -oE 'PUSH4 [0-9a-f]{8}' opcodes.txt | awk '{print "0x"$2}' | sort -u | grep -v '0xffffffff' | grep -v '0x4e487b71' | grep -v '0x08c379a0'
```
Comment on lines +65 to +67
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Selector extraction example can miss uppercase PUSH4 values.

The regex only matches lowercase hex, so valid selectors with A-F get dropped from the report.

🔧 Proposed fix
-  grep -oE 'PUSH4 [0-9a-f]{8}' opcodes.txt | awk '{print "0x"$2}' | sort -u | grep -v '0xffffffff' | grep -v '0x4e487b71' | grep -v '0x08c379a0'
+  grep -oEi 'PUSH4 [0-9a-f]{8}' opcodes.txt | awk '{print "0x"tolower($2)}' | sort -u | grep -v '0xffffffff' | grep -v '0x4e487b71' | grep -v '0x08c379a0'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.cursor/commands/analyze-unverified-contract.md around lines 65 - 67, The
selector extraction regex 'PUSH4 [0-9a-f]{8}' only matches lowercase hex and
drops uppercase A–F; update the pattern in the command (the string "PUSH4
[0-9a-f]{8}") to accept both cases (e.g. 'PUSH4 [0-9a-fA-F]{8}' or use a POSIX
class like 'PUSH4 [[:xdigit:]]{8}') so uppercase hex selectors are included,
leaving the rest of the pipeline (awk, sort, grep -v filters) unchanged.

- **Output**: Write to e.g. `opcodes-selectors.md` or `<contract>-selectors.md` (list or table Selector | Signature).

---

## 7. Enrich selectors with signatures

**Use this order for speed**: resolve via known + local + cast first (no network delay), then **always** run the 4byte.directory step for every selector still unresolved after cast.

**D. Known selectors** (instant) — 0x01ffc9a7→supportsInterface(bytes4), 0x1626ba7e→isValidSignature(bytes32,bytes), 0x150b7a02→onERC721Received(…), 0xf23a6e61→onERC1155Received(…), 0xbc197c81→onERC1155BatchReceived(…), 0x52d1902d→proxiableUUID(), 0x4f1ef286→upgradeToAndCall(address,bytes), 0x3f707e6b→execute((address,uint256,bytes)[]), 0xb61d27f6→execute(address,uint256,bytes). Add common ERC20/ERC721 (e.g. balanceOf, transfer, approve) if useful.

**B. Local repo** (instant) — `out/<ContractName>.sol/<ContractName>.json` → key `methodIdentifiers`. `grep -r "<selector>" out/` or jq over methodIdentifiers.

**C. cast** (fast, no rate limit) — `cast 4byte <selector>` returns signature; use for each unresolved selector before hitting the API.

**A. 4byte.directory (mandatory for unresolved)** — For every selector still showing no signature after D/B/C, query 4byte.directory and merge any results into the selectors file. **Preferred**: source `script/helperFunctions.sh` and `script/playgroundHelpers.sh`, then run `decodeSelectors4byte` with no args (reads from the selectors file) or pass only the unresolved selectors; use `FOURBYTE_DELAY=0.3`–0.5. **Fallback**: if sourcing the repo scripts fails (e.g. in a minimal env), call the API directly: `GET https://www.4byte.directory/api/v1/signatures/?hex_signature=0x<selector>`, parse `results[].text_signature`, use ~0.3s delay between requests, and update the selectors file with any new signatures. Selectors that remain unknown after 4byte stay as "—" in the table.

Comment on lines +68 to +83
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify default-file behavior when using decodeSelectors4byte with no args.

Line 68 allows custom selector filenames, but no-arg decodeSelectors4byte reads opcodes-selectors.md by default. Call this out explicitly to avoid a confusing “file not found” path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.cursor/commands/analyze-unverified-contract.md around lines 68 - 83, The
documentation lacks an explicit statement that running decodeSelectors4byte with
no arguments will read and update opcodes-selectors.md by default; update the
text around the decodeSelectors4byte call to explicitly state that the tool uses
opcodes-selectors.md as the default input/output file (and that you can supply a
custom filename to override), and mention the expected error (“file not found”)
if that default file is missing; reference the decodeSelectors4byte helper and
the opcodes-selectors.md filename so readers know the exact default behavior and
how to pass a custom selector file.

Merge all resolved signatures into the selectors file as table **Selector** | **Signature**.

---

## 8. Produce a single well-structured report file

**Requirement**: Write exactly one report file that contains all insights in a clear structure and **no duplicate information** (e.g. do not repeat the full selector table in multiple sections).

- **Path**: `report-unverified-<network>-<short_addr>.md` (e.g. `report-unverified-base-36d3cbd8.md`) in the repo root or a dedicated folder (e.g. `docs/analysis/`). Short addr = first 8 hex chars of address (lowercase).
- **Structure** (use these sections; each piece of information appears only once):

1. **Metadata** — Contract address, network (and chainId if known), RPC source, analysis date.
2. **Input & validation** — How the address/network were obtained (e.g. URL parsed), validation result.
3. **Artifacts** — Table or list of paths only: opcodes file, selectors file (with signature table), optional decompile dir, optional dump/cfg paths. No inline duplication of selector table here.
4. **Selectors** — Either embed the full Selector | Signature table once in the report, or reference the selectors file and give total count + resolved vs unresolved. Do not repeat the same table elsewhere in the report.
5. **Interface hints** — Concise mapping: which selectors imply which interfaces (ERC165, ERC1271, ERC721/1155 receivers, UUPS/proxy, Safe-style execute, ERC-4337, EIP-712, etc.). No raw selector list again; refer to section 4 or the selectors file.
6. **Structure summary** — One-line characterization of the contract (e.g. "Smart account with ERC1271 and UUPS"). If decompile or dump/cfg was run, add 1–3 short bullets (dispatch pattern, proxy slot, dangerous patterns) without repeating selector or artifact paths.
7. **Optional extras** (only if produced) — Call-flow hints, suggested `cast call` examples for key selectors, storage/CFG notes, repo comparison. Keep brief; reference section 4 for selectors.

- **Chat summary**: In the conversation, give a short summary and point to the report file path; do not paste the full report again.

---

## 9. Repo file reference

| Purpose | Path |
|----------------------|------|
| Network list + RPC | `config/networks.json` (`.rpcUrl`) |
| getRPCUrl | `script/helperFunctions.sh` |
| decodeSelectors4byte (function) | `script/playgroundHelpers.sh` (source after `script/helperFunctions.sh`) |
| Selectors file | `opcodes-selectors.md` or `<contract>-selectors.md` |
| Report file (output) | `report-unverified-<network>-<short_addr>.md` (Section 8) |
| Heimdall commands | `script/playground.sh` (commented) |

---

## 10. Additional benefits (optional, for maximum insight)

If run, fold the results into the report (Section 8) in the appropriate subsection; do not duplicate elsewhere.

| Benefit | Description |
|--------|-------------|
| **Interface detection** | Map selectors → interfaces (Section 8.5). ERC165, ERC1271, ERC721/1155 receivers, UUPS/proxy, Safe-style execute. |
| **Call-flow hints** | For patterns (e.g. execute + isValidSignature), state which selectors are needed; add to report Section 8.7. |
| **Decompiled code review** | Note structure (dispatch table, delegatecall targets, storage layout), dangerous patterns, proxy slots; add to report Section 8.6. |
| **cast call examples** | Suggest `cast call <ADDRESS> "<signature>" [args] --rpc-url <RPC>` for key selectors; add to report Section 8.7. |
| **Storage / CFG** | `heimdall dump` / `heimdall cfg`; add artifact paths and brief notes to report Sections 8.3 and 8.6. |
| **Repo comparison** | Compare selector set with `out/*/*.json` methodIdentifiers; add one line to report Section 8.7 if relevant. |
| **Transaction inspect** | For a specific tx: `heimdall inspect <TX_HASH> -r <RPC> -d`; can be referenced in the report if needed. |

Main deliverable: opcodes file, selectors file (with full table), and **one report file** (Section 8) containing all insights without duplication.
106 changes: 106 additions & 0 deletions script/playgroundHelpers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2055,6 +2055,110 @@ function transferStagingDiamondOwnership() {
fi
}

# =============================================================================
# 4BYTE SELECTOR DECODE FUNCTIONS
# =============================================================================

# decodeSelector4byte: Query 4byte.directory API for one function selector; print "SELECTOR\tsig1|sig2|..." or "(no match)".
#
# Usage: decodeSelector4byte SELECTOR
# SELECTOR - bytes4 hex selector (0x + 8 hex chars)
#
# Returns: 0; output to stdout
# Example: decodeSelector4byte "0x8388464e"
function decodeSelector4byte() {
local SELECTOR="${1:-}"
if ! isValidSelector "$SELECTOR"; then
error "decodeSelector4byte: invalid selector '$SELECTOR'"
return 1
fi

local FOURBYTE_URL="${FOURBYTE_URL:-https://www.4byte.directory/api/v1/signatures}"
local RESPONSE
RESPONSE=$(curl -sS -L "${FOURBYTE_URL}/?hex_signature=${SELECTOR}" 2>/dev/null || echo '{"count":0}')
local COUNT
COUNT=$(echo "$RESPONSE" | jq -r '.count // 0' 2>/dev/null) || COUNT=""
if [[ -z "$COUNT" || "$COUNT" == "0" || "$COUNT" == "null" ]]; then
printf "%s\t%s\n" "$SELECTOR" "(no match)"
return 0
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
local SIGNATURES
SIGNATURES=$(echo "$RESPONSE" | jq -r '.results[].text_signature' | paste -sd '|' -)
printf "%s\t%s\n" "$SELECTOR" "$SIGNATURES"
}

# decodeSelectors4byte: Resolve function selectors to text signatures via 4byte.directory API.
# Selectors from: positional args, stdin (--stdin), or default file opcodes-selectors.md in repo root.
# Adds a short delay between requests (FOURBYTE_DELAY env, default 0.3s). Requires curl and jq.
#
# Usage: decodeSelectors4byte [--stdin] [SELECTOR ...]
# --stdin - Optional: read one selector per line from stdin (0x + 8 hex)
# SELECTOR - Optional: one or more selectors; if omitted and not --stdin, read from opcodes-selectors.md
#
# Returns: 0 on success; 1 if no selectors or dependency missing
# Example: decodeSelectors4byte 0x8388464e 0x12345678
# Example: decodeSelectors4byte
# Example: grep -oE '0x[0-9a-f]{8}' opcodes-selectors.md | decodeSelectors4byte --stdin
function decodeSelectors4byte() {
local DELAY_SEC="${FOURBYTE_DELAY:-0.3}"
local SELECTORS=()

command -v curl >/dev/null 2>&1 || {
error "decodeSelectors4byte: curl is required"
return 1
}
command -v jq >/dev/null 2>&1 || {
error "decodeSelectors4byte: jq is required"
return 1
}

if [[ "${1:-}" == "--stdin" ]]; then
local LINE
while read -r LINE; do
if isValidSelector "$LINE"; then
SELECTORS+=("$LINE")
fi
done
elif [[ $# -gt 0 ]]; then
local ARG
for ARG in "$@"; do
if isValidSelector "$ARG"; then
SELECTORS+=("$ARG")
fi
done
else
local REPO_ROOT
REPO_ROOT=$(getContractsDirectory 2>/dev/null) || true
if [[ -z "$REPO_ROOT" ]]; then
local SCRIPT_DIR
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
fi
local MD="${REPO_ROOT}/opcodes-selectors.md"
if [[ ! -f "$MD" ]]; then
error "decodeSelectors4byte: no selectors provided and default file not found: $MD"
error "Usage: decodeSelectors4byte [--stdin] [SELECTOR ...]"
return 1
fi
while read -r LINE; do
if [[ "$LINE" =~ (0x[0-9a-fA-F]{8}) ]]; then
SELECTORS+=("${BASH_REMATCH[1]}")
fi
done < <(grep -oE '0x[0-9a-fA-F]{8}' "$MD" | sort -u)
fi

if [[ ${#SELECTORS[@]} -eq 0 ]]; then
warning "decodeSelectors4byte: no selectors to decode"
return 0
fi
Comment on lines +2098 to +2153
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix return-code docs to match implementation.

The comment says “Returns: 0 on success; 1 if no selectors…”, but no selectors currently returns 0 (Line 2152).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@script/playgroundHelpers.sh` around lines 2098 - 2153, Update the leading
comment for the decodeSelectors4byte function to match its actual return
behavior: it returns 1 when required dependencies or the default file are
missing (error paths where error "decodeSelectors4byte: ..." is called), and
returns 0 on success or when there are no selectors to decode (the warning path
that currently returns 0). Edit the comment line that currently reads "Returns:
0 on success; 1 if no selectors or dependency missing" to reflect "Returns: 0 on
success or when no selectors; 1 on missing dependency or default file error" so
it aligns with the implementation (referencing decodeSelectors4byte, SELECTORS,
and the early dependency checks).


local SELECTOR
for SELECTOR in "${SELECTORS[@]}"; do
decodeSelector4byte "$SELECTOR"
sleep "$DELAY_SEC"
done
}

# =============================================================================
# EXPORT FUNCTIONS FOR USE IN OTHER SCRIPTS
# =============================================================================
Expand Down Expand Up @@ -2093,3 +2197,5 @@ export -f logWithTimestamp
export -f logNetworkResult
export -f analyzeFailingTx
export -f transferStagingDiamondOwnership
export -f decodeSelector4byte
export -f decodeSelectors4byte
Loading