From e37f3ccc42470f849d86ee8e5ed49189bd513699 Mon Sep 17 00:00:00 2001 From: caydyan Date: Tue, 23 Jun 2026 00:45:26 +0800 Subject: [PATCH] Allow exact StackingDAO slippage bounds --- contracts/stableswap-stackingDAO.clar | 20 ++--- tests/stableswap-stackingDAO_test.ts | 114 +++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 13 deletions(-) diff --git a/contracts/stableswap-stackingDAO.clar b/contracts/stableswap-stackingDAO.clar index 2897f6b..3bc6625 100644 --- a/contracts/stableswap-stackingDAO.clar +++ b/contracts/stableswap-stackingDAO.clar @@ -373,8 +373,8 @@ ;; Assert that x-amount is less than x10 of current-balance-x (asserts! (< x-amount (* u10 current-balance-x)) (err "err-x-amount-too-high")) - ;; Assert that dy is greater than min-y-amount - (asserts! (> dy min-y-amount) (err "err-min-y-amount")) + ;; Assert that dy is greater than or equal to min-y-amount + (asserts! (>= dy min-y-amount) (err "err-min-y-amount")) ;; Transfer updated-x-amount tokens from tx-sender to this contract (if (> updated-x-amount u0) @@ -491,8 +491,8 @@ ;; Assert that y-amount is less than x10 of current-balance-y (asserts! (< y-amount (* u10 current-balance-y)) (err "err-y-amount-too-high")) - ;; Assert that dx is greater than min-x-amount - (asserts! (> dx min-x-amount) (err "err-min-x-amount")) + ;; Assert that dx is greater than or equal to min-x-amount + (asserts! (>= dx min-x-amount) (err "err-min-x-amount")) ;; Transfer y-amount tokens from tx-sender to this contract (if (> y-amount u0) @@ -633,8 +633,8 @@ ;; Assert that d2 is greater than d0 (asserts! (> d2 d0) (err "err-d2-less-than-d0")) - ;; Assert that derived mint amount is greater than min-lp-amount - (asserts! (> (/ (* current-total-shares (- d2 d0)) d0) min-lp-amount) (err "err-derived-amount-less-than-lp")) + ;; Assert that derived mint amount is greater than or equal to min-lp-amount + (asserts! (>= (/ (* current-total-shares (- d2 d0)) d0) min-lp-amount) (err "err-derived-amount-less-than-lp")) ;; ;; Transfer x-amount-added tokens from tx-sender to this contract (if (> x-amount-added-updated u0) @@ -707,11 +707,11 @@ (new-d (get-D new-balance-x-scaled new-balance-y-scaled current-amplification-coefficient)) ) - ;; Assert that withdrawal-balance-x is greater than min-x-amount - (asserts! (> withdrawal-balance-x min-x-amount) (err "err-withdrawal-balance-x-less-than-min-x-amount")) + ;; Assert that withdrawal-balance-x is greater than or equal to min-x-amount + (asserts! (>= withdrawal-balance-x min-x-amount) (err "err-withdrawal-balance-x-less-than-min-x-amount")) - ;; Assert that withdrawal-balance-y is greater than min-y-amount - (asserts! (> withdrawal-balance-y min-y-amount) (err "err-withdrawal-balance-y-less-than-min-y-amount")) + ;; Assert that withdrawal-balance-y is greater than or equal to min-y-amount + (asserts! (>= withdrawal-balance-y min-y-amount) (err "err-withdrawal-balance-y-less-than-min-y-amount")) ;; Burn LP tokens from tx-sender (unwrap! (contract-call? lp-token burn liquidity-remover lp-amount) (err "err-burning-lp-tokens")) diff --git a/tests/stableswap-stackingDAO_test.ts b/tests/stableswap-stackingDAO_test.ts index 4e75bb3..c6fdcef 100644 --- a/tests/stableswap-stackingDAO_test.ts +++ b/tests/stableswap-stackingDAO_test.ts @@ -104,6 +104,34 @@ Clarinet.test({ }, }); +// Test swap x for y exact minimum output +Clarinet.test({ + name: "Ensure swap x-token for y-token allows exact min-y amount", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + const wallet_1 = accounts.get("wallet_1")!; + + chain.mineBlock([ + Tx.contractCall("ststx-token", "mint", [types.uint(100000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("susdt-token", "mint", [types.uint(100000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(100), types.ascii("test"), types.uint(10000000000000), types.uint(10000000000000)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "swap-x-for-y", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000), types.uint(9995)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectUint(9995) + console.log(JSON.stringify(block.receipts)); + }, +}); + // Test swap x for y Clarinet.test({ name: "Ensure we can swap x-token for y-token", @@ -607,6 +635,34 @@ Clarinet.test({ }, }); +// Test swap y for x exact minimum output +Clarinet.test({ + name: "Ensure swap y-token for x-token allows exact min-x amount", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + const wallet_1 = accounts.get("wallet_1")!; + + chain.mineBlock([ + Tx.contractCall("ststx-token", "mint", [types.uint(100000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("susdt-token", "mint", [types.uint(100000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(100), types.ascii("test"), types.uint(10000000000000), types.uint(10000000000000)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "swap-y-for-x", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000), types.uint(9995)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectUint(9995) + console.log(JSON.stringify(block.receipts)); + }, +}); + // Test swap y for x Clarinet.test({ name: "Ensure we can swap y-token for x-token", @@ -1266,6 +1322,30 @@ Clarinet.test({ }, }); +// Test add liquidity exact minimum LP output +Clarinet.test({ + name: "Ensure add liquidity allows exact min-lp amount", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + const wallet_1 = accounts.get("wallet_1")!; + + chain.mineBlock([ + Tx.contractCall("ststx-token", "mint", [types.uint(500000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(100), types.ascii("test"), types.uint(100000000000000), types.uint(100000000000000)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "add-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000000), types.uint(0), types.uint(9998500)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectUint(9998500) + console.log(JSON.stringify(block.receipts)); + }, +}); + // Test add liquidity Clarinet.test({ name: "Ensure we can add liquidity of one token to a pool with different previous balance", @@ -1517,6 +1597,34 @@ Clarinet.test({ }, }); +// Test remove liquidity exact minimum outputs +Clarinet.test({ + name: "Ensure withdraw liquidity allows exact min token amounts", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + const wallet_1 = accounts.get("wallet_1")!; + + chain.mineBlock([ + Tx.contractCall("ststx-token", "mint", [types.uint(500000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(100), types.ascii("test"), types.uint(100000000000000), types.uint(100000000000000)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "add-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(100000000000000), types.uint(100000000000000), types.uint(0)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap-stackingDAO", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000000000000), types.uint(5000000000000), types.uint(5000000000000)], deployer.address) + ]); + + block.receipts[0].result.expectOk() + console.log(JSON.stringify(block.receipts)); + }, +}); + // Test remove liquidity Clarinet.test({ name: "Ensure we can NOT withdraw liquidity from a pool if not min x tokens met", @@ -1538,7 +1646,7 @@ Clarinet.test({ ]); const block = chain.mineBlock([ - Tx.contractCall("stableswap-stackingDAO", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000000000000), types.uint(5000000000000), types.uint(50000000000)], deployer.address) + Tx.contractCall("stableswap-stackingDAO", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000000000000), types.uint(5000000000001), types.uint(50000000000)], deployer.address) ]); block.receipts[0].result.expectErr() @@ -1567,7 +1675,7 @@ Clarinet.test({ ]); const block = chain.mineBlock([ - Tx.contractCall("stableswap-stackingDAO", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000000000000), types.uint(5000000000000), types.uint(50000000000)], deployer.address) + Tx.contractCall("stableswap-stackingDAO", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.ststx-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.stx-ststx-lp-token"), types.uint(10000000000000), types.uint(0), types.uint(5000000000001)], deployer.address) ]); block.receipts[0].result.expectErr() @@ -1606,4 +1714,4 @@ Clarinet.test({ block.receipts[0].result.expectErr() console.log(JSON.stringify(block.receipts)); }, -}); \ No newline at end of file +});