From ba9c0d372ad8e71456565e83760483e6d4fbc18f Mon Sep 17 00:00:00 2001 From: rndrntwrk <180591682+rndrntwrk@users.noreply.github.com> Date: Thu, 30 Apr 2026 09:31:08 -0500 Subject: [PATCH 1/2] fix(amm): disable unsafe dynamic liquidity markets --- .../contracts/lvr_amm/Router.sol | 2 + packages/evm-contracts/test/LvrMarket.t.sol | 37 +++++++++++++------ .../anchor/programs/lvr_amm/src/error.rs | 3 ++ .../lvr_amm/src/instructions/create_bet.rs | 1 + .../anchor/target/idl/lvr_amm.json | 5 +++ .../anchor/target/types/lvr_amm.ts | 5 +++ .../anchor/tests/amm-test-helpers.ts | 2 +- .../anchor/tests/lvr_amm_security.anchor.ts | 15 ++++++++ .../hyperbet-solana/app/src/idl/lvr_amm.json | 5 +++ .../hyperbet-solana/app/src/idl/lvr_amm.ts | 5 +++ .../keeper/src/idl/lvr_amm.json | 5 +++ .../hyperbet-solana/keeper/src/idl/lvr_amm.ts | 5 +++ 12 files changed, 78 insertions(+), 12 deletions(-) diff --git a/packages/evm-contracts/contracts/lvr_amm/Router.sol b/packages/evm-contracts/contracts/lvr_amm/Router.sol index 52151715..e3b28d8f 100644 --- a/packages/evm-contracts/contracts/lvr_amm/Router.sol +++ b/packages/evm-contracts/contracts/lvr_amm/Router.sol @@ -52,6 +52,7 @@ contract Router is ReentrancyGuard, IMarketBuyCallback, IMarketSellCallback, IMa error MarketNotAllowed(); error SlippageExceeded(); error ConfigFrozen(); + error DynamicMarketsDisabled(); event FeeConfigUpdated(address indexed treasury, uint256 feeBps); event FeeConfigFrozen(address indexed admin); @@ -114,6 +115,7 @@ contract Router is ReentrancyGuard, IMarketBuyCallback, IMarketSellCallback, IMa uint256 collateralIn ) public { _checkRole(MARKET_OPERATOR_ROLE); + if (isDynamic) revert DynamicMarketsDisabled(); bytes32 marketId = keccak256(abi.encode(title, msg.sender, block.timestamp)); if (knownMarketIds[marketId]) revert MarketAlreadyExists(); diff --git a/packages/evm-contracts/test/LvrMarket.t.sol b/packages/evm-contracts/test/LvrMarket.t.sol index ec9a5efd..dfd6d0e4 100644 --- a/packages/evm-contracts/test/LvrMarket.t.sol +++ b/packages/evm-contracts/test/LvrMarket.t.sol @@ -57,7 +57,7 @@ contract LvrMarketTest is Test { "Market for BTC price event", "coingecko", TEST_DUEL_KEY, - true, + false, DURATION, collateralIn ); @@ -71,11 +71,26 @@ contract LvrMarketTest is Test { vm.stopPrank(); } + function test_DynamicMarketsDisabled() public { + vm.startPrank(admin); + vm.expectRevert(Router.DynamicMarketsDisabled.selector); + router.create( + "Dynamic Market", + "Disabled until dynamic liquidity is safe", + "coingecko", + TEST_DUEL_KEY, + true, + DURATION, + 100 * 10**18 + ); + vm.stopPrank(); + } + function test_BuyYes() public { uint256 collateralIn = 100 * 10**18; vm.prank(admin); - router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, collateralIn); + router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, collateralIn); address marketAddr = _marketAt(0); LvrMarket market = LvrMarket(marketAddr); @@ -99,7 +114,7 @@ contract LvrMarketTest is Test { uint256 collateralIn = 100 * 10**18; vm.prank(admin); - router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, collateralIn); + router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, collateralIn); address marketAddr = _marketAt(0); LvrMarket market = LvrMarket(marketAddr); @@ -170,7 +185,7 @@ contract LvrMarketTest is Test { function test_CreateRequiresMarketOperatorRole() public { vm.startPrank(user1); vm.expectRevert(); - router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, 100 * 10**18); + router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, 100 * 10**18); vm.stopPrank(); } @@ -178,7 +193,7 @@ contract LvrMarketTest is Test { uint256 collateralIn = 100 * 10**18; vm.prank(admin); - router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, collateralIn); + router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, collateralIn); address marketAddr = _marketAt(0); vm.warp(block.timestamp + 1 hours); @@ -195,7 +210,7 @@ contract LvrMarketTest is Test { uint256 collateralIn = 100 * 10**18; vm.prank(admin); - router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, collateralIn); + router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, collateralIn); address marketAddr = _marketAt(0); LvrMarket market = LvrMarket(marketAddr); @@ -212,7 +227,7 @@ contract LvrMarketTest is Test { function test_SettleMarketRequiresPendingState() public { vm.prank(admin); - router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, 100 * 10**18); + router.create("Test Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, 100 * 10**18); address marketAddr = _marketAt(0); vm.expectRevert(bytes("Invalid Market State")); @@ -249,13 +264,13 @@ contract LvrMarketTest is Test { function test_ZeroDurationReverts() public { vm.startPrank(admin); vm.expectRevert(bytes("Duration must be positive")); - router.create("Zero Duration", "Desc", "Src", keccak256("zero-dur"), true, 0, 100 * 10**18); + router.create("Zero Duration", "Desc", "Src", keccak256("zero-dur"), false, 0, 100 * 10**18); vm.stopPrank(); } function test_GetPriceAfterDeadline() public { vm.prank(admin); - router.create("Expiring Market", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, 100 * 10**18); + router.create("Expiring Market", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, 100 * 10**18); address marketAddr = _marketAt(0); LvrMarket market = LvrMarket(marketAddr); @@ -298,7 +313,7 @@ contract LvrMarketTest is Test { function test_AdminResolveSelectorRemoved() public { vm.prank(admin); - router.create("Bond Slash Test", "Desc", "Src", TEST_DUEL_KEY, true, DURATION, 100 * 10**18); + router.create("Bond Slash Test", "Desc", "Src", TEST_DUEL_KEY, false, DURATION, 100 * 10**18); address marketAddr = _marketAt(0); (bool success, ) = marketAddr.call(abi.encodeWithSignature("adminResolve(uint256)", 1)); assertFalse(success, "adminResolve should not remain callable"); @@ -309,7 +324,7 @@ contract LvrMarketTest is Test { _setupOracleDuel(duelKey); vm.prank(admin); - router.create("Bond Return Test", "Desc", "Src", duelKey, true, DURATION, 100 * 10**18); + router.create("Bond Return Test", "Desc", "Src", duelKey, false, DURATION, 100 * 10**18); address marketAddr = _lastMarketAddress(); LvrMarket market = LvrMarket(marketAddr); diff --git a/packages/hyperbet-solana/anchor/programs/lvr_amm/src/error.rs b/packages/hyperbet-solana/anchor/programs/lvr_amm/src/error.rs index 39dc92b6..b0e5de84 100644 --- a/packages/hyperbet-solana/anchor/programs/lvr_amm/src/error.rs +++ b/packages/hyperbet-solana/anchor/programs/lvr_amm/src/error.rs @@ -88,4 +88,7 @@ pub enum PredictionMarketError { #[msg("AMM fixed-point overflow")] MathFixedPointOverflow, + + #[msg("Dynamic AMM markets are disabled")] + DynamicMarketsDisabled, } diff --git a/packages/hyperbet-solana/anchor/programs/lvr_amm/src/instructions/create_bet.rs b/packages/hyperbet-solana/anchor/programs/lvr_amm/src/instructions/create_bet.rs index a969050e..2246a7de 100644 --- a/packages/hyperbet-solana/anchor/programs/lvr_amm/src/instructions/create_bet.rs +++ b/packages/hyperbet-solana/anchor/programs/lvr_amm/src/instructions/create_bet.rs @@ -17,6 +17,7 @@ pub fn create_bet( ) -> Result<()> { let config = &ctx.accounts.amm_config; require!(!config.paused, PredictionMarketError::MarketPaused); + require!(!is_dynamic, PredictionMarketError::DynamicMarketsDisabled); require!( initial_liq > 0, diff --git a/packages/hyperbet-solana/anchor/target/idl/lvr_amm.json b/packages/hyperbet-solana/anchor/target/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-solana/anchor/target/idl/lvr_amm.json +++ b/packages/hyperbet-solana/anchor/target/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-solana/anchor/target/types/lvr_amm.ts b/packages/hyperbet-solana/anchor/target/types/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-solana/anchor/target/types/lvr_amm.ts +++ b/packages/hyperbet-solana/anchor/target/types/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-solana/anchor/tests/amm-test-helpers.ts b/packages/hyperbet-solana/anchor/tests/amm-test-helpers.ts index 04d92471..bf80265c 100644 --- a/packages/hyperbet-solana/anchor/tests/amm-test-helpers.ts +++ b/packages/hyperbet-solana/anchor/tests/amm-test-helpers.ts @@ -401,7 +401,7 @@ export async function initializeCanonicalMarket( const ammConfig = deriveAmmConfigPda(program.programId); const initialLiq = new BN(LAMPORTS_PER_SOL * 5); // 5 SOL initial liquidity - const isDynamic = true; + const isDynamic = false; const description = "Test AMM Open Market"; const expirationAt = new BN(Date.now() / 1000 + 3600); diff --git a/packages/hyperbet-solana/anchor/tests/lvr_amm_security.anchor.ts b/packages/hyperbet-solana/anchor/tests/lvr_amm_security.anchor.ts index 1ca2da61..5da0d855 100644 --- a/packages/hyperbet-solana/anchor/tests/lvr_amm_security.anchor.ts +++ b/packages/hyperbet-solana/anchor/tests/lvr_amm_security.anchor.ts @@ -182,6 +182,21 @@ async function createBetFixture(options?: { } describe("lvr_amm security", () => { + it("rejects dynamic market creation while dynamic liquidity is disabled", async () => { + try { + await createBetFixture({ + isDynamic: true, + expirationOffsetSecs: 300, + }); + assert.fail("create_bet_account accepted a dynamic market"); + } catch (error: unknown) { + assert.ok( + hasProgramError(error, "DynamicMarketsDisabled"), + `expected DynamicMarketsDisabled, got ${String(error)}`, + ); + } + }); + it("rejects settlement with a duel PDA derived from a different duel key", async () => { const fixture = await createBetFixture({ expirationOffsetSecs: -60 }); const now = Math.floor(Date.now() / 1000); diff --git a/packages/hyperbet-solana/app/src/idl/lvr_amm.json b/packages/hyperbet-solana/app/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-solana/app/src/idl/lvr_amm.json +++ b/packages/hyperbet-solana/app/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-solana/app/src/idl/lvr_amm.ts b/packages/hyperbet-solana/app/src/idl/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-solana/app/src/idl/lvr_amm.ts +++ b/packages/hyperbet-solana/app/src/idl/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-solana/keeper/src/idl/lvr_amm.json b/packages/hyperbet-solana/keeper/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-solana/keeper/src/idl/lvr_amm.json +++ b/packages/hyperbet-solana/keeper/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-solana/keeper/src/idl/lvr_amm.ts b/packages/hyperbet-solana/keeper/src/idl/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-solana/keeper/src/idl/lvr_amm.ts +++ b/packages/hyperbet-solana/keeper/src/idl/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ From f8137a65544ba74f98108871cbeb511d74cb1a75 Mon Sep 17 00:00:00 2001 From: rndrntwrk <180591682+rndrntwrk@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:32:24 -0500 Subject: [PATCH 2/2] chore(idl): sync downstream lvr_amm IDLs to dynamic-disable change Adding `DynamicMarketsDisabled` to `PredictionMarketError` shifts the generated IDL's error array. Anchor regenerates the IDL on every build, and `Solana Program Build Gate` requires every downstream IDL mirror to match the canonical generated artifact. Propagates the regenerated IDL into: - packages/hyperbet-avax/keeper/src/idl - packages/hyperbet-bsc/{app,keeper}/src/idl - packages/hyperbet-evm/keeper/src/idl - packages/hyperbet-ui/src/idl - packages/market-maker-bot/src/idl Mechanical sync only, produced by `bun run --cwd packages/hyperbet-solana/anchor build` through the existing `sync-anchor-artifacts` script. No runtime behavior change; the dynamic-create rejection lives in `programs/lvr_amm/src/instructions/create_bet.rs`. --- packages/hyperbet-avax/keeper/src/idl/lvr_amm.json | 5 +++++ packages/hyperbet-avax/keeper/src/idl/lvr_amm.ts | 5 +++++ packages/hyperbet-bsc/app/src/idl/lvr_amm.json | 5 +++++ packages/hyperbet-bsc/app/src/idl/lvr_amm.ts | 5 +++++ packages/hyperbet-bsc/keeper/src/idl/lvr_amm.json | 5 +++++ packages/hyperbet-bsc/keeper/src/idl/lvr_amm.ts | 5 +++++ packages/hyperbet-evm/keeper/src/idl/lvr_amm.json | 5 +++++ packages/hyperbet-evm/keeper/src/idl/lvr_amm.ts | 5 +++++ packages/hyperbet-ui/src/idl/lvr_amm.json | 5 +++++ packages/market-maker-bot/src/idl/lvr_amm.json | 5 +++++ 10 files changed, 50 insertions(+) diff --git a/packages/hyperbet-avax/keeper/src/idl/lvr_amm.json b/packages/hyperbet-avax/keeper/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-avax/keeper/src/idl/lvr_amm.json +++ b/packages/hyperbet-avax/keeper/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-avax/keeper/src/idl/lvr_amm.ts b/packages/hyperbet-avax/keeper/src/idl/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-avax/keeper/src/idl/lvr_amm.ts +++ b/packages/hyperbet-avax/keeper/src/idl/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-bsc/app/src/idl/lvr_amm.json b/packages/hyperbet-bsc/app/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-bsc/app/src/idl/lvr_amm.json +++ b/packages/hyperbet-bsc/app/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-bsc/app/src/idl/lvr_amm.ts b/packages/hyperbet-bsc/app/src/idl/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-bsc/app/src/idl/lvr_amm.ts +++ b/packages/hyperbet-bsc/app/src/idl/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.json b/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.json +++ b/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.ts b/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.ts +++ b/packages/hyperbet-bsc/keeper/src/idl/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-evm/keeper/src/idl/lvr_amm.json b/packages/hyperbet-evm/keeper/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-evm/keeper/src/idl/lvr_amm.json +++ b/packages/hyperbet-evm/keeper/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-evm/keeper/src/idl/lvr_amm.ts b/packages/hyperbet-evm/keeper/src/idl/lvr_amm.ts index 1b3f8ba6..964a5b5c 100644 --- a/packages/hyperbet-evm/keeper/src/idl/lvr_amm.ts +++ b/packages/hyperbet-evm/keeper/src/idl/lvr_amm.ts @@ -1797,6 +1797,11 @@ export type LvrAmm = { "code": 6028, "name": "mathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "dynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/hyperbet-ui/src/idl/lvr_amm.json b/packages/hyperbet-ui/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/hyperbet-ui/src/idl/lvr_amm.json +++ b/packages/hyperbet-ui/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [ diff --git a/packages/market-maker-bot/src/idl/lvr_amm.json b/packages/market-maker-bot/src/idl/lvr_amm.json index 7cb1d36e..8c493d8d 100644 --- a/packages/market-maker-bot/src/idl/lvr_amm.json +++ b/packages/market-maker-bot/src/idl/lvr_amm.json @@ -1791,6 +1791,11 @@ "code": 6028, "name": "MathFixedPointOverflow", "msg": "AMM fixed-point overflow" + }, + { + "code": 6029, + "name": "DynamicMarketsDisabled", + "msg": "Dynamic AMM markets are disabled" } ], "types": [