diff --git a/clarity/contracts/dlmm-swap-router-v-1-1.clar b/clarity/contracts/dlmm-swap-router-v-1-1.clar index 7bd0640..92a1923 100644 --- a/clarity/contracts/dlmm-swap-router-v-1-1.clar +++ b/clarity/contracts/dlmm-swap-router-v-1-1.clar @@ -62,12 +62,13 @@ (let ( (swap-result (try! (fold fold-swap-x-for-y-same-multi swaps (ok {x-token-trait: x-token-trait, y-token-trait: y-token-trait, results: (list ), x-amount-for-swap: amount, y-amount: u0, unfavorable: u0})))) (y-amount-total (get y-amount swap-result)) + (x-amount-spent (- amount (get x-amount-for-swap swap-result))) (unfavorable (get unfavorable swap-result)) ) (asserts! (> (len swaps) u0) ERR_EMPTY_SWAPS_LIST) (asserts! (<= unfavorable max-unfavorable-bins) ERR_BIN_SLIPPAGE) (asserts! (>= y-amount-total min-y-amount-total) ERR_MINIMUM_Y_AMOUNT) - (ok {results: (get results swap-result), y-amount: y-amount-total, unfavorable: unfavorable}) + (ok {results: (get results swap-result), x-amount-spent: x-amount-spent, y-amount: y-amount-total, unfavorable: unfavorable}) ) ) @@ -80,12 +81,13 @@ (let ( (swap-result (try! (fold fold-swap-y-for-x-same-multi swaps (ok {x-token-trait: x-token-trait, y-token-trait: y-token-trait, results: (list ), y-amount-for-swap: amount, x-amount: u0, unfavorable: u0})))) (x-amount-total (get x-amount swap-result)) + (y-amount-spent (- amount (get y-amount-for-swap swap-result))) (unfavorable (get unfavorable swap-result)) ) (asserts! (> (len swaps) u0) ERR_EMPTY_SWAPS_LIST) (asserts! (<= unfavorable max-unfavorable-bins) ERR_BIN_SLIPPAGE) (asserts! (>= x-amount-total min-x-amount-total) ERR_MINIMUM_X_AMOUNT) - (ok {results: (get results swap-result), x-amount: x-amount-total, unfavorable: unfavorable}) + (ok {results: (get results swap-result), y-amount-spent: y-amount-spent, x-amount: x-amount-total, unfavorable: unfavorable}) ) ) @@ -289,4 +291,4 @@ ;; Get absolute value of a signed int as uint (define-private (abs-int (value int)) (to-uint (if (>= value 0) value (- value))) -) \ No newline at end of file +) diff --git a/clarity/tests/clarigen-types.ts b/clarity/tests/clarigen-types.ts index 341b390..d4e448c 100644 --- a/clarity/tests/clarigen-types.ts +++ b/clarity/tests/clarigen-types.ts @@ -2747,7 +2747,7 @@ dlmmSwapRouterV11: { }[]; "unfavorable": bigint; }, bigint>>, - swapXForYSameMulti: {"name":"swap-x-for-y-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-y-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"y-amount","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ + swapXForYSameMulti: {"name":"swap-x-for-y-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-y-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"x-amount-spent","type":"uint128"},{"name":"y-amount","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ "expectedBinId": number | bigint; "minReceived": number | bigint; "poolTrait": string; @@ -2757,13 +2757,14 @@ dlmmSwapRouterV11: { "out": bigint; }[]; "unfavorable": bigint; + "xAmountSpent": bigint; "yAmount": bigint; }, bigint>>, swapXForYSimpleMulti: {"name":"swap-x-for-y-simple-multi","access":"public","args":[{"name":"pool-trait","type":"trait_reference"},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"x-amount","type":"uint128"},{"name":"min-dy","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[poolTrait: TypedAbiArg, xTokenTrait: TypedAbiArg, yTokenTrait: TypedAbiArg, xAmount: TypedAbiArg, minDy: TypedAbiArg], Response<{ "in": bigint; "out": bigint; }, bigint>>, - swapYForXSameMulti: {"name":"swap-y-for-x-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-x-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"x-amount","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ + swapYForXSameMulti: {"name":"swap-y-for-x-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-x-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"x-amount","type":"uint128"},{"name":"y-amount-spent","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ "expectedBinId": number | bigint; "minReceived": number | bigint; "poolTrait": string; @@ -2774,6 +2775,7 @@ dlmmSwapRouterV11: { }[]; "unfavorable": bigint; "xAmount": bigint; + "yAmountSpent": bigint; }, bigint>>, swapYForXSimpleMulti: {"name":"swap-y-for-x-simple-multi","access":"public","args":[{"name":"pool-trait","type":"trait_reference"},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"y-amount","type":"uint128"},{"name":"min-dx","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[poolTrait: TypedAbiArg, xTokenTrait: TypedAbiArg, yTokenTrait: TypedAbiArg, yAmount: TypedAbiArg, minDx: TypedAbiArg], Response<{ "in": bigint; @@ -4166,4 +4168,3 @@ export const project = { contracts, deployments, } as const; - \ No newline at end of file diff --git a/clarity/tests/helpers/clarigen-types.ts b/clarity/tests/helpers/clarigen-types.ts index 341b390..d4e448c 100644 --- a/clarity/tests/helpers/clarigen-types.ts +++ b/clarity/tests/helpers/clarigen-types.ts @@ -2747,7 +2747,7 @@ dlmmSwapRouterV11: { }[]; "unfavorable": bigint; }, bigint>>, - swapXForYSameMulti: {"name":"swap-x-for-y-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-y-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"y-amount","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ + swapXForYSameMulti: {"name":"swap-x-for-y-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-y-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"x-amount-spent","type":"uint128"},{"name":"y-amount","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ "expectedBinId": number | bigint; "minReceived": number | bigint; "poolTrait": string; @@ -2757,13 +2757,14 @@ dlmmSwapRouterV11: { "out": bigint; }[]; "unfavorable": bigint; + "xAmountSpent": bigint; "yAmount": bigint; }, bigint>>, swapXForYSimpleMulti: {"name":"swap-x-for-y-simple-multi","access":"public","args":[{"name":"pool-trait","type":"trait_reference"},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"x-amount","type":"uint128"},{"name":"min-dy","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[poolTrait: TypedAbiArg, xTokenTrait: TypedAbiArg, yTokenTrait: TypedAbiArg, xAmount: TypedAbiArg, minDy: TypedAbiArg], Response<{ "in": bigint; "out": bigint; }, bigint>>, - swapYForXSameMulti: {"name":"swap-y-for-x-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-x-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"x-amount","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ + swapYForXSameMulti: {"name":"swap-y-for-x-same-multi","access":"public","args":[{"name":"swaps","type":{"list":{"type":{"tuple":[{"name":"expected-bin-id","type":"int128"},{"name":"min-received","type":"uint128"},{"name":"pool-trait","type":"trait_reference"}]},"length":350}}},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"amount","type":"uint128"},{"name":"min-x-amount-total","type":"uint128"},{"name":"max-unfavorable-bins","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"results","type":{"list":{"type":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"length":350}}},{"name":"unfavorable","type":"uint128"},{"name":"x-amount","type":"uint128"},{"name":"y-amount-spent","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[swaps: TypedAbiArg<{ "expectedBinId": number | bigint; "minReceived": number | bigint; "poolTrait": string; @@ -2774,6 +2775,7 @@ dlmmSwapRouterV11: { }[]; "unfavorable": bigint; "xAmount": bigint; + "yAmountSpent": bigint; }, bigint>>, swapYForXSimpleMulti: {"name":"swap-y-for-x-simple-multi","access":"public","args":[{"name":"pool-trait","type":"trait_reference"},{"name":"x-token-trait","type":"trait_reference"},{"name":"y-token-trait","type":"trait_reference"},{"name":"y-amount","type":"uint128"},{"name":"min-dx","type":"uint128"}],"outputs":{"type":{"response":{"ok":{"tuple":[{"name":"in","type":"uint128"},{"name":"out","type":"uint128"}]},"error":"uint128"}}}} as TypedAbiFunction<[poolTrait: TypedAbiArg, xTokenTrait: TypedAbiArg, yTokenTrait: TypedAbiArg, yAmount: TypedAbiArg, minDx: TypedAbiArg], Response<{ "in": bigint; @@ -4166,4 +4168,3 @@ export const project = { contracts, deployments, } as const; - \ No newline at end of file diff --git a/clarity/tests/routers/swap-router.test.ts b/clarity/tests/routers/swap-router.test.ts index b145767..69ca059 100644 --- a/clarity/tests/routers/swap-router.test.ts +++ b/clarity/tests/routers/swap-router.test.ts @@ -218,6 +218,74 @@ describe('DLMM Swap Helper Functions', () => { expect(received).toBeGreaterThan(0n); }); + it('should report aggregate spent amount for X-for-Y same-token multi swaps', async () => { + const swaps = [{ + poolTrait: sbtcUsdcPool.identifier, + expectedBinId: 0, + minReceived: 1n + }]; + const amount = 1000000n; // 0.01 BTC + const maxUnfavorableBins = 5n; + + const initialXBalance = rovOk(mockSbtcToken.getBalance(alice)); + const initialYBalance = rovOk(mockUsdcToken.getBalance(alice)); + + const response = txOk(dlmmSwapRouter.swapXForYSameMulti( + swaps, + mockSbtcToken.identifier, + mockUsdcToken.identifier, + amount, + 1n, + maxUnfavorableBins + ), alice); + + const finalXBalance = rovOk(mockSbtcToken.getBalance(alice)); + const finalYBalance = rovOk(mockUsdcToken.getBalance(alice)); + const result = cvToValue(response.result); + const spent = result.results.reduce((sum: bigint, r: {in: bigint, out: bigint}) => sum + r.in, 0n); + const received = result.results.reduce((sum: bigint, r: {in: bigint, out: bigint}) => sum + r.out, 0n); + + expect(result.xAmountSpent).toBe(spent); + expect(result.yAmount).toBe(received); + expect(finalXBalance).toBe(initialXBalance - result.xAmountSpent); + expect(finalYBalance).toBe(initialYBalance + result.yAmount); + expect(result.xAmountSpent).toBeGreaterThan(0n); + }); + + it('should report aggregate spent amount for Y-for-X same-token multi swaps', async () => { + const swaps = [{ + poolTrait: sbtcUsdcPool.identifier, + expectedBinId: 0, + minReceived: 1n + }]; + const amount = 50000000n; // 50 USDC + const maxUnfavorableBins = 5n; + + const initialXBalance = rovOk(mockSbtcToken.getBalance(alice)); + const initialYBalance = rovOk(mockUsdcToken.getBalance(alice)); + + const response = txOk(dlmmSwapRouter.swapYForXSameMulti( + swaps, + mockSbtcToken.identifier, + mockUsdcToken.identifier, + amount, + 1n, + maxUnfavorableBins + ), alice); + + const finalXBalance = rovOk(mockSbtcToken.getBalance(alice)); + const finalYBalance = rovOk(mockUsdcToken.getBalance(alice)); + const result = cvToValue(response.result); + const spent = result.results.reduce((sum: bigint, r: {in: bigint, out: bigint}) => sum + r.in, 0n); + const received = result.results.reduce((sum: bigint, r: {in: bigint, out: bigint}) => sum + r.out, 0n); + + expect(result.yAmountSpent).toBe(spent); + expect(result.xAmount).toBe(received); + expect(finalYBalance).toBe(initialYBalance - result.yAmountSpent); + expect(finalXBalance).toBe(initialXBalance + result.xAmount); + expect(result.yAmountSpent).toBeGreaterThan(0n); + }); + it('should fail when minimum received not met', async () => { const swaps = [{ poolTrait: sbtcUsdcPool.identifier, @@ -544,4 +612,4 @@ describe('DLMM Swap Helper Functions', () => { expect(cvToValue(response.result)).toBe(errors.dlmmCore.ERR_INVALID_Y_TOKEN); }); }); -}); \ No newline at end of file +});