@@ -4,6 +4,7 @@ pragma solidity =0.8.25;
44
55import {Test, Vm} from "forge-std-1.17.0/src/Test.sol " ;
66import {NegativeBounty, ClearZeroAmount} from "src/error/ErrRaindexV6.sol " ;
7+ import {ExponentUnderflow} from "rain-math-float-0.3.10/src/error/ErrDecimalFloat.sol " ;
78import {
89 OrderV4,
910 OrderConfigV4,
@@ -23,6 +24,19 @@ import {MockToken} from "test/util/concrete/MockToken.sol";
2324import {LibOrder} from "src/lib/LibOrder.sol " ;
2425import {CalcInterpreter} from "test/util/concrete/CalcInterpreter.sol " ;
2526import {IRaindexV6ClearDriverHarness} from "test/util/interface/IRaindexV6ClearDriverHarness.sol " ;
27+ import {IParserV2} from "rainlang-interface-0.2.13/src/interface/IParserV2.sol " ;
28+ import {FullyQualifiedNamespace} from "rainlang-interface-0.2.13/src/interface/IInterpreterStoreV3.sol " ;
29+ import {StateNamespace} from "rainlang-interface-0.2.13/src/interface/IInterpreterV4.sol " ;
30+ import {LibNamespace} from "rainlang-interface-0.2.13/src/lib/ns/LibNamespace.sol " ;
31+ import {
32+ LibTestInterpreterDeploy,
33+ TEST_INTERPRETER_ADDRESS,
34+ TEST_STORE_ADDRESS,
35+ TEST_EXPRESSION_DEPLOYER_ADDRESS
36+ } from "rainlang-0.2.19/test/lib/deploy/LibTestInterpreterDeploy.sol " ;
37+ import {
38+ CREATION_CODE as DECIMAL_FLOAT_CREATION_CODE
39+ } from "rain-math-float-deploy-0.1.7/src/generated/0_1_5/DecimalFloat.sol " ;
2640
2741/// @title RaindexV6ClearFreshDriverTest
2842/// @notice Drives the FULL `clear3` against fresh-compiled RaindexV6 bytecode so
@@ -287,30 +301,128 @@ contract RaindexV6ClearFreshDriverTest is Test {
287301 // ClearZeroAmount
288302 // ---------------------------------------------------------------------
289303
290- /// Both orders evaluate to a zero max output -> both outputs are zero, so
291- /// clear3 reverts ClearZeroAmount (after handle IO). Pins B12.
292- function testFreshClearZeroAmountReverts () external {
293- (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) = wireOrders (f (1 , 0 ), f (0 , 0 ), f (1 , 0 ), f (0 , 0 ), f (10 , 0 ));
304+ function expectClearZeroAmount (Float aliceRatio , Float aliceMax , Float bobRatio , Float bobMax ) internal {
305+ (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) =
306+ wireOrders (aliceRatio, aliceMax, bobRatio, bobMax, f (10 , 0 ));
294307
295308 vm.prank (bountyBot);
296309 vm.expectRevert (ClearZeroAmount.selector );
297310 raindex.clear3 (aliceOrder, bobOrder, clearConfig (), new SignedContextV1 [](0 ), new SignedContextV1 [](0 ));
298311 }
299312
300- /// Only ONE side zero is NOT a zero clear: alice max 1, bob max 0. bobOutput
301- /// is 0 but aliceOutput is 1, so the AND guard (both zero) does NOT trip and
302- /// the clear succeeds. Pins that ClearZeroAmount requires BOTH outputs zero,
303- /// not either.
304- function testFreshClearOneSideZeroSucceeds () external {
305- // Alice ratio 0 so aliceInput=0 (bobBounty = bobOutput - aliceInput stays
306- // >= 0); bob max 0 so bobOutput=0, bobInput=0.
307- (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) = wireOrders (f (0 , 0 ), f (1 , 0 ), f (1 , 0 ), f (0 , 0 ), f (10 , 0 ));
313+ function testFreshClearBothZeroOutputReverts () external {
314+ expectClearZeroAmount (f (1 , 0 ), f (0 , 0 ), f (1 , 0 ), f (0 , 0 ));
315+ }
316+
317+ function testFreshClearAliceZeroOutputReverts () external {
318+ expectClearZeroAmount (f (1 , 0 ), f (0 , 0 ), f (0 , 0 ), f (1 , 0 ));
319+ }
320+
321+ function testFreshClearBobZeroOutputReverts () external {
322+ expectClearZeroAmount (f (0 , 0 ), f (1 , 0 ), f (1 , 0 ), f (0 , 0 ));
323+ }
324+
325+ function testFreshClearAliceNegativeOutputReverts () external {
326+ expectClearZeroAmount (f (1 , 0 ), f (- 1 , 0 ), f (1 , 0 ), f (1 , 0 ));
327+ }
328+
329+ function testFreshClearBobNegativeOutputReverts () external {
330+ expectClearZeroAmount (f (1 , 0 ), f (1 , 0 ), f (1 , 0 ), f (- 1 , 0 ));
331+ }
332+
333+ function testFreshClearAliceNegativeRatioReverts () external {
334+ expectClearZeroAmount (f (- 1 , 0 ), f (1 , 0 ), f (1 , 0 ), f (1 , 0 ));
335+ }
336+
337+ function testFreshClearBobNegativeRatioReverts () external {
338+ expectClearZeroAmount (f (1 , 0 ), f (1 , 0 ), f (- 1 , 0 ), f (1 , 0 ));
339+ }
340+
341+ /// Alice's calculate sets a store key while her output is zero against a
342+ /// zero-ratio Bob; the refused clear leaves her store untouched.
343+ function testFreshClearAliceZeroOutputLeavesStoreUnchanged () external {
344+ LibRainDeploy.deployZoltu (DECIMAL_FLOAT_CREATION_CODE);
345+ LibTestInterpreterDeploy.etchTestRainlang (vm);
346+ IParserV2 parser = IParserV2 (TEST_EXPRESSION_DEPLOYER_ADDRESS);
347+ IInterpreterStoreV3 store = IInterpreterStoreV3 (TEST_STORE_ADDRESS);
348+
349+ (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) = wireOrders (f (1 , 0 ), f (1 , 0 ), f (1 , 0 ), f (1 , 0 ), f (10 , 0 ));
350+ aliceOrder.evaluable = EvaluableV4 ({
351+ interpreter: IInterpreterV4 (TEST_INTERPRETER_ADDRESS),
352+ store: store,
353+ bytecode: parser.parse2 (":set(1 2),_ _:0 1;:; " )
354+ });
355+ bobOrder.evaluable = EvaluableV4 ({
356+ interpreter: IInterpreterV4 (TEST_INTERPRETER_ADDRESS), store: store, bytecode: parser.parse2 ("_ _:1 0;:; " )
357+ });
358+ raindex.setOrderLive (aliceOrder.hash ());
359+ raindex.setOrderLive (bobOrder.hash ());
360+
361+ FullyQualifiedNamespace aliceNamespace =
362+ LibNamespace.qualifyNamespace (StateNamespace.wrap (uint256 (uint160 (alice))), address (raindex));
363+
364+ vm.prank (bountyBot);
365+ vm.expectRevert (ClearZeroAmount.selector );
366+ raindex.clear3 (aliceOrder, bobOrder, clearConfig (), new SignedContextV1 [](0 ), new SignedContextV1 [](0 ));
367+
368+ assertEq (store.get (aliceNamespace, bytes32 (uint256 (1 ))), bytes32 (0 ), "alice store unchanged " );
369+ }
370+
371+ /// A zero ratio against a positive counterparty clears: the zero-ratio side
372+ /// gives its output for nothing.
373+ function testFreshClearAliceZeroRatioClears () external {
374+ (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) = wireOrders (f (0 , 0 ), f (1 , 0 ), f (1 , 0 ), f (1 , 0 ), f (10 , 0 ));
308375
309376 doClear (aliceOrder, bobOrder);
310377
311- // Alice output moved 1 out; bob output stayed.
312378 assertTrue (raindex.vaultBalance2 (alice, address (token0), ALICE_OUT_VAULT).eq (f (9 , 0 )), "alice out 9 " );
313- assertTrue (raindex.vaultBalance2 (bob, address (token1), BOB_OUT_VAULT).eq (f (10 , 0 )), "bob out untouched " );
379+ assertTrue (raindex.vaultBalance2 (bob, address (token1), BOB_OUT_VAULT).eq (f (9 , 0 )), "bob out 9 " );
380+ assertTrue (raindex.vaultBalance2 (alice, address (token1), ALICE_IN_VAULT).isZero (), "alice in 0 " );
381+ assertTrue (raindex.vaultBalance2 (bob, address (token0), BOB_IN_VAULT).eq (f (1 , 0 )), "bob in 1 " );
382+ assertTrue (raindex.vaultBalance2 (bountyBot, address (token1), BOB_BOUNTY_VAULT).eq (f (1 , 0 )), "bob bounty 1 " );
383+ }
384+
385+ function testFreshClearBobZeroRatioClears () external {
386+ (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) = wireOrders (f (1 , 0 ), f (1 , 0 ), f (0 , 0 ), f (1 , 0 ), f (10 , 0 ));
387+
388+ doClear (aliceOrder, bobOrder);
389+
390+ assertTrue (raindex.vaultBalance2 (alice, address (token0), ALICE_OUT_VAULT).eq (f (9 , 0 )), "alice out 9 " );
391+ assertTrue (raindex.vaultBalance2 (bob, address (token1), BOB_OUT_VAULT).eq (f (9 , 0 )), "bob out 9 " );
392+ assertTrue (raindex.vaultBalance2 (alice, address (token1), ALICE_IN_VAULT).eq (f (1 , 0 )), "alice in 1 " );
393+ assertTrue (raindex.vaultBalance2 (bob, address (token0), BOB_IN_VAULT).isZero (), "bob in 0 " );
394+ assertTrue (raindex.vaultBalance2 (bountyBot, address (token0), ALICE_BOUNTY_VAULT).eq (f (1 , 0 )), "alice bounty 1 " );
395+ }
396+
397+ /// A zero ratio caps against a negative counterparty max, which would
398+ /// divide by that zero ratio if the sides were not refused first.
399+ function testFreshClearAliceZeroRatioBobNegativeOutputReverts () external {
400+ expectClearZeroAmount (f (0 , 0 ), f (1 , 0 ), f (1 , 0 ), f (- 1 , 0 ));
401+ }
402+
403+ function testFreshClearBobZeroRatioAliceNegativeOutputReverts () external {
404+ expectClearZeroAmount (f (1 , 0 ), f (- 1 , 0 ), f (0 , 0 ), f (1 , 0 ));
405+ }
406+
407+ /// Both sides pass the refusal but the capped output, the smallest max
408+ /// over the largest ratio, is below every non-zero Float: the division
409+ /// reverts rather than yielding a zero output.
410+ function testFreshClearAliceCappedOutputUnderflowReverts () external {
411+ (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) =
412+ wireOrders (f (1 , type (int32 ).max), f (1 , 0 ), f (1 , 0 ), f (1 , type (int32 ).min), f (10 , 0 ));
413+
414+ vm.prank (bountyBot);
415+ vm.expectPartialRevert (ExponentUnderflow.selector );
416+ raindex.clear3 (aliceOrder, bobOrder, clearConfig (), new SignedContextV1 [](0 ), new SignedContextV1 [](0 ));
417+ }
418+
419+ function testFreshClearBobCappedOutputUnderflowReverts () external {
420+ (OrderV4 memory aliceOrder , OrderV4 memory bobOrder ) =
421+ wireOrders (f (1 , 0 ), f (1 , type (int32 ).min), f (1 , type (int32 ).max), f (1 , 0 ), f (10 , 0 ));
422+
423+ vm.prank (bountyBot);
424+ vm.expectPartialRevert (ExponentUnderflow.selector );
425+ raindex.clear3 (aliceOrder, bobOrder, clearConfig (), new SignedContextV1 [](0 ), new SignedContextV1 [](0 ));
314426 }
315427
316428 // ---------------------------------------------------------------------
0 commit comments