From 2b9bf1b46657f031096f439c43cc03f4527a67ca Mon Sep 17 00:00:00 2001 From: rndrntwrk <180591682+rndrntwrk@users.noreply.github.com> Date: Fri, 1 May 2026 00:02:15 -0500 Subject: [PATCH] fix(amm): settle lvr markets from duel oracle --- .../contracts/lvr_amm/LvrMarket.sol | 10 ++-- packages/evm-contracts/test/LvrMarket.t.sol | 55 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/packages/evm-contracts/contracts/lvr_amm/LvrMarket.sol b/packages/evm-contracts/contracts/lvr_amm/LvrMarket.sol index a4c7f384..d04f295c 100644 --- a/packages/evm-contracts/contracts/lvr_amm/LvrMarket.sol +++ b/packages/evm-contracts/contracts/lvr_amm/LvrMarket.sol @@ -127,14 +127,14 @@ contract LvrMarket is ReentrancyGuard { function settleMarket() external isRouter nonReentrant { require(state == MarketState.PENDING, "Invalid Market State"); require(block.timestamp >= resolutionTimestamp, "Challenge Window Open"); - // Return bond to proposer with Reward collected through market fees - state = MarketState.RESOLVED; - IERC20(collateralToken).safeTransfer(proposer, bondValue); // Add fees and then reward the proposer - - emit MarketSettled(outcome, proposer, bondValue); + _settleFromOracle(); } function settleFromOracle() external isRouter nonReentrant { + _settleFromOracle(); + } + + function _settleFromOracle() private { require(state == MarketState.OPEN || state == MarketState.PENDING || state == MarketState.DISPUTED, "Invalid Market State"); // Capture proposer's outcome before overwriting (for bond slashing) diff --git a/packages/evm-contracts/test/LvrMarket.t.sol b/packages/evm-contracts/test/LvrMarket.t.sol index ec9a5efd..10d433a6 100644 --- a/packages/evm-contracts/test/LvrMarket.t.sol +++ b/packages/evm-contracts/test/LvrMarket.t.sol @@ -219,6 +219,61 @@ contract LvrMarketTest is Test { router.settleMarket(marketAddr); } + function test_ManualSettlementCannotResolveWithoutOracleResult() public { + bytes32 duelKey = keccak256("manual-settle-no-oracle-result"); + + vm.prank(admin); + router.create("Manual Oracle Gate", "Desc", "Src", duelKey, false, DURATION, 100 * 10**18); + address marketAddr = _lastMarketAddress(); + LvrMarket market = LvrMarket(marketAddr); + + vm.warp(block.timestamp + DURATION); + + uint256 bond = market.bondValue(); + vm.prank(user1); + mUSD.approve(address(router), bond); + vm.prank(user1); + router.proposerOutcome(marketAddr, 0); + + vm.warp(block.timestamp + 5 minutes + 1); + + vm.expectRevert(bytes("Oracle duel not resolved")); + router.settleMarket(marketAddr); + + (LvrMarket.MarketState currentState,,,,,,,) = market.getMarketDetails(); + assertEq(uint256(currentState), uint256(LvrMarket.MarketState.PENDING), "market should stay pending"); + } + + function test_SettleMarketUsesOracleOutcomeAfterManualProposal() public { + bytes32 duelKey = keccak256("manual-settle-oracle-authority"); + _setupOracleDuel(duelKey); + + vm.prank(admin); + router.create("Manual Oracle Override", "Desc", "Src", duelKey, false, DURATION, 100 * 10**18); + address marketAddr = _lastMarketAddress(); + LvrMarket market = LvrMarket(marketAddr); + + vm.warp(block.timestamp + DURATION); + + uint256 bond = market.bondValue(); + vm.prank(user1); + mUSD.approve(address(router), bond); + vm.prank(user1); + router.proposerOutcome(marketAddr, 0); + + _proposeAndFinalizeOracle(duelKey, DuelOutcomeOracle.Side.B); + + uint256 treasuryBefore = mUSD.balanceOf(treasury); + uint256 proposerBefore = mUSD.balanceOf(user1); + router.settleMarket(marketAddr); + + (LvrMarket.MarketState currentState,, uint256 mktOutcome,,,,,) = market.getMarketDetails(); + assertEq(uint256(currentState), uint256(LvrMarket.MarketState.RESOLVED)); + assertEq(mktOutcome, 1, "manual settlement must use the oracle outcome"); + assertEq(mUSD.balanceOf(treasury), treasuryBefore + bond, "wrong proposer bond should be slashed"); + assertEq(mUSD.balanceOf(user1), proposerBefore, "wrong proposer should not receive bond"); + } + function test_RedeemCollateralPaysWinningSide() public { bytes32 duelKey = keccak256("redeem-winning-side"); _setupOracleDuel(duelKey);