Merge pull request 'fix: feat: Push3 default outputs — crash/no-output falls back to bear strategy (#634)' (#651) from fix/issue-634 into master
This commit is contained in:
commit
b8a503c2df
4 changed files with 48 additions and 25 deletions
|
|
@ -361,6 +361,13 @@ contract Optimizer is Initializable, UUPSUpgradeable {
|
|||
// malformed evolved program — would cause abi.decode to revert; guard here
|
||||
// so all failure modes fall back via _bearDefaults().
|
||||
if (ret.length < 128) return _bearDefaults();
|
||||
return abi.decode(ret, (uint256, uint256, uint24, uint256));
|
||||
(capitalInefficiency, anchorShare, anchorWidth, discoveryDepth) =
|
||||
abi.decode(ret, (uint256, uint256, uint24, uint256));
|
||||
// Clamp fraction outputs to [0, 1e18] so a buggy evolved program cannot
|
||||
// produce out-of-range values that confuse the LiquidityManager.
|
||||
// anchorWidth is already bounded by uint24 at the ABI level.
|
||||
if (capitalInefficiency > 1e18) capitalInefficiency = 1e18;
|
||||
if (anchorShare > 1e18) anchorShare = 1e18;
|
||||
if (discoveryDepth > 1e18) discoveryDepth = 1e18;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,18 +11,9 @@ import {OptimizerInput} from "./IOptimizer.sol";
|
|||
contract OptimizerV3Push3 {
|
||||
/**
|
||||
* @notice Compute liquidity parameters from 8 dyadic rational inputs.
|
||||
* @dev capitalInefficiency (ci) is intentionally hardcoded to 0 in both the bear
|
||||
* and bull branches of this implementation. CI is a pure risk lever that
|
||||
* controls the VWAP bias applied when placing the floor position: CI=0 means
|
||||
* the floor tracks the raw VWAP with no upward adjustment, which is the
|
||||
* safest setting and carries zero effect on fee revenue. Any integrating
|
||||
* proxy (e.g. ThreePositionStrategy) must therefore treat the floor scarcity
|
||||
* and VWAP adjustment as if no capital-inefficiency premium is active.
|
||||
* Future optimizer versions that expose non-zero CI values should document
|
||||
* the resulting floor-placement and eth-scarcity effects explicitly.
|
||||
* @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). Always 0 in this implementation.
|
||||
* @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).
|
||||
|
|
@ -40,9 +31,20 @@ contract OptimizerV3Push3 {
|
|||
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 {
|
||||
uint256 percentagestaked = uint256(uint256(inputs[0].mantissa));
|
||||
uint256 taxrate = uint256(uint256(inputs[1].mantissa));
|
||||
uint256 staked = uint256(((percentagestaked * 100) / 1000000000000000000));
|
||||
uint256 staked = uint256((1000000000000000000 == 0 ? 0 : (percentagestaked * 100) / 1000000000000000000));
|
||||
uint256 r37;
|
||||
uint256 r38;
|
||||
uint256 r39;
|
||||
|
|
@ -242,7 +244,7 @@ contract OptimizerV3Push3 {
|
|||
uint256 r34;
|
||||
uint256 r35;
|
||||
uint256 r36;
|
||||
if ((((((deltas * deltas) * deltas) * effidx) / 20) < 50)) {
|
||||
if (((20 == 0 ? 0 : (((deltas * deltas) * deltas) * effidx) / 20) < 50)) {
|
||||
r33 = uint256(1000000000000000000);
|
||||
r34 = uint256(20);
|
||||
r35 = uint256(1000000000000000000);
|
||||
|
|
@ -267,5 +269,6 @@ contract OptimizerV3Push3 {
|
|||
anchorShare = uint256(r39);
|
||||
anchorWidth = uint24(r38);
|
||||
discoveryDepth = uint256(r37);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,23 @@ function main(): void {
|
|||
' 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});`,
|
||||
` discoveryDepth = uint256(${discoveryDepthVar});`,
|
||||
' }',
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@ function emit(state: TranspilerState, line: string): void {
|
|||
|
||||
function dpop(state: TranspilerState, ctx: string): string {
|
||||
const v = state.dStack.pop();
|
||||
if (v === undefined) throw new Error(`DYADIC stack underflow at ${ctx}`);
|
||||
// Stack underflow → Push3 no-op semantics: treat missing value as 0
|
||||
if (v === undefined) return '0';
|
||||
return v;
|
||||
}
|
||||
|
||||
function bpop(state: TranspilerState, ctx: string): string {
|
||||
const v = state.bStack.pop();
|
||||
if (v === undefined) throw new Error(`BOOLEAN stack underflow at ${ctx}`);
|
||||
// Stack underflow → Push3 no-op semantics: treat missing bool as false
|
||||
if (v === undefined) return 'false';
|
||||
return v;
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +104,8 @@ function processInstruction(name: string, state: TranspilerState): void {
|
|||
case 'DYADIC./': {
|
||||
const b = dpop(state, 'DYADIC./');
|
||||
const a = dpop(state, 'DYADIC./');
|
||||
state.dStack.push(`(${a} / ${b})`);
|
||||
// Safe division: div-by-zero → 0, matching Push3 no-op semantics.
|
||||
state.dStack.push(`(${b} == 0 ? 0 : ${a} / ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.+': {
|
||||
|
|
@ -391,15 +394,13 @@ export function transpile(program: Node): TranspileResult {
|
|||
processItems(program.items, state);
|
||||
|
||||
// Pop 4 outputs: top → ci, then anchorShare, anchorWidth, discoveryDepth.
|
||||
if (state.dStack.length !== 4) {
|
||||
throw new Error(
|
||||
`Program must leave exactly 4 values on the DYADIC stack; found ${state.dStack.length}`,
|
||||
);
|
||||
}
|
||||
const ciVar = state.dStack.pop()!;
|
||||
const anchorShareVar = state.dStack.pop()!;
|
||||
const anchorWidthVar = state.dStack.pop()!;
|
||||
const discoveryDepthVar = state.dStack.pop()!;
|
||||
// If the stack has fewer than 4 values, fall back to bear defaults for the
|
||||
// missing positions — matches Push3 no-op semantics (empty stack → 0/default).
|
||||
// If the stack has more than 4 values, take the top 4 and discard the rest.
|
||||
const ciVar = state.dStack.pop() ?? '0';
|
||||
const anchorShareVar = state.dStack.pop() ?? '300000000000000000';
|
||||
const anchorWidthVar = state.dStack.pop() ?? '100';
|
||||
const discoveryDepthVar = state.dStack.pop() ?? '300000000000000000';
|
||||
|
||||
return { functionBody: state.lines, ciVar, anchorShareVar, anchorWidthVar, discoveryDepthVar };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue