solidity-argus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +37 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/package.json +43 -0
- package/skills/INVENTORY.md +79 -0
- package/skills/README.md +56 -0
- package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +424 -0
- package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +157 -0
- package/skills/checklists/cyfrin-defi-core/SKILL.md +373 -0
- package/skills/checklists/cyfrin-defi-integrations/SKILL.md +412 -0
- package/skills/checklists/cyfrin-gas/SKILL.md +55 -0
- package/skills/checklists/general-audit/SKILL.md +433 -0
- package/skills/methodology/audit-workflow/SKILL.md +129 -0
- package/skills/methodology/report-template/SKILL.md +190 -0
- package/skills/methodology/severity-classification/SKILL.md +179 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +229 -0
- package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +317 -0
- package/skills/protocol-patterns/dao-governance/SKILL.md +281 -0
- package/skills/protocol-patterns/lending-borrowing/SKILL.md +221 -0
- package/skills/protocol-patterns/staking-vesting/SKILL.md +247 -0
- package/skills/references/exploit-reference/SKILL.md +259 -0
- package/skills/references/smartbugs-examples/SKILL.md +296 -0
- package/skills/vulnerability-patterns/access-control/SKILL.md +298 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +59 -0
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +59 -0
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +61 -0
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +55 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +62 -0
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +60 -0
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +59 -0
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +72 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +249 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +51 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +52 -0
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +61 -0
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +60 -0
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +59 -0
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +61 -0
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +61 -0
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +333 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +60 -0
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +66 -0
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +67 -0
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +252 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +65 -0
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +61 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +266 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +72 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +59 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +63 -0
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +52 -0
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +65 -0
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +61 -0
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +63 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +56 -0
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +80 -0
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +69 -0
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +70 -0
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +81 -0
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +77 -0
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +294 -0
- package/src/agents/argus-prompt.ts +407 -0
- package/src/agents/pythia-prompt.ts +134 -0
- package/src/agents/scribe-prompt.ts +87 -0
- package/src/agents/sentinel-prompt.ts +133 -0
- package/src/cli/cli-program.ts +67 -0
- package/src/cli/commands/doctor.ts +83 -0
- package/src/cli/commands/init.ts +46 -0
- package/src/cli/commands/install.ts +55 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/tui-prompts.ts +75 -0
- package/src/cli/types.ts +9 -0
- package/src/config/index.ts +3 -0
- package/src/config/loader.ts +36 -0
- package/src/config/schema.ts +82 -0
- package/src/config/types.ts +4 -0
- package/src/constants/defaults.ts +6 -0
- package/src/create-hooks.ts +84 -0
- package/src/create-managers.ts +26 -0
- package/src/create-tools.ts +30 -0
- package/src/features/audit-enforcer/audit-enforcer.ts +34 -0
- package/src/features/audit-enforcer/index.ts +1 -0
- package/src/features/background-agent/background-manager.ts +200 -0
- package/src/features/background-agent/index.ts +1 -0
- package/src/features/context-monitor/context-monitor.ts +48 -0
- package/src/features/context-monitor/index.ts +4 -0
- package/src/features/context-monitor/tool-output-truncator.ts +17 -0
- package/src/features/error-recovery/index.ts +2 -0
- package/src/features/error-recovery/session-recovery.ts +27 -0
- package/src/features/error-recovery/tool-error-recovery.ts +35 -0
- package/src/features/index.ts +5 -0
- package/src/features/persistent-state/audit-state-manager.ts +121 -0
- package/src/features/persistent-state/index.ts +1 -0
- package/src/hooks/compaction-hook.ts +50 -0
- package/src/hooks/config-handler.ts +116 -0
- package/src/hooks/event-hook-v2.ts +93 -0
- package/src/hooks/event-hook.ts +74 -0
- package/src/hooks/hook-system.ts +9 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/knowledge-sync-hook.ts +57 -0
- package/src/hooks/safe-create-hook.ts +15 -0
- package/src/hooks/system-prompt-hook.ts +126 -0
- package/src/hooks/tool-tracking-hook.ts +234 -0
- package/src/hooks/types.ts +16 -0
- package/src/index.ts +36 -0
- package/src/knowledge/scvd-client.ts +242 -0
- package/src/knowledge/scvd-index.ts +183 -0
- package/src/knowledge/scvd-sync.ts +85 -0
- package/src/managers/index.ts +1 -0
- package/src/managers/types.ts +85 -0
- package/src/plugin-interface.ts +38 -0
- package/src/shared/binary-utils.ts +63 -0
- package/src/shared/deep-merge.ts +71 -0
- package/src/shared/file-utils.ts +56 -0
- package/src/shared/index.ts +5 -0
- package/src/shared/jsonc-parser.ts +39 -0
- package/src/shared/logger.ts +36 -0
- package/src/state/audit-state.ts +27 -0
- package/src/state/finding-store.ts +126 -0
- package/src/state/plugin-state.ts +14 -0
- package/src/state/types.ts +61 -0
- package/src/tools/contract-analyzer-tool.ts +184 -0
- package/src/tools/forge-fuzz-tool.ts +311 -0
- package/src/tools/forge-test-tool.ts +397 -0
- package/src/tools/pattern-checker-tool.ts +337 -0
- package/src/tools/report-generator-tool.ts +308 -0
- package/src/tools/slither-tool.ts +465 -0
- package/src/tools/solodit-search-tool.ts +131 -0
- package/src/tools/sync-knowledge-tool.ts +116 -0
- package/src/utils/project-detector.ts +133 -0
- package/src/utils/solidity-parser.ts +174 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
|
|
4
|
+
export interface ProjectConfig {
|
|
5
|
+
type: "foundry" | "hardhat" | "mixed" | "unknown";
|
|
6
|
+
srcDir: string;
|
|
7
|
+
testDir: string;
|
|
8
|
+
solcVersion?: string;
|
|
9
|
+
remappings: string[];
|
|
10
|
+
rootDir: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detects the Solidity framework (Foundry/Hardhat) from config files
|
|
15
|
+
* @param dir Directory to scan for config files
|
|
16
|
+
* @returns ProjectConfig with detected framework type and settings
|
|
17
|
+
*/
|
|
18
|
+
export async function detectProject(dir: string): Promise<ProjectConfig> {
|
|
19
|
+
const rootDir = resolve(dir);
|
|
20
|
+
const foundryTomlPath = join(rootDir, "foundry.toml");
|
|
21
|
+
const hardhatConfigTsPath = join(rootDir, "hardhat.config.ts");
|
|
22
|
+
const hardhatConfigJsPath = join(rootDir, "hardhat.config.js");
|
|
23
|
+
|
|
24
|
+
const hasFoundry = existsSync(foundryTomlPath);
|
|
25
|
+
const hasHardhatTs = existsSync(hardhatConfigTsPath);
|
|
26
|
+
const hasHardhatJs = existsSync(hardhatConfigJsPath);
|
|
27
|
+
const hasHardhat = hasHardhatTs || hasHardhatJs;
|
|
28
|
+
|
|
29
|
+
// Determine project type
|
|
30
|
+
let type: "foundry" | "hardhat" | "mixed" | "unknown";
|
|
31
|
+
if (hasFoundry && hasHardhat) {
|
|
32
|
+
type = "mixed";
|
|
33
|
+
} else if (hasFoundry) {
|
|
34
|
+
type = "foundry";
|
|
35
|
+
} else if (hasHardhat) {
|
|
36
|
+
type = "hardhat";
|
|
37
|
+
} else {
|
|
38
|
+
type = "unknown";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Default values
|
|
42
|
+
let srcDir = "src";
|
|
43
|
+
let testDir = "test";
|
|
44
|
+
let solcVersion: string | undefined;
|
|
45
|
+
let remappings: string[] = [];
|
|
46
|
+
|
|
47
|
+
// Parse Foundry config if present
|
|
48
|
+
if (hasFoundry) {
|
|
49
|
+
const foundryConfig = await parseFoundryToml(foundryTomlPath);
|
|
50
|
+
srcDir = foundryConfig.srcDir || srcDir;
|
|
51
|
+
testDir = foundryConfig.testDir || testDir;
|
|
52
|
+
solcVersion = foundryConfig.solcVersion;
|
|
53
|
+
remappings = foundryConfig.remappings;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Set Hardhat defaults if it's a Hardhat project
|
|
57
|
+
if (hasHardhat && !hasFoundry) {
|
|
58
|
+
srcDir = "contracts";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
type,
|
|
63
|
+
srcDir,
|
|
64
|
+
testDir,
|
|
65
|
+
solcVersion,
|
|
66
|
+
remappings,
|
|
67
|
+
rootDir,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parses foundry.toml file using regex-based parsing
|
|
73
|
+
*/
|
|
74
|
+
async function parseFoundryToml(
|
|
75
|
+
filePath: string
|
|
76
|
+
): Promise<{
|
|
77
|
+
srcDir?: string;
|
|
78
|
+
testDir?: string;
|
|
79
|
+
solcVersion?: string;
|
|
80
|
+
remappings: string[];
|
|
81
|
+
}> {
|
|
82
|
+
const content = await Bun.file(filePath).text();
|
|
83
|
+
|
|
84
|
+
const result = {
|
|
85
|
+
srcDir: undefined as string | undefined,
|
|
86
|
+
testDir: undefined as string | undefined,
|
|
87
|
+
solcVersion: undefined as string | undefined,
|
|
88
|
+
remappings: [] as string[],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Extract [profile.default] section - stop at next section or EOF
|
|
92
|
+
const profileDefaultMatch = content.match(
|
|
93
|
+
/\[profile\.default\]([\s\S]*?)(?:\n\[|$)/
|
|
94
|
+
);
|
|
95
|
+
if (!profileDefaultMatch || !profileDefaultMatch[1]) {
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const profileSection = profileDefaultMatch[1];
|
|
100
|
+
|
|
101
|
+
// Parse src = "..."
|
|
102
|
+
const srcMatch = profileSection.match(/^\s*src\s*=\s*["']([^"']+)["']/m);
|
|
103
|
+
if (srcMatch && srcMatch[1]) {
|
|
104
|
+
result.srcDir = srcMatch[1];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Parse test = "..."
|
|
108
|
+
const testMatch = profileSection.match(/^\s*test\s*=\s*["']([^"']+)["']/m);
|
|
109
|
+
if (testMatch && testMatch[1]) {
|
|
110
|
+
result.testDir = testMatch[1];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Parse solc = "..."
|
|
114
|
+
const solcMatch = profileSection.match(/^\s*solc\s*=\s*["']([^"']+)["']/m);
|
|
115
|
+
if (solcMatch && solcMatch[1]) {
|
|
116
|
+
result.solcVersion = solcMatch[1];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse remappings array - handles both single line and multiline
|
|
120
|
+
const remappingsMatch = profileSection.match(
|
|
121
|
+
/remappings\s*=\s*\[([\s\S]*?)\]/
|
|
122
|
+
);
|
|
123
|
+
if (remappingsMatch && remappingsMatch[1]) {
|
|
124
|
+
const remappingsContent = remappingsMatch[1];
|
|
125
|
+
// Extract quoted strings from the array
|
|
126
|
+
const remappingMatches = remappingsContent.match(/["']([^"']+)["']/g);
|
|
127
|
+
if (remappingMatches) {
|
|
128
|
+
result.remappings = remappingMatches.map((m) => m.slice(1, -1));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { ContractProfile } from "../state/types";
|
|
2
|
+
|
|
3
|
+
interface ABIFunction {
|
|
4
|
+
type: string;
|
|
5
|
+
name: string;
|
|
6
|
+
inputs?: Array<{ name: string; type: string }>;
|
|
7
|
+
outputs?: Array<{ name: string; type: string }>;
|
|
8
|
+
stateMutability?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface StorageLayoutItem {
|
|
12
|
+
label: string;
|
|
13
|
+
type: string;
|
|
14
|
+
slot: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface StorageLayout {
|
|
18
|
+
storage: StorageLayoutItem[];
|
|
19
|
+
types: Record<string, { label: string }>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract contract information using forge inspect
|
|
24
|
+
* Runs forge inspect <contractName> abi and storage-layout
|
|
25
|
+
* Parses ABI to extract functions and state variables
|
|
26
|
+
* Detects access control patterns
|
|
27
|
+
*/
|
|
28
|
+
export async function extractContractInfo(
|
|
29
|
+
contractName: string,
|
|
30
|
+
projectDir: string
|
|
31
|
+
): Promise<ContractProfile> {
|
|
32
|
+
const result: ContractProfile = {
|
|
33
|
+
name: contractName,
|
|
34
|
+
filePath: "",
|
|
35
|
+
functions: [],
|
|
36
|
+
stateVars: [],
|
|
37
|
+
inheritance: [],
|
|
38
|
+
accessControlPattern: "none",
|
|
39
|
+
externalCalls: [],
|
|
40
|
+
riskIndicators: [],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Run forge inspect abi
|
|
45
|
+
const abiResult = Bun.spawnSync(
|
|
46
|
+
["forge", "inspect", contractName, "abi"],
|
|
47
|
+
{
|
|
48
|
+
cwd: projectDir,
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "pipe",
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!abiResult.success) {
|
|
55
|
+
const errorMsg = abiResult.stderr?.toString() || "Unknown error";
|
|
56
|
+
result.error = `Failed to inspect ABI: ${errorMsg}`;
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Run forge inspect storage-layout
|
|
61
|
+
const storageResult = Bun.spawnSync(
|
|
62
|
+
["forge", "inspect", contractName, "storage-layout"],
|
|
63
|
+
{
|
|
64
|
+
cwd: projectDir,
|
|
65
|
+
stdout: "pipe",
|
|
66
|
+
stderr: "pipe",
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!storageResult.success) {
|
|
71
|
+
const errorMsg = storageResult.stderr?.toString() || "Unknown error";
|
|
72
|
+
result.error = `Failed to inspect storage layout: ${errorMsg}`;
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Parse ABI
|
|
77
|
+
const abiOutput = abiResult.stdout?.toString() || "[]";
|
|
78
|
+
let abi: ABIFunction[] = [];
|
|
79
|
+
try {
|
|
80
|
+
abi = JSON.parse(abiOutput);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
result.error = `Failed to parse ABI JSON: ${e instanceof Error ? e.message : "Unknown error"}`;
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Parse storage layout
|
|
87
|
+
const storageOutput = storageResult.stdout?.toString() || "{}";
|
|
88
|
+
let storageLayout: StorageLayout = { storage: [], types: {} };
|
|
89
|
+
try {
|
|
90
|
+
storageLayout = JSON.parse(storageOutput);
|
|
91
|
+
} catch (e) {
|
|
92
|
+
result.error = `Failed to parse storage layout JSON: ${e instanceof Error ? e.message : "Unknown error"}`;
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Extract functions from ABI
|
|
97
|
+
const functions = abi.filter((item) => item.type === "function");
|
|
98
|
+
result.functions = functions.map((func) => ({
|
|
99
|
+
name: func.name || "",
|
|
100
|
+
visibility: mapStateMutabilityToVisibility(func.stateMutability || "nonpayable"),
|
|
101
|
+
mutability: func.stateMutability || "nonpayable",
|
|
102
|
+
modifiers: [],
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
// Extract state variables from storage layout
|
|
106
|
+
result.stateVars = storageLayout.storage.map((item) => {
|
|
107
|
+
const typeInfo = storageLayout.types[item.type];
|
|
108
|
+
const typeLabel = typeInfo?.label || item.type;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
name: item.label,
|
|
112
|
+
type: typeLabel,
|
|
113
|
+
visibility: "internal", // Default visibility for storage vars
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Detect access control pattern
|
|
118
|
+
result.accessControlPattern = detectAccessControlPattern(result.functions);
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
result.error = `Unexpected error: ${e instanceof Error ? e.message : "Unknown error"}`;
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Map Solidity stateMutability to visibility
|
|
129
|
+
* ABI doesn't directly specify visibility, so we infer from mutability
|
|
130
|
+
*/
|
|
131
|
+
function mapStateMutabilityToVisibility(
|
|
132
|
+
stateMutability: string
|
|
133
|
+
): string {
|
|
134
|
+
switch (stateMutability) {
|
|
135
|
+
case "pure":
|
|
136
|
+
case "view":
|
|
137
|
+
return "view";
|
|
138
|
+
case "payable":
|
|
139
|
+
case "nonpayable":
|
|
140
|
+
return "external";
|
|
141
|
+
default:
|
|
142
|
+
return "external";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Detect access control pattern from function names and signatures
|
|
148
|
+
*/
|
|
149
|
+
function detectAccessControlPattern(
|
|
150
|
+
functions: Array<{ name: string; visibility: string; mutability: string; modifiers: string[] }>
|
|
151
|
+
): "ownable" | "access-control" | "custom" | "none" {
|
|
152
|
+
const functionNames = functions.map((f) => f.name.toLowerCase());
|
|
153
|
+
|
|
154
|
+
// Check for Ownable pattern
|
|
155
|
+
if (functionNames.includes("owner") || functionNames.includes("transferownership")) {
|
|
156
|
+
return "ownable";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check for AccessControl pattern (OpenZeppelin)
|
|
160
|
+
if (functionNames.includes("hasrole") || functionNames.includes("grantrole")) {
|
|
161
|
+
return "access-control";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check for custom access control patterns
|
|
165
|
+
if (
|
|
166
|
+
functionNames.some((name) =>
|
|
167
|
+
name.includes("onlyadmin") || name.includes("requireadmin")
|
|
168
|
+
)
|
|
169
|
+
) {
|
|
170
|
+
return "custom";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return "none";
|
|
174
|
+
}
|