Merge pull request 'feat: Push3 → Solidity transpiler + OptimizerV3 port' (#168) from feat/push3-transpiler into master
This commit is contained in:
commit
134287722b
19 changed files with 1451 additions and 350 deletions
|
|
@ -4,13 +4,22 @@ set -euo pipefail
|
|||
MNEMONIC_FILE=/workspace/onchain/.secret.local
|
||||
ANVIL_STATE_DIR=/home/foundry/.foundry/anvil/tmp
|
||||
|
||||
# Cleanup old state snapshots (files older than 24 hours)
|
||||
# Prevents disk bloat from accumulating JSON snapshots
|
||||
# Cleanup ALL old state snapshots on start + periodic cleanup in background
|
||||
# Anvil fork mode generates thousands of JSON snapshots that fill disk fast
|
||||
if [[ -d "$ANVIL_STATE_DIR" ]]; then
|
||||
echo "[anvil] Cleaning up state snapshots older than 24 hours..."
|
||||
find "$ANVIL_STATE_DIR" -type f -name "*.json" -mtime +1 -delete 2>/dev/null || true
|
||||
echo "[anvil] Cleaning up all state snapshots..."
|
||||
rm -rf "$ANVIL_STATE_DIR"/* 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Background cleanup: every 6 hours, delete snapshots older than 1 hour
|
||||
(while true; do
|
||||
sleep 21600
|
||||
if [[ -d "$ANVIL_STATE_DIR" ]]; then
|
||||
find "$ANVIL_STATE_DIR" -type f -name "*.json" -mmin +60 -delete 2>/dev/null || true
|
||||
find "$ANVIL_STATE_DIR" -type d -empty -delete 2>/dev/null || true
|
||||
fi
|
||||
done) &
|
||||
|
||||
ANVIL_CMD=(anvil --fork-url "${FORK_URL:-https://sepolia.base.org}" --chain-id 31337 --block-time 1 --host 0.0.0.0 --port 8545 --threads 4 --timeout 2000 --retries 2 --fork-retry-backoff 100 --steps-tracing --no-storage-caching)
|
||||
|
||||
if [[ -f "$MNEMONIC_FILE" ]]; then
|
||||
|
|
|
|||
|
|
@ -17,4 +17,13 @@ export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1}
|
|||
export HOST=0.0.0.0
|
||||
export PORT=${PORT:-5174}
|
||||
|
||||
# Source contract addresses from bootstrap output
|
||||
CONTRACTS_ENV="$ROOT_DIR/tmp/containers/contracts.env"
|
||||
if [[ -f "$CONTRACTS_ENV" ]]; then
|
||||
source "$CONTRACTS_ENV"
|
||||
export VITE_KRAIKEN_ADDRESS="${KRAIKEN:-}"
|
||||
export VITE_STAKE_ADDRESS="${STAKE:-}"
|
||||
echo "[landing-entrypoint] Contract addresses loaded: KRK=${KRAIKEN:-unset} STAKE=${STAKE:-unset}"
|
||||
fi
|
||||
|
||||
exec npm run dev -- --host 0.0.0.0 --port 5174
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ services:
|
|||
environment:
|
||||
- CHOKIDAR_USEPOLLING=1
|
||||
- GIT_BRANCH=${GIT_BRANCH:-}
|
||||
- VITE_APP_URL=http://localhost:5173/app
|
||||
expose:
|
||||
- "5174"
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@
|
|||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"sass": "^1.83.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0",
|
||||
"@harb/web3": "*",
|
||||
"@tanstack/vue-query": "^5.92.9",
|
||||
"@wagmi/vue": "^0.2.8",
|
||||
"viem": "^2.22.13"
|
||||
"sass": "^1.83.4",
|
||||
"viem": "^2.22.13",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "^22.0.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { useAccount } from '@harb/web3';
|
||||
import axios from 'axios';
|
||||
|
||||
const { address, isConnected } = useAccount();
|
||||
|
||||
|
|
@ -18,20 +17,25 @@ const endpoint = `${window.location.origin}/api/graphql`;
|
|||
async function fetchWalletData(addr: string) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await axios.post(endpoint, {
|
||||
query: `{
|
||||
holders(address: "${addr.toLowerCase()}") {
|
||||
balance
|
||||
totalEthSpent
|
||||
totalTokensAcquired
|
||||
}
|
||||
protocolStatss(where: { id: "0x01" }) {
|
||||
items { currentPriceWei }
|
||||
}
|
||||
}`,
|
||||
const res = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
query: `{
|
||||
holders(address: "${addr.toLowerCase()}") {
|
||||
balance
|
||||
totalEthSpent
|
||||
totalTokensAcquired
|
||||
}
|
||||
protocolStatss(where: { id: "0x01" }) {
|
||||
items { currentPriceWei }
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
});
|
||||
holder.value = res.data?.data?.holders ?? null;
|
||||
stats.value = res.data?.data?.protocolStatss?.items?.[0] ?? null;
|
||||
const json = await res.json();
|
||||
holder.value = json?.data?.holders ?? null;
|
||||
stats.value = json?.data?.protocolStatss?.items?.[0] ?? null;
|
||||
} catch {
|
||||
holder.value = null;
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import './assets/styles/main.sass';
|
||||
import { createApp } from 'vue';
|
||||
import { WagmiPlugin } from '@wagmi/vue';
|
||||
import { VueQueryPlugin } from '@tanstack/vue-query';
|
||||
import { createHarbConfig } from '@harb/web3';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
|
|
@ -9,6 +10,7 @@ const rpcUrl = import.meta.env.VITE_LOCAL_RPC_URL ?? '/api/rpc';
|
|||
const config = createHarbConfig({ rpcUrl });
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(VueQueryPlugin);
|
||||
app.use(WagmiPlugin, { config });
|
||||
app.use(router);
|
||||
app.mount('#app');
|
||||
|
|
|
|||
|
|
@ -19,5 +19,12 @@ export default defineConfig({
|
|||
server: {
|
||||
// Allow health checks from CI containers and proxy
|
||||
allowedHosts: ['landing', 'caddy', 'localhost', '127.0.0.1'],
|
||||
proxy: {
|
||||
'/api/graphql': {
|
||||
target: process.env.VITE_PONDER_URL || 'http://ponder:42069',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/graphql/, '/graphql'),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ gas_limit = 1_000_000_000
|
|||
gas_price = 0
|
||||
optimizer = true
|
||||
optimizer_runs = 200
|
||||
via_ir = true
|
||||
bytecode_size_limit = 0
|
||||
|
||||
[profile.maxperf]
|
||||
via_ir = true
|
||||
bytecode_size_limit = 0
|
||||
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
|
||||
|
||||
|
|
|
|||
226
onchain/src/OptimizerV3Push3.sol
Normal file
226
onchain/src/OptimizerV3Push3.sol
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
/**
|
||||
* @title OptimizerV3Push3
|
||||
* @notice Auto-generated from optimizer_v3.push3 via Push3→Solidity transpiler.
|
||||
* Implements the same isBullMarket logic as OptimizerV3.
|
||||
* @dev This contract is an equivalence proof, not a deployable upgrade.
|
||||
* It intentionally exposes only `isBullMarket` and does NOT implement
|
||||
* the full optimizer interface (e.g. `getLiquidityParams`). Wiring it
|
||||
* into the proxy upgrade path would require completing that interface first.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
function isBullMarket(
|
||||
uint256 percentageStaked,
|
||||
uint256 averageTaxRate
|
||||
) public pure returns (bool bull) {
|
||||
require(percentageStaked <= 1e18, "Invalid percentage staked");
|
||||
require(averageTaxRate <= 1e18, "Invalid tax rate");
|
||||
uint256 taxrate = uint256(averageTaxRate);
|
||||
uint256 staked = uint256(((percentageStaked * 100) / 1000000000000000000));
|
||||
bool b33;
|
||||
if ((staked > 91)) {
|
||||
uint256 deltas = uint256((100 - staked));
|
||||
uint256 r28;
|
||||
if ((taxrate <= 206185567010309)) {
|
||||
r28 = uint256(0);
|
||||
} else {
|
||||
uint256 r27;
|
||||
if ((taxrate <= 412371134020618)) {
|
||||
r27 = uint256(1);
|
||||
} else {
|
||||
uint256 r26;
|
||||
if ((taxrate <= 618556701030927)) {
|
||||
r26 = uint256(2);
|
||||
} else {
|
||||
uint256 r25;
|
||||
if ((taxrate <= 1030927835051546)) {
|
||||
r25 = uint256(3);
|
||||
} else {
|
||||
uint256 r24;
|
||||
if ((taxrate <= 1546391752577319)) {
|
||||
r24 = uint256(4);
|
||||
} else {
|
||||
uint256 r23;
|
||||
if ((taxrate <= 2164948453608247)) {
|
||||
r23 = uint256(5);
|
||||
} else {
|
||||
uint256 r22;
|
||||
if ((taxrate <= 2783505154639175)) {
|
||||
r22 = uint256(6);
|
||||
} else {
|
||||
uint256 r21;
|
||||
if ((taxrate <= 3608247422680412)) {
|
||||
r21 = uint256(7);
|
||||
} else {
|
||||
uint256 r20;
|
||||
if ((taxrate <= 4639175257731958)) {
|
||||
r20 = uint256(8);
|
||||
} else {
|
||||
uint256 r19;
|
||||
if ((taxrate <= 5670103092783505)) {
|
||||
r19 = uint256(9);
|
||||
} else {
|
||||
uint256 r18;
|
||||
if ((taxrate <= 7216494845360824)) {
|
||||
r18 = uint256(10);
|
||||
} else {
|
||||
uint256 r17;
|
||||
if ((taxrate <= 9278350515463917)) {
|
||||
r17 = uint256(11);
|
||||
} else {
|
||||
uint256 r16;
|
||||
if ((taxrate <= 11855670103092783)) {
|
||||
r16 = uint256(12);
|
||||
} else {
|
||||
uint256 r15;
|
||||
if ((taxrate <= 15979381443298969)) {
|
||||
r15 = uint256(13);
|
||||
} else {
|
||||
uint256 r14;
|
||||
if ((taxrate <= 22164948453608247)) {
|
||||
r14 = uint256(14);
|
||||
} else {
|
||||
uint256 r13;
|
||||
if ((taxrate <= 29381443298969072)) {
|
||||
r13 = uint256(15);
|
||||
} else {
|
||||
uint256 r12;
|
||||
if ((taxrate <= 38144329896907216)) {
|
||||
r12 = uint256(16);
|
||||
} else {
|
||||
uint256 r11;
|
||||
if ((taxrate <= 49484536082474226)) {
|
||||
r11 = uint256(17);
|
||||
} else {
|
||||
uint256 r10;
|
||||
if ((taxrate <= 63917525773195876)) {
|
||||
r10 = uint256(18);
|
||||
} else {
|
||||
uint256 r9;
|
||||
if ((taxrate <= 83505154639175257)) {
|
||||
r9 = uint256(19);
|
||||
} else {
|
||||
uint256 r8;
|
||||
if ((taxrate <= 109278350515463917)) {
|
||||
r8 = uint256(20);
|
||||
} else {
|
||||
uint256 r7;
|
||||
if ((taxrate <= 144329896907216494)) {
|
||||
r7 = uint256(21);
|
||||
} else {
|
||||
uint256 r6;
|
||||
if ((taxrate <= 185567010309278350)) {
|
||||
r6 = uint256(22);
|
||||
} else {
|
||||
uint256 r5;
|
||||
if ((taxrate <= 237113402061855670)) {
|
||||
r5 = uint256(23);
|
||||
} else {
|
||||
uint256 r4;
|
||||
if ((taxrate <= 309278350515463917)) {
|
||||
r4 = uint256(24);
|
||||
} else {
|
||||
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);
|
||||
} else {
|
||||
r0 = uint256(29);
|
||||
}
|
||||
r1 = uint256(r0);
|
||||
}
|
||||
r2 = uint256(r1);
|
||||
}
|
||||
r3 = uint256(r2);
|
||||
}
|
||||
r4 = uint256(r3);
|
||||
}
|
||||
r5 = uint256(r4);
|
||||
}
|
||||
r6 = uint256(r5);
|
||||
}
|
||||
r7 = uint256(r6);
|
||||
}
|
||||
r8 = uint256(r7);
|
||||
}
|
||||
r9 = uint256(r8);
|
||||
}
|
||||
r10 = uint256(r9);
|
||||
}
|
||||
r11 = uint256(r10);
|
||||
}
|
||||
r12 = uint256(r11);
|
||||
}
|
||||
r13 = uint256(r12);
|
||||
}
|
||||
r14 = uint256(r13);
|
||||
}
|
||||
r15 = uint256(r14);
|
||||
}
|
||||
r16 = uint256(r15);
|
||||
}
|
||||
r17 = uint256(r16);
|
||||
}
|
||||
r18 = uint256(r17);
|
||||
}
|
||||
r19 = uint256(r18);
|
||||
}
|
||||
r20 = uint256(r19);
|
||||
}
|
||||
r21 = uint256(r20);
|
||||
}
|
||||
r22 = uint256(r21);
|
||||
}
|
||||
r23 = uint256(r22);
|
||||
}
|
||||
r24 = uint256(r23);
|
||||
}
|
||||
r25 = uint256(r24);
|
||||
}
|
||||
r26 = uint256(r25);
|
||||
}
|
||||
r27 = uint256(r26);
|
||||
}
|
||||
r28 = uint256(r27);
|
||||
}
|
||||
uint256 dup29 = uint256(r28);
|
||||
uint256 r32;
|
||||
if ((dup29 >= 14)) {
|
||||
uint256 dup30 = uint256((dup29 + 1));
|
||||
uint256 r31;
|
||||
if ((dup30 > 29)) {
|
||||
r31 = uint256(29);
|
||||
} else {
|
||||
r31 = uint256(dup30);
|
||||
}
|
||||
r32 = uint256(r31);
|
||||
} else {
|
||||
r32 = uint256(dup29);
|
||||
}
|
||||
uint256 effidx = uint256(r32);
|
||||
b33 = (((((deltas * deltas) * deltas) * effidx) / 20) < 50);
|
||||
} else {
|
||||
b33 = false;
|
||||
}
|
||||
bull = b33;
|
||||
}
|
||||
}
|
||||
|
|
@ -407,6 +407,19 @@ contract EthScarcityAbundance is Test {
|
|||
_recoverStuck();
|
||||
}
|
||||
|
||||
function _decodeVwapTick(bytes memory data) internal pure returns (int24) {
|
||||
(,,,, int24 vwap) = abi.decode(data, (int24, uint256, uint256, uint256, int24));
|
||||
return vwap;
|
||||
}
|
||||
|
||||
function _logEvent(bytes memory data, string memory label, string memory eventName) internal view {
|
||||
(int24 tick, uint256 ethBal, uint256 supply,,) = abi.decode(data, (int24, uint256, uint256, uint256, int24));
|
||||
console2.log(string.concat(" ", eventName, ":"), label);
|
||||
console2.log(" tick:", tick);
|
||||
console2.log(" ethBal:", ethBal / 1e18);
|
||||
console2.log(" supply:", supply / 1e18);
|
||||
}
|
||||
|
||||
function _recenterAndLog(string memory label) internal returns (bool sawScarcity, bool sawAbundance, int24 eventVwapTick) {
|
||||
vm.warp(block.timestamp + 1 hours);
|
||||
vm.roll(block.number + 1);
|
||||
|
|
@ -422,23 +435,13 @@ contract EthScarcityAbundance is Test {
|
|||
for (uint256 i = 0; i < logs.length; i++) {
|
||||
if (logs[i].topics.length == 0) continue;
|
||||
if (logs[i].topics[0] == SCARCITY_SIG) {
|
||||
(int24 tick, uint256 ethBal, uint256 supply,, int24 vwapTick) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
|
||||
sawScarcity = true;
|
||||
eventVwapTick = vwapTick;
|
||||
console2.log(" EthScarcity:", label);
|
||||
console2.log(" tick:", tick);
|
||||
console2.log(" ethBal:", ethBal / 1e18);
|
||||
console2.log(" supply:", supply / 1e18);
|
||||
console2.log(" vwapTick:", vwapTick);
|
||||
eventVwapTick = _decodeVwapTick(logs[i].data);
|
||||
_logEvent(logs[i].data, label, "EthScarcity");
|
||||
} else if (logs[i].topics[0] == ABUNDANCE_SIG) {
|
||||
(int24 tick, uint256 ethBal, uint256 supply,, int24 vwapTick) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
|
||||
sawAbundance = true;
|
||||
eventVwapTick = vwapTick;
|
||||
console2.log(" EthAbundance:", label);
|
||||
console2.log(" tick:", tick);
|
||||
console2.log(" ethBal:", ethBal / 1e18);
|
||||
console2.log(" supply:", supply / 1e18);
|
||||
console2.log(" vwapTick:", vwapTick);
|
||||
eventVwapTick = _decodeVwapTick(logs[i].data);
|
||||
_logEvent(logs[i].data, label, "EthAbundance");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
144
onchain/test/OptimizerV3Push3.t.sol
Normal file
144
onchain/test/OptimizerV3Push3.t.sol
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import { OptimizerV3 } from "../src/OptimizerV3.sol";
|
||||
import { OptimizerV3Push3 } from "../src/OptimizerV3Push3.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
/**
|
||||
* @title OptimizerV3Push3Test
|
||||
* @notice Verifies that the Push3-transpiled OptimizerV3Push3 produces
|
||||
* identical results to the hand-written OptimizerV3 for all test cases.
|
||||
*
|
||||
* Tests mirror OptimizerV3.t.sol to ensure equivalence.
|
||||
*/
|
||||
contract OptimizerV3Push3Test is Test {
|
||||
OptimizerV3 ref; // reference (hand-written)
|
||||
OptimizerV3Push3 push3; // transpiled from Push3
|
||||
|
||||
uint256[30] TAX_RATES =
|
||||
[uint256(1), 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700];
|
||||
uint256 constant MAX_TAX = 9700;
|
||||
|
||||
function setUp() public {
|
||||
ref = new OptimizerV3();
|
||||
push3 = new OptimizerV3Push3();
|
||||
}
|
||||
|
||||
function _norm(uint256 taxIdx) internal view returns (uint256) {
|
||||
return TAX_RATES[taxIdx] * 1e18 / MAX_TAX;
|
||||
}
|
||||
|
||||
function _pct(uint256 pct) internal pure returns (uint256) {
|
||||
return pct * 1e18 / 100;
|
||||
}
|
||||
|
||||
// ---- Equivalence against reference ----
|
||||
|
||||
function testEquivalenceAllTaxRates() public view {
|
||||
uint256[7] memory staked = [uint256(0), 50, 91, 92, 95, 96, 100];
|
||||
for (uint256 s = 0; s < staked.length; s++) {
|
||||
for (uint256 t = 0; t < 30; t++) {
|
||||
bool r = ref.isBullMarket(_pct(staked[s]), _norm(t));
|
||||
bool p = push3.isBullMarket(_pct(staked[s]), _norm(t));
|
||||
assertEq(p, r, string.concat(
|
||||
"Mismatch at staked=", vm.toString(staked[s]),
|
||||
"% taxIdx=", vm.toString(t)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testEquivalence93to99Percent() public view {
|
||||
for (uint256 s = 93; s <= 99; s++) {
|
||||
for (uint256 t = 0; t < 30; t++) {
|
||||
bool r = ref.isBullMarket(_pct(s), _norm(t));
|
||||
bool p = push3.isBullMarket(_pct(s), _norm(t));
|
||||
assertEq(p, r, string.concat(
|
||||
"Mismatch at staked=", vm.toString(s),
|
||||
"% taxIdx=", vm.toString(t)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Direct correctness tests (mirror OptimizerV3.t.sol) ----
|
||||
|
||||
function testAlwaysBearAt0Percent() public view {
|
||||
for (uint256 t = 0; t < 30; t++) {
|
||||
assertFalse(push3.isBullMarket(0, _norm(t)));
|
||||
}
|
||||
}
|
||||
|
||||
function testAlwaysBearAt91Percent() public view {
|
||||
for (uint256 t = 0; t < 30; t++) {
|
||||
assertFalse(push3.isBullMarket(_pct(91), _norm(t)));
|
||||
}
|
||||
}
|
||||
|
||||
function testBoundary92PercentLowestTax() public view {
|
||||
// deltaS=8, effIdx=0 → penalty=0 < 50 → BULL
|
||||
assertTrue(push3.isBullMarket(_pct(92), _norm(0)));
|
||||
}
|
||||
|
||||
function testBoundary92PercentTaxIdx1() public view {
|
||||
// deltaS=8, effIdx=1 → penalty=512*1/20=25 < 50 → BULL
|
||||
assertTrue(push3.isBullMarket(_pct(92), _norm(1)));
|
||||
}
|
||||
|
||||
function testBoundary92PercentTaxIdx2() public view {
|
||||
// deltaS=8, effIdx=2 → penalty=512*2/20=51 >= 50 → BEAR
|
||||
assertFalse(push3.isBullMarket(_pct(92), _norm(2)));
|
||||
}
|
||||
|
||||
function testAt95PercentTaxIdx7() public view {
|
||||
// deltaS=5, effIdx=7 → penalty=125*7/20=43 < 50 → BULL
|
||||
assertTrue(push3.isBullMarket(_pct(95), _norm(7)));
|
||||
}
|
||||
|
||||
function testAt95PercentTaxIdx8() public view {
|
||||
// deltaS=5, effIdx=8 → penalty=125*8/20=50 NOT < 50 → BEAR
|
||||
assertFalse(push3.isBullMarket(_pct(95), _norm(8)));
|
||||
}
|
||||
|
||||
function testAt97PercentHighTax() public view {
|
||||
// deltaS=3, effIdx=29 → penalty=27*29/20=39 < 50 → BULL
|
||||
assertTrue(push3.isBullMarket(_pct(97), _norm(29)));
|
||||
}
|
||||
|
||||
function testAt100PercentAlwaysBull() public view {
|
||||
for (uint256 t = 0; t < 30; t++) {
|
||||
assertTrue(push3.isBullMarket(1e18, _norm(t)));
|
||||
}
|
||||
}
|
||||
|
||||
function testEffIdxShiftAtBoundary() public view {
|
||||
// taxIdx=13: effIdx=13, penalty=64*13/20=41 < 50 → BULL
|
||||
assertTrue(push3.isBullMarket(_pct(96), _norm(13)));
|
||||
// taxIdx=14: effIdx=15 (shift!), penalty=64*15/20=48 < 50 → BULL
|
||||
assertTrue(push3.isBullMarket(_pct(96), _norm(14)));
|
||||
// taxIdx=15: effIdx=16, penalty=64*16/20=51 >= 50 → BEAR
|
||||
assertFalse(push3.isBullMarket(_pct(96), _norm(15)));
|
||||
}
|
||||
|
||||
function testRevertsAbove100Percent() public {
|
||||
vm.expectRevert("Invalid percentage staked");
|
||||
push3.isBullMarket(1e18 + 1, 0);
|
||||
}
|
||||
|
||||
// ---- Fuzz ----
|
||||
|
||||
function testFuzzEquivalence(uint256 percentageStaked, uint256 averageTaxRate) public view {
|
||||
percentageStaked = bound(percentageStaked, 0, 1e18);
|
||||
averageTaxRate = bound(averageTaxRate, 0, 1e18);
|
||||
bool r = ref.isBullMarket(percentageStaked, averageTaxRate);
|
||||
bool p = push3.isBullMarket(percentageStaked, averageTaxRate);
|
||||
assertEq(p, r, "Push3 result must match reference for all inputs");
|
||||
}
|
||||
|
||||
function testFuzzNeverReverts(uint256 percentageStaked, uint256 averageTaxRate) public view {
|
||||
percentageStaked = bound(percentageStaked, 0, 1e18);
|
||||
averageTaxRate = bound(averageTaxRate, 0, 1e18);
|
||||
push3.isBullMarket(percentageStaked, averageTaxRate);
|
||||
}
|
||||
}
|
||||
322
package-lock.json
generated
322
package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "harb",
|
||||
"name": "workspace",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
@ -64,6 +64,7 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@harb/web3": "*",
|
||||
"@tanstack/vue-query": "^5.92.9",
|
||||
"@wagmi/vue": "^0.2.8",
|
||||
"sass": "^1.83.4",
|
||||
"viem": "^2.22.13",
|
||||
|
|
@ -257,7 +258,6 @@
|
|||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
|
|
@ -1101,7 +1101,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
|
|
@ -1142,7 +1141,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
|
|
@ -4801,7 +4799,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz",
|
||||
"integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
|
|
@ -4876,266 +4873,6 @@
|
|||
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@paulmillr/qr": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@paulmillr/qr/-/qr-0.2.1.tgz",
|
||||
|
|
@ -6124,7 +5861,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
@ -6470,7 +6206,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
@ -6768,7 +6503,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
@ -7290,7 +7024,6 @@
|
|||
"integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^8.13.0",
|
||||
"eslint-visitor-keys": "^4.2.0",
|
||||
|
|
@ -7745,7 +7478,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/ms": "*"
|
||||
}
|
||||
|
|
@ -7841,7 +7573,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
|
|
@ -7943,7 +7674,6 @@
|
|||
"integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.56.0",
|
||||
"@typescript-eslint/types": "8.56.0",
|
||||
|
|
@ -8738,7 +8468,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz",
|
||||
"integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"eventemitter3": "5.0.1",
|
||||
"mipd": "0.0.7",
|
||||
|
|
@ -9340,7 +9069,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
@ -9543,7 +9271,6 @@
|
|||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -10135,7 +9862,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
|
|
@ -10219,7 +9945,6 @@
|
|||
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
|
|
@ -10494,7 +10219,6 @@
|
|||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
|
|
@ -10997,7 +10721,6 @@
|
|||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
|
||||
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.7.0"
|
||||
}
|
||||
|
|
@ -11034,7 +10757,6 @@
|
|||
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz",
|
||||
"integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"uncrypto": "^0.1.3"
|
||||
}
|
||||
|
|
@ -11551,7 +11273,6 @@
|
|||
"resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.17.tgz",
|
||||
"integrity": "sha512-TOOURki4G7sD1wDCjj7NfLaXZZ49dFOeEb5y39IXpb8p0hRzVvfvzZHOi5JcT+PpyAbi/Y+lxPb8eTag2WYH8w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ecies/ciphers": "^0.2.5",
|
||||
"@noble/ciphers": "^1.3.0",
|
||||
|
|
@ -11981,7 +11702,6 @@
|
|||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -12058,7 +11778,6 @@
|
|||
"integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
|
|
@ -12476,8 +12195,7 @@
|
|||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
|
||||
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
|
|
@ -12598,7 +12316,6 @@
|
|||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.0",
|
||||
|
|
@ -13392,7 +13109,6 @@
|
|||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
|
||||
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||
}
|
||||
|
|
@ -13474,7 +13190,6 @@
|
|||
"integrity": "sha512-yoLRW+KRlDmnnROdAu7sX77VNLC0bsFoZyGQJLy1cF+X/SkLg/fWkRGrEEYQK8o2cafJ2wmEaMqMEZB3U3DYDg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
|
|
@ -13550,7 +13265,6 @@
|
|||
"integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-mimetype": "^3.0.0"
|
||||
|
|
@ -14183,7 +13897,7 @@
|
|||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
@ -14231,7 +13945,7 @@
|
|||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
|
|
@ -14594,7 +14308,6 @@
|
|||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^29.7.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
|
|
@ -15594,7 +15307,6 @@
|
|||
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@acemir/cssom": "^0.9.28",
|
||||
"@asamuzakjp/dom-selector": "^6.7.6",
|
||||
|
|
@ -15917,7 +15629,6 @@
|
|||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
||||
"devOptional": true,
|
||||
"license": "MPL-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
|
|
@ -16343,15 +16054,13 @@
|
|||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
|
||||
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-unified": {
|
||||
"version": "1.0.3",
|
||||
|
|
@ -18647,7 +18356,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
@ -18672,7 +18380,6 @@
|
|||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
|
|
@ -18798,7 +18505,6 @@
|
|||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
|
|
@ -19019,7 +18725,6 @@
|
|||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
||||
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
|
|
@ -19607,7 +19312,6 @@
|
|||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
|
||||
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.4.1",
|
||||
|
|
@ -20080,8 +19784,7 @@
|
|||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
|
||||
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss-animate": {
|
||||
"version": "1.0.7",
|
||||
|
|
@ -20573,7 +20276,6 @@
|
|||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -20959,7 +20661,6 @@
|
|||
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
|
|
@ -21015,7 +20716,6 @@
|
|||
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz",
|
||||
"integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"derive-valtio": "0.1.0",
|
||||
"proxy-compare": "2.6.0",
|
||||
|
|
@ -21067,7 +20767,6 @@
|
|||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@noble/curves": "1.9.1",
|
||||
"@noble/hashes": "1.8.0",
|
||||
|
|
@ -21215,7 +20914,6 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
|
|
@ -21493,7 +21191,6 @@
|
|||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
|
||||
"integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.28",
|
||||
"@vue/compiler-sfc": "3.5.28",
|
||||
|
|
@ -21522,7 +21219,6 @@
|
|||
"integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"eslint-scope": "^8.2.0 || ^9.0.0",
|
||||
|
|
@ -21831,7 +21527,6 @@
|
|||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
|
|
@ -22019,7 +21714,6 @@
|
|||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
|
|
|||
201
tools/push3-transpiler/optimizer_v3.push3
Normal file
201
tools/push3-transpiler/optimizer_v3.push3
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
;; OptimizerV3 in Push3
|
||||
;;
|
||||
;; Computes isBullMarket(percentageStaked_1e18, averageTaxRate_1e18)
|
||||
;;
|
||||
;; 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)
|
||||
;;
|
||||
;; Output on BOOLEAN stack:
|
||||
;; top: TRUE if bull market, FALSE if bear market
|
||||
;;
|
||||
;; Logic mirrors OptimizerV3.isBullMarket:
|
||||
;; stakedPct = percentageStaked * 100 / 1e18 (0-100)
|
||||
;; if stakedPct <= 91 → FALSE (always bear)
|
||||
;; deltaS = 100 - stakedPct
|
||||
;; effIdx = _taxRateToEffectiveIndex(averageTaxRate) (0-29, with +1 shift at >=14)
|
||||
;; penalty = deltaS^3 * effIdx / 20
|
||||
;; return penalty < 50
|
||||
|
||||
(
|
||||
;; Step 1: Bind inputs to names.
|
||||
;; Stack on entry: [percentageStaked_1e18 (top), averageTaxRate_1e18 (below)]
|
||||
DYADIC.SWAP
|
||||
;; Stack: [averageTaxRate_1e18 (top), percentageStaked_1e18 (below)]
|
||||
TAXRATE DYADIC.DEFINE
|
||||
;; Stack: [percentageStaked_1e18]
|
||||
|
||||
;; Step 2: Compute stakedPct = percentageStaked * 100 / 1e18 (integer 0..100)
|
||||
100 DYADIC.*
|
||||
1000000000000000000 DYADIC./
|
||||
;; Stack: [stakedPct]
|
||||
STAKED DYADIC.DEFINE
|
||||
;; Stack: []
|
||||
|
||||
;; Step 3: Main conditional — stakedPct > 91?
|
||||
STAKED 91 DYADIC.>
|
||||
;; bool_stack: [stakedPct > 91]
|
||||
|
||||
EXEC.IF
|
||||
|
||||
;; TRUE branch: stakedPct > 91 — compute penalty
|
||||
(
|
||||
;; deltaS = 100 - stakedPct
|
||||
100 STAKED DYADIC.-
|
||||
;; Stack: [deltaS]
|
||||
DELTAS DYADIC.DEFINE
|
||||
;; Stack: []
|
||||
|
||||
;; Compute raw tax index via 30-way threshold lookup.
|
||||
;; Each level: if TAXRATE <= threshold then push index, else go deeper.
|
||||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
;; Stack: [raw_idx (0-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 )
|
||||
( )
|
||||
)
|
||||
( )
|
||||
;; Stack: [effIdx (0-29)]
|
||||
|
||||
EFFIDX DYADIC.DEFINE
|
||||
;; Stack: []
|
||||
|
||||
;; Compute penalty = deltaS^3 * effIdx / 20
|
||||
DELTAS DELTAS DYADIC.*
|
||||
DELTAS DYADIC.*
|
||||
EFFIDX DYADIC.*
|
||||
20 DYADIC./
|
||||
;; Stack: [penalty]
|
||||
|
||||
;; Return penalty < 50
|
||||
50 DYADIC.<
|
||||
;; bool_stack: [penalty < 50]
|
||||
)
|
||||
|
||||
;; FALSE branch: stakedPct <= 91 — always bear
|
||||
(
|
||||
FALSE
|
||||
)
|
||||
)
|
||||
237
tools/push3-transpiler/package-lock.json
generated
Normal file
237
tools/push3-transpiler/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
{
|
||||
"name": "push3-transpiler",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "push3-transpiler",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
|
||||
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
|
||||
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
||||
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
|
||||
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
tools/push3-transpiler/package.json
Normal file
16
tools/push3-transpiler/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "push3-transpiler",
|
||||
"version": "1.0.0",
|
||||
"description": "Push3 to Solidity transpiler for OptimizerV3",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"transpile": "ts-node src/index.ts"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.0",
|
||||
"ts-node": "^10.0.0",
|
||||
"@types/node": "^20.0.0"
|
||||
}
|
||||
}
|
||||
70
tools/push3-transpiler/src/index.ts
Normal file
70
tools/push3-transpiler/src/index.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Push3 → Solidity transpiler CLI entry point.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { parse } from './parser';
|
||||
import { transpile } from './transpiler';
|
||||
|
||||
function main(): void {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 2) {
|
||||
console.error('Usage: ts-node 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, resultVar } = transpile(ast);
|
||||
|
||||
const solidityLines = [
|
||||
'// SPDX-License-Identifier: GPL-3.0-or-later',
|
||||
'pragma solidity ^0.8.19;',
|
||||
'',
|
||||
'/**',
|
||||
' * @title OptimizerV3Push3',
|
||||
' * @notice Auto-generated from optimizer_v3.push3 via Push3→Solidity transpiler.',
|
||||
' * Implements the same isBullMarket logic as OptimizerV3.',
|
||||
' */',
|
||||
'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.',
|
||||
' */',
|
||||
' function isBullMarket(',
|
||||
' uint256 percentageStaked,',
|
||||
' uint256 averageTaxRate',
|
||||
' ) public pure returns (bool bull) {',
|
||||
' require(percentageStaked <= 1e18, "Invalid percentage staked");',
|
||||
...functionBody,
|
||||
` bull = ${resultVar};`,
|
||||
' }',
|
||||
'}',
|
||||
'',
|
||||
];
|
||||
|
||||
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(` Result var: ${resultVar}`);
|
||||
}
|
||||
|
||||
main();
|
||||
81
tools/push3-transpiler/src/parser.ts
Normal file
81
tools/push3-transpiler/src/parser.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Push3 parser — converts Push3 source text into an AST.
|
||||
*
|
||||
* Node types:
|
||||
* { kind: 'int', value: bigint } — integer literal (1e18 etc.)
|
||||
* { kind: 'bool', value: boolean } — TRUE / FALSE
|
||||
* { kind: 'instr', name: string } — DYADIC.+, EXEC.IF, etc.
|
||||
* { kind: 'name', text: string } — unbound identifier (TAXRATE etc.)
|
||||
* { kind: 'list', items: Node[] } — ( ... )
|
||||
*/
|
||||
|
||||
export type Node =
|
||||
| { kind: 'int'; value: bigint }
|
||||
| { kind: 'bool'; value: boolean }
|
||||
| { kind: 'instr'; name: string }
|
||||
| { kind: 'name'; text: string }
|
||||
| { kind: 'list'; items: Node[] };
|
||||
|
||||
// Known instruction prefixes / exact names
|
||||
const KNOWN_INSTR_PREFIXES = [
|
||||
'DYADIC.', 'EXEC.', 'BOOLEAN.', 'CODE.', 'NAME.', 'INDEX.',
|
||||
'INTVECTOR.', 'FLOATVECTOR.', 'BOOLVECTOR.', 'GRAPH.',
|
||||
];
|
||||
|
||||
function isInstruction(token: string): boolean {
|
||||
for (const pfx of KNOWN_INSTR_PREFIXES) {
|
||||
if (token.startsWith(pfx)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tokenize(src: string): string[] {
|
||||
// Strip comments (;; to end of line)
|
||||
const noComments = src.replace(/;;[^\n]*/g, ' ');
|
||||
// Split on whitespace, treating ( and ) as separate tokens
|
||||
const spaced = noComments.replace(/\(/g, ' ( ').replace(/\)/g, ' ) ');
|
||||
return spaced.trim().split(/\s+/).filter(t => t.length > 0);
|
||||
}
|
||||
|
||||
function parseTokens(tokens: string[], pos: number): [Node, number] {
|
||||
const token = tokens[pos];
|
||||
if (token === undefined) throw new Error('Unexpected end of tokens');
|
||||
|
||||
if (token === '(') {
|
||||
// Parse list until matching ')'
|
||||
const items: Node[] = [];
|
||||
let i = pos + 1;
|
||||
while (i < tokens.length && tokens[i] !== ')') {
|
||||
const [node, next] = parseTokens(tokens, i);
|
||||
items.push(node);
|
||||
i = next;
|
||||
}
|
||||
if (tokens[i] !== ')') throw new Error('Unmatched (');
|
||||
return [{ kind: 'list', items }, i + 1];
|
||||
}
|
||||
|
||||
if (token === 'TRUE') return [{ kind: 'bool', value: true }, pos + 1];
|
||||
if (token === 'FALSE') return [{ kind: 'bool', value: false }, pos + 1];
|
||||
|
||||
// Integer literal — may be large (BigInt)
|
||||
if (/^-?\d+$/.test(token)) {
|
||||
return [{ kind: 'int', value: BigInt(token) }, pos + 1];
|
||||
}
|
||||
|
||||
if (isInstruction(token)) {
|
||||
return [{ kind: 'instr', name: token }, pos + 1];
|
||||
}
|
||||
|
||||
// Otherwise: unbound name (e.g. TAXRATE, STAKED, DELTAS, EFFIDX)
|
||||
return [{ kind: 'name', text: token }, pos + 1];
|
||||
}
|
||||
|
||||
export function parse(src: string): Node {
|
||||
const tokens = tokenize(src);
|
||||
if (tokens.length === 0) throw new Error('Empty program');
|
||||
const [node, consumed] = parseTokens(tokens, 0);
|
||||
if (consumed !== tokens.length) {
|
||||
throw new Error(`Unexpected tokens after position ${consumed}: ${tokens[consumed]}`);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
379
tools/push3-transpiler/src/transpiler.ts
Normal file
379
tools/push3-transpiler/src/transpiler.ts
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/**
|
||||
* Push3 → Solidity transpiler.
|
||||
*
|
||||
* Uses symbolic stack simulation (SSA-style). Each stack holds Solidity expression strings.
|
||||
* EXEC.IF runs both branches speculatively, then emits an if/else with result variables
|
||||
* for any stack positions that differ between branches.
|
||||
*
|
||||
* Only the subset of instructions used in optimizer_v3.push3 is implemented.
|
||||
*/
|
||||
|
||||
import { Node } from './parser';
|
||||
|
||||
interface TranspilerState {
|
||||
dStack: string[]; // DYADIC stack (Solidity expressions)
|
||||
bStack: string[]; // BOOLEAN stack
|
||||
nameStack: string[]; // NAME stack (unbound identifiers)
|
||||
lines: string[]; // emitted Solidity statements
|
||||
bindings: Map<string, string>; // identifier → Solidity variable name
|
||||
varCounter: number; // fresh variable counter (shared across branches)
|
||||
indent: number; // current indentation level
|
||||
}
|
||||
|
||||
function freshVar(state: TranspilerState, prefix = 'v'): string {
|
||||
return `${prefix}${state.varCounter++}`;
|
||||
}
|
||||
|
||||
function emit(state: TranspilerState, line: string): void {
|
||||
const pad = ' '.repeat(state.indent);
|
||||
state.lines.push(pad + line);
|
||||
}
|
||||
|
||||
function dpop(state: TranspilerState, ctx: string): string {
|
||||
const v = state.dStack.pop();
|
||||
if (v === undefined) throw new Error(`DYADIC stack underflow at ${ctx}`);
|
||||
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}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function processNode(node: Node, state: TranspilerState): void {
|
||||
switch (node.kind) {
|
||||
case 'int':
|
||||
state.dStack.push(node.value.toString());
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
state.bStack.push(node.value ? 'true' : 'false');
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
// Unbound name goes to the NAME stack (separate from DYADIC stack)
|
||||
state.nameStack.push(node.text);
|
||||
break;
|
||||
|
||||
case 'instr':
|
||||
processInstruction(node.name, state);
|
||||
break;
|
||||
|
||||
case 'list':
|
||||
for (const item of node.items) {
|
||||
processNode(item, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function processInstruction(name: string, state: TranspilerState): void {
|
||||
switch (name) {
|
||||
// ---- DYADIC stack ops ----
|
||||
case 'DYADIC.SWAP': {
|
||||
const a = dpop(state, 'DYADIC.SWAP');
|
||||
const b = dpop(state, 'DYADIC.SWAP');
|
||||
state.dStack.push(a);
|
||||
state.dStack.push(b);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.DUP': {
|
||||
const a = dpop(state, 'DYADIC.DUP');
|
||||
// Materialise complex expressions before duplicating
|
||||
const vname = freshVar(state, 'dup');
|
||||
emit(state, `uint256 ${vname} = uint256(${a});`);
|
||||
state.dStack.push(vname);
|
||||
state.dStack.push(vname);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.POP': {
|
||||
dpop(state, 'DYADIC.POP');
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- DYADIC arithmetic ----
|
||||
case 'DYADIC.*': {
|
||||
const b = dpop(state, 'DYADIC.*');
|
||||
const a = dpop(state, 'DYADIC.*');
|
||||
state.dStack.push(`(${a} * ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC./': {
|
||||
const b = dpop(state, 'DYADIC./');
|
||||
const a = dpop(state, 'DYADIC./');
|
||||
state.dStack.push(`(${a} / ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.+': {
|
||||
const b = dpop(state, 'DYADIC.+');
|
||||
const a = dpop(state, 'DYADIC.+');
|
||||
state.dStack.push(`(${a} + ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.-': {
|
||||
const b = dpop(state, 'DYADIC.-');
|
||||
const a = dpop(state, 'DYADIC.-');
|
||||
state.dStack.push(`(${a} - ${b})`);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- DYADIC comparisons → BOOLEAN ----
|
||||
case 'DYADIC.>': {
|
||||
const b = dpop(state, 'DYADIC.>');
|
||||
const a = dpop(state, 'DYADIC.>');
|
||||
state.bStack.push(`(${a} > ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.<': {
|
||||
const b = dpop(state, 'DYADIC.<');
|
||||
const a = dpop(state, 'DYADIC.<');
|
||||
state.bStack.push(`(${a} < ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.>=': {
|
||||
const b = dpop(state, 'DYADIC.>=');
|
||||
const a = dpop(state, 'DYADIC.>=');
|
||||
state.bStack.push(`(${a} >= ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'DYADIC.<=': {
|
||||
const b = dpop(state, 'DYADIC.<=');
|
||||
const a = dpop(state, 'DYADIC.<=');
|
||||
state.bStack.push(`(${a} <= ${b})`);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Name binding ----
|
||||
case 'DYADIC.DEFINE': {
|
||||
const val = dpop(state, 'DYADIC.DEFINE (value)');
|
||||
const id = state.nameStack.pop();
|
||||
if (id === undefined) throw new Error('DYADIC.DEFINE: NAME stack underflow');
|
||||
const varName = id.toLowerCase();
|
||||
emit(state, `uint256 ${varName} = uint256(${val});`);
|
||||
state.bindings.set(id, varName);
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- BOOLEAN ----
|
||||
case 'BOOLEAN.NOT': {
|
||||
const a = bpop(state, 'BOOLEAN.NOT');
|
||||
state.bStack.push(`!(${a})`);
|
||||
break;
|
||||
}
|
||||
case 'BOOLEAN.AND': {
|
||||
const b = bpop(state, 'BOOLEAN.AND');
|
||||
const a = bpop(state, 'BOOLEAN.AND');
|
||||
state.bStack.push(`(${a} && ${b})`);
|
||||
break;
|
||||
}
|
||||
case 'BOOLEAN.OR': {
|
||||
const b = bpop(state, 'BOOLEAN.OR');
|
||||
const a = bpop(state, 'BOOLEAN.OR');
|
||||
state.bStack.push(`(${a} || ${b})`);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'EXEC.IF':
|
||||
throw new Error('EXEC.IF must be handled by processItems');
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported instruction: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a list of nodes sequentially, specially handling EXEC.IF by consuming
|
||||
* the next two items as true/false branches.
|
||||
*/
|
||||
function processItems(items: Node[], state: TranspilerState): void {
|
||||
let i = 0;
|
||||
while (i < items.length) {
|
||||
const item = items[i];
|
||||
|
||||
// Bound identifier → push its Solidity variable to dStack
|
||||
if (item.kind === 'name' && state.bindings.has(item.text)) {
|
||||
state.dStack.push(state.bindings.get(item.text)!);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// EXEC.IF — consume it plus the next two items (branches)
|
||||
if (item.kind === 'instr' && item.name === 'EXEC.IF') {
|
||||
const trueBranch = items[i + 1];
|
||||
const falseBranch = items[i + 2];
|
||||
if (!trueBranch || !falseBranch) throw new Error('EXEC.IF: missing branches');
|
||||
i += 3;
|
||||
processExecIf(trueBranch, falseBranch, state);
|
||||
continue;
|
||||
}
|
||||
|
||||
processNode(item, state);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
function makeSubState(parent: TranspilerState, indentOffset = 1): TranspilerState {
|
||||
return {
|
||||
dStack: [...parent.dStack],
|
||||
bStack: [...parent.bStack],
|
||||
nameStack: [...parent.nameStack],
|
||||
lines: [],
|
||||
bindings: new Map(parent.bindings),
|
||||
varCounter: parent.varCounter,
|
||||
indent: parent.indent + indentOffset,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an if/else block for EXEC.IF.
|
||||
*
|
||||
* Both branches are simulated speculatively. We compare final stacks elementwise:
|
||||
* - Positions unchanged in both branches → keep as-is in parent
|
||||
* - Positions that differ → emit a result variable, assign in each branch
|
||||
*
|
||||
* This correctly handles both "new value produced" and "existing value mutated" cases.
|
||||
*/
|
||||
function processExecIf(
|
||||
trueBranch: Node,
|
||||
falseBranch: Node,
|
||||
state: TranspilerState,
|
||||
): void {
|
||||
const cond = bpop(state, 'EXEC.IF condition');
|
||||
|
||||
const dBefore = [...state.dStack];
|
||||
const bBefore = [...state.bStack];
|
||||
|
||||
// --- Simulate TRUE branch ---
|
||||
const trueState = makeSubState(state);
|
||||
processItems(toItems(trueBranch), trueState);
|
||||
state.varCounter = trueState.varCounter;
|
||||
|
||||
// --- Simulate FALSE branch ---
|
||||
const falseState = makeSubState(state);
|
||||
state.varCounter = falseState.varCounter; // will be updated after false branch too
|
||||
processItems(toItems(falseBranch), falseState);
|
||||
state.varCounter = falseState.varCounter;
|
||||
|
||||
// --- Compare dStacks elementwise ---
|
||||
const maxDLen = Math.max(trueState.dStack.length, falseState.dStack.length);
|
||||
// result var for each position that changed or is new
|
||||
const dResultMap = new Map<number, string>(); // position → result var name
|
||||
for (let k = 0; k < maxDLen; k++) {
|
||||
const tv = trueState.dStack[k];
|
||||
const fv = falseState.dStack[k];
|
||||
const bv = dBefore[k]; // undefined for positions beyond dBefore
|
||||
if (tv !== undefined && fv !== undefined && tv === fv && tv === bv) {
|
||||
continue; // identical in both branches and unchanged from before — skip
|
||||
}
|
||||
const rv = freshVar(state, 'r');
|
||||
emit(state, `uint256 ${rv};`);
|
||||
dResultMap.set(k, rv);
|
||||
}
|
||||
|
||||
// --- Compare bStacks elementwise ---
|
||||
const maxBLen = Math.max(trueState.bStack.length, falseState.bStack.length);
|
||||
const bResultMap = new Map<number, string>();
|
||||
for (let k = 0; k < maxBLen; k++) {
|
||||
const tv = trueState.bStack[k];
|
||||
const fv = falseState.bStack[k];
|
||||
const bv = bBefore[k];
|
||||
if (tv !== undefined && fv !== undefined && tv === fv && tv === bv) {
|
||||
continue;
|
||||
}
|
||||
const rv = freshVar(state, 'b');
|
||||
emit(state, `bool ${rv};`);
|
||||
bResultMap.set(k, rv);
|
||||
}
|
||||
|
||||
// --- Build branch assignments ---
|
||||
const buildAssignments = (
|
||||
branchState: TranspilerState,
|
||||
dMap: Map<number, string>,
|
||||
bMap: Map<number, string>,
|
||||
indentLevel: number,
|
||||
): string[] => {
|
||||
const pad = ' '.repeat(indentLevel);
|
||||
const assignments: string[] = [];
|
||||
for (const [k, rv] of dMap) {
|
||||
const val = branchState.dStack[k] ?? '0';
|
||||
assignments.push(`${pad}${rv} = uint256(${val});`);
|
||||
}
|
||||
for (const [k, rv] of bMap) {
|
||||
const val = branchState.bStack[k] ?? 'false';
|
||||
assignments.push(`${pad}${rv} = ${val};`);
|
||||
}
|
||||
return assignments;
|
||||
};
|
||||
|
||||
const trueAssign = buildAssignments(trueState, dResultMap, bResultMap, state.indent + 1);
|
||||
const falseAssign = buildAssignments(falseState, dResultMap, bResultMap, state.indent + 1);
|
||||
|
||||
// --- Emit if/else ---
|
||||
const pad = ' '.repeat(state.indent);
|
||||
state.lines.push(`${pad}if (${cond}) {`);
|
||||
state.lines.push(...trueState.lines);
|
||||
state.lines.push(...trueAssign);
|
||||
|
||||
const falseBody = [...falseState.lines, ...falseAssign];
|
||||
if (falseBody.length > 0) {
|
||||
state.lines.push(`${pad}} else {`);
|
||||
state.lines.push(...falseBody);
|
||||
}
|
||||
state.lines.push(`${pad}}`);
|
||||
|
||||
// --- Reconstruct parent stacks ---
|
||||
const newDStack: string[] = [];
|
||||
for (let k = 0; k < maxDLen; k++) {
|
||||
const rv = dResultMap.get(k);
|
||||
newDStack.push(rv ?? (dBefore[k] ?? trueState.dStack[k] ?? '0'));
|
||||
}
|
||||
const newBStack: string[] = [];
|
||||
for (let k = 0; k < maxBLen; k++) {
|
||||
const rv = bResultMap.get(k);
|
||||
newBStack.push(rv ?? (bBefore[k] ?? trueState.bStack[k] ?? 'false'));
|
||||
}
|
||||
|
||||
state.dStack = newDStack;
|
||||
state.bStack = newBStack;
|
||||
}
|
||||
|
||||
function toItems(node: Node): Node[] {
|
||||
return node.kind === 'list' ? node.items : [node];
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
|
||||
export interface TranspileResult {
|
||||
functionBody: string[];
|
||||
resultVar: 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)
|
||||
*
|
||||
* The Push3 program's first instruction is DYADIC.SWAP so it binds averageTaxRate
|
||||
* first, then computes stakedPct.
|
||||
*/
|
||||
export function transpile(program: Node): TranspileResult {
|
||||
if (program.kind !== 'list') throw new Error('Expected top-level list');
|
||||
|
||||
const state: TranspilerState = {
|
||||
dStack: ['averageTaxRate', 'percentageStaked'],
|
||||
bStack: [],
|
||||
nameStack: [],
|
||||
lines: [],
|
||||
bindings: new Map(),
|
||||
varCounter: 0,
|
||||
indent: 2,
|
||||
};
|
||||
|
||||
processItems(program.items, state);
|
||||
|
||||
const resultVar = state.bStack[state.bStack.length - 1] ?? 'false';
|
||||
return { functionBody: state.lines, resultVar };
|
||||
}
|
||||
15
tools/push3-transpiler/tsconfig.json
Normal file
15
tools/push3-transpiler/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2020"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue