@@ -49,13 +49,13 @@ access(all) fun setup() {
4949 Test .expect (err , Test .beNil ())
5050 Test .commitBlock ()
5151
52- err = Test .deployContract (
53- name : " DeFiActions" ,
54- path : " ../../contracts/interfaces/DeFiActions.cdc" ,
55- arguments : []
56- )
57- Test .expect (err , Test .beNil ())
58- Test .commitBlock ()
52+ // err = Test.deployContract(
53+ // name: "DeFiActions",
54+ // path: "../../contracts/interfaces/DeFiActions.cdc",
55+ // arguments: []
56+ // )
57+ // Test.expect(err, Test.beNil())
58+ // Test.commitBlock()
5959
6060 err = Test .deployContract (
6161 name : " SwapConnectors" ,
@@ -371,14 +371,27 @@ access(all) fun runSwapTests(
371371
372372/// Asserts swap dust/overshoot properties for each result row.
373373///
374- /// Verifies:
375- /// - vaultBalance == quoteOutAmount (caller gets exactly what was quoted)
376- /// - coaDustAfter >= coaDustBefore (overshoot/dust accumulates in COA)
374+ /// The swap transaction uses amountOutMin = desiredOut (NOT quoteIn.outAmount).
375+ /// This guarantees outUFix ≈ quoteIn.outAmount > desiredOut = amountOutMin for
376+ /// any amount where the quoter overshoots, forcing the trimming guard to fire.
377+ ///
378+ /// With the NEW guard (whole-quantum overshoot passes through):
379+ /// vaultBalance ≈ quoteOutAmount (whole-quantum overshoot passed to caller)
380+ /// COA accumulates only sub-quantum EVM wei (< 1 quantum = 0.00000001)
381+ ///
382+ /// With the OLD guard (cap at amountOutMin = desiredOut):
383+ /// vaultBalance = desiredOut < quoteOutAmount → assertion FAILS
384+ /// COA accumulates the entire quote overshoot (up to 87 quanta)
385+ ///
386+ /// Example: EVM overshoot = 0.0000001234, desiredOut = amountOutMin
387+ /// Whole quanta passed through to caller: 0.00000012 (12 × 0.00000001)
388+ /// Sub-quantum remainder stays in COA: 0.0000000034
377389///
378390access (all ) fun assertSwapDust (results : [[UFix64 ]]) {
379391 var testedCount = 0
380392 var skippedCount = 0
381- var totalOvershoot = 0.0
393+ var totalQuoteOvershoot = 0.0
394+ var totalVaultOvershoot = 0.0
382395 var totalDustInCOA = 0.0
383396
384397 for row in results {
@@ -400,10 +413,16 @@ access(all) fun assertSwapDust(results: [[UFix64]]) {
400413
401414 testedCount = testedCount + 1
402415
403- let overshoot = quoteOutAmount > = desiredOut
416+ let quoteOvershoot = quoteOutAmount > = desiredOut
404417 ? quoteOutAmount - desiredOut
405418 : 0.0
406- totalOvershoot = totalOvershoot + overshoot
419+ totalQuoteOvershoot = totalQuoteOvershoot + quoteOvershoot
420+
421+ // Whole-quantum overshoot received by caller (sub-quantum remainder stays in COA)
422+ let vaultOvershoot = vaultBalance > quoteOutAmount
423+ ? vaultBalance - quoteOutAmount
424+ : 0.0
425+ totalVaultOvershoot = totalVaultOvershoot + vaultOvershoot
407426
408427 let dustInCOA = coaDustAfter > = coaDustBefore
409428 ? coaDustAfter - coaDustBefore
@@ -412,18 +431,23 @@ access(all) fun assertSwapDust(results: [[UFix64]]) {
412431
413432 log (" ---" )
414433 log (" [SWAP] Desired: \( desiredOut .toString ()) , Quote: \( quoteOutAmount .toString ()) , Returned: \( vaultBalance .toString ()) " )
415- log (" Overshoot/Dust => quote vs desired: +\( overshoot .toString ()) , stayed in COA : +\( dustInCOA .toString ()) " )
416- log (" COA balance => \( coaDustBefore .toString ()) → \( coaDustAfter .toString ()) " )
434+ log (" Quote overshoot vs desired: +\( quoteOvershoot .toString ()) , whole-quantum overshoot to caller : +\( vaultOvershoot .toString ()) " )
435+ log (" COA balance => \( coaDustBefore .toString ()) → \( coaDustAfter .toString ()) (sub-quantum dust: + \( dustInCOA . toString () ) ) " )
417436
418- Test .assertEqual (quoteOutAmount , vaultBalance )
437+ // Caller must receive at least the quoted amount; whole-quantum overshoot passes through.
438+ Test .assert (
439+ vaultBalance > = quoteOutAmount ,
440+ message : " Vault balance \( vaultBalance .toString ()) < quoteOutAmount \( quoteOutAmount .toString ()) "
441+ )
419442
420443 Test .assert (coaDustAfter > = coaDustBefore ,
421- message : " COA output-token balance decreased — overshoot/dust should accumulate in COA" )
444+ message : " COA output-token balance decreased — sub-quantum remainder should accumulate in COA" )
422445 }
423446
424447 Test .assert (testedCount > 0 , message : " No test amounts could be swapped" )
425448 log (" === PASSED: \( testedCount .toString ()) swaps, \( skippedCount .toString ()) skipped ===" )
426- log (" === Total overshoot/dust that stayed in COA: \( totalDustInCOA .toString ()) ===" )
449+ log (" === Total whole-quantum overshoot passed to callers: \( totalVaultOvershoot .toString ()) ===" )
450+ log (" === Total sub-quantum dust accumulated in COA: \( totalDustInCOA .toString ()) ===" )
427451}
428452
429453// --- Swap tests ---------------------------------------------------------------
@@ -457,14 +481,20 @@ access(all) fun testSwapDustStaysInCOA() {
457481
458482/// Demonstrates the trimming guard in action on PYUSD → MOET swaps.
459483///
460- /// MOET is an 18-decimal ERC20, so `toCadenceOut` floors actual output to the
461- /// nearest 10^10 wei (1 UFix64 quantum). When the router produces even slightly
462- /// more output than the quoter predicted — because ceiled input crosses a
463- /// quantum boundary — the trimming guard caps the bridged amount at
464- /// `amountOutMin` and the excess stays in the COA.
484+ /// MOET is an 18-decimal ERC20, so `toCadenceOut` floors the actual EVM output
485+ /// to the nearest 10^10 wei (1 UFix64 quantum = 0.00000001). When the router
486+ /// produces more output than the quoter predicted — because ceiled input crosses
487+ /// a quantum boundary — the overshoot is handled as follows:
488+ ///
489+ /// Whole-quantum portion (N × 0.00000001): passed through to caller
490+ /// Sub-quantum EVM remainder (< 0.00000001): stays in COA
491+ ///
492+ /// Example: EVM overshoot = 0.0000001234
493+ /// Caller receives quoteOutAmount + 0.00000012 (12 whole quanta)
494+ /// COA retains 0.0000000034 (sub-quantum remainder)
465495///
466496/// Many fractional amounts are tested to maximise the chance of hitting
467- /// quantum-boundary crossings that produce observable dust .
497+ /// quantum-boundary crossings that produce observable overshoot .
468498///
469499access (all ) fun testSwapOvershootStaysInCOA () {
470500 let signer = Test .getAccount (0x47f544294e3b7656 )
@@ -475,22 +505,22 @@ access(all) fun testSwapOvershootStaysInCOA() {
475505
476506 let testAmounts = [
477507 0.00987654 ,
478- 0.01000000 ,
508+ 0.01000000 , // +69 quanta quote overshoot (highest observed in quote test)
479509 0.03456789 ,
480510 0.05432109 ,
481511 0.10000000 ,
482- 0.12345678 , // dust hit in first run
512+ 0.12345678 , // +44 quanta quote overshoot — confirmed COA dust accumulation
483513 0.20000000 ,
484- 0.23456789 , // dust hit
514+ 0.23456789 , // +8 quanta quote overshoot — confirmed COA dust accumulation
485515 0.34567890 ,
486516 0.45019707 ,
487517 0.56789012 ,
488518 0.67890123 ,
489- 0.78901234 , // dust hit
490- 1.00000000 , // dust hit (even with 0 quote overshoot)
519+ 0.78901234 , // confirmed COA dust accumulation
520+ 1.00000000 , // confirmed COA dust accumulation (swap-level overshoot, 0 quote overshoot)
491521 1.23456789 ,
492522 1.50000000 ,
493- 2.34567890 , // dust hit
523+ 2.34567890 , // confirmed COA dust accumulation
494524 3.45678901 ,
495525 5.00000000
496526 ]
@@ -504,5 +534,29 @@ access(all) fun testSwapOvershootStaysInCOA() {
504534 )
505535
506536 assertSwapDust (results : results )
537+
538+ // Spot-check: 0.01000000 (+69 quanta quote overshoot) and 0.12345678 (+44 quanta)
539+ // are confirmed cases where the quoter overshoots the desired output.
540+ // Because the swap uses amountOutMin = desiredOut, the trimming guard fires and
541+ // the full quote overshoot (in whole quanta) must reach the caller's vault.
542+ // With the OLD guard (cap at amountOutMin = desiredOut):
543+ // vaultBalance = desiredOut < quoteOutAmount → FAILS
544+ // With the NEW guard (whole-quantum overshoot passes through):
545+ // vaultBalance ≈ quoteOutAmount, coaDelta < 1 quantum → PASSES
546+ for row in results {
547+ let desiredOut = row [0 ]
548+ let quoteOutAmount = row [2 ]
549+ let vaultBalance = row [3 ]
550+ let coaDustBefore = row [4 ]
551+ let coaDustAfter = row [5 ]
552+ // With the new guard, whole-quantum overshoot reaches the caller.
553+ // vaultBalance must be at least quoteOutAmount (= desiredOut + quote_overshoot).
554+ Test .assert (vaultBalance > = quoteOutAmount ,
555+ message : " [\( desiredOut .toString ()) ] vaultBalance \( vaultBalance .toString ()) < quoteOutAmount \( quoteOutAmount .toString ()) — old cap-at-minimum guard still in effect" )
556+ // Sub-quantum EVM remainder must be the only thing left in COA.
557+ let coaDelta = coaDustAfter > coaDustBefore ? coaDustAfter - coaDustBefore : 0.0
558+ Test .assert (coaDelta < = 0.00000001 ,
559+ message : " [\( desiredOut .toString ()) ] COA accumulated \( coaDelta .toString ()) MOET — whole-quantum overshoot should have been passed to caller, not left in COA" )
560+ }
507561}
508562
0 commit comments