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>
162 lines
6.2 KiB
TypeScript
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();
|