harb/tools/push3-transpiler/src/index.ts
openhands 4a24217030 fix: inject.sh uses ts-node which is broken on Node >= 22 ESM (#1008)
Replace ts-node with tsx across all push3-transpiler scripts:
- inject.sh: npx ts-node → npx tsx
- test_inject_extraction.sh: node --loader ts-node/esm → npx tsx
- test_transpiler_clamping.sh: node --loader ts-node/esm → npx tsx
- package.json: ts-node devDep → tsx, transpile script updated
- tsconfig.json: removed ts-node config block
- src/index.ts: updated usage comments

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:44:29 +00:00

162 lines
6.2 KiB
TypeScript

/**
* Push3 → Solidity transpiler CLI entry point.
*
* Usage: tsx src/index.ts <input.push3> <output.sol>
*
* Reads a Push3 program that consumes 8 dyadic rational inputs (slot 0 on top
* of the DYADIC stack) and leaves 4 values at termination, then emits a
* Solidity contract implementing calculateParams(OptimizerInput[8]).
*/
import * as fs from 'fs';
import * as path from 'path';
import { parse } from './parser.js';
import { transpile } from './transpiler.js';
const INT_LITERAL_RE = /^-?\d+$/;
/**
* Validates that a transpiler output variable is a non-negative integer literal
* (or a Solidity expression, which is unchecked). Throws a clear error rather
* than silently emitting `uint256(<negative>)` which Solidity would reject with a
* cryptic compile-time message.
*/
function assertNonNegativeLiteral(value: string, name: string): void {
if (INT_LITERAL_RE.test(value)) {
const n = BigInt(value);
if (n < 0n) {
throw new Error(
`Transpiler error: ${name} is a negative literal (${value}). ` +
`uint256(${value}) is rejected by the Solidity compiler. ` +
`Fix the Push3 program to produce a non-negative value for ${name}.`,
);
}
}
}
/**
* Validates that a transpiler output variable for a uint24 field is within the
* valid range [0, 16777215] when it is a literal constant. Large literals that
* are computed at runtime are still wrapped with % (2**24) in the generated code.
*/
function assertUint24Range(value: string, name: string): void {
if (INT_LITERAL_RE.test(value)) {
const n = BigInt(value);
if (n < 0n || n > 16777215n) {
throw new Error(
`Transpiler error: ${name} literal (${value}) is outside uint24 range [0, 16777215]. ` +
`Fix the Push3 program to produce a value in [0, 16777215] for ${name}.`,
);
}
}
}
/**
* Validates that a transpiler output variable for a uint256 field scaled to 1e18
* does not exceed 1e18 when it is a literal constant. Values > 1e18 violate
* LiquidityManager invariants (these fields represent fractions in [0, 1]).
*/
function assertUint256Max1e18(value: string, name: string): void {
if (INT_LITERAL_RE.test(value)) {
const n = BigInt(value);
if (n > 1000000000000000000n) {
throw new Error(
`Transpiler error: ${name} literal (${value}) exceeds 1e18 (max allowed). ` +
`Fix the Push3 program to produce a value in [0, 1e18] for ${name}.`,
);
}
}
}
function main(): void {
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('Usage: tsx src/index.ts <input.push3> <output.sol>');
process.exit(1);
}
const inputPath = args[0];
const outputPath = args[1];
const src = fs.readFileSync(inputPath, 'utf8');
console.log(`Parsing ${path.basename(inputPath)}...`);
const ast = parse(src);
console.log('Transpiling...');
const { functionBody, ciVar, anchorShareVar, anchorWidthVar, discoveryDepthVar } = transpile(ast);
assertNonNegativeLiteral(ciVar, 'ci');
assertUint256Max1e18(ciVar, 'ci');
assertNonNegativeLiteral(anchorShareVar, 'anchorShare');
assertUint256Max1e18(anchorShareVar, 'anchorShare');
assertUint24Range(anchorWidthVar, 'anchorWidth');
assertNonNegativeLiteral(discoveryDepthVar, 'discoveryDepth');
assertUint256Max1e18(discoveryDepthVar, 'discoveryDepth');
const solidityLines = [
'// SPDX-License-Identifier: GPL-3.0-or-later',
'pragma solidity ^0.8.19;',
'',
'import {OptimizerInput} from "./IOptimizer.sol";',
'',
'/**',
' * @title OptimizerV3Push3',
' * @notice Auto-generated from optimizer_v3.push3 via Push3→Solidity transpiler.',
' * Implements calculateParams with 8 dyadic rational inputs and 4 outputs.',
' */',
'contract OptimizerV3Push3 {',
' /**',
' * @notice Compute liquidity parameters from 8 dyadic rational inputs.',
' * @param inputs 8-slot dyadic rational array: slot 0 = percentageStaked (top of Push3 stack),',
' * slot 1 = averageTaxRate, slots 2-7 = extended metrics (0 if unavailable).',
' * @return ci Capital inefficiency (0..1e18).',
' * @return anchorShare Fraction of non-floor ETH in anchor (0..1e18).',
' * @return anchorWidth Anchor position width in tick units.',
' * @return discoveryDepth Discovery liquidity density (0..1e18).',
' */',
' function calculateParams(OptimizerInput[8] memory inputs)',
' public',
' pure',
' returns (uint256 ci, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)',
' {',
' // Validate mantissa for percentageStaked',
' require(inputs[0].mantissa <= 1e18, "mantissa overflow");',
'',
' // Validate that shift is 0 (future-only field, not yet supported)',
' for (uint256 k = 0; k < 8; k++) {',
' require(inputs[k].shift == 0, "shift not yet supported");',
' }',
'',
' // Layer A: bear defaults — any output not overwritten by the program keeps these.',
' // Matches Push3 no-op semantics: a program that crashes or produces no output',
' // returns safe bear-mode parameters rather than reverting.',
' ci = 0;',
' anchorShare = 300000000000000000;',
' anchorWidth = 100;',
' discoveryDepth = 300000000000000000;',
'',
' // Layer C: unchecked arithmetic — overflow wraps (matches Push3 semantics).',
' // Division by zero is guarded at the expression level (b == 0 ? 0 : a / b).',
' unchecked {',
...functionBody,
` ci = uint256(${ciVar});`,
` anchorShare = uint256(${anchorShareVar});`,
` anchorWidth = uint24(${anchorWidthVar} % (2**24));`,
` discoveryDepth = uint256(${discoveryDepthVar});`,
' }',
' }',
'}',
'',
];
const output = solidityLines.join('\n');
fs.writeFileSync(outputPath, output, 'utf8');
console.log(`Written: ${outputPath}`);
// Print a summary
console.log(` Function body: ${functionBody.length} lines`);
console.log(` Outputs: ci=${ciVar}, anchorShare=${anchorShareVar}, anchorWidth=${anchorWidthVar}, discoveryDepth=${discoveryDepthVar}`);
}
main();