neo-devpack-solidity 0.22.0

Production-focused Solidity-to-NeoVM compilation system
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
#!/usr/bin/env node
'use strict';

const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');

const ROOT = path.resolve(__dirname, '..');
const TARGETS_PATH = path.join(ROOT, 'docs/data/famous-contracts-targets.json');
const REPORT_MD_PATH = path.join(ROOT, 'docs/solidity/famous-contracts-neo-audit.md');
const REPORT_JSON_PATH = path.join(ROOT, 'docs/data/famous-contracts-audit-results.json');
const AUDIT_DIR = process.env.NEO_FAMOUS_AUDIT_DIR || '/tmp/neo-famous-contracts-audit';
const BUILD_DIR = process.env.NEO_FAMOUS_BUILD_DIR || '/tmp/neo-famous-contracts-build';
const VENDORED_SOURCES_ROOT = path.join(ROOT, 'third_party/famous-contracts/sources');

const NPM_PACKAGES = [
  '@openzeppelin/contracts@5.4.0',
  '@openzeppelin/contracts-upgradeable@5.4.0',
  '@aave/core-v3@1.19.3',
  '@safe-global/safe-contracts@1.4.1-2',
  '@chainlink/contracts@1.5.0',
  'solmate',
  '@uniswap/v4-core@1.0.2',
  '@uniswap/v4-periphery@1.0.3',
  '@uniswap/v3-core@1.0.1',
  '@uniswap/v3-periphery@1.4.4'
];

const REQUIRED_PACKAGE_PROBES = [
  '@openzeppelin/contracts/package.json',
  '@openzeppelin/contracts-upgradeable/package.json',
  '@aave/core-v3/package.json',
  '@safe-global/safe-contracts/package.json',
  '@chainlink/contracts/package.json',
  'solmate/package.json',
  '@uniswap/v2-core/package.json',
  '@uniswap/v4-core/package.json',
  '@uniswap/v4-periphery/package.json'
];

const OPENZEPPELIN_ALIAS_CANDIDATES = ['4.7.3', '4.8.3', '4.9.6', '5.0.2', '5.1.0'];

function runCommand(cmd, args, options = {}) {
  const result = spawnSync(cmd, args, {
    encoding: 'utf8',
    maxBuffer: 50 * 1024 * 1024,
    ...options
  });

  if (result.error) {
    throw result.error;
  }

  return result;
}

function ensureNeoSolc() {
  const releaseBin = path.join(ROOT, 'target/release/neo-solc');
  if (fs.existsSync(releaseBin)) {
    return releaseBin;
  }

  console.log('[audit] Building neo-solc release binary...');
  const build = runCommand('cargo', ['build', '--release', '--bin', 'neo-solc'], { cwd: ROOT });
  if (build.status !== 0) {
    throw new Error(`failed to build neo-solc:\n${build.stdout}\n${build.stderr}`);
  }

  if (!fs.existsSync(releaseBin)) {
    throw new Error('neo-solc binary missing after build');
  }

  return releaseBin;
}

function ensureAuditWorkspace() {
  fs.mkdirSync(AUDIT_DIR, { recursive: true });
  const nodeModulesDir = path.join(AUDIT_DIR, 'node_modules');

  const hasAllPackages = REQUIRED_PACKAGE_PROBES.every((probe) =>
    fs.existsSync(path.join(nodeModulesDir, probe))
  );

  if (hasAllPackages) {
    return;
  }

  console.log('[audit] Installing famous protocol npm dependencies...');
  if (!fs.existsSync(path.join(AUDIT_DIR, 'package.json'))) {
    const init = runCommand('npm', ['init', '-y'], { cwd: AUDIT_DIR });
    if (init.status !== 0) {
      throw new Error(`npm init failed:\n${init.stdout}\n${init.stderr}`);
    }
  }

  const install = runCommand('npm', ['install', '--silent', ...NPM_PACKAGES], { cwd: AUDIT_DIR });
  if (install.status !== 0) {
    throw new Error(`npm install failed:\n${install.stdout}\n${install.stderr}`);
  }
}

function ensureOpenZeppelinVersionAliases() {
  const ozRoot = path.join(AUDIT_DIR, 'node_modules', '@openzeppelin');
  if (!fs.existsSync(ozRoot)) {
    return;
  }

  for (const version of OPENZEPPELIN_ALIAS_CANDIDATES) {
    const contractsVersionDir = path.join(ozRoot, `contracts-${version}`);
    const contractsAlias = path.join(ozRoot, `contracts@${version}`);
    if (fs.existsSync(contractsVersionDir)) {
      try {
        fs.rmSync(contractsAlias, { recursive: true, force: true });
      } catch {
        // noop
      }
      fs.symlinkSync(`contracts-${version}`, contractsAlias);
    }

    const upgradeableVersionDir = path.join(ozRoot, `contracts-upgradeable-${version}`);
    const upgradeableAlias = path.join(ozRoot, `contracts-upgradeable@${version}`);
    if (fs.existsSync(upgradeableVersionDir)) {
      try {
        fs.rmSync(upgradeableAlias, { recursive: true, force: true });
      } catch {
        // noop
      }
      fs.symlinkSync(`contracts-upgradeable-${version}`, upgradeableAlias);
    }
  }
}

function ensurePermit2Alias() {
  const nodeModulesDir = path.join(AUDIT_DIR, 'node_modules');
  const vendoredPermit2Dir = path.join(
    nodeModulesDir,
    '@uniswap',
    'v4-periphery',
    'lib',
    'permit2'
  );
  const aliasPermit2Dir = path.join(nodeModulesDir, 'permit2');

  if (!fs.existsSync(vendoredPermit2Dir)) {
    return;
  }

  try {
    if (fs.existsSync(aliasPermit2Dir)) {
      return;
    }
    fs.symlinkSync(vendoredPermit2Dir, aliasPermit2Dir, 'dir');
  } catch {
    if (!fs.existsSync(aliasPermit2Dir)) {
      fs.cpSync(vendoredPermit2Dir, aliasPermit2Dir, { recursive: true });
    }
  }
}

function parseDiagnostics(stderr) {
  const diagnostics = [];
  for (const rawLine of stderr.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (!line) {
      continue;
    }

    if (line.startsWith('{') && line.endsWith('}')) {
      try {
        const parsed = JSON.parse(line);
        diagnostics.push({
          severity: parsed.severity || 'error',
          code: parsed.code || 'UNKNOWN',
          message: parsed.message || parsed.formattedMessage || line
        });
        continue;
      } catch {
        // fall through
      }
    }

    const severity = /warning/i.test(line) ? 'warning' : 'error';
    diagnostics.push({ severity, code: 'RAW', message: line });
  }

  return diagnostics;
}

function normalizeMessage(msg) {
  return msg.replace(/\s+/g, ' ').trim();
}

function classifyBlocker(message) {
  const lower = message.toLowerCase();

  if (lower.includes('inline assembly is not supported')) {
    return {
      tag: 'assembly',
      fix: '需要移除 `assembly`,改用 `NativeCalls.sol` / `Syscalls.sol` / 高级 Solidity 语义重写'
    };
  }

  if (lower.includes("unsupported low-level evm call 'delegatecall'")) {
    return {
      tag: 'delegatecall',
      fix: '需要改为显式 `Syscalls.contractCall` 跨合约调用,并按 Neo 存储隔离模型重构状态共享'
    };
  }

  if (lower.includes('unsupported low-level evm call')) {
    return {
      tag: 'low_level_call',
      fix: '需要将低层 `call/staticcall/callcode` 迁移到受控 `Syscalls.contractCall` + 明确 ABI 序列化'
    };
  }

  if (lower.includes('unsupported type') && lower.includes('mapping(')) {
    return {
      tag: 'named_mapping',
      fix: '需要编译器补齐命名 `mapping(address key => T)` 语法 lowering,或改写为 `mapping(address => T)`'
    };
  }

  if (lower.includes('import cycle detected')) {
    return {
      tag: 'import_cycle',
      fix: '需要编译器导入解析支持循环依赖图,或对上游源码做解环拆分'
    };
  }

  if (lower.includes('overloaded function') && lower.includes('not supported')) {
    return {
      tag: 'abi_overload',
      fix: '需要避免同参数个数的重载(重命名公开方法),或扩展 Neo ABI 到签名级调度'
    };
  }

  if (lower.includes('error resolving imports') || lower.includes('cannot resolve import')) {
    return {
      tag: 'import_resolution',
      fix: '需要补齐依赖库和 include path;若为工具链导入限制,需扩展导入解析能力'
    };
  }

  if (lower.includes('unknown identifier')) {
    return {
      tag: 'name_resolution',
      fix: '需要补齐对应语义 lowering(命名解析/继承展平),或对源码做 Neo 兼容重写'
    };
  }

  if (lower.includes('pragma') && lower.includes('solidity')) {
    return {
      tag: 'solidity_version',
      fix: '需要将源码迁移到 Solidity 0.8.x 范围并处理破坏性变更'
    };
  }

  if (lower.includes('unsupported solidity version')) {
    return {
      tag: 'solidity_version',
      fix: '需要将源码迁移到 Solidity 0.8.x 范围并处理破坏性变更'
    };
  }

  if (lower.includes('function call options (`{...}`) are not supported (value)')) {
    return {
      tag: 'value_call_options',
      fix: '需要把 `{value: ...}` 风格调用改成显式 NEP-17 转账(`NativeCalls.gasTransfer` / `NativeCalls.neoTransfer`)并使用 `onNEP17Payment` 回调'
    };
  }

  if (lower.includes('abi.encodewithselector is only supported for neo contract calls')) {
    return {
      tag: 'abi_encode_selector',
      fix: '需要改用 `Syscalls.contractCall` / `Syscalls.contractCallWithFlags` / `NativeCalls.*`,不要依赖原生 EVM calldata 字节流'
    };
  }

  if (lower.includes('inheritance linearization failed')) {
    return {
      tag: 'inheritance_linearization',
      fix: '需要调整继承层次(或扩展编译器的 C3 线性化兼容),避免多重继承顺序冲突'
    };
  }

  if (lower.includes('modifier/constructor argument mismatch')) {
    return {
      tag: 'ctor_modifier_mismatch',
      fix: '需要修复构造器/修饰器参数传递路径,或扩展编译器对复杂构造器链的 lowering'
    };
  }

  if (lower.includes('uses unsupported type')) {
    return {
      tag: 'unsupported_param_type',
      fix: '需要扩展接口/结构体参数类型 lowering(复杂参数序列化),或先重构为基础类型边界'
    };
  }

  if (lower.includes('duplicate state variable')) {
    return {
      tag: 'duplicate_state_var',
      fix: '需要修复状态变量命名冲突解析(编译器语义分析)或在源码层拆分冲突字段'
    };
  }

  return {
    tag: 'other',
    fix: '需要扩展 neo-devpack-solidity 对该语义的 IR lowering,或用 Neo 等价模式重写该模块'
  };
}

function sanitizeName(input) {
  return input.replace(/[^a-zA-Z0-9_.-]+/g, '_');
}

function resolveSourcePath(target) {
  if (target.source === 'repo') {
    return path.join(ROOT, target.path);
  }

  if (target.source === 'npm') {
    return path.join(AUDIT_DIR, 'node_modules', target.path);
  }

  return path.join(ROOT, target.path || '');
}

function compileTarget(neoSolc, target, index, total) {
  const sourcePath = resolveSourcePath(target);
  const vendoredSourcePath =
    target.source === 'npm' ? path.join(VENDORED_SOURCES_ROOT, target.path) : null;

  const progress = `[${String(index + 1).padStart(2, '0')}/${total}]`;

  if (!fs.existsSync(sourcePath)) {
    console.log(`${progress} ${target.project} / ${target.contract}: missing source`);
    return {
      ...target,
      sourcePath,
      status: 'missing',
      exitCode: null,
      diagnostics: [],
      mainIssue: 'source file not found',
      blockerTag: 'missing_source',
      neoRequirement: '需要修正目标路径或更换可公开获取的官方源码入口'
    };
  }

  const prefix = path.join(BUILD_DIR, sanitizeName(`${index}_${target.project}_${target.contract}`));
  const args = [
    sourcePath,
    '-I',
    path.join(ROOT, 'devpack'),
    '-I',
    path.join(AUDIT_DIR, 'node_modules'),
    '--json-errors',
    '--json-warnings',
    '-o',
    prefix
  ];

  const result = runCommand(neoSolc, args, { cwd: ROOT });
  const diagnostics = parseDiagnostics(result.stderr || '');
  const errors = diagnostics.filter((d) => d.severity !== 'warning');
  const status = result.status === 0 ? 'pass' : 'fail';

  let mainIssue = '';
  let blockerTag = 'none';
  let neoRequirement = '可直接编译为 NeoVM(如需生产使用,仍需做 manifest 权限最小化和业务安全审计)';

  if (status !== 'pass') {
    const primary = errors[0] || diagnostics[0];
    mainIssue = primary ? normalizeMessage(primary.message) : `compiler exited with ${result.status}`;
    const classified = classifyBlocker(mainIssue);
    blockerTag = classified.tag;
    neoRequirement = classified.fix;
  }

  console.log(`${progress} ${target.project} / ${target.contract}: ${status}`);

  return {
    ...target,
    sourcePath,
    vendoredSourcePath: vendoredSourcePath && fs.existsSync(vendoredSourcePath) ? vendoredSourcePath : null,
    status,
    exitCode: result.status,
    diagnostics,
    mainIssue,
    blockerTag,
    neoRequirement
  };
}

function topBlockers(results) {
  const counter = new Map();
  for (const row of results) {
    if (row.status !== 'fail') {
      continue;
    }
    const key = row.blockerTag || 'other';
    counter.set(key, (counter.get(key) || 0) + 1);
  }
  return [...counter.entries()].sort((a, b) => b[1] - a[1]);
}

function short(text, max = 140) {
  if (!text) {
    return '-';
  }
  return text.length <= max ? text : `${text.slice(0, max - 1)}`;
}

function cleanPathText(text) {
  if (!text) {
    return '';
  }
  return String(text)
    .replaceAll(
      `${VENDORED_SOURCES_ROOT.split(path.sep).join('/')}/`,
      'third_party/famous-contracts/sources/'
    )
    .replaceAll('/private/tmp/neo-famous-contracts-audit/node_modules/', 'node_modules/')
    .replaceAll('/tmp/neo-famous-contracts-audit/node_modules/', 'node_modules/');
}

function sourceForReport(row) {
  if (row.vendoredSourcePath && row.vendoredSourcePath.startsWith(ROOT)) {
    return path.relative(ROOT, row.vendoredSourcePath).split(path.sep).join('/');
  }

  if (row.sourcePath && row.sourcePath.startsWith(ROOT)) {
    return path.relative(ROOT, row.sourcePath).split(path.sep).join('/');
  }

  if (row.source === 'npm') {
    return `third_party/famous-contracts/sources/${row.path}`;
  }

  if (row.path) {
    return row.path;
  }

  return row.sourcePath || '-';
}

function generateMarkdown(neoSolcVersion, results, generatedAt = new Date().toISOString()) {
  const upstreamResults = results.filter((r) => r.source === 'npm');

  const upstreamPass = upstreamResults.filter((r) => r.status === 'pass').length;
  const upstreamFail = upstreamResults.filter((r) => r.status === 'fail').length;
  const upstreamMissing = upstreamResults.filter((r) => r.status === 'missing').length;

  const blockers = topBlockers(upstreamResults);

  const lines = [];
  lines.push('# Famous Solidity Contracts on NeoVM: Compatibility Audit');
  lines.push('');
  lines.push(`- Generated at (UTC): \`${generatedAt}\``);
  lines.push('- Snapshot scope: historical compatibility output; rerun `npm run audit:famous-contracts` before treating these results as current release evidence.');
  lines.push(`- Contracts shown in this report (upstream, vendored in repo): \`${upstreamResults.length}\``);
  lines.push(`- Compile success: \`${upstreamPass}\``);
  lines.push(`- Compile failed: \`${upstreamFail}\``);
  if (upstreamMissing > 0) {
    lines.push(`- Missing source entries: \`${upstreamMissing}\``);
  }
  lines.push('');
  lines.push('Primary navigation for original upstream contracts: [Original Famous Contracts (Per Contract)](/solidity/original-contracts/).');
  lines.push('');
  lines.push('## What "Need XXX to Implement" Means');
  lines.push('');
  lines.push('- This report marks each failing contract with the **primary unsupported point** in current `neo-devpack-solidity`.');
  lines.push('- The "Need on Neo" column states what is required to make that pattern work:');
  lines.push('  1) compiler capability expansion, and/or');
  lines.push('  2) Solidity source refactor to Neo-native patterns (`Runtime`, `Syscalls`, `NativeCalls`, `onNEP17Payment`, etc.).');
  lines.push('');
  lines.push('## Top Blockers (Upstream Contracts)');
  lines.push('');

  if (blockers.length === 0) {
    lines.push('- No blockers in this run.');
  } else {
    for (const [tag, count] of blockers) {
      lines.push(`- \`${tag}\`: ${count}`);
    }
  }

  lines.push('');
  lines.push('## Per-Contract Results (Upstream Famous Contracts)');
  lines.push('');
  lines.push('| # | Project | Contract | Result | Primary Unsupported Point | Need on Neo | Source |');
  lines.push('|---:|---|---|---|---|---|---|');

  upstreamResults.forEach((row, idx) => {
    const resultMark = row.status === 'pass' ? '✅ pass' : row.status === 'missing' ? '⚪ missing' : '❌ fail';
    lines.push(
      `| ${idx + 1} | ${row.project} | ${row.contract} | ${resultMark} | ${short(cleanPathText(row.mainIssue))} | ${short(
        row.neoRequirement,
        160
      )} | \`${sourceForReport(row)}\` |`
    );
  });

  lines.push('');
  lines.push('## Notes');
  lines.push('');
  lines.push('- This report prioritizes **upstream famous contracts vendored in this repository** in the main results table.');
  lines.push('- A **pass** means the contract compiled through `neo-solc` in this environment.');
  lines.push('- A **fail** does not mean the contract is impossible on Neo; it means current source + current compiler need refactor or feature work.');
  lines.push('- Use this as a migration backlog: prioritize high-value blockers (`delegatecall`, import cycles, ABI overload collisions, named mapping syntax).');

  return lines.join('\n');
}

function main() {
  if (process.argv.includes('--render-only')) {
    if (!fs.existsSync(REPORT_JSON_PATH)) {
      throw new Error(`render-only requested but JSON report not found: ${REPORT_JSON_PATH}`);
    }

    const reportJson = JSON.parse(fs.readFileSync(REPORT_JSON_PATH, 'utf8'));
    fs.writeFileSync(
      REPORT_MD_PATH,
      generateMarkdown(
        reportJson.compiler || 'neo-solc (version unavailable)',
        Array.isArray(reportJson.results) ? reportJson.results : [],
        reportJson.generatedAt || new Date().toISOString()
      )
    );
    console.log(`[audit] Rendered Markdown from existing JSON: ${path.relative(ROOT, REPORT_MD_PATH)}`);
    return;
  }

  fs.mkdirSync(BUILD_DIR, { recursive: true });

  const neoSolc = ensureNeoSolc();
  ensureAuditWorkspace();
  ensureOpenZeppelinVersionAliases();
  ensurePermit2Alias();

  const versionResult = runCommand(neoSolc, ['--version'], { cwd: ROOT });
  const neoSolcVersion = versionResult.status === 0 ? versionResult.stdout : 'neo-solc (version unavailable)';

  const targets = JSON.parse(fs.readFileSync(TARGETS_PATH, 'utf8'));
  const results = [];

  console.log(`[audit] Starting compilation of ${targets.length} famous contracts...`);

  for (let i = 0; i < targets.length; i++) {
    results.push(compileTarget(neoSolc, targets[i], i, targets.length));
  }

  const reportJson = {
    generatedAt: new Date().toISOString(),
    compiler: neoSolcVersion.trim(),
    root: ROOT,
    auditDir: AUDIT_DIR,
    buildDir: BUILD_DIR,
    totals: {
      total: results.length,
      pass: results.filter((r) => r.status === 'pass').length,
      fail: results.filter((r) => r.status === 'fail').length,
      missing: results.filter((r) => r.status === 'missing').length
    },
    blockerStats: Object.fromEntries(topBlockers(results)),
    results
  };

  fs.writeFileSync(REPORT_JSON_PATH, JSON.stringify(reportJson, null, 2));
  fs.writeFileSync(REPORT_MD_PATH, generateMarkdown(neoSolcVersion, results, reportJson.generatedAt));

  console.log(`[audit] Wrote JSON: ${path.relative(ROOT, REPORT_JSON_PATH)}`);
  console.log(`[audit] Wrote Markdown: ${path.relative(ROOT, REPORT_MD_PATH)}`);
}

main();