/** * Push3 → Solidity transpiler CLI entry point. * * Usage: tsx src/index.ts * * 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()` 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 '); 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();