diff --git a/contracts/stableswap.clar b/contracts/stableswap.clar index 5f65c51..062e937 100644 --- a/contracts/stableswap.clar +++ b/contracts/stableswap.clar @@ -355,8 +355,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 at least min-y-amount + (asserts! (>= dy min-y-amount) (err "err-min-y-amount")) ;; Transfer updated-x-balance tokens from tx-sender to this contract (if (> updated-x-amount u0) @@ -459,8 +459,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 at least min-x-amount + (asserts! (>= dx min-x-amount) (err "err-min-x-amount")) ;; Transfer updated-y-balance tokens from tx-sender to this contract (if (> updated-y-amount u0) @@ -596,8 +596,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 at least 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) @@ -670,11 +670,14 @@ (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 lp-amount is greater than zero + (asserts! (> lp-amount u0) (err "err-lp-amount-zero")) - ;; 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-x is at least 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 at least 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")) @@ -1032,4 +1035,3 @@ (ok staking-contract) ) ) - diff --git a/tests/stableswap_test.ts b/tests/stableswap_test.ts index 3a7b055..367d731 100644 --- a/tests/stableswap_test.ts +++ b/tests/stableswap_test.ts @@ -133,6 +133,32 @@ Clarinet.test({ }, }); +// Test swap x for y accepts an exact min-y bound +Clarinet.test({ + name: "Ensure standard swap x-token for y-token accepts exact min-y amount", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + + chain.mineBlock([ + Tx.contractCall("usda-token", "mint", [types.uint(100000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("susdt-token", "mint", [types.uint(10000000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(100), types.ascii("test"), types.uint(1000000000000000), types.uint(10000000000000)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap", "swap-x-for-y", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000), types.uint(99949981)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectUint(99949981) + }, +}); + // Test swap x for y Clarinet.test({ name: "Ensure we can swap x-token for y-token get pair data back", @@ -610,6 +636,32 @@ Clarinet.test({ }, }); +// Test swap y for x accepts an exact min-x bound +Clarinet.test({ + name: "Ensure standard swap y-token for x-token accepts exact min-x amount", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + + chain.mineBlock([ + Tx.contractCall("usda-token", "mint", [types.uint(100000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("susdt-token", "mint", [types.uint(10000000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(100), types.ascii("test"), types.uint(1000000000000000), types.uint(10000000000000)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap", "swap-y-for-x", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(100000000), types.uint(9994998022)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectUint(9994998022) + }, +}); + // Test swap y for x Clarinet.test({ name: "Ensure we can swap y-token for x-token get pair data back", @@ -1274,6 +1326,32 @@ Clarinet.test({ }, }); +// Test add liquidity accepts an exact min-lp bound +Clarinet.test({ + name: "Ensure standard add liquidity accepts exact min-lp amount", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + + chain.mineBlock([ + Tx.contractCall("usda-token", "mint", [types.uint(500000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("susdt-token", "mint", [types.uint(50000000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(100), types.ascii("test"), types.uint(10000000000000000), types.uint(100000000000000)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap", "add-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(100000000), types.uint(1000000), types.uint(200000000)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectUint(200000000) + }, +}); + // Test add liquidity Clarinet.test({ name: "Ensure we can add liquidity of one token to a pool with same previous balance small amount", @@ -1577,6 +1655,36 @@ Clarinet.test({ }, }); +// Test remove liquidity accepts exact min token bounds +Clarinet.test({ + name: "Ensure standard withdraw liquidity accepts exact min token amounts", + async fn(chain: Chain, accounts: Map) { + const deployer = accounts.get("deployer")!; + + chain.mineBlock([ + Tx.contractCall("usda-token", "mint", [types.uint(500000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("susdt-token", "mint", [types.uint(50000000000000000), types.principal(deployer.address)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap", "create-pair", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(100), types.ascii("test"), types.uint(10000000000000000), types.uint(100000000000000)], deployer.address) + ]); + + chain.mineBlock([ + Tx.contractCall("stableswap", "add-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000000000), types.uint(100000000000000), types.uint(0)], deployer.address) + ]); + + const block = chain.mineBlock([ + Tx.contractCall("stableswap", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000000), types.uint(5000000000000), types.uint(50000000000)], deployer.address) + ]); + + block.receipts[0].result.expectOk().expectTuple() + }, +}); + // Test remove liquidity Clarinet.test({ name: "Ensure we can NOT withdraw liquidity from a pool if not min x tokens met", @@ -1601,7 +1709,7 @@ Clarinet.test({ ]); const block = chain.mineBlock([ - Tx.contractCall("stableswap", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000000), types.uint(5000000000000), types.uint(50000000000)], deployer.address) + Tx.contractCall("stableswap", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000000), types.uint(5000000000001), types.uint(0)], deployer.address) ]); block.receipts[0].result.expectErr() @@ -1633,7 +1741,7 @@ Clarinet.test({ ]); const block = chain.mineBlock([ - Tx.contractCall("stableswap", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000000), types.uint(5000000000000), types.uint(50000000000)], deployer.address) + Tx.contractCall("stableswap", "withdraw-liquidity", [types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.susdt-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-token"), types.principal("ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.usda-susdt-lp-token"), types.uint(10000000000000), types.uint(0), types.uint(50000000001)], deployer.address) ]); block.receipts[0].result.expectErr() @@ -1675,4 +1783,4 @@ Clarinet.test({ block.receipts[0].result.expectErr() console.log(JSON.stringify(block.receipts)); }, -}); \ No newline at end of file +});