fix: Push3 optimizer: dyadic rational input interface (8 slots) + 4-output redesign (#548)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-11 15:23:36 +00:00
parent c3792b2a63
commit 5d204e5649
5 changed files with 409 additions and 229 deletions

View file

@ -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
);
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
{
uint256
r1;
if (
(
taxrate
<=
680412371134020618
)
)
{
r1
=
uint256(
27
);
r1 = uint256(r0);
}
else
{
uint256
r0;
if (
(
taxrate
<=
886597938144329896
)
)
{
r0
=
uint256(
28
);
r2 = uint256(r1);
}
else
{
r0
=
uint256(
29
);
r3 = uint256(r2);
}
r1
=
uint256(
r0
);
r4 = uint256(r3);
}
r2
=
uint256(
r1
);
}
r3 =
uint256(
r2
);
}
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);
} else {
b33 = false;
}
uint256 r33;
uint256 r34;
uint256 r35;
uint256 r36;
uint256 r37;
if (b33) {
r34 = uint256(1000000000000000000);
r35 = uint256(20);
r36 = uint256(1000000000000000000);
r37 = uint256(0);
if ((((((deltas * deltas) * deltas) * effidx) / 20) < 50)) {
r33 = uint256(1000000000000000000);
r34 = uint256(20);
r35 = uint256(1000000000000000000);
r36 = uint256(0);
} else {
r34 = uint256(300000000000000000);
r35 = uint256(100);
r36 = uint256(300000000000000000);
r37 = uint256(0);
r33 = uint256(300000000000000000);
r34 = uint256(100);
r35 = uint256(300000000000000000);
r36 = uint256(0);
}
ci = uint256(r37);
anchorShare = uint256(r36);
anchorWidth = uint24(r35);
discoveryDepth = uint256(r34);
r37 = uint256(r33);
r38 = uint256(r34);
r39 = uint256(r35);
r40 = uint256(r36);
} else {
r37 = uint256(300000000000000000);
r38 = uint256(100);
r39 = uint256(300000000000000000);
r40 = uint256(0);
}
ci = uint256(r40);
anchorShare = uint256(r39);
anchorWidth = uint24(r38);
discoveryDepth = uint256(r37);
}
}

View file

@ -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
)
)

View file

@ -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]
@ -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
)
)

View file

@ -3,8 +3,9 @@
*
* Usage: ts-node src/index.ts <input.push3> <output.sol>
*
* 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();

View file

@ -346,24 +346,40 @@ function toItems(node: Node): Node[] {
export interface TranspileResult {
functionBody: string[];
resultVar: 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 };
}