web3crit-scanner 7.0.1
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/README.md +685 -0
- package/bin/web3crit +10 -0
- package/package.json +59 -0
- package/src/analyzers/control-flow.js +256 -0
- package/src/analyzers/data-flow.js +720 -0
- package/src/analyzers/exploit-chain.js +751 -0
- package/src/analyzers/immunefi-classifier.js +515 -0
- package/src/analyzers/poc-validator.js +396 -0
- package/src/analyzers/solodit-enricher.js +1122 -0
- package/src/cli.js +546 -0
- package/src/detectors/access-control-enhanced.js +458 -0
- package/src/detectors/base-detector.js +213 -0
- package/src/detectors/callback-reentrancy.js +362 -0
- package/src/detectors/cross-contract-reentrancy.js +697 -0
- package/src/detectors/delegatecall.js +167 -0
- package/src/detectors/deprecated-functions.js +62 -0
- package/src/detectors/flash-loan.js +408 -0
- package/src/detectors/frontrunning.js +553 -0
- package/src/detectors/gas-griefing.js +701 -0
- package/src/detectors/governance-attacks.js +366 -0
- package/src/detectors/integer-overflow.js +487 -0
- package/src/detectors/oracle-manipulation.js +524 -0
- package/src/detectors/permit-exploits.js +368 -0
- package/src/detectors/precision-loss.js +408 -0
- package/src/detectors/price-manipulation-advanced.js +548 -0
- package/src/detectors/proxy-vulnerabilities.js +651 -0
- package/src/detectors/readonly-reentrancy.js +473 -0
- package/src/detectors/rebasing-token-vault.js +416 -0
- package/src/detectors/reentrancy-enhanced.js +359 -0
- package/src/detectors/selfdestruct.js +259 -0
- package/src/detectors/share-manipulation.js +412 -0
- package/src/detectors/signature-replay.js +409 -0
- package/src/detectors/storage-collision.js +446 -0
- package/src/detectors/timestamp-dependence.js +494 -0
- package/src/detectors/toctou.js +427 -0
- package/src/detectors/token-standard-compliance.js +465 -0
- package/src/detectors/unchecked-call.js +214 -0
- package/src/detectors/vault-inflation.js +421 -0
- package/src/index.js +42 -0
- package/src/package-lock.json +2874 -0
- package/src/package.json +39 -0
- package/src/scanner-enhanced.js +816 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
const BaseDetector = require('./base-detector');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Storage Collision Detector for Proxy Contracts
|
|
5
|
+
* Detects storage layout conflicts between proxy and implementation contracts
|
|
6
|
+
*
|
|
7
|
+
* Attack vectors detected:
|
|
8
|
+
* 1. Unstructured storage collision - Implementation overwrites proxy admin slot
|
|
9
|
+
* 2. Inherited storage mismatch - Proxy and impl have different inheritance
|
|
10
|
+
* 3. Missing storage gap - Upgradeable contracts without __gap
|
|
11
|
+
* 4. Non-upgradeable base - Using non-upgradeable OZ contracts
|
|
12
|
+
* 5. Function selector collision - Proxy and impl have same selector
|
|
13
|
+
*/
|
|
14
|
+
class StorageCollisionDetector extends BaseDetector {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(
|
|
17
|
+
'Storage Collision',
|
|
18
|
+
'Detects proxy storage collisions and upgrade safety issues',
|
|
19
|
+
'CRITICAL'
|
|
20
|
+
);
|
|
21
|
+
this.currentContract = null;
|
|
22
|
+
this.isProxy = false;
|
|
23
|
+
this.isUpgradeable = false;
|
|
24
|
+
this.stateVariables = [];
|
|
25
|
+
this.inheritedContracts = [];
|
|
26
|
+
this.proxyPatterns = [];
|
|
27
|
+
this.storageSlots = new Map();
|
|
28
|
+
|
|
29
|
+
// Known proxy admin slots (EIP-1967)
|
|
30
|
+
this.KNOWN_SLOTS = {
|
|
31
|
+
IMPLEMENTATION: '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc',
|
|
32
|
+
ADMIN: '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103',
|
|
33
|
+
BEACON: '0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async detect(ast, sourceCode, fileName, cfg, dataFlow) {
|
|
38
|
+
this.findings = [];
|
|
39
|
+
this.ast = ast;
|
|
40
|
+
this.sourceCode = sourceCode;
|
|
41
|
+
this.fileName = fileName;
|
|
42
|
+
this.sourceLines = sourceCode.split('\n');
|
|
43
|
+
this.cfg = cfg;
|
|
44
|
+
this.dataFlow = dataFlow;
|
|
45
|
+
this.stateVariables = [];
|
|
46
|
+
this.inheritedContracts = [];
|
|
47
|
+
this.proxyPatterns = [];
|
|
48
|
+
|
|
49
|
+
this.traverse(ast);
|
|
50
|
+
|
|
51
|
+
// Analyze if this is a proxy or upgradeable contract
|
|
52
|
+
this.analyzeProxyPatterns();
|
|
53
|
+
|
|
54
|
+
return this.findings;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
visitContractDefinition(node) {
|
|
58
|
+
this.currentContract = node.name;
|
|
59
|
+
this.stateVariables = [];
|
|
60
|
+
this.inheritedContracts = [];
|
|
61
|
+
|
|
62
|
+
// Check if this is a proxy contract
|
|
63
|
+
const contractCode = this.getCodeSnippet(node.loc);
|
|
64
|
+
this.isProxy = this.detectProxyPattern(contractCode, node);
|
|
65
|
+
this.isUpgradeable = this.detectUpgradeablePattern(contractCode, node);
|
|
66
|
+
|
|
67
|
+
// Track inherited contracts
|
|
68
|
+
if (node.baseContracts) {
|
|
69
|
+
node.baseContracts.forEach(base => {
|
|
70
|
+
if (base.baseName) {
|
|
71
|
+
this.inheritedContracts.push({
|
|
72
|
+
name: base.baseName.namePath || base.baseName.name,
|
|
73
|
+
loc: base.loc
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
visitStateVariableDeclaration(node) {
|
|
81
|
+
if (!node.variables) return;
|
|
82
|
+
|
|
83
|
+
node.variables.forEach((variable, index) => {
|
|
84
|
+
this.stateVariables.push({
|
|
85
|
+
name: variable.name,
|
|
86
|
+
type: this.getTypeName(variable.typeName),
|
|
87
|
+
visibility: variable.visibility,
|
|
88
|
+
isConstant: variable.isDeclaredConst,
|
|
89
|
+
isImmutable: variable.isImmutable,
|
|
90
|
+
loc: variable.loc,
|
|
91
|
+
slot: this.calculateStorageSlot(variable, index)
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
visitFunctionDefinition(node) {
|
|
97
|
+
if (!this.isProxy && !this.isUpgradeable) return;
|
|
98
|
+
|
|
99
|
+
const funcName = node.name || '';
|
|
100
|
+
const funcCode = node.body ? this.getCodeSnippet(node.loc) : '';
|
|
101
|
+
|
|
102
|
+
// Check for delegatecall in proxy
|
|
103
|
+
if (funcCode.includes('delegatecall')) {
|
|
104
|
+
this.proxyPatterns.push({
|
|
105
|
+
type: 'delegatecall',
|
|
106
|
+
function: funcName,
|
|
107
|
+
loc: node.loc
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check for assembly storage access
|
|
112
|
+
if (funcCode.includes('sstore') || funcCode.includes('sload')) {
|
|
113
|
+
this.analyzeAssemblyStorageAccess(funcCode, node);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for initializer patterns
|
|
117
|
+
if (/initialize|init/i.test(funcName)) {
|
|
118
|
+
this.analyzeInitializer(node, funcCode);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
detectProxyPattern(code, node) {
|
|
123
|
+
const proxyIndicators = [
|
|
124
|
+
/Proxy|proxy/,
|
|
125
|
+
/delegatecall/,
|
|
126
|
+
/implementation/i,
|
|
127
|
+
/ERC1967|EIP1967/i,
|
|
128
|
+
/TransparentUpgradeable/i,
|
|
129
|
+
/UUPSUpgradeable/i,
|
|
130
|
+
/BeaconProxy/i,
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
return proxyIndicators.some(p => p.test(code));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
detectUpgradeablePattern(code, node) {
|
|
137
|
+
const upgradeableIndicators = [
|
|
138
|
+
/Upgradeable/i,
|
|
139
|
+
/Initializable/i,
|
|
140
|
+
/UUPSUpgradeable/i,
|
|
141
|
+
/__gap/,
|
|
142
|
+
/initializer\s+modifier/i,
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
return upgradeableIndicators.some(p => p.test(code));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
analyzeAssemblyStorageAccess(funcCode, node) {
|
|
149
|
+
// Extract slots used in assembly
|
|
150
|
+
const slotPatterns = [
|
|
151
|
+
/sstore\s*\(\s*([x0-9a-fA-F]+)/g,
|
|
152
|
+
/sload\s*\(\s*([x0-9a-fA-F]+)/g,
|
|
153
|
+
/\.slot/g,
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
for (const pattern of slotPatterns) {
|
|
157
|
+
let match;
|
|
158
|
+
while ((match = pattern.exec(funcCode)) !== null) {
|
|
159
|
+
const slot = match[1];
|
|
160
|
+
if (this.isKnownProxySlot(slot)) {
|
|
161
|
+
// Safe - using known proxy slot
|
|
162
|
+
} else if (this.couldCollide(slot)) {
|
|
163
|
+
this.reportPotentialSlotCollision(node, slot);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
analyzeInitializer(node, funcCode) {
|
|
170
|
+
const funcName = node.name || 'initialize';
|
|
171
|
+
|
|
172
|
+
// Check for initializer modifier
|
|
173
|
+
const hasInitializerMod = node.modifiers &&
|
|
174
|
+
node.modifiers.some(m => /initializer|onlyInitializing/i.test(m.name));
|
|
175
|
+
|
|
176
|
+
// Check for reinitializer
|
|
177
|
+
const hasReinitializer = node.modifiers &&
|
|
178
|
+
node.modifiers.some(m => /reinitializer/i.test(m.name));
|
|
179
|
+
|
|
180
|
+
if (!hasInitializerMod && !hasReinitializer) {
|
|
181
|
+
this.addFinding({
|
|
182
|
+
title: 'Missing Initializer Modifier',
|
|
183
|
+
description: `Function '${funcName}' appears to be an initializer but lacks the 'initializer' modifier. Without this modifier, the function can be called multiple times, potentially allowing re-initialization attacks.`,
|
|
184
|
+
location: `Contract: ${this.currentContract}, Function: ${funcName}`,
|
|
185
|
+
line: node.loc?.start?.line || 0,
|
|
186
|
+
column: node.loc?.start?.column || 0,
|
|
187
|
+
code: funcCode.substring(0, 200),
|
|
188
|
+
severity: 'CRITICAL',
|
|
189
|
+
confidence: 'HIGH',
|
|
190
|
+
exploitable: true,
|
|
191
|
+
exploitabilityScore: 90,
|
|
192
|
+
attackVector: 're-initialization',
|
|
193
|
+
recommendation: `Add the initializer modifier from OpenZeppelin:
|
|
194
|
+
function initialize(...) external initializer {
|
|
195
|
+
__Ownable_init();
|
|
196
|
+
// ... rest of initialization
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
For version upgrades, use reinitializer(version):
|
|
200
|
+
function initializeV2(...) external reinitializer(2) { ... }`,
|
|
201
|
+
references: [
|
|
202
|
+
'https://docs.openzeppelin.com/contracts/4.x/api/proxy#Initializable'
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check for _disableInitializers in constructor
|
|
208
|
+
if (!this.sourceCode.includes('_disableInitializers')) {
|
|
209
|
+
this.addFinding({
|
|
210
|
+
title: 'Missing _disableInitializers in Constructor',
|
|
211
|
+
description: `Upgradeable contract '${this.currentContract}' does not call _disableInitializers() in constructor. This allows an attacker to initialize the implementation contract directly, potentially causing issues with the proxy.`,
|
|
212
|
+
location: `Contract: ${this.currentContract}`,
|
|
213
|
+
line: 1,
|
|
214
|
+
column: 0,
|
|
215
|
+
code: '',
|
|
216
|
+
severity: 'HIGH',
|
|
217
|
+
confidence: 'MEDIUM',
|
|
218
|
+
exploitable: true,
|
|
219
|
+
exploitabilityScore: 70,
|
|
220
|
+
attackVector: 'implementation-initialization',
|
|
221
|
+
recommendation: `Add constructor that disables initializers:
|
|
222
|
+
constructor() {
|
|
223
|
+
_disableInitializers();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
This prevents the implementation contract from being initialized directly.`
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
analyzeProxyPatterns() {
|
|
232
|
+
if (!this.isProxy && !this.isUpgradeable) return;
|
|
233
|
+
|
|
234
|
+
// Check for storage gap in upgradeable contracts
|
|
235
|
+
if (this.isUpgradeable && !this.hasStorageGap()) {
|
|
236
|
+
this.reportMissingStorageGap();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check for non-upgradeable base contracts
|
|
240
|
+
this.checkNonUpgradeableBases();
|
|
241
|
+
|
|
242
|
+
// Check for storage collision risks
|
|
243
|
+
this.checkStorageCollisionRisks();
|
|
244
|
+
|
|
245
|
+
// Check for function selector collision
|
|
246
|
+
this.checkSelectorCollision();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
hasStorageGap() {
|
|
250
|
+
return this.stateVariables.some(v =>
|
|
251
|
+
v.name === '__gap' || v.name.includes('__gap')
|
|
252
|
+
) || this.sourceCode.includes('__gap');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
checkNonUpgradeableBases() {
|
|
256
|
+
const nonUpgradeableOZ = [
|
|
257
|
+
'Ownable', 'ERC20', 'ERC721', 'ERC1155', 'ReentrancyGuard',
|
|
258
|
+
'Pausable', 'AccessControl', 'ERC20Permit'
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
for (const base of this.inheritedContracts) {
|
|
262
|
+
// Check if using non-upgradeable version
|
|
263
|
+
if (nonUpgradeableOZ.some(nuo => base.name === nuo)) {
|
|
264
|
+
this.addFinding({
|
|
265
|
+
title: 'Non-Upgradeable Base Contract in Upgradeable Contract',
|
|
266
|
+
description: `Contract '${this.currentContract}' inherits from '${base.name}' which is not upgradeable-safe. This can cause storage collisions when upgrading.
|
|
267
|
+
|
|
268
|
+
The non-upgradeable version uses a constructor which doesn't work with proxies, and may have different storage layout than the upgradeable version.`,
|
|
269
|
+
location: `Contract: ${this.currentContract}, Base: ${base.name}`,
|
|
270
|
+
line: base.loc?.start?.line || 0,
|
|
271
|
+
column: base.loc?.start?.column || 0,
|
|
272
|
+
code: `inherits ${base.name}`,
|
|
273
|
+
severity: 'CRITICAL',
|
|
274
|
+
confidence: 'HIGH',
|
|
275
|
+
exploitable: true,
|
|
276
|
+
exploitabilityScore: 85,
|
|
277
|
+
attackVector: 'storage-collision',
|
|
278
|
+
recommendation: `Use the upgradeable version:
|
|
279
|
+
- Ownable → OwnableUpgradeable
|
|
280
|
+
- ERC20 → ERC20Upgradeable
|
|
281
|
+
- ERC721 → ERC721Upgradeable
|
|
282
|
+
- ReentrancyGuard → ReentrancyGuardUpgradeable
|
|
283
|
+
- Pausable → PausableUpgradeable
|
|
284
|
+
- AccessControl → AccessControlUpgradeable
|
|
285
|
+
|
|
286
|
+
And call __ContractName_init() in your initializer.`,
|
|
287
|
+
references: [
|
|
288
|
+
'https://docs.openzeppelin.com/contracts/4.x/upgradeable'
|
|
289
|
+
]
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
checkStorageCollisionRisks() {
|
|
296
|
+
// Check if first state variable could collide with proxy slots
|
|
297
|
+
if (this.stateVariables.length > 0) {
|
|
298
|
+
const firstVar = this.stateVariables[0];
|
|
299
|
+
|
|
300
|
+
// Slot 0 collision with some proxy patterns
|
|
301
|
+
if (!firstVar.isConstant && !firstVar.isImmutable) {
|
|
302
|
+
// Check if this contract is used as implementation
|
|
303
|
+
if (this.isUpgradeable) {
|
|
304
|
+
// Ensure first variable doesn't collide with proxy admin
|
|
305
|
+
// Most modern proxies use EIP-1967 slots, but older patterns used slot 0
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
checkSelectorCollision() {
|
|
312
|
+
if (!this.cfg) return;
|
|
313
|
+
|
|
314
|
+
const selectors = new Map();
|
|
315
|
+
|
|
316
|
+
for (const [funcKey, funcInfo] of this.cfg.functions) {
|
|
317
|
+
if (funcInfo.visibility !== 'public' && funcInfo.visibility !== 'external') {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const selector = this.calculateSelector(funcInfo.name, funcInfo.parameters);
|
|
322
|
+
if (selector) {
|
|
323
|
+
if (selectors.has(selector)) {
|
|
324
|
+
// Selector collision
|
|
325
|
+
this.addFinding({
|
|
326
|
+
title: 'Function Selector Collision',
|
|
327
|
+
description: `Functions '${selectors.get(selector)}' and '${funcInfo.name}' have the same selector. In a proxy pattern, this can cause unexpected behavior as calls to one function may be routed to another.`,
|
|
328
|
+
location: `Contract: ${this.currentContract}`,
|
|
329
|
+
line: funcInfo.node?.loc?.start?.line || 0,
|
|
330
|
+
column: 0,
|
|
331
|
+
code: '',
|
|
332
|
+
severity: 'HIGH',
|
|
333
|
+
confidence: 'HIGH',
|
|
334
|
+
exploitable: true,
|
|
335
|
+
exploitabilityScore: 75,
|
|
336
|
+
attackVector: 'selector-collision',
|
|
337
|
+
recommendation: `Rename one of the functions to avoid selector collision. You can use tools like 'cast sig' to check selectors:
|
|
338
|
+
cast sig "functionName(type1,type2)"`
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
selectors.set(selector, funcInfo.name);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
reportMissingStorageGap() {
|
|
347
|
+
this.addFinding({
|
|
348
|
+
title: 'Missing Storage Gap in Upgradeable Contract',
|
|
349
|
+
description: `Contract '${this.currentContract}' is upgradeable but doesn't include a __gap storage variable. Without a gap, adding new state variables in future upgrades will shift storage layout, corrupting existing data.`,
|
|
350
|
+
location: `Contract: ${this.currentContract}`,
|
|
351
|
+
line: 1,
|
|
352
|
+
column: 0,
|
|
353
|
+
code: this.sourceLines.slice(0, 10).join('\n'),
|
|
354
|
+
severity: 'HIGH',
|
|
355
|
+
confidence: 'HIGH',
|
|
356
|
+
exploitable: false,
|
|
357
|
+
exploitabilityScore: 30,
|
|
358
|
+
attackVector: 'upgrade-storage-collision',
|
|
359
|
+
recommendation: `Add a storage gap at the end of your state variables:
|
|
360
|
+
uint256[50] private __gap;
|
|
361
|
+
|
|
362
|
+
This reserves 50 storage slots for future upgrades. When adding new variables, reduce the gap size accordingly.
|
|
363
|
+
|
|
364
|
+
Example:
|
|
365
|
+
// V1
|
|
366
|
+
uint256 public value1;
|
|
367
|
+
uint256[49] private __gap; // 49 slots reserved
|
|
368
|
+
|
|
369
|
+
// V2 - adding value2
|
|
370
|
+
uint256 public value1;
|
|
371
|
+
uint256 public value2; // New variable
|
|
372
|
+
uint256[48] private __gap; // Reduced to 48`,
|
|
373
|
+
references: [
|
|
374
|
+
'https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps'
|
|
375
|
+
]
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
reportPotentialSlotCollision(node, slot) {
|
|
380
|
+
this.addFinding({
|
|
381
|
+
title: 'Potential Storage Slot Collision',
|
|
382
|
+
description: `Assembly code accesses storage slot ${slot} which may collide with inherited contract storage or proxy admin slots. Manual slot access bypasses Solidity's storage layout, risking data corruption.`,
|
|
383
|
+
location: `Contract: ${this.currentContract}, Function: ${node.name || 'unknown'}`,
|
|
384
|
+
line: node.loc?.start?.line || 0,
|
|
385
|
+
column: node.loc?.start?.column || 0,
|
|
386
|
+
code: this.getCodeSnippet(node.loc)?.substring(0, 200) || '',
|
|
387
|
+
severity: 'HIGH',
|
|
388
|
+
confidence: 'MEDIUM',
|
|
389
|
+
exploitable: true,
|
|
390
|
+
exploitabilityScore: 60,
|
|
391
|
+
attackVector: 'storage-collision',
|
|
392
|
+
recommendation: `Use EIP-1967 standard slots for proxy storage:
|
|
393
|
+
bytes32 constant IMPLEMENTATION_SLOT = 0x360894...;
|
|
394
|
+
bytes32 constant ADMIN_SLOT = 0xb53127...;
|
|
395
|
+
|
|
396
|
+
Or use namespaced storage (ERC-7201):
|
|
397
|
+
bytes32 constant STORAGE_LOCATION = keccak256("myprotocol.storage.main");`
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Helper methods
|
|
402
|
+
|
|
403
|
+
calculateStorageSlot(variable, index) {
|
|
404
|
+
// Simplified - actual slot calculation is complex
|
|
405
|
+
if (variable.isDeclaredConst || variable.isImmutable) {
|
|
406
|
+
return null; // No storage slot
|
|
407
|
+
}
|
|
408
|
+
return index; // Simplified sequential slots
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
isKnownProxySlot(slot) {
|
|
412
|
+
const normalizedSlot = slot.toLowerCase();
|
|
413
|
+
return Object.values(this.KNOWN_SLOTS).some(known =>
|
|
414
|
+
known.toLowerCase() === normalizedSlot
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
couldCollide(slot) {
|
|
419
|
+
// Check if slot could collide with regular storage (low slots)
|
|
420
|
+
try {
|
|
421
|
+
const slotNum = BigInt(slot);
|
|
422
|
+
return slotNum < 100n; // Low slots more likely to collide
|
|
423
|
+
} catch {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
calculateSelector(funcName, parameters) {
|
|
429
|
+
if (!funcName) return null;
|
|
430
|
+
// Simplified selector calculation
|
|
431
|
+
// Real implementation would use keccak256
|
|
432
|
+
const signature = `${funcName}(${parameters.map(p => p.type).join(',')})`;
|
|
433
|
+
return signature; // Would be first 4 bytes of keccak256
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
getTypeName(typeName) {
|
|
437
|
+
if (!typeName) return 'unknown';
|
|
438
|
+
if (typeName.type === 'ElementaryTypeName') return typeName.name;
|
|
439
|
+
if (typeName.type === 'UserDefinedTypeName') return typeName.namePath;
|
|
440
|
+
if (typeName.type === 'ArrayTypeName') return `${this.getTypeName(typeName.baseTypeName)}[]`;
|
|
441
|
+
if (typeName.type === 'Mapping') return 'mapping';
|
|
442
|
+
return 'complex';
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
module.exports = StorageCollisionDetector;
|