diff --git a/onchain/src/OptimizerV3Push3.sol b/onchain/src/OptimizerV3Push3.sol index af5b2b4..83d70ab 100644 --- a/onchain/src/OptimizerV3Push3.sol +++ b/onchain/src/OptimizerV3Push3.sol @@ -31,25 +31,13 @@ contract OptimizerV3Push3 { require(inputs[k].shift == 0, "shift not yet supported"); } - // Decode dyadic rational inputs (mantissa * 2^(-shift); shift=0 for current inputs) - uint256 _d0 = uint256(inputs[0].mantissa); - uint256 _d1 = uint256(inputs[1].mantissa); - /* _d2 = uint256(inputs[2].mantissa); */ - // Available but not used in current implementation - /* _d3 = uint256(inputs[3].mantissa); */ - // Available but not used in current implementation - /* _d4 = uint256(inputs[4].mantissa); */ - // Available but not used in current implementation - /* _d5 = uint256(inputs[5].mantissa); */ - // Available but not used in current implementation - /* _d6 = uint256(inputs[6].mantissa); */ - // Available but not used in current implementation - /* _d7 = uint256(inputs[7].mantissa); */ - // Available but not used in current implementation - uint256 rawpct = uint256(_d0); - uint256 taxrate = uint256(_d1); - uint256 staked = uint256(((rawpct * 100) / 1000000000000000000)); - bool b33; + uint256 percentagestaked = uint256(uint256(inputs[0].mantissa)); + uint256 taxrate = uint256(uint256(inputs[1].mantissa)); + uint256 staked = uint256(((percentagestaked * 100) / 1000000000000000000)); + uint256 r37; + uint256 r38; + uint256 r39; + uint256 r40; if ((staked > 91)) { uint256 deltas = uint256((100 - staked)); uint256 r28; @@ -125,172 +113,60 @@ contract OptimizerV3Push3 { r11 = uint256(17); } else { uint256 r10; - if ((taxrate <= 63917525773195876)) - { + if ((taxrate <= 63917525773195876)) { r10 = uint256(18); } else { uint256 r9; - if ( - ( - taxrate - <= 83505154639175257 - ) - ) { + if ((taxrate <= 83505154639175257)) { r9 = uint256(19); } else { uint256 r8; - if ( - ( - taxrate - <= - 109278350515463917 - ) - ) { + if ((taxrate <= 109278350515463917)) { r8 = uint256(20); } else { uint256 r7; - if ( - ( - taxrate - <= - 144329896907216494 - ) - ) { + if ((taxrate <= 144329896907216494)) { r7 = uint256(21); } else { uint256 r6; - if ( - ( - taxrate - <= - 185567010309278350 - ) - ) { + if ((taxrate <= 185567010309278350)) { r6 = uint256(22); } else { uint256 r5; - if ( - ( - taxrate - <= - 237113402061855670 - ) - ) { - r5 = uint256( - 23 - ); + if ((taxrate <= 237113402061855670)) { + r5 = uint256(23); } else { uint256 r4; - if ( - ( - taxrate - <= - 309278350515463917 - ) - ) { - r4 = - uint256( - 24 - ); + if ((taxrate <= 309278350515463917)) { + r4 = uint256(24); } else { - uint256 - r3; - if ( - ( - taxrate - <= - 402061855670103092 - ) - ) { - r3 = - uint256( - 25 - ); + uint256 r3; + if ((taxrate <= 402061855670103092)) { + r3 = uint256(25); } else { - uint256 - r2; - if ( - ( - taxrate - <= - 520618556701030927 - ) - ) { - r2 - = - uint256( - 26 - ); - } - else - { - uint256 - r1; - if ( - ( - taxrate - <= - 680412371134020618 - ) - ) - { - r1 - = - uint256( - 27 - ); - } - else - { - uint256 - r0; - if ( - ( - taxrate - <= - 886597938144329896 - ) - ) - { - r0 - = - uint256( - 28 - ); + uint256 r2; + if ((taxrate <= 520618556701030927)) { + r2 = uint256(26); + } else { + uint256 r1; + if ((taxrate <= 680412371134020618)) { + r1 = uint256(27); + } else { + uint256 r0; + if ((taxrate <= 886597938144329896)) { + r0 = uint256(28); + } else { + r0 = uint256(29); } - else - { - r0 - = - uint256( - 29 - ); - } - r1 - = - uint256( - r0 - ); + r1 = uint256(r0); } - r2 - = - uint256( - r1 - ); + r2 = uint256(r1); } - r3 = - uint256( - r2 - ); + r3 = uint256(r2); } - r4 = - uint256( - r3 - ); + r4 = uint256(r3); } - r5 = uint256( - r4 - ); + r5 = uint256(r4); } r6 = uint256(r5); } @@ -353,28 +229,34 @@ contract OptimizerV3Push3 { r32 = uint256(dup29); } uint256 effidx = uint256(r32); - b33 = (((((deltas * deltas) * deltas) * effidx) / 20) < 50); + uint256 r33; + uint256 r34; + uint256 r35; + uint256 r36; + if ((((((deltas * deltas) * deltas) * effidx) / 20) < 50)) { + r33 = uint256(1000000000000000000); + r34 = uint256(20); + r35 = uint256(1000000000000000000); + r36 = uint256(0); + } else { + r33 = uint256(300000000000000000); + r34 = uint256(100); + r35 = uint256(300000000000000000); + r36 = uint256(0); + } + r37 = uint256(r33); + r38 = uint256(r34); + r39 = uint256(r35); + r40 = uint256(r36); } else { - b33 = false; + r37 = uint256(300000000000000000); + r38 = uint256(100); + r39 = uint256(300000000000000000); + r40 = uint256(0); } - uint256 r34; - uint256 r35; - uint256 r36; - uint256 r37; - if (b33) { - r34 = uint256(1000000000000000000); - r35 = uint256(20); - r36 = uint256(1000000000000000000); - r37 = uint256(0); - } else { - r34 = uint256(300000000000000000); - r35 = uint256(100); - r36 = uint256(300000000000000000); - r37 = uint256(0); - } - ci = uint256(r37); - anchorShare = uint256(r36); - anchorWidth = uint24(r35); - discoveryDepth = uint256(r34); + ci = uint256(r40); + anchorShare = uint256(r39); + anchorWidth = uint24(r38); + discoveryDepth = uint256(r37); } } diff --git a/tools/push3-transpiler/optimizer_seed.push3 b/tools/push3-transpiler/optimizer_seed.push3 new file mode 100644 index 0000000..4414ff2 --- /dev/null +++ b/tools/push3-transpiler/optimizer_seed.push3 @@ -0,0 +1,222 @@ +;; optimizer_seed.push3 — initial seed for Push3 evolution (#544, #545, #546) +;; +;; This is the starting population member for the evolutionary optimizer. +;; It implements the same binary bear/bull logic as optimizer_v3.push3 and +;; uses all 8 input slots of the dyadic rational interface (slots 2-7 reserved +;; for future trackers; currently passed as 0 and discarded here). +;; +;; Inputs on DYADIC stack (slot 0 on top, slot 7 at bottom): +;; [0] percentageStaked (0 to 1e18, where 1e18 = 100%) +;; [1] averageTaxRate (0 to 1e18, normalized from Stake contract) +;; [2] vwapX96 (future; 0 if unavailable) +;; [3] currentTick (future; 0 if unavailable) +;; [4] recentVolume (future; 0 if unavailable) +;; [5] timeSinceLastRecenter (future; 0 if unavailable) +;; [6] movingAveragePrice (future; 0 if unavailable) +;; [7] reserved 0 +;; +;; Outputs on DYADIC stack at termination (top to bottom): +;; top: capitalInefficiency (0..1e18) +;; anchorShare (0..1e18) +;; anchorWidth (tick units) +;; bot: discoveryDepth (0..1e18) +;; +;; BULL params: CI=0, AS=1e18, AW=20, DD=1e18 +;; BEAR params: CI=0, AS=0.3e18, AW=100, DD=0.3e18 + +( + ;; Step 1: Bind slot 0 (top) to PERCENTAGESTAKED, slot 1 to TAXRATE. + PERCENTAGESTAKED DYADIC.DEFINE + TAXRATE DYADIC.DEFINE + + ;; Step 2: Discard unused inputs (slots 2-7) — 6 pops. + DYADIC.POP + DYADIC.POP + DYADIC.POP + DYADIC.POP + DYADIC.POP + DYADIC.POP + + ;; Step 3: Compute stakedPct = percentageStaked * 100 / 1e18 (integer 0..100) + PERCENTAGESTAKED + 100 DYADIC.* + 1000000000000000000 DYADIC./ + STAKED DYADIC.DEFINE + + ;; Step 4: Main conditional — stakedPct > 91? + STAKED 91 DYADIC.> + + EXEC.IF + + ;; TRUE branch: stakedPct > 91 — compute penalty + ( + ;; deltaS = 100 - stakedPct + 100 STAKED DYADIC.- + DELTAS DYADIC.DEFINE + + ;; Compute raw tax index via 30-way threshold lookup. + TAXRATE 206185567010309 DYADIC.<= + EXEC.IF + 0 + ( TAXRATE 412371134020618 DYADIC.<= + EXEC.IF + 1 + ( TAXRATE 618556701030927 DYADIC.<= + EXEC.IF + 2 + ( TAXRATE 1030927835051546 DYADIC.<= + EXEC.IF + 3 + ( TAXRATE 1546391752577319 DYADIC.<= + EXEC.IF + 4 + ( TAXRATE 2164948453608247 DYADIC.<= + EXEC.IF + 5 + ( TAXRATE 2783505154639175 DYADIC.<= + EXEC.IF + 6 + ( TAXRATE 3608247422680412 DYADIC.<= + EXEC.IF + 7 + ( TAXRATE 4639175257731958 DYADIC.<= + EXEC.IF + 8 + ( TAXRATE 5670103092783505 DYADIC.<= + EXEC.IF + 9 + ( TAXRATE 7216494845360824 DYADIC.<= + EXEC.IF + 10 + ( TAXRATE 9278350515463917 DYADIC.<= + EXEC.IF + 11 + ( TAXRATE 11855670103092783 DYADIC.<= + EXEC.IF + 12 + ( TAXRATE 15979381443298969 DYADIC.<= + EXEC.IF + 13 + ( TAXRATE 22164948453608247 DYADIC.<= + EXEC.IF + 14 + ( TAXRATE 29381443298969072 DYADIC.<= + EXEC.IF + 15 + ( TAXRATE 38144329896907216 DYADIC.<= + EXEC.IF + 16 + ( TAXRATE 49484536082474226 DYADIC.<= + EXEC.IF + 17 + ( TAXRATE 63917525773195876 DYADIC.<= + EXEC.IF + 18 + ( TAXRATE 83505154639175257 DYADIC.<= + EXEC.IF + 19 + ( TAXRATE 109278350515463917 DYADIC.<= + EXEC.IF + 20 + ( TAXRATE 144329896907216494 DYADIC.<= + EXEC.IF + 21 + ( TAXRATE 185567010309278350 DYADIC.<= + EXEC.IF + 22 + ( TAXRATE 237113402061855670 DYADIC.<= + EXEC.IF + 23 + ( TAXRATE 309278350515463917 DYADIC.<= + EXEC.IF + 24 + ( TAXRATE 402061855670103092 DYADIC.<= + EXEC.IF + 25 + ( TAXRATE 520618556701030927 DYADIC.<= + EXEC.IF + 26 + ( TAXRATE 680412371134020618 DYADIC.<= + EXEC.IF + 27 + ( TAXRATE 886597938144329896 DYADIC.<= + EXEC.IF + 28 + 29 + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + + ;; Apply effIdx shift: if raw_idx >= 14, effIdx = min(raw_idx + 1, 29) + DYADIC.DUP 14 DYADIC.>= + EXEC.IF + ( + 1 DYADIC.+ + DYADIC.DUP 29 DYADIC.> + EXEC.IF + ( DYADIC.POP 29 ) + ( ) + ) + ( ) + + EFFIDX DYADIC.DEFINE + + ;; penalty = deltaS^3 * effIdx / 20 + DELTAS DELTAS DYADIC.* + DELTAS DYADIC.* + EFFIDX DYADIC.* + 20 DYADIC./ + + ;; penalty < 50 → BULL; else → BEAR + 50 DYADIC.< + + EXEC.IF + ;; BULL: push 4 outputs bottom-first (discoveryDepth at bottom, ci at top) + ( + 1000000000000000000 + 20 + 1000000000000000000 + 0 + ) + ;; BEAR + ( + 300000000000000000 + 100 + 300000000000000000 + 0 + ) + ) + + ;; FALSE branch: stakedPct <= 91 — always bear + ( + 300000000000000000 + 100 + 300000000000000000 + 0 + ) +) diff --git a/tools/push3-transpiler/optimizer_v3.push3 b/tools/push3-transpiler/optimizer_v3.push3 index 97503f9..80b2489 100644 --- a/tools/push3-transpiler/optimizer_v3.push3 +++ b/tools/push3-transpiler/optimizer_v3.push3 @@ -1,38 +1,57 @@ -;; OptimizerV3 in Push3 +;; OptimizerV3 in Push3 — 8-input / 4-output redesign ;; -;; Computes isBullMarket(percentageStaked_1e18, averageTaxRate_1e18) +;; Inputs on DYADIC stack (slot 0 on top, slot 7 at bottom): +;; [0] percentageStaked (0 to 1e18, where 1e18 = 100%) +;; [1] averageTaxRate (0 to 1e18, normalized from Stake contract) +;; [2] vwapX96 (future; 0 if unavailable) +;; [3] currentTick (future; 0 if unavailable) +;; [4] recentVolume (future; 0 if unavailable) +;; [5] timeSinceLastRecenter (future; 0 if unavailable) +;; [6] movingAveragePrice (future; 0 if unavailable) +;; [7] reserved 0 ;; -;; Inputs on DYADIC stack (top to bottom when called): -;; top: percentageStaked (0 to 1e18, where 1e18 = 100%) -;; below: averageTaxRate (0 to 1e18, normalized from Stake contract) +;; Outputs on DYADIC stack at termination (top to bottom): +;; top: capitalInefficiency (0..1e18) +;; anchorShare (0..1e18) +;; anchorWidth (tick units) +;; bot: discoveryDepth (0..1e18) ;; -;; Output on BOOLEAN stack: -;; top: TRUE if bull market, FALSE if bear market -;; -;; Logic mirrors OptimizerV3.isBullMarket: +;; Logic: binary bear/bull from slots 0 and 1 ;; stakedPct = percentageStaked * 100 / 1e18 (0-100) -;; if stakedPct <= 91 → FALSE (always bear) +;; if stakedPct <= 91 → BEAR ;; deltaS = 100 - stakedPct ;; effIdx = _taxRateToEffectiveIndex(averageTaxRate) (0-29, with +1 shift at >=14) ;; penalty = deltaS^3 * effIdx / 20 -;; return penalty < 50 +;; if penalty < 50 → BULL, else → BEAR +;; +;; BULL params: CI=0, AS=1e18, AW=20, DD=1e18 +;; BEAR params: CI=0, AS=0.3e18, AW=100, DD=0.3e18 ( - ;; Step 1: Bind inputs to names. - ;; Stack on entry: [percentageStaked_1e18 (top), averageTaxRate_1e18 (below)] - DYADIC.SWAP - ;; Stack: [averageTaxRate_1e18 (top), percentageStaked_1e18 (below)] + ;; Step 1: Bind slot 0 (top) to PERCENTAGESTAKED, slot 1 to TAXRATE. + PERCENTAGESTAKED DYADIC.DEFINE + ;; Stack: [slot7(bot), slot6, slot5, slot4, slot3, slot2, slot1(top)] TAXRATE DYADIC.DEFINE - ;; Stack: [percentageStaked_1e18] + ;; Stack: [slot7(bot), slot6, slot5, slot4, slot3, slot2(top)] - ;; Step 2: Compute stakedPct = percentageStaked * 100 / 1e18 (integer 0..100) + ;; Step 2: Discard unused inputs (slots 2-7) — 6 pops. + DYADIC.POP + DYADIC.POP + DYADIC.POP + DYADIC.POP + DYADIC.POP + DYADIC.POP + ;; Stack: [] + + ;; Step 3: Compute stakedPct = percentageStaked * 100 / 1e18 (integer 0..100) + PERCENTAGESTAKED 100 DYADIC.* 1000000000000000000 DYADIC./ ;; Stack: [stakedPct] STAKED DYADIC.DEFINE ;; Stack: [] - ;; Step 3: Main conditional — stakedPct > 91? + ;; Step 4: Main conditional — stakedPct > 91? STAKED 91 DYADIC.> ;; bool_stack: [stakedPct > 91] @@ -164,7 +183,7 @@ ) ) ) - ;; Stack: [raw_idx (0-29)] + ;; Stack: [raw_idx (0-29)] ;; Apply effIdx shift: if raw_idx >= 14, effIdx = min(raw_idx + 1, 29) DYADIC.DUP 14 DYADIC.>= @@ -177,7 +196,7 @@ ( ) ) ( ) - ;; Stack: [effIdx (0-29)] + ;; Stack: [effIdx (0-29)] EFFIDX DYADIC.DEFINE ;; Stack: [] @@ -189,13 +208,32 @@ 20 DYADIC./ ;; Stack: [penalty] - ;; Return penalty < 50 + ;; penalty < 50 → BULL; else → BEAR 50 DYADIC.< ;; bool_stack: [penalty < 50] + + EXEC.IF + ;; BULL: push 4 outputs bottom-first (discoveryDepth at bottom, ci at top) + ( + 1000000000000000000 + 20 + 1000000000000000000 + 0 + ) + ;; BEAR + ( + 300000000000000000 + 100 + 300000000000000000 + 0 + ) ) ;; FALSE branch: stakedPct <= 91 — always bear ( - FALSE + 300000000000000000 + 100 + 300000000000000000 + 0 ) ) diff --git a/tools/push3-transpiler/src/index.ts b/tools/push3-transpiler/src/index.ts index ce855ca..1a38061 100644 --- a/tools/push3-transpiler/src/index.ts +++ b/tools/push3-transpiler/src/index.ts @@ -3,8 +3,9 @@ * * Usage: ts-node src/index.ts * - * Reads a Push3 program, transpiles isBullMarket logic, and emits a - * Solidity contract that can be compared against the hand-written OptimizerV3. + * 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'; @@ -28,31 +29,47 @@ function main(): void { const ast = parse(src); console.log('Transpiling...'); - const { functionBody, resultVar } = transpile(ast); + const { functionBody, ciVar, anchorShareVar, anchorWidthVar, discoveryDepthVar } = transpile(ast); 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 the same isBullMarket logic as OptimizerV3.', + ' * Implements calculateParams with 8 dyadic rational inputs and 4 outputs.', ' */', 'contract OptimizerV3Push3 {', ' /**', - ' * @notice Determines if the market is in bull configuration.', - ' * @param percentageStaked Percentage of authorized stake in use (0 to 1e18).', - ' * @param averageTaxRate Normalized average tax rate from Stake contract (0 to 1e18).', - ' * @return bull True if bull config, false if bear.', + ' * @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 isBullMarket(', - ' uint256 percentageStaked,', - ' uint256 averageTaxRate', - ' ) public pure returns (bool bull) {', - ' require(percentageStaked <= 1e18, "Invalid percentage staked");', + ' 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");', + ' }', + '', ...functionBody, - ` bull = ${resultVar};`, + ` ci = uint256(${ciVar});`, + ` anchorShare = uint256(${anchorShareVar});`, + ` anchorWidth = uint24(${anchorWidthVar});`, + ` discoveryDepth = uint256(${discoveryDepthVar});`, ' }', '}', '', @@ -64,7 +81,7 @@ function main(): void { // Print a summary console.log(` Function body: ${functionBody.length} lines`); - console.log(` Result var: ${resultVar}`); + console.log(` Outputs: ci=${ciVar}, anchorShare=${anchorShareVar}, anchorWidth=${anchorWidthVar}, discoveryDepth=${discoveryDepthVar}`); } main(); diff --git a/tools/push3-transpiler/src/transpiler.ts b/tools/push3-transpiler/src/transpiler.ts index 326a315..4f13c17 100644 --- a/tools/push3-transpiler/src/transpiler.ts +++ b/tools/push3-transpiler/src/transpiler.ts @@ -345,25 +345,41 @@ function toItems(node: Node): Node[] { // ---- Public API ---- export interface TranspileResult { - functionBody: string[]; - resultVar: string; + functionBody: string[]; + ciVar: string; + anchorShareVar: string; + anchorWidthVar: string; + discoveryDepthVar: string; } /** * Transpile a Push3 program (top-level list) into Solidity function body lines. * - * Inputs are primed on the DYADIC stack: - * bottom: averageTaxRate (0 to 1e18) - * top: percentageStaked (0 to 1e18) + * Inputs are primed on the DYADIC stack as 8 dyadic rationals (slot 0 on top, + * slot 7 at bottom). Each slot is represented as uint256(inputs[i].mantissa); + * shift support is reserved for future evolution — callers must pass shift=0. * - * The Push3 program's first instruction is DYADIC.SWAP so it binds averageTaxRate - * first, then computes stakedPct. + * The program must leave exactly 4 values on the DYADIC stack at termination: + * top (index 3): capitalInefficiency + * index 2: anchorShare + * index 1: anchorWidth + * bottom (index 0): discoveryDepth */ export function transpile(program: Node): TranspileResult { if (program.kind !== 'list') throw new Error('Expected top-level list'); + // Prime DYADIC stack: slot 7 at bottom (index 0), slot 0 at top (index 7). const state: TranspilerState = { - dStack: ['averageTaxRate', 'percentageStaked'], + dStack: [ + 'uint256(inputs[7].mantissa)', + 'uint256(inputs[6].mantissa)', + 'uint256(inputs[5].mantissa)', + 'uint256(inputs[4].mantissa)', + 'uint256(inputs[3].mantissa)', + 'uint256(inputs[2].mantissa)', + 'uint256(inputs[1].mantissa)', + 'uint256(inputs[0].mantissa)', + ], bStack: [], nameStack: [], lines: [], @@ -374,6 +390,11 @@ export function transpile(program: Node): TranspileResult { processItems(program.items, state); - const resultVar = state.bStack[state.bStack.length - 1] ?? 'false'; - return { functionBody: state.lines, resultVar }; + // Pop 4 outputs: top → ci, then anchorShare, anchorWidth, discoveryDepth. + const ciVar = state.dStack.pop() ?? '0'; + const anchorShareVar = state.dStack.pop() ?? '0'; + const anchorWidthVar = state.dStack.pop() ?? '0'; + const discoveryDepthVar = state.dStack.pop() ?? '0'; + + return { functionBody: state.lines, ciVar, anchorShareVar, anchorWidthVar, discoveryDepthVar }; }