From fb877f636b6b614d48ebaacb08138ff49fd77a59 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 1 Jun 2021 12:33:20 +0300 Subject: [PATCH 01/63] divest tested --- README.md | 15 +- Token_token.test.md | 98 ++----- test/DivestTTLiquidity.spec.ts | 490 +++++++++++++++++++-------------- 3 files changed, 317 insertions(+), 286 deletions(-) diff --git a/README.md b/README.md index aaefc4ed..f36624ba 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,14 @@ The current implementation supports [FA1.2](https://gitlab.com/tzip/tzip/-/blob/ ![Architecture](Architecture.png) -The solution consists of 4 types of contracts: +The solution consists of 6 types of contracts: -1. `Factory` - singleton used to deploy new exchange pair; +1. `Factory` - singleton used to deploy new TokenX-XTZ exchange pair; 2. `Dex` - contract for TokenX-XTZ pair exchanges; -3. `Token` - FA token implementation. -4. `MetadataStorage` - contract to store and upgrade the shares token metadata. +3. `TTDex` - contract for TokenX-TokenY pair exchanges; +4. `Token` - FA token implementation. +5. `BakerRegistry` - bakery registrar. +6. `MetadataStorage` - contract to store and upgrade the shares token metadata. # Project structure @@ -61,14 +63,13 @@ To compile and deploy contracts to Delphinet 1. Chose configure the version - `FA12` or `FA2` - by setting `EXCHANGE_TOKEN_STANDARD` in `.env` and run: ``` -yarn run migrate-delphinet +yarn migrate ``` For other networks: ``` -yarn run migrate # development -yarn run migrate --network NAME # other networks +yarn migrate --network NAME ``` # Usage diff --git a/Token_token.test.md b/Token_token.test.md index da0bb06e..f11e22fb 100644 --- a/Token_token.test.md +++ b/Token_token.test.md @@ -60,8 +60,8 @@ tokens_amount = shares_purchased * token_pool / total_supply **Scenario 1**: Test the investment -- [ ] without provided liquidity -- [ ] with provided liquidity +- [x] without provided liquidity +- [x] with provided liquidity **Scope**: Test various min shared. @@ -73,16 +73,15 @@ tokens_amount = shares_purchased * token_pool / total_supply **Scenario 1**: Test the investment with minimal shares of -- [ ] 0 -- [ ] 1 -- [ ] enough -- [ ] exact -- [ ] too many +- [x] 0 +- [x] 1 +- [x] enough +- [x] exact **Scenario 2**: Test purchased shares -- [ ] 0 -- [ ] > 0 +- [x] 0 +- [x] > 0 ## Test Item: DivestLiquidity Entrypoint @@ -112,8 +111,8 @@ tokens_divested = token_pool * burnt_shares / total_supply **Scenario 1**: Test the divestment -- [ ] without provided liquidity -- [ ] with provided liquidity +- [x] without provided liquidity +- [x] with provided liquidity **Scope**: Test various burnt shared. @@ -125,74 +124,27 @@ tokens_divested = token_pool * burnt_shares / total_supply **Scenario 1**: Test the divestment with burnt shares of -- [ ] 0 -- [ ] 1 -- [ ] enough -- [ ] exact -- [ ] too many +- [x] 0 +- [x] 1 +- [x] enough +- [x] exact +- [x] too many **Scenario 2**: Test calculated received amount -- [ ] Received tez are zero -- [ ] Reseived tokens are zero +- [x] Received token a are zero +- [x] Reseived token b are zero **Scenario 3**: Test expected amount when -- [ ] Expected tez are smaller -- [ ] Expected tokens are smaller -- [ ] Expected tez are exact -- [ ] Expected tokens are exact -- [ ] Expected tez are higher -- [ ] Expected tokens are higher -- [ ] Expected tez are 0 -- [ ] Expected tokens are 0 - -## Test Item: SetXFunctions Entrypoint - -### General Requirements: - -1. The function can be set only once. -2. Only functions with index between 0 and 8 can be set as exchange functions. -3. Only functions with index between 0 and 4 can be set as token functions. - -**Scope**: Test the all functions can be added. - -**Action**: Invoke the SetXFunctions entrypoint. - -**Test Notes and Preconditions**: Create new empty factory. - -**Verification Steps**: Verify the function can be set only once. - -**Scenario 1**: Test adding of all - -- [ ] exchange fuctions -- [ ] token functions - -**Scope**: Test the function replacement. - -**Action**: Invoke the SetXFunctions entrypoint. - -**Test Notes and Preconditions**: Create new empty factory. - -**Verification Steps**: Verify the function can be set only once. - -**Scenario 1**: Test the replacement of - -- [ ] exchange fuction -- [ ] token function - -**Scope**: Test the function count. - -**Action**: Invoke the SetXFunctions entrypoint. - -**Test Notes and Preconditions**: Create new empty factory. - -**Verification Steps**: Verify only 9 exchange and 4 token functions can be set. - -**Scenario 1**: Test the function setting - -- [ ] of 5th token function -- [ ] of 9th token function +- [x] Expected token a are smaller +- [x] Expected token b are smaller +- [x] Expected token a are exact +- [x] Expected token b are exact +- [x] Expected token a are higher +- [x] Expected token b are higher +- [x] Expected token a are 0 +- [x] Expected token b are 0 ## Test Item: TokenAToTokenB Entrypoint diff --git a/test/DivestTTLiquidity.spec.ts b/test/DivestTTLiquidity.spec.ts index e2f72589..3a949054 100644 --- a/test/DivestTTLiquidity.spec.ts +++ b/test/DivestTTLiquidity.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; -contract("DivestTTLiquidity()", function () { +contract.only("DivestTTLiquidity()", function () { let context: TTContext; const tokenAAmount: number = 1000; const tokenBAmount: number = 100000; @@ -68,9 +68,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -176,9 +175,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -268,9 +266,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -360,9 +357,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -428,205 +424,287 @@ contract("DivestTTLiquidity()", function () { }); }); - // describe("Test calculated received amount", () => { - // it("revert in case of calculated tez are zero", async function () { - // const initToken = 1000000; - // const initTez = 100; - // const share = 1; - // await context.pairs[0].initializeExchange(initToken, initTez); - // await context.pairs[0].tokenToTezPayment(100000, 1, bobAddress); - // await rejects(context.pairs[0].divestLiquidity(1, 1, share), (err) => { - // ok(err.message == "Dex/high-expectation", "Error message mismatch"); - // return true; - // }); - // }); + describe("Test calculated received amount", () => { + it("revert in case of calculated tez are zero", async function () { + const initTokenA = 100000; + const initTokenB = 100; + const share = 1; + await context.dex.initializeExchange( + tokenAAddress, + tokenBAddress, + initTokenA, + initTokenB + ); + await context.dex.tokenToTokenPayment( + tokenAAddress, + tokenBAddress, + "sell", + 2000, + 1, + bobAddress + ); + await rejects(context.dex.divestLiquidity("0", 1, 1, share), (err) => { + ok(err.message == "Dex/high-expectation", "Error message mismatch"); + return true; + }); + await context.dex.divestLiquidity("0", 1, 1, initTokenB); + }); - // it("revert in case of calculated tokens are zero", async function () { - // const initTez = 1000000; - // const initToken = 100; - // await context.pairs[0].divestLiquidity(1, 1, initToken); - // await context.pairs[0].initializeExchange(initToken, initTez); - // await context.pairs[0].tezToTokenPayment(1, 100000, bobAddress); - // const share = 1; - // await rejects(context.pairs[0].divestLiquidity(1, 1, share), (err) => { - // ok(err.message == "Dex/high-expectation", "Error message mismatch"); - // return true; - // }); - // }); - // }); + it("revert in case of calculated tokens are zero", async function () { + const initTokenA = 100; + const initTokenB = 100000; + const share = 1; + await context.dex.initializeExchange( + tokenAAddress, + tokenBAddress, + initTokenA, + initTokenB + ); + await context.dex.tokenToTokenPayment( + tokenAAddress, + tokenBAddress, + "buy", + 2000, + 1, + bobAddress + ); + await rejects(context.dex.divestLiquidity("0", 1, 1, share), (err) => { + ok(err.message == "Dex/high-expectation", "Error message mismatch"); + return true; + }); + await context.dex.divestLiquidity("0", 1, 1, initTokenA); + }); + }); - // describe("Test expected amount when", () => { - // before(async () => { - // const initTez = 1000000; - // const initToken = 100; - // await context.pairs[0].divestLiquidity(1, 1, initTez); - // await context.pairs[0].initializeExchange(initToken, initTez); - // }); + describe("Test expected amount", () => { + before(async () => { + const initTokenB = 100000; + const initTokenA = 100; + await context.dex.initializeExchange( + tokenAAddress, + tokenBAddress, + initTokenA, + initTokenB + ); + }); - // it("revert in case of expected tez are higher", async function () { - // const share = 100; - // await rejects( - // context.pairs[0].divestLiquidity(1, 100000000, share), - // (err) => { - // ok(err.message == "Dex/high-expectation", "Error message mismatch"); - // return true; - // } - // ); - // }); + it("revert in case of expected tez are higher", async function () { + const share = 100; + await rejects( + context.dex.divestLiquidity("0", 1, 100000000, share), + (err) => { + ok(err.message == "Dex/high-expectation", "Error message mismatch"); + return true; + } + ); + }); - // it("revert in case of expected tokens are higher", async function () { - // const share = 100; - // await rejects( - // context.pairs[0].divestLiquidity(100000000, 1, share), - // (err) => { - // ok(err.message == "Dex/high-expectation", "Error message mismatch"); - // return true; - // } - // ); - // }); + it("revert in case of expected tokens are higher", async function () { + const share = 100; + await rejects( + context.dex.divestLiquidity("0", 100000000, 1, share), + (err) => { + ok(err.message == "Dex/high-expectation", "Error message mismatch"); + return true; + } + ); + }); - // it("revert in case of expected tokens are 0", async function () { - // const share = 1; - // await rejects(context.pairs[0].divestLiquidity(0, 1, share), (err) => { - // ok(err.message == "Dex/dust-output", "Error message mismatch"); - // return true; - // }); - // }); + it("revert in case of expected tokens are 0", async function () { + const share = 1; + await rejects(context.dex.divestLiquidity("0", 0, 1, share), (err) => { + ok(err.message == "Dex/dust-output", "Error message mismatch"); + return true; + }); + }); - // it("revert in case of expected tez are 0", async function () { - // const share = 1; - // await rejects(context.pairs[0].divestLiquidity(1, 0, share), (err) => { - // ok(err.message == "Dex/dust-output", "Error message mismatch"); - // return true; - // }); - // }); + it("revert in case of expected tez are 0", async function () { + const share = 1; + await rejects(context.dex.divestLiquidity("0", 1, 0, share), (err) => { + ok(err.message == "Dex/dust-output", "Error message mismatch"); + return true; + }); + }); - // it("success in case the of the expected amount smaller than calculated", async function () { - // const expectedTez = 100000; - // const expectedToken = 10; - // const minBurntShares = 500000; - // const minReceivedTezAmount: number = 500000; - // const minReceivedTokenAmount: number = 50; - // await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - // await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - // const initialStorage = await context.pairs[0].storage; - // const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - // const aliceInitTokenBalance = ( - // (await context.tokens[0].storage.ledger[aliceAddress]) || - // defaultAccountInfo - // ).balance; - // await context.pairs[0].divestLiquidity( - // expectedToken, - // expectedTez, - // minBurntShares - // ); - // await context.tokens[0].updateStorage({ - // ledger: [aliceAddress, pairAddress], - // }); - // await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - // const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - // const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - // aliceAddress - // ].balance; - // const pairTokenBalance = await context.tokens[0].storage.ledger[ - // pairAddress - // ].balance; - // const pairTezBalance = await tezos.tz.getBalance(pairAddress); - // strictEqual( - // aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - // aliceFinalTokenBalance.toNumber() - // ); - // ok( - // aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - // aliceFinalTezBalance.toNumber() - // ); - // strictEqual( - // pairTokenBalance.toNumber(), - // initialStorage.token_pool.toNumber() - minReceivedTokenAmount - // ); - // strictEqual( - // pairTezBalance.toNumber(), - // initialStorage.tez_pool.toNumber() - minReceivedTezAmount - // ); - // strictEqual( - // context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - // initialStorage.ledger[aliceAddress].balance.toNumber() - minBurntShares - // ); - // strictEqual( - // context.pairs[0].storage.total_supply.toNumber(), - // initialStorage.total_supply.toNumber() - minBurntShares - // ); - // strictEqual( - // context.pairs[0].storage.tez_pool.toNumber(), - // initialStorage.tez_pool.toNumber() - minReceivedTezAmount - // ); - // strictEqual( - // context.pairs[0].storage.token_pool.toNumber(), - // initialStorage.token_pool.toNumber() - minReceivedTokenAmount - // ); - // }); + it("success in case the of the expected amount smaller than calculated", async function () { + const receivedTokenAAmount: number = 20; + const receivedTokenBAmount: number = 20000; + const burntShares: number = 20; + const pairAddress = context.dex.contract.address; + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); + const aliceInitTokenABalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const aliceInitTokenBBalance = ( + (await context.tokens[1].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const pairInitTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairInitTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const initDexPair = context.dex.storage.pairs[0]; + await context.dex.divestLiquidity("0", 10, 10, burntShares); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const finalDexPair = context.dex.storage.pairs[0]; + const aliceFinalTokenABalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const aliceFinalTokenBBalance = await context.tokens[1].storage.ledger[ + aliceAddress + ].balance; + const pairTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + strictEqual( + aliceInitTokenABalance.toNumber() + receivedTokenAAmount, + aliceFinalTokenABalance.toNumber() + ); + strictEqual( + aliceInitTokenBBalance.toNumber() + receivedTokenBAmount, + aliceFinalTokenBBalance.toNumber() + ); + strictEqual( + pairTokenABalance.toNumber(), + pairInitTokenABalance.minus(receivedTokenAAmount).toNumber() + ); + strictEqual( + pairTokenBBalance.toNumber(), + pairInitTokenBBalance.minus(receivedTokenBAmount).toNumber() + ); + strictEqual( + context.dex.storage.ledger[aliceAddress].balance.toNumber(), + pairInitTokenABalance.minus(receivedTokenAAmount).toNumber() + ); + strictEqual( + finalDexPair.token_a_pool.toNumber(), + pairInitTokenABalance.minus(receivedTokenAAmount).toNumber() + ); + strictEqual( + finalDexPair.token_b_pool.toNumber(), + pairInitTokenBBalance.minus(receivedTokenBAmount).toNumber() + ); + }); - // it("success in case the of the exact expected tez and tokens", async function () { - // const minBurntShares = 500000; - // const minReceivedTezAmount: number = 500000; - // const minReceivedTokenAmount: number = 50; - // await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - // await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - // const initialStorage = await context.pairs[0].storage; - // const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - // const aliceInitTokenBalance = ( - // (await context.tokens[0].storage.ledger[aliceAddress]) || - // defaultAccountInfo - // ).balance; - // await context.pairs[0].divestLiquidity( - // minReceivedTokenAmount, - // minReceivedTezAmount, - // minBurntShares - // ); - // await context.tokens[0].updateStorage({ - // ledger: [aliceAddress, pairAddress], - // }); - // await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - // const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - // const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - // aliceAddress - // ].balance; - // const pairTokenBalance = await context.tokens[0].storage.ledger[ - // pairAddress - // ].balance; - // const pairTezBalance = await tezos.tz.getBalance(pairAddress); - // strictEqual( - // aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - // aliceFinalTokenBalance.toNumber() - // ); - // ok( - // aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - // aliceFinalTezBalance.toNumber() - // ); - // strictEqual( - // pairTokenBalance.toNumber(), - // initialStorage.token_pool.toNumber() - minReceivedTokenAmount - // ); - // strictEqual( - // pairTezBalance.toNumber(), - // initialStorage.tez_pool.toNumber() - minReceivedTezAmount - // ); - // strictEqual( - // context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - // initialStorage.ledger[aliceAddress].balance.toNumber() - minBurntShares - // ); - // strictEqual( - // context.pairs[0].storage.total_supply.toNumber(), - // initialStorage.total_supply.toNumber() - minBurntShares - // ); - // strictEqual( - // context.pairs[0].storage.tez_pool.toNumber(), - // initialStorage.tez_pool.toNumber() - minReceivedTezAmount - // ); - // strictEqual( - // context.pairs[0].storage.token_pool.toNumber(), - // initialStorage.token_pool.toNumber() - minReceivedTokenAmount - // ); - // }); - // }); + it("success in case the of the exact expected tez and tokens", async function () { + const receivedTokenAAmount: number = 70; + const receivedTokenBAmount: number = 70000; + const burntShares: number = 70; + const pairAddress = context.dex.contract.address; + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); + const aliceInitTokenABalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const aliceInitTokenBBalance = ( + (await context.tokens[1].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const pairInitTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairInitTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const initDexPair = context.dex.storage.pairs[0]; + await context.dex.divestLiquidity( + "0", + receivedTokenAAmount, + receivedTokenBAmount, + burntShares + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const finalDexPair = context.dex.storage.pairs[0]; + const aliceFinalTokenABalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const aliceFinalTokenBBalance = await context.tokens[1].storage.ledger[ + aliceAddress + ].balance; + const pairTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + strictEqual( + aliceInitTokenABalance.toNumber() + receivedTokenAAmount, + aliceFinalTokenABalance.toNumber() + ); + strictEqual( + aliceInitTokenBBalance.toNumber() + receivedTokenBAmount, + aliceFinalTokenBBalance.toNumber() + ); + strictEqual( + pairTokenABalance.toNumber(), + pairInitTokenABalance.minus(receivedTokenAAmount).toNumber() + ); + strictEqual( + pairTokenBBalance.toNumber(), + pairInitTokenBBalance.minus(receivedTokenBAmount).toNumber() + ); + strictEqual( + context.dex.storage.ledger[aliceAddress].balance.toNumber(), + pairInitTokenABalance.minus(receivedTokenAAmount).toNumber() + ); + strictEqual( + finalDexPair.token_a_pool.toNumber(), + pairInitTokenABalance.minus(receivedTokenAAmount).toNumber() + ); + strictEqual( + finalDexPair.token_b_pool.toNumber(), + pairInitTokenBBalance.minus(receivedTokenBAmount).toNumber() + ); + }); + }); }); From e39120caafefdd7b664331be90ab697b4f7d5af2 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 1 Jun 2021 15:19:29 +0300 Subject: [PATCH 02/63] update achitectrure --- Architecture.png | Bin 20216 -> 43097 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Architecture.png b/Architecture.png index d36dc146282b9aa6257fe2b04b131edfb4f6a4c4..428708fd750bd8072b77dc449550bda38a7bd51b 100644 GIT binary patch literal 43097 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 O(|kwI9#n;$r+<#hVNByb}Eg-!8aY>)_5rns`&Q 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 Date: Tue, 1 Jun 2021 15:20:18 +0300 Subject: [PATCH 03/63] clarify error msg --- contracts/partials/TTMethodDex.ligo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index b8832419..bcec518b 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -188,7 +188,7 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this (* update XTZ pool *) pair.token_b_pool := abs(pair.token_b_pool - token_b_out); pair.token_a_pool := pair.token_a_pool + params.amount_in; - } else failwith("Dex/wrong-out"); + } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) @@ -235,7 +235,7 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this (* update XTZ pool *) pair.token_a_pool := abs(pair.token_a_pool - token_a_out); pair.token_b_pool := pair.token_b_pool + params.amount_in; - } else failwith("Dex/wrong-out"); + } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) operations := list[ From a77f439e734d5c6ee09ca45f96b5b15633ad4a0d Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 10:29:56 +0300 Subject: [PATCH 04/63] token to token one direction tested --- test/DivestTTLiquidity.spec.ts | 2 +- test/TokenAToTokenB.spec.ts | 241 +++++++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 test/TokenAToTokenB.spec.ts diff --git a/test/DivestTTLiquidity.spec.ts b/test/DivestTTLiquidity.spec.ts index 3a949054..45f39a8f 100644 --- a/test/DivestTTLiquidity.spec.ts +++ b/test/DivestTTLiquidity.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; -contract.only("DivestTTLiquidity()", function () { +contract("DivestTTLiquidity()", function () { let context: TTContext; const tokenAAmount: number = 1000; const tokenBAmount: number = 100000; diff --git a/test/TokenAToTokenB.spec.ts b/test/TokenAToTokenB.spec.ts new file mode 100644 index 00000000..5f27e80f --- /dev/null +++ b/test/TokenAToTokenB.spec.ts @@ -0,0 +1,241 @@ +import { TTContext } from "./helpers/ttContext"; +import { strictEqual, ok, notStrictEqual, rejects } from "assert"; +import BigNumber from "bignumber.js"; +import accounts from "./accounts/accounts"; +import { defaultAccountInfo } from "./constants"; + +contract("TokenAToTokenB()", function () { + let context: TTContext; + const tokenAAmount: number = 100; + const tokenBAmount: number = 100; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + let tokenAAddress; + let tokenBAddress; + + before(async () => { + context = await TTContext.init([], false, "alice", false); + await context.setAllDexFunctions(); + await context.createPair({ + tokenAAmount, + tokenBAmount, + }); + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + if (tokenAAddress > tokenBAddress) { + const tmp = context.tokens[0]; + context.tokens[0] = context.tokens[1]; + context.tokens[1] = tmp; + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + } + }); + + function tokenAToTokenBSuccessCase( + decription, + tokenAAmount, + tokenBAmount, + tokensLeftover + ) { + it(decription, async function () { + const pairAddress = context.dex.contract.address; + await context.tokens[0].updateStorage({ + ledger: [bobAddress, aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [bobAddress, aliceAddress, pairAddress], + }); + const aliceInitTezLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceInitTezBalance = aliceInitTezLedger + ? aliceInitTezLedger.balance + : new BigNumber(0); + const aliceInitTokenLedger = await context.tokens[1].storage.ledger[ + aliceAddress + ]; + const aliceInitTokenBalance = aliceInitTokenLedger + ? aliceInitTokenLedger.balance + : new BigNumber(0); + const bobInitTokenLedger = await context.tokens[1].storage.ledger[ + bobAddress + ]; + const bobInitTokenBalance = bobInitTokenLedger + ? bobInitTokenLedger.balance + : new BigNumber(0); + const prevPairTokenBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const prevPairTezBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + await context.dex.updateStorage({ + ledger: [[aliceAddress, bobAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const prevStorage = context.dex.storage; + await context.dex.tokenToTokenPayment( + tokenAAddress, + tokenBAddress, + "sell", + tokenAAmount, + tokenBAmount, + bobAddress + ); + await context.dex.updateStorage({ + ledger: [[aliceAddress, bobAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, bobAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, bobAddress, pairAddress], + }); + const aliceFinalTezLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceFinalTezBalance = aliceFinalTezLedger + ? aliceFinalTezLedger.balance + : new BigNumber(0); + const aliceFinalTokenLedger = await context.tokens[1].storage.ledger[ + aliceAddress + ]; + const aliceFinalTokenBalance = aliceFinalTokenLedger + ? aliceFinalTokenLedger.balance + : new BigNumber(0); + const bobFinalTokenLedger = await context.tokens[1].storage.ledger[ + bobAddress + ]; + const bobFinalTokenBalance = bobFinalTokenLedger + ? bobFinalTokenLedger.balance + : new BigNumber(0); + const pairTokenBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await context.tokens[0].storage.ledger[pairAddress] + .balance; + strictEqual( + bobInitTokenBalance.toNumber() + tokenBAmount + tokensLeftover, + bobFinalTokenBalance.toNumber() + ); + strictEqual( + aliceInitTokenBalance.toNumber(), + aliceFinalTokenBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + prevPairTokenBalance.toNumber() - tokenBAmount - tokensLeftover + ); + ok( + aliceInitTezBalance.toNumber() - tokenAAmount == + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTezBalance.toNumber(), + prevPairTezBalance.toNumber() + tokenAAmount + ); + await context.dex.updateStorage({ + ledger: [[aliceAddress, bobAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + strictEqual( + context.dex.storage.pairs[0].token_b_pool.toNumber(), + prevStorage.pairs[0].token_b_pool.toNumber() - + tokenBAmount - + tokensLeftover + ); + strictEqual( + context.dex.storage.pairs[0].token_a_pool.toNumber(), + prevStorage.pairs[0].token_a_pool.toNumber() + tokenAAmount + ); + }); + } + + function tokenAToTokenBFailCase( + decription, + tokenAAmount, + tokenBAmount, + errorMsg + ) { + it(decription, async function () { + await rejects( + context.dex.tokenToTokenPayment( + tokenAAddress, + tokenBAddress, + "sell", + tokenAAmount, + tokenBAmount, + bobAddress + ), + (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + } + ); + }); + } + + describe("Test different amount of tokens to be swapped", () => { + tokenAToTokenBFailCase( + "revert in case of 0 token a to be swapped", + 0, + 1, + "Dex/zero-amount-in" + ); + tokenAToTokenBFailCase( + "revert in case of 100% of reserves to be swapped", + 100, + 1, + "Dex/high-out" + ); + tokenAToTokenBFailCase( + "revert in case of 10000% of reserves to be swapped", + 10000, + 1, + "Dex/high-out" + ); + tokenAToTokenBFailCase( + "revert in case of 1% of reserves to be swapped", + 1, + 1, + "Dex/wrong-min-out" + ); + tokenAToTokenBSuccessCase( + "success in case of ~30% of reserves to be swapped", + 31, + 23, + 0 + ); + }); + + describe("Test different minimal desirable output amount", () => { + tokenAToTokenBFailCase( + "reevert in case of 0 tokens expected", + 10, + 0, + "Dex/zero-min-amount-out" + ); + tokenAToTokenBFailCase( + "revert in case of too many tokens expected", + 10, + 7, + "Dex/wrong-min-out" + ); + tokenAToTokenBSuccessCase( + "success in case of exact amount of tokens expected", + 10, + 5, + 0 + ); + tokenAToTokenBSuccessCase( + "success in case of smaller amount of tokens expected", + 10, + 3, + 1 + ); + }); +}); From 0a5cb47060d3eb5032e12eacab080e1f50cc98c1 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 11:08:45 +0300 Subject: [PATCH 05/63] token to token tested --- test/TokenBToTokenA.spec.ts | 241 ++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 test/TokenBToTokenA.spec.ts diff --git a/test/TokenBToTokenA.spec.ts b/test/TokenBToTokenA.spec.ts new file mode 100644 index 00000000..75bad093 --- /dev/null +++ b/test/TokenBToTokenA.spec.ts @@ -0,0 +1,241 @@ +import { TTContext } from "./helpers/ttContext"; +import { strictEqual, ok, notStrictEqual, rejects } from "assert"; +import BigNumber from "bignumber.js"; +import accounts from "./accounts/accounts"; +import { defaultAccountInfo } from "./constants"; + +contract.only("TokenBToTokenA()", function () { + let context: TTContext; + const tokenAAmount: number = 100; + const tokenBAmount: number = 100; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + let tokenAAddress; + let tokenBAddress; + + before(async () => { + context = await TTContext.init([], false, "alice", false); + await context.setAllDexFunctions(); + await context.createPair({ + tokenAAmount, + tokenBAmount, + }); + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + if (tokenAAddress > tokenBAddress) { + const tmp = context.tokens[0]; + context.tokens[0] = context.tokens[1]; + context.tokens[1] = tmp; + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + } + }); + + function tokenAToTokenBSuccessCase( + decription, + tokenAAmount, + tokenBAmount, + tokensLeftover + ) { + it(decription, async function () { + const pairAddress = context.dex.contract.address; + await context.tokens[0].updateStorage({ + ledger: [bobAddress, aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [bobAddress, aliceAddress, pairAddress], + }); + const aliceInitTezLedger = await context.tokens[1].storage.ledger[ + aliceAddress + ]; + const aliceInitTezBalance = aliceInitTezLedger + ? aliceInitTezLedger.balance + : new BigNumber(0); + const aliceInitTokenLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceInitTokenBalance = aliceInitTokenLedger + ? aliceInitTokenLedger.balance + : new BigNumber(0); + const bobInitTokenLedger = await context.tokens[0].storage.ledger[ + bobAddress + ]; + const bobInitTokenBalance = bobInitTokenLedger + ? bobInitTokenLedger.balance + : new BigNumber(0); + const prevPairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const prevPairTezBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + await context.dex.updateStorage({ + ledger: [[aliceAddress, bobAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const prevStorage = context.dex.storage; + await context.dex.tokenToTokenPayment( + tokenAAddress, + tokenBAddress, + "buy", + tokenAAmount, + tokenBAmount, + bobAddress + ); + await context.dex.updateStorage({ + ledger: [[aliceAddress, bobAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, bobAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, bobAddress, pairAddress], + }); + const aliceFinalTezLedger = await context.tokens[1].storage.ledger[ + aliceAddress + ]; + const aliceFinalTezBalance = aliceFinalTezLedger + ? aliceFinalTezLedger.balance + : new BigNumber(0); + const aliceFinalTokenLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceFinalTokenBalance = aliceFinalTokenLedger + ? aliceFinalTokenLedger.balance + : new BigNumber(0); + const bobFinalTokenLedger = await context.tokens[0].storage.ledger[ + bobAddress + ]; + const bobFinalTokenBalance = bobFinalTokenLedger + ? bobFinalTokenLedger.balance + : new BigNumber(0); + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await context.tokens[1].storage.ledger[pairAddress] + .balance; + strictEqual( + bobInitTokenBalance.toNumber() + tokenBAmount + tokensLeftover, + bobFinalTokenBalance.toNumber() + ); + strictEqual( + aliceInitTokenBalance.toNumber(), + aliceFinalTokenBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + prevPairTokenBalance.toNumber() - tokenBAmount - tokensLeftover + ); + ok( + aliceInitTezBalance.toNumber() - tokenAAmount == + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTezBalance.toNumber(), + prevPairTezBalance.toNumber() + tokenAAmount + ); + await context.dex.updateStorage({ + ledger: [[aliceAddress, bobAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + strictEqual( + context.dex.storage.pairs[0].token_a_pool.toNumber(), + prevStorage.pairs[0].token_a_pool.toNumber() - + tokenBAmount - + tokensLeftover + ); + strictEqual( + context.dex.storage.pairs[0].token_b_pool.toNumber(), + prevStorage.pairs[0].token_b_pool.toNumber() + tokenAAmount + ); + }); + } + + function tokenAToTokenBFailCase( + decription, + tokenAAmount, + tokenBAmount, + errorMsg + ) { + it(decription, async function () { + await rejects( + context.dex.tokenToTokenPayment( + tokenAAddress, + tokenBAddress, + "buy", + tokenAAmount, + tokenBAmount, + bobAddress + ), + (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + } + ); + }); + } + + describe("Test different amount of tokens to be swapped", () => { + tokenAToTokenBFailCase( + "revert in case of 0 token a to be swapped", + 0, + 1, + "Dex/zero-amount-in" + ); + tokenAToTokenBFailCase( + "revert in case of 100% of reserves to be swapped", + 100, + 1, + "Dex/high-out" + ); + tokenAToTokenBFailCase( + "revert in case of 10000% of reserves to be swapped", + 10000, + 1, + "Dex/high-out" + ); + tokenAToTokenBFailCase( + "revert in case of 1% of reserves to be swapped", + 1, + 1, + "Dex/wrong-min-out" + ); + tokenAToTokenBSuccessCase( + "success in case of ~30% of reserves to be swapped", + 31, + 23, + 0 + ); + }); + + describe("Test different minimal desirable output amount", () => { + tokenAToTokenBFailCase( + "reevert in case of 0 tokens expected", + 10, + 0, + "Dex/zero-min-amount-out" + ); + tokenAToTokenBFailCase( + "revert in case of too many tokens expected", + 10, + 7, + "Dex/wrong-min-out" + ); + tokenAToTokenBSuccessCase( + "success in case of exact amount of tokens expected", + 10, + 5, + 0 + ); + tokenAToTokenBSuccessCase( + "success in case of smaller amount of tokens expected", + 10, + 3, + 1 + ); + }); +}); From 1e4e7488c70f944f4d081d852b260fb4e04cbf53 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 11:12:52 +0300 Subject: [PATCH 06/63] tests added --- Token_token.test.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Token_token.test.md b/Token_token.test.md index f11e22fb..30224187 100644 --- a/Token_token.test.md +++ b/Token_token.test.md @@ -173,11 +173,11 @@ tokens_out = token_pool * (tez_in - fee) / (tez_pool + tez_in - fee) **Scenario 1**: Test swap of -- [ ] 0 XTZ -- [ ] 1% of reserves -- [ ] 30% of reserves -- [ ] 100% of reserves -- [ ] 10000% of reserves +- [x] 0 XTZ +- [x] 1% of reserves +- [x] 30% of reserves +- [x] 100% of reserves +- [x] 10000% of reserves **Scope**: Test different minimal desirable output amount. @@ -189,10 +189,10 @@ tokens_out = token_pool * (tez_in - fee) / (tez_pool + tez_in - fee) **Scenario 1**: Test swap of -- [ ] 0 tokens -- [ ] too many tokens -- [ ] smaller amount of tokens -- [ ] exact tokens +- [x] 0 tokens +- [x] too many tokens +- [x] smaller amount of tokens +- [x] exact tokens ## Test Item: TokenBToTokenA Entrypoint @@ -221,11 +221,11 @@ tokens_out = token_pool * (tez_in - fee) / (tez_pool + tez_in - fee) **Scenario 1**: Test swap of -- [ ] 0 tokens -- [ ] 0.01% of reserves -- [ ] 30% of reserves -- [ ] 100% of reserves -- [ ] 10000% of reserves +- [x] 0 tokens +- [x] 0.01% of reserves +- [x] 30% of reserves +- [x] 100% of reserves +- [x] 10000% of reserves **Scope**: Test different minimal desirable output amount. @@ -237,6 +237,6 @@ tokens_out = token_pool * (tez_in - fee) / (tez_pool + tez_in - fee) **Scenario 1**: Test swap of -- [ ] 0 XTZ -- [ ] too many XTZ -- [ ] exact XTZ +- [x] 0 XTZ +- [x] too many XTZ +- [x] exact XTZ From 89c5625c9004b540081a427a9040a2ff95295c7a Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 11:13:48 +0300 Subject: [PATCH 07/63] remove .only --- test/TokenBToTokenA.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TokenBToTokenA.spec.ts b/test/TokenBToTokenA.spec.ts index 75bad093..0b40e240 100644 --- a/test/TokenBToTokenA.spec.ts +++ b/test/TokenBToTokenA.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; -contract.only("TokenBToTokenA()", function () { +contract("TokenBToTokenA()", function () { let context: TTContext; const tokenAAmount: number = 100; const tokenBAmount: number = 100; From 4757e8c9385817e9d89bc16a857836e0ce6df8e3 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 11:49:56 +0300 Subject: [PATCH 08/63] fix tests --- test/BuyToken.spec.ts | 9 ++++----- test/SellToken.spec.ts | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/test/BuyToken.spec.ts b/test/BuyToken.spec.ts index e77904eb..62515b7f 100644 --- a/test/BuyToken.spec.ts +++ b/test/BuyToken.spec.ts @@ -49,9 +49,8 @@ contract("BuyToken()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -156,13 +155,13 @@ contract("BuyToken()", function () { "revert in case of 100% of reserves to be swapped", 1000, 1000, - "Dex/wrong-out" + "Dex/high-out" ); tokenToTokenFailCase( "revert in case of 10000% of reserves to be swapped", 100000, 1, - "Dex/wrong-out" + "Dex/high-out" ); tokenToTokenSuccessCase( diff --git a/test/SellToken.spec.ts b/test/SellToken.spec.ts index fb7baad7..d9f4bbe8 100644 --- a/test/SellToken.spec.ts +++ b/test/SellToken.spec.ts @@ -49,9 +49,8 @@ contract("SellToken()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -156,13 +155,13 @@ contract("SellToken()", function () { "revert in case of 100% of reserves to be swapped", 100000, 300, - "Dex/wrong-out" + "Dex/high-out" ); tokenToTokenFailCase( "revert in case of 10000% of reserves to be swapped", 100000000, 1, - "Dex/wrong-out" + "Dex/high-out" ); tokenToTokenFailCase( "revert in case of 1% of reserves to be swapped", From 3efa3b2a13773c744c6b488d4c626e63920caaf4 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 12:43:48 +0300 Subject: [PATCH 09/63] readme updated --- README.md | 277 ------------------------------------------------------ 1 file changed, 277 deletions(-) diff --git a/README.md b/README.md index f36624ba..8d0957de 100644 --- a/README.md +++ b/README.md @@ -103,283 +103,6 @@ yarn migrate Addresses of deployed contracts are displayed in terminal. At this stage, new MetadataStorage, Factory are originated. Aditionaly, for testnets two new pairs are deployed. -# Entrypoints - -The Ligo interfaces of the contracts can be found in `contracts/partials/I__CONTRACT_NAME__.ligo` - -## Factory - -Factory contains the code template for the `Dex` Token-XTZ pair contracts, deploys them and keeps the list of deployed pairs. The functions for `Dex` are stored in `Factory` contract but because of gas and operation limits their code cannot be stored in Factory contract during the origination: they are added separately one by one. - -New exchange pairs are registered and deployed via `LaunchExchange`. The only difference between factory standards is the token identifier type, for F1.2 it is the token address and for FA2 it is the address the token id: - -``` -#if FA2_STANDARD_ENABLED -type token_identifier is (address * nat) -#else -type token_identifier is address -#endif -``` - -The contract has the following entrypoints: - -``` -type launch_exchange_params is record [ - token : token_identifier; - token_amount : nat; -] - -type set_token_function_params is record [ - func : token_func; - index : nat; -] -type set_dex_function_params is record [ - func : dex_func; - index : nat; -] - -type exchange_action is -| LaunchExchange of launch_exchange_params -| SetDexFunction of set_dex_function_params -| SetTokenFunction of set_token_function_params -``` - -### SetDexFunction - -Sets the dex specific function. Is used before the whole system is launched. - -`index` : the key in functions map; - -`func` : function code. - -Each `index` is designed for a specific `func` which functionality is described below. - -### SetTokenFunction - -Sets the FA1.2 function. Is used before the whole system is launched. - -`index` : the key in functions map; - -`func` : function code. - -Each `index` is designed for a specific `func` which functionality is described below. - -### LaunchExchange - -Deploys a new empty `Dex` for `token`, stores the address of the new contract, and puts initial liquidity; has to be called with XTZ amount that will be used as initial liquidity. - -`token` : address(address and token id for FA2) of the paired token; - -`token_amount` : amount of tokens that will be withdrawn from the user account and used as initial liquidity. - -`tez_amount`(not an argument) : the XTZ for initial liquidity should be sent along with the launch transaction. - -## Dex - -`Dex` fully implements FA1.2/FA2 token interface. For more details check the this [spec](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-7/tzip-7.md) and this [spec](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md). And the extends it with other exchange-related methods. - -The contract has the following entrypoints common for both standards: - -``` - -type tez_to_token_payment_params is record [ - amount : nat; - receiver : address; -] - -type token_to_tez_payment_params is record [ - amount : nat; - min_out : nat; - receiver : address; -] - -type divest_liquidity_params is record [ - min_tez : nat; - min_tokens : nat; - shares : nat; -] - -type vote_params is record [ - candidate : key_hash; - value : nat; - voter : address; -] - -type veto_params is record [ - value : nat; - voter : address; -] - -type dex_action is -| InitializeExchange of (nat) -| TezToTokenPayment of tez_to_token_payment_params -| TokenToTezPayment of token_to_tez_payment_params -| InvestLiquidity of (nat) -| DivestLiquidity of divest_liquidity_params -| Vote of vote_params -| Veto of veto_params -| WithdrawProfit of (address) - -type default_params is unit -type use_params is dex_action -``` - -For FA1.2 standard compatibility the following entrypoints are implemented: - -``` -type transfer_params is michelson_pair(address, "from", michelson_pair(address, "to", nat, "value"), "") -type approve_params is michelson_pair(address, "spender", nat, "value") -type balance_params is michelson_pair(address, "owner", contract(nat), "") -type allowance_params is michelson_pair(michelson_pair(address, "owner", address, "spender"), "", contract(nat), "") -type total_supply_params is (unit * contract(nat)) - -type token_action is -| ITransfer of transfer_params -| IApprove of approve_params -| IGetBalance of balance_params -| IGetAllowance of allowance_params -| IGetTotalSupply of total_supply_params - -type full_action is -| Use of use_params -| Default of default_params -| Transfer of transfer_params -| Approve of approve_params -| GetBalance of balance_params -| GetAllowance of allowance_params -| GetTotalSupply of total_supply_params -``` - -For FA2 standard compatibility the following entrypoints are implemented: - -``` -type transfer_params is list (transfer_param) -type token_metadata_registry_params is contract (address) -type update_operator_params is list (update_operator_param) - -type token_action is -| ITransfer of transfer_params -| IBalance_of of balance_params -| IToken_metadata_registry of token_metadata_registry_params -| IUpdate_operators of update_operator_params - -type full_action is -| Use of use_params -| Default of default_params -| Transfer of transfer_params -| Balance_of of balance_params -| Token_metadata_registry of token_metadata_registry_params -| Update_operators of update_operator_params -``` - -### Default (index 8) - -Used to collect rewards from bakers, donations or misguided payments without specified entrypoint. - -### Use - -Executes the exchange-related which code is stored in map of lamdas and thus the `index` param is needed. - -Actions have the following parameters (index in the list matches the index in `lambdas`): - -#### InitializeExchange (index 0) - -Sets initial liquidity, XTZ must be sent. - -`amount` : the token amount for initial liquidity; - -`tez_amount`(not an argument) : the XTZ for initial liquidity should be send along with the launch transaction. - -#### TezToTokenPayment (index 1) - -Exchanges XTZ to tokens and sends them to `receiver`; operation is reverted if the amount of exchanged tokens is less than `amount`. - -`amount` : min amount of tokens received to accept exchange; - -`receiver` : tokens received; - -`tez_amount`(not an argument) : the XTZ to be exchanged. - -#### TokenToTezPayment (index 2) - -Exchanges `amount` tokens to XTZ and sends them to `receiver`; operation is reverted if the amount of exchanged XTZ is less than `min_out`. - -`amount` : amount of tokens to be exchanged; - -`min_out` : min amount of XTZ received to accept exchange; - -`receiver` : tokens received; - -#### WithdrawProfit (index 3) - -Withdraws delegation reward of the sender to `receiver` address. - -`receiver` : XTZ received; - -#### InvestLiquidity (index 4) - -Mints `min_shares` by investing tokens and XTZ; the corresponding amount of XTZ has to be sent via transaction and max amount of tokens to be spent should be approved for `Dex`. - -`max_tokens` : the max amount of tokens to be invested; - -`tez_amount`(not an argument) : the XTZ to be provided as liquidity. - -#### DivestLiquidity (index 5) - -Burns `shares` and sends tokens and XTZ to the owner; operation is reverted if the amount of divested tokens is smaller than `min_tokens` or the amount of divested XTZ is smaller than `min_tez`. - -`shares` : amount of shares to be burnt; - -`min_tez` : min amount of XTZ received to accept the divestment; - -`min_tokens` : min amount of tokens received to accept the divestment; - -#### Vote (index 6) - -Votes for `candidate` with shares of `voter`. - -`candidate` : the chosen baker; - -`value` : amount of shares that are used to vote; - -`voter` : the account from which the voting is done. - -#### Veto (index 7) - -Votes against current delegate with `value` shares of `voter`; the `value` is frozen and can't be transferred or used for another voting. - -`value` : amount of shares that are used to vote against the chosen baker; - -`voter` : the account from which the veto voting is done. - -## Token - -Implements [FA1.2](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-7/tzip-7.md) or [FA2](https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-12/tzip-12.md) token interface. - -## MetadataStorage - -Stores the metadata for Dex as it can't be placed inside the `Dex` contract because of operation size limits under the current protocol rules. - -The metadata can be updated by authorities to follow the unstable metadata standards. - -``` -type update_owner_type is record [ - owner : address; - add : bool; -] -type metadata_type is map (string, bytes) - -type storage is record [ - metadata : metadata_type; - owners : set(address); -] - -type storage_action is -| Update_owners of update_owner_type -| Update_storage of metadata_type -| Get_metadata of contract (metadata_type) -``` - # Testing If you'd like to run tests on the local environment, you might want to run `ganache-cli` for Tezos using the following command: From 3881463138f985d33cc1a3403e39bfbcfc49146f Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 2 Jun 2021 13:16:52 +0300 Subject: [PATCH 10/63] fix tests --- test/TokenAToTokenB.spec.ts | 15 ++++++++++++--- test/TokenBToTokenA.spec.ts | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/test/TokenAToTokenB.spec.ts b/test/TokenAToTokenB.spec.ts index 5f27e80f..1f6c7674 100644 --- a/test/TokenAToTokenB.spec.ts +++ b/test/TokenAToTokenB.spec.ts @@ -70,7 +70,10 @@ contract("TokenAToTokenB()", function () { pairAddress ].balance; await context.dex.updateStorage({ - ledger: [[aliceAddress, bobAddress, 0]], + ledger: [ + [aliceAddress, 0], + [bobAddress, 0], + ], tokens: ["0"], pairs: ["0"], }); @@ -84,7 +87,10 @@ contract("TokenAToTokenB()", function () { bobAddress ); await context.dex.updateStorage({ - ledger: [[aliceAddress, bobAddress, 0]], + ledger: [ + [aliceAddress, 0], + [bobAddress, 0], + ], tokens: ["0"], pairs: ["0"], }); @@ -138,7 +144,10 @@ contract("TokenAToTokenB()", function () { prevPairTezBalance.toNumber() + tokenAAmount ); await context.dex.updateStorage({ - ledger: [[aliceAddress, bobAddress, 0]], + ledger: [ + [aliceAddress, 0], + [bobAddress, 0], + ], tokens: ["0"], pairs: ["0"], }); diff --git a/test/TokenBToTokenA.spec.ts b/test/TokenBToTokenA.spec.ts index 0b40e240..f9d462ab 100644 --- a/test/TokenBToTokenA.spec.ts +++ b/test/TokenBToTokenA.spec.ts @@ -70,7 +70,10 @@ contract("TokenBToTokenA()", function () { pairAddress ].balance; await context.dex.updateStorage({ - ledger: [[aliceAddress, bobAddress, 0]], + ledger: [ + [aliceAddress, 0], + [bobAddress, 0], + ], tokens: ["0"], pairs: ["0"], }); @@ -84,7 +87,10 @@ contract("TokenBToTokenA()", function () { bobAddress ); await context.dex.updateStorage({ - ledger: [[aliceAddress, bobAddress, 0]], + ledger: [ + [aliceAddress, 0], + [bobAddress, 0], + ], tokens: ["0"], pairs: ["0"], }); @@ -138,7 +144,10 @@ contract("TokenBToTokenA()", function () { prevPairTezBalance.toNumber() + tokenAAmount ); await context.dex.updateStorage({ - ledger: [[aliceAddress, bobAddress, 0]], + ledger: [ + [aliceAddress, 0], + [bobAddress, 0], + ], tokens: ["0"], pairs: ["0"], }); From 833ae19196e3462141a62cf7a0bc7c6f717d37c7 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 14:41:36 +0300 Subject: [PATCH 11/63] new contracts --- contracts/main/TTDexFA2FA12.ligo | 19 ++++++ contracts/partials/ITTDex.ligo | 8 +++ contracts/partials/TTMethodDex.ligo | 97 ++++++++++++++++++++++++++--- 3 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 contracts/main/TTDexFA2FA12.ligo diff --git a/contracts/main/TTDexFA2FA12.ligo b/contracts/main/TTDexFA2FA12.ligo new file mode 100644 index 00000000..27bc1ea8 --- /dev/null +++ b/contracts/main/TTDexFA2FA12.ligo @@ -0,0 +1,19 @@ +#define FA2_STANDARD_ENABLED +#define FA2FA12_STANDARD_ENABLED +#define TOKEN_TO_TOKEN_ENABLED +#include "../partials/TTDex.ligo" +#include "../partials/TTMethodDex.ligo" + +(* DexFA2 - Contract for exchanges for XTZ - FA2 token pair *) +function main (const p : full_action; const s : full_dex_storage) : full_return is + block { + const this: address = Tezos.self_address; + } with case p of + | Use(params) -> call_dex(params, this, s) + | Transfer(params) -> call_token(ITransfer(params), this, 0n, s) + | Balance_of(params) -> call_token(IBalance_of(params), this, 2n, s) + | Update_operators(params) -> call_token(IUpdate_operators(params), this, 1n, s) + | Get_reserves(params) -> get_reserves(params, s) + | SetDexFunction(params) -> ((nil:list(operation)), if params.index > 3n then (failwith("Dex/wrong-index") : full_dex_storage) else set_dex_function(params.index, params.func, s)) + | SetTokenFunction(params) -> ((nil:list(operation)), if params.index > 2n then (failwith("Dex/wrong-index") : full_dex_storage) else set_token_function(params.index, params.func, s)) + end diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 0cbe2810..27f56387 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -14,6 +14,11 @@ type token_identifier is record [ token_address : address; token_id : nat; ] +#if FA2FA12_STANDARD_ENABLED +type token_transfer_params_fa12 is michelson_pair(address, "from", michelson_pair(address, "to", nat, "value"), "") +type token_identifier_fa12 is address +type transfer_type_fa12 is TransferTypeFA12 of token_transfer_params_fa12 +#endif #else type token_transfer_params is michelson_pair(address, "from", michelson_pair(address, "to", nat, "value"), "") type token_identifier is address @@ -30,8 +35,11 @@ type tokens_info is record [ token_b_address : address; #if FA2_STANDARD_ENABLED token_a_id : nat; +#if FA2FA12_STANDARD_ENABLED +#else token_b_id : nat; #endif +#endif ] type token_pair is bytes diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index b8832419..03141429 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -39,6 +39,10 @@ function wrap_transfer_trx(const owner : address; const receiver : address; cons ] ] ] ]) +#if FA2FA12_STANDARD_ENABLED +function wrap_fa12_transfer_trx(const owner : address; const receiver : address; const value : nat) : transfer_type_fa12 is + TransferTypeFA12(owner, (receiver, value)) +#endif #else function wrap_transfer_trx(const owner : address; const receiver : address; const value : nat) : transfer_type is TransferType(owner, (receiver, value)) @@ -51,6 +55,14 @@ function get_token_contract(const token_address : address) : contract(transfer_t | None -> (failwith("Dex/not-token") : contract(transfer_type)) end; +#if FA2FA12_STANDARD_ENABLED +function get_fa12_token_contract(const token_address : address) : contract(transfer_type_fa12) is + case (Tezos.get_entrypoint_opt("%transfer", token_address) : option(contract(transfer_type_fa12))) of + Some(contr) -> contr + | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) + end; +#endif + #include "../partials/TTMethodFA2.ligo" (* Initialize exchange after the previous liquidity was drained *) @@ -120,15 +132,28 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con get_token_contract(params.pair.token_a_address) ); Tezos.transaction( - wrap_transfer_trx(Tezos.sender, +#if FA2FA12_STANDARD_ENABLED + wrap_fa12_transfer_trx( +#else + wrap_transfer_trx( +#endif + Tezos.sender, this, params.token_b_in #if FA2_STANDARD_ENABLED +#if FA2FA12_STANDARD_ENABLED +#else , params.pair.token_b_id +#endif #endif ), 0mutez, - get_token_contract(params.pair.token_b_address) +#if FA2FA12_STANDARD_ENABLED + get_fa12_token_contract( +#else + get_token_contract( +#endif + params.pair.token_b_address) )]; } | TokenToTokenPayment(n) -> skip @@ -205,15 +230,28 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this get_token_contract(params.pair.token_a_address) ); Tezos.transaction( - wrap_transfer_trx(this, +#if FA2FA12_STANDARD_ENABLED + wrap_fa12_transfer_trx( +#else + wrap_transfer_trx( +#endif + this, params.receiver, token_b_out #if FA2_STANDARD_ENABLED +#if FA2FA12_STANDARD_ENABLED +#else , params.pair.token_b_id +#endif #endif ), 0mutez, - get_token_contract(params.pair.token_b_address) +#if FA2FA12_STANDARD_ENABLED + get_fa12_token_contract( +#else + get_token_contract( +#endif + params.pair.token_b_address) )]; } | Buy -> { @@ -240,15 +278,28 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this (* prepare operations to withdraw user's tokens and transfer XTZ *) operations := list[ Tezos.transaction( - wrap_transfer_trx(Tezos.sender, +#if FA2FA12_STANDARD_ENABLED + wrap_fa12_transfer_trx( +#else + wrap_transfer_trx( +#endif + Tezos.sender, this, params.amount_in #if FA2_STANDARD_ENABLED +#if FA2FA12_STANDARD_ENABLED +#else , params.pair.token_b_id +#endif #endif ), 0mutez, - get_token_contract(params.pair.token_b_address) +#if FA2FA12_STANDARD_ENABLED + get_fa12_token_contract( +#else + get_token_contract( +#endif + params.pair.token_b_address) ); Tezos.transaction( wrap_transfer_trx(this, @@ -356,15 +407,28 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th get_token_contract(params.pair.token_a_address) ); Tezos.transaction( - wrap_transfer_trx(Tezos.sender, +#if FA2FA12_STANDARD_ENABLED + wrap_fa12_transfer_trx( +#else + wrap_transfer_trx( +#endif + Tezos.sender, this, tokens_b_required #if FA2_STANDARD_ENABLED +#if FA2FA12_STANDARD_ENABLED +#else , params.pair.token_b_id +#endif #endif ), 0mutez, - get_token_contract(params.pair.token_b_address) +#if FA2FA12_STANDARD_ENABLED + get_fa12_token_contract( +#else + get_token_contract( +#endif + params.pair.token_b_address) )]; } | DivestLiquidity(n) -> skip @@ -453,15 +517,28 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th get_token_contract(params.pair.token_a_address) ); transaction( - wrap_transfer_trx(this, +#if FA2FA12_STANDARD_ENABLED + wrap_fa12_transfer_trx( +#else + wrap_transfer_trx( +#endif + this, Tezos.sender, token_b_divested #if FA2_STANDARD_ENABLED +#if FA2FA12_STANDARD_ENABLED +#else , params.pair.token_b_id +#endif #endif ), 0mutez, - get_token_contract(params.pair.token_b_address) +#if FA2FA12_STANDARD_ENABLED + get_fa12_token_contract( +#else + get_token_contract( +#endif + params.pair.token_b_address) ); ]; } From a8854495ec7f238b03588da093ad9227622e6645 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 15:01:57 +0300 Subject: [PATCH 12/63] migrations updated --- migrations/2_deploy_token_to_token_dex.js | 50 ++-- migrations/3_baker_registry_migration.js | 2 + migrations/4_deploy_dex.js | 276 +++++++++++----------- 3 files changed, 172 insertions(+), 156 deletions(-) diff --git a/migrations/2_deploy_token_to_token_dex.js b/migrations/2_deploy_token_to_token_dex.js index 5c081477..8a975632 100644 --- a/migrations/2_deploy_token_to_token_dex.js +++ b/migrations/2_deploy_token_to_token_dex.js @@ -1,4 +1,5 @@ const standard = process.env.EXCHANGE_TOKEN_STANDARD; +const usedStandard = standard == "FA2FA12" ? "FA2" : standard; const TTDex = artifacts.require("TTDex" + standard); const MetadataStorage = artifacts.require("MetadataStorage"); const dexStorage = require("../storage/TTDex"); @@ -7,8 +8,12 @@ const { InMemorySigner } = require("@taquito/signer"); const { MichelsonMap } = require("@taquito/michelson-encoder"); const { dexFunctions, tokenFunctions } = require("../storage/TTFunctions"); const { execSync } = require("child_process"); -const Token = artifacts.require("Token" + standard); -const tokenStorage = require("../storage/Token" + standard); +const Token = artifacts.require("Token" + usedStandard); +const TokenFA12 = artifacts.require("TokenFA12"); +const TokenFA2 = artifacts.require("TokenFA2"); +const tokenStorage = require("../storage/Token" + usedStandard); +const tokenFA12Storage = require("../storage/TokenFA12"); +const tokenFA2Storage = require("../storage/TokenFA2"); const { getLigo } = require("../scripts/utils"); const accountsStored = require("../scripts/sandbox/accounts"); @@ -75,10 +80,14 @@ module.exports = async (deployer, network, accounts) => { if (network !== "mainnet") { let token0Instance = await tezos.contract.at( - (await Token.new(tokenStorage)).address.toString() + standard == "FA2FA12" + ? (await TokenFA2.new(tokenFA2Storage)).address.toString() + : (await Token.new(tokenStorage)).address.toString() ); let token1Instance = await tezos.contract.at( - (await Token.new(tokenStorage)).address.toString() + standard == "FA2FA12" + ? (await TokenFA12.new(tokenFA12Storage)).address.toString() + : (await Token.new(tokenStorage)).address.toString() ); console.log(`Token 1 address: ${token0Instance.address}`); console.log(`Token 2 address: ${token1Instance.address}`); @@ -123,24 +132,25 @@ module.exports = async (deployer, network, accounts) => { ]) .send(); await operation.confirmation(); - operation = await token1Instance.methods - .update_operators([ - { - add_operator: { - owner: accounts[0], - operator: dexInstance.address.toString(), - token_id: defaultTokenId, + if (standard == "FA2FA12") { + operation = await token1Instance.methods + .approve(dexInstance.address.toString(), initialTokenAmount) + .send(); + } else { + operation = await token1Instance.methods + .update_operators([ + { + add_operator: { + owner: accounts[0], + operator: dexInstance.address.toString(), + token_id: defaultTokenId, + }, }, - }, - ]) - .send(); + ]) + .send(); + } await operation.confirmation(); - let pair = { - token_a_address: token0Instance.address.toString(), - token_b_address: token1Instance.address.toString(), - token_a_id: 0, - token_b_id: 0, - }; + operation = await dex.methods .use( "initializeExchange", diff --git a/migrations/3_baker_registry_migration.js b/migrations/3_baker_registry_migration.js index 22dea056..3f175748 100644 --- a/migrations/3_baker_registry_migration.js +++ b/migrations/3_baker_registry_migration.js @@ -2,6 +2,8 @@ const BakerRegistry = artifacts.require("BakerRegistry"); const { MichelsonMap } = require("@taquito/michelson-encoder"); module.exports = async (deployer) => { + const standard = process.env.EXCHANGE_TOKEN_STANDARD; + if (!["FA2", "FA12"].includes(standard)) return; await deployer.deploy(BakerRegistry, MichelsonMap.fromLiteral({})); const bakerRegistryInstance = await BakerRegistry.deployed(); console.log(`BakerRegistry address: ${bakerRegistryInstance.address}`); diff --git a/migrations/4_deploy_dex.js b/migrations/4_deploy_dex.js index 62351005..b9c0505c 100644 --- a/migrations/4_deploy_dex.js +++ b/migrations/4_deploy_dex.js @@ -1,151 +1,155 @@ const standard = process.env.EXCHANGE_TOKEN_STANDARD; -const Factory = artifacts.require("Factory" + standard); -const MetadataStorage = artifacts.require("MetadataStorage"); -const factoryStorage = require("../storage/Factory"); -const { TezosToolkit } = require("@taquito/taquito"); -const { InMemorySigner } = require("@taquito/signer"); -const { MichelsonMap } = require("@taquito/michelson-encoder"); -const { dexFunctions, tokenFunctions } = require("../storage/Functions"); -const { execSync } = require("child_process"); -const Token = artifacts.require("Token" + standard); -const tokenStorage = require("../storage/Token" + standard); -const { getLigo } = require("../scripts/utils"); -const accountsStored = require("../scripts/sandbox/accounts"); -const BakerRegistry = artifacts.require("BakerRegistry"); -const { confirmOperation } = require("./confirmation"); +if (["FA2", "FA12"].includes(standard)) { + const Factory = artifacts.require("Factory" + standard); + const MetadataStorage = artifacts.require("MetadataStorage"); + const factoryStorage = require("../storage/Factory"); + const { TezosToolkit } = require("@taquito/taquito"); + const { InMemorySigner } = require("@taquito/signer"); + const { MichelsonMap } = require("@taquito/michelson-encoder"); + const { dexFunctions, tokenFunctions } = require("../storage/Functions"); + const { execSync } = require("child_process"); + const Token = artifacts.require("Token" + standard); + const tokenStorage = require("../storage/Token" + standard); + const { getLigo } = require("../scripts/utils"); + const accountsStored = require("../scripts/sandbox/accounts"); + const BakerRegistry = artifacts.require("BakerRegistry"); + const { confirmOperation } = require("./confirmation"); -const initialTezAmount = 1; -const initialTokenAmount = 1000000; -const defaultTokenId = 0; + const initialTezAmount = 1; + const initialTokenAmount = 1000000; + const defaultTokenId = 0; -module.exports = async (deployer, network, accounts) => { - tezos = new TezosToolkit(tezos.rpc.url); - if (network === "development") return; - const secretKey = accountsStored.alice.sk.trim(); - tezos.setProvider({ - config: { - confirmationPollingTimeoutSecond: 500, - }, - signer: await InMemorySigner.fromSecretKey(secretKey), - }); - - const metadataStorageInstance = await MetadataStorage.deployed(); - factoryStorage.baker_validator = ( - await BakerRegistry.deployed() - ).address.toString(); - factoryStorage.metadata = MichelsonMap.fromLiteral({ - "": Buffer( - "tezos-storage://" + - metadataStorageInstance.address.toString() + - "/quipu", - "ascii" - ).toString("hex"), - }); - await deployer.deploy(Factory, factoryStorage); - const factoryInstance = await Factory.deployed(); - console.log(`Factory address: ${factoryInstance.address}`); - - const ligo = getLigo(true); - - for (dexFunction of dexFunctions) { - const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/Factory${standard}.ligo main 'SetDexFunction(record index =${dexFunction.index}n; func = ${dexFunction.name}; end)'`, - { maxBuffer: 1024 * 500 } - ); - const operation = await tezos.contract.transfer({ - to: factoryInstance.address, - amount: 0, - parameter: { - entrypoint: "setDexFunction", - value: JSON.parse(stdout.toString()).args[0].args[0], + module.exports = async (deployer, network, accounts) => { + tezos = new TezosToolkit(tezos.rpc.url); + if (network === "development") return; + const secretKey = accountsStored.alice.sk.trim(); + tezos.setProvider({ + config: { + confirmationPollingTimeoutSecond: 500, }, + signer: await InMemorySigner.fromSecretKey(secretKey), }); - await confirmOperation(tezos, operation.hash); - } - for (tokenFunction of tokenFunctions[standard]) { - const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/Factory${standard}.ligo main 'SetTokenFunction(record index =${tokenFunction.index}n; func = ${tokenFunction.name}; end)'`, - { maxBuffer: 1024 * 500 } - ); - const operation = await tezos.contract.transfer({ - to: factoryInstance.address, - amount: 0, - parameter: { - entrypoint: "setTokenFunction", - value: JSON.parse(stdout.toString()).args[0], - }, + + const metadataStorageInstance = await MetadataStorage.deployed(); + factoryStorage.baker_validator = ( + await BakerRegistry.deployed() + ).address.toString(); + factoryStorage.metadata = MichelsonMap.fromLiteral({ + "": Buffer( + "tezos-storage://" + + metadataStorageInstance.address.toString() + + "/quipu", + "ascii" + ).toString("hex"), }); - await confirmOperation(tezos, operation.hash); - } + await deployer.deploy(Factory, factoryStorage); + const factoryInstance = await Factory.deployed(); + console.log(`Factory address: ${factoryInstance.address}`); - if (network !== "mainnet") { - let token0Instance = await tezos.contract.at( - (await Token.new(tokenStorage)).address.toString() - ); - let token1Instance = await tezos.contract.at( - (await Token.new(tokenStorage)).address.toString() - ); - console.log(`Token 1 address: ${token0Instance.address}`); - console.log(`Token 2 address: ${token1Instance.address}`); + const ligo = getLigo(true); - if (standard === "FA12") { - let operation = await token0Instance.methods - .approve(factoryInstance.address.toString(), initialTokenAmount) - .send(); + for (dexFunction of dexFunctions) { + const stdout = execSync( + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/Factory${standard}.ligo main 'SetDexFunction(record index =${dexFunction.index}n; func = ${dexFunction.name}; end)'`, + { maxBuffer: 1024 * 500 } + ); + const operation = await tezos.contract.transfer({ + to: factoryInstance.address, + amount: 0, + parameter: { + entrypoint: "setDexFunction", + value: JSON.parse(stdout.toString()).args[0].args[0], + }, + }); await confirmOperation(tezos, operation.hash); - operation = await token1Instance.methods - .approve(factoryInstance.address.toString(), initialTokenAmount) - .send(); + } + for (tokenFunction of tokenFunctions[standard]) { + const stdout = execSync( + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/Factory${standard}.ligo main 'SetTokenFunction(record index =${tokenFunction.index}n; func = ${tokenFunction.name}; end)'`, + { maxBuffer: 1024 * 500 } + ); + const operation = await tezos.contract.transfer({ + to: factoryInstance.address, + amount: 0, + parameter: { + entrypoint: "setTokenFunction", + value: JSON.parse(stdout.toString()).args[0], + }, + }); await confirmOperation(tezos, operation.hash); - await factoryInstance.launchExchange( - token0Instance.address.toString(), - initialTokenAmount, - { amount: initialTezAmount } + } + + if (network !== "mainnet") { + let token0Instance = await tezos.contract.at( + (await Token.new(tokenStorage)).address.toString() ); - await factoryInstance.launchExchange( - token1Instance.address.toString(), - initialTokenAmount, - { amount: initialTezAmount } + let token1Instance = await tezos.contract.at( + (await Token.new(tokenStorage)).address.toString() ); - } else { - let operation = await token0Instance.methods - .update_operators([ - { - add_operator: { - owner: accounts[0], - operator: factoryInstance.address.toString(), - token_id: defaultTokenId, + console.log(`Token 1 address: ${token0Instance.address}`); + console.log(`Token 2 address: ${token1Instance.address}`); + + if (standard === "FA12") { + let operation = await token0Instance.methods + .approve(factoryInstance.address.toString(), initialTokenAmount) + .send(); + await confirmOperation(tezos, operation.hash); + operation = await token1Instance.methods + .approve(factoryInstance.address.toString(), initialTokenAmount) + .send(); + await confirmOperation(tezos, operation.hash); + await factoryInstance.launchExchange( + token0Instance.address.toString(), + initialTokenAmount, + { amount: initialTezAmount } + ); + await factoryInstance.launchExchange( + token1Instance.address.toString(), + initialTokenAmount, + { amount: initialTezAmount } + ); + } else { + let operation = await token0Instance.methods + .update_operators([ + { + add_operator: { + owner: accounts[0], + operator: factoryInstance.address.toString(), + token_id: defaultTokenId, + }, }, - }, - ]) - .send(); - await confirmOperation(tezos, operation.hash); - operation = await token1Instance.methods - .update_operators([ - { - add_operator: { - owner: accounts[0], - operator: factoryInstance.address.toString(), - token_id: defaultTokenId, + ]) + .send(); + await confirmOperation(tezos, operation.hash); + operation = await token1Instance.methods + .update_operators([ + { + add_operator: { + owner: accounts[0], + operator: factoryInstance.address.toString(), + token_id: defaultTokenId, + }, }, - }, - ]) - .send(); - await confirmOperation(tezos, operation.hash); - operation = await factoryInstance.launchExchange( - token0Instance.address.toString(), - defaultTokenId, - initialTokenAmount, - { amount: initialTezAmount } - ); - await confirmOperation(tezos, operation.hash); - operation = await factoryInstance.launchExchange( - token1Instance.address.toString(), - defaultTokenId, - initialTokenAmount, - { amount: initialTezAmount } - ); - await confirmOperation(tezos, operation.hash); + ]) + .send(); + await confirmOperation(tezos, operation.hash); + operation = await factoryInstance.launchExchange( + token0Instance.address.toString(), + defaultTokenId, + initialTokenAmount, + { amount: initialTezAmount } + ); + await confirmOperation(tezos, operation.hash); + operation = await factoryInstance.launchExchange( + token1Instance.address.toString(), + defaultTokenId, + initialTokenAmount, + { amount: initialTezAmount } + ); + await confirmOperation(tezos, operation.hash); + } } - } -}; + }; +} else { + module.exports = async (deployer) => {}; +} From 017b28a4373c532dee5a63502fa854fba2dd3ffa Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 15:15:02 +0300 Subject: [PATCH 13/63] update migrations --- migrations/2_deploy_token_to_token_dex.js | 49 ++-- storage/TTFunctions.js | 1 + test/helpers/ttdexFA2FA12.ts | 262 ++++++++++++++++++++++ 3 files changed, 296 insertions(+), 16 deletions(-) create mode 100644 test/helpers/ttdexFA2FA12.ts diff --git a/migrations/2_deploy_token_to_token_dex.js b/migrations/2_deploy_token_to_token_dex.js index 8a975632..1ab0aa10 100644 --- a/migrations/2_deploy_token_to_token_dex.js +++ b/migrations/2_deploy_token_to_token_dex.js @@ -23,7 +23,7 @@ const defaultTokenId = 0; module.exports = async (deployer, network, accounts) => { tezos = new TezosToolkit(tezos.rpc.url); - if (network === "development") return; + // if (network === "development") return; const secretKey = accountsStored.alice.sk.trim(); tezos.setProvider({ config: { @@ -151,21 +151,38 @@ module.exports = async (deployer, network, accounts) => { } await operation.confirmation(); - operation = await dex.methods - .use( - "initializeExchange", - ordered - ? token0Instance.address.toString() - : token1Instance.address.toString(), - 0, - ordered - ? token1Instance.address.toString() - : token0Instance.address.toString(), - 0, - initialTokenAmount, - initialTokenAmount - ) - .send(); + if (standard === "FA2FA12") { + operation = await dex.methods + .use( + "initializeExchange", + ordered + ? token0Instance.address.toString() + : token1Instance.address.toString(), + 0, + ordered + ? token1Instance.address.toString() + : token0Instance.address.toString(), + initialTokenAmount, + initialTokenAmount + ) + .send(); + } else { + operation = await dex.methods + .use( + "initializeExchange", + ordered + ? token0Instance.address.toString() + : token1Instance.address.toString(), + 0, + ordered + ? token1Instance.address.toString() + : token0Instance.address.toString(), + 0, + initialTokenAmount, + initialTokenAmount + ) + .send(); + } await operation.confirmation(); } } diff --git a/storage/TTFunctions.js b/storage/TTFunctions.js index 16854336..8557e415 100644 --- a/storage/TTFunctions.js +++ b/storage/TTFunctions.js @@ -34,5 +34,6 @@ let tokenFunctions = [ ]; module.exports.tokenFunctions = { FA12: tokenFunctions, + FA2FA12: tokenFunctions, FA2: tokenFunctions, }; diff --git a/test/helpers/ttdexFA2FA12.ts b/test/helpers/ttdexFA2FA12.ts new file mode 100644 index 00000000..74d41dff --- /dev/null +++ b/test/helpers/ttdexFA2FA12.ts @@ -0,0 +1,262 @@ +import { ContractAbstraction, ContractProvider } from "@taquito/taquito"; +import { BatchOperation } from "@taquito/taquito/dist/types/operations/batch-operation"; +import { TransactionOperation } from "@taquito/taquito/dist/types/operations/transaction-operation"; +import { BigNumber } from "bignumber.js"; +import { TokenFA2 } from "./tokenFA2"; +import { TTDexStorage } from "./types"; +import { getLigo } from "./utils"; +import { execSync } from "child_process"; +import { confirmOperation } from "./confirmation"; + +const standard = process.env.EXCHANGE_TOKEN_STANDARD; + +export class TTDex extends TokenFA2 { + public contract: ContractAbstraction; + public storage: TTDexStorage; + + constructor(contract: ContractAbstraction) { + super(contract); + } + + static async init(dexAddress: string): Promise { + return new TTDex(await tezos.contract.at(dexAddress)); + } + + async updateStorage( + maps: { + tokens?: string[]; + token_to_id?: string[]; + pairs?: string[]; + ledger?: any[]; + dex_lambdas?: number[]; + token_lambdas?: number[]; + } = {} + ): Promise { + const storage: any = await this.contract.storage(); + this.storage = { + pairs_count: storage.storage.pairs_count, + tokens: {}, + token_to_id: {}, + pairs: {}, + ledger: {}, + dex_lambdas: {}, + token_lambdas: {}, + }; + for (let key in maps) { + if (["dex_lambdas", "token_lambdas"].includes(key)) continue; + this.storage[key] = await maps[key].reduce(async (prev, current) => { + try { + return { + ...(await prev), + [key == "ledger" ? current[0] : current]: await storage.storage[ + key + ].get(current), + }; + } catch (ex) { + console.log(ex); + return { + ...(await prev), + }; + } + }, Promise.resolve({})); + } + for (let key in maps) { + if (!["dex_lambdas", "token_lambdas"].includes(key)) continue; + this[key] = await maps[key].reduce(async (prev, current) => { + try { + return { + ...(await prev), + [current]: await storage[key].get(current.toString()), + }; + } catch (ex) { + return { + ...(await prev), + }; + } + }, Promise.resolve({})); + } + } + + async initializeExchange( + tokenAAddress: string, + tokenBAddress: string, + tokenAAmount: number, + tokenBAmount: number, + tokenAid: BigNumber = new BigNumber(0), + tokenBid: BigNumber = new BigNumber(0), + approve: boolean = true + ): Promise { + if (approve) { + await this.approveToken( + tokenAAddress, + tokenAid, + tokenAAmount, + this.contract.address + ); + await this.approveToken( + tokenBAddress, + tokenBid, + tokenBAmount, + this.contract.address + ); + } + const operation = await this.contract.methods + .use( + "initializeExchange", + tokenAAddress, + tokenAid, + tokenBAddress, + tokenBid, + tokenAAmount, + tokenBAmount + ) + .send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + + async tokenToTokenPayment( + tokenAAddress: string, + tokenBAddress: string, + opType: string, + amountIn: number, + minAmountOut: number, + receiver: string, + tokenAid: BigNumber = new BigNumber(0), + tokenBid: BigNumber = new BigNumber(0) + ): Promise { + const operation = await this.contract.methods + .use( + "tokenToTokenPayment", + tokenAAddress, + tokenAid, + tokenBAddress, + tokenBid, + opType, + null, + amountIn, + minAmountOut, + receiver + ) + .send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + + async investLiquidity( + pairId: string, + tokenAAmount: number, + tokenBAmount: number, + minShares: number + ): Promise { + await this.updateStorage({ tokens: [pairId] }); + let pair = this.storage.tokens[pairId]; + await this.approveToken( + pair.token_a_address, + pair.token_a_id, + tokenAAmount, + this.contract.address + ); + await this.approveToken( + pair.token_b_address, + pair.token_b_id, + tokenBAmount, + this.contract.address + ); + const operation = await this.contract.methods + .use( + "investLiquidity", + pair.token_a_address, + pair.token_a_id, + pair.token_b_address, + pair.token_b_id, + tokenAAmount, + tokenBAmount, + minShares + ) + .send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + + async divestLiquidity( + pairId: string, + tokenAAmount: number, + tokenBAmount: number, + sharesBurned: number + ): Promise { + await this.updateStorage({ tokens: [pairId] }); + let pair = this.storage.tokens[pairId]; + const operation = await this.contract.methods + .use( + "divestLiquidity", + pair.token_a_address, + pair.token_a_id, + pair.token_b_address, + pair.token_b_id, + tokenAAmount, + tokenBAmount, + sharesBurned + ) + .send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + + async approveToken( + tokenAddress: string, + tokenId: BigNumber, + tokenAmount: number, + address: string + ): Promise { + await this.updateStorage(); + let token = await tezos.contract.at(tokenAddress); + let operation = await token.methods + .update_operators([ + { + [tokenAmount ? "add_operator" : "remove_operator"]: { + owner: await tezos.signer.publicKeyHash(), + operator: address, + token_id: tokenId, + }, + }, + ]) + .send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + + async setDexFunction(index: number, lambdaName: string): Promise { + let ligo = getLigo(true); + const stdout = execSync( + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetDexFunction(record index =${index}n; func = ${lambdaName}; end)'`, + { maxBuffer: 1024 * 500 } + ); + const operation = await tezos.contract.transfer({ + to: this.contract.address, + amount: 0, + parameter: { + entrypoint: "setDexFunction", + value: JSON.parse(stdout.toString()).args[0].args[0].args[0], + }, + }); + await confirmOperation(tezos, operation.hash); + } + + async setTokenFunction(index: number, lambdaName: string): Promise { + let ligo = getLigo(true); + const stdout = execSync( + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetTokenFunction(record index =${index}n; func = ${lambdaName}; end)'`, + { maxBuffer: 1024 * 500 } + ); + const operation = await tezos.contract.transfer({ + to: this.contract.address, + amount: 0, + parameter: { + entrypoint: "setTokenFunction", + value: JSON.parse(stdout.toString()).args[0].args[0].args[0], + }, + }); + await confirmOperation(tezos, operation.hash); + } +} From ad7d7188412c5325c52fc70044c5a8a2e2aff99b Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 16:32:01 +0300 Subject: [PATCH 14/63] fix order requirement for FA2/FA12 pairs --- contracts/partials/TTMethodDex.ligo | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 03141429..9509fb21 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -72,10 +72,12 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con case p of | InitializeExchange(params) -> { (* check preconditions *) +#if FA2FA12_STANDARD_ENABLED +#else if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; - +#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); const pair : pair_info = res.0; @@ -170,10 +172,13 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this case p of | InitializeExchange(n) -> skip | TokenToTokenPayment(params) -> { +#if FA2FA12_STANDARD_ENABLED +#else (* check preconditions *) if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; +#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -329,11 +334,13 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th | InitializeExchange(n) -> skip | TokenToTokenPayment(n) -> skip | InvestLiquidity(params) -> { - +#if FA2FA12_STANDARD_ENABLED +#else (* check preconditions *) if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; +#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -444,10 +451,13 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenPayment(n) -> skip | InvestLiquidity(n) -> skip | DivestLiquidity(params) -> { +#if FA2FA12_STANDARD_ENABLED +#else (* check preconditions *) if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; +#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); From 49e63eaf776e7f79938bef60c350050d5829cb26 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 17:20:48 +0300 Subject: [PATCH 15/63] update tests --- migrations/2_deploy_token_to_token_dex.js | 2 +- scripts/cli.js | 1 + test/BuyToken.spec.ts | 11 +- test/Default.spec.ts | 328 ++++---- test/DivestLiquidity.spec.ts | 893 +++++++++++----------- test/DivestTTLiquidity.spec.ts | 23 +- test/InitializeExchangeTest.spec.ts | 482 ++++++------ test/InitializeTTExchangeTest.spec.ts | 3 +- test/InvestLiquidity.spec.ts | 613 +++++++-------- test/InvestTTLiquidity.spec.ts | 23 +- test/MetadataStorage.spec.ts | 202 ++--- test/SellToken.spec.ts | 8 +- test/SetFunctionsTest.spec.ts | 169 ++-- test/TezToTokenPayment.spec.ts | 339 ++++---- test/TokenToTezPayment.spec.ts | 333 ++++---- test/TokenToTokenPayment.spec.ts | 307 ++++---- test/Veto.spec.ts | 504 ++++++------ test/Vote.spec.ts | 579 +++++++------- test/helpers/ttContext.ts | 79 +- test/helpers/ttdexFA2FA12.ts | 46 +- test/storage/TTFunctions.ts | 1 + truffle.d.ts | 1 + 22 files changed, 2550 insertions(+), 2397 deletions(-) diff --git a/migrations/2_deploy_token_to_token_dex.js b/migrations/2_deploy_token_to_token_dex.js index 1ab0aa10..e448f259 100644 --- a/migrations/2_deploy_token_to_token_dex.js +++ b/migrations/2_deploy_token_to_token_dex.js @@ -23,7 +23,7 @@ const defaultTokenId = 0; module.exports = async (deployer, network, accounts) => { tezos = new TezosToolkit(tezos.rpc.url); - // if (network === "development") return; + if (network === "development") return; const secretKey = accountsStored.alice.sk.trim(); tezos.setProvider({ config: { diff --git a/scripts/cli.js b/scripts/cli.js index 90613937..c596fc89 100755 --- a/scripts/cli.js +++ b/scripts/cli.js @@ -73,6 +73,7 @@ program .option("-j, --no-json", "The format of output file") .option("-g, --no-dockerized_ligo", "Switch global ligo") .action(function (options) { + if (process.env.EXCHANGE_TOKEN_STANDARD == "FA2FA12") return; let contractName = `./main/Dex${process.env.EXCHANGE_TOKEN_STANDARD}`; exec("mkdir -p " + options.output_dir); if (contractName === "*") { diff --git a/test/BuyToken.spec.ts b/test/BuyToken.spec.ts index e77904eb..f83f4109 100644 --- a/test/BuyToken.spec.ts +++ b/test/BuyToken.spec.ts @@ -3,8 +3,12 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("BuyToken()", function () { + if (standard === "FA2FA12") { + this.skip(); + } let context: TTContext; const tokenAAmount: number = 100000; const tokenBAmount: number = 1000; @@ -21,7 +25,7 @@ contract("BuyToken()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; @@ -49,9 +53,8 @@ contract("BuyToken()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo diff --git a/test/Default.spec.ts b/test/Default.spec.ts index cac8c290..e892a0d1 100644 --- a/test/Default.spec.ts +++ b/test/Default.spec.ts @@ -4,185 +4,193 @@ import BigNumber from "bignumber.js"; import { calculateFee, bakeBlocks } from "./helpers/utils"; import accounts from "./accounts/accounts"; import { defaultAccountInfo, accuracy } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("Default()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; +if (standard !== "FA2FA12") { + contract("Default()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(3, "withdraw_profit"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - await context.setDexFactoryFunction(8, "receive_reward"); - pairAddress = await context.createPair({ - tezAmount: 100, - tokenAmount: 100, + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(3, "withdraw_profit"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + await context.setDexFactoryFunction(8, "receive_reward"); + pairAddress = await context.createPair({ + tezAmount: 100, + tokenAmount: 100, + }); + tokenAddress = await context.pairs[0].contract.address; }); - tokenAddress = await context.pairs[0].contract.address; - }); - - function defaultSuccessCase(decription, sender, wait, amount) { - it(decription, async function () { - await context.updateActor(sender); - await context.pairs[0].updateStorage(); - const initRewardInfo = context.pairs[0].storage; - const initTotalSupply = context.pairs[0].storage.total_supply; - if (wait) { - await bakeBlocks(wait); - } - await context.pairs[0].sendReward(amount); - await context.pairs[0].updateStorage(); - const finalRewardInfo = context.pairs[0].storage; - const accumulatedReward = new BigNumber(1) - .multipliedBy( - Math.floor( - (Date.parse(finalRewardInfo.last_update_time) - - Date.parse(initRewardInfo.last_update_time)) / - 1000 - ) - ) - .multipliedBy(initRewardInfo.reward_per_sec); - if (initRewardInfo.period_finish == finalRewardInfo.period_finish) { - strictEqual( - finalRewardInfo.reward.toString(), - initRewardInfo.reward.plus(amount).toString() - ); - strictEqual( - finalRewardInfo.reward.toNumber(), - initRewardInfo.reward.toNumber() + amount - ); - strictEqual( - finalRewardInfo.reward_per_share.toString(), - initRewardInfo.reward_per_share - .plus( - accumulatedReward - .div(initRewardInfo.total_supply) - .integerValue(BigNumber.ROUND_DOWN) - ) - .toString() - ); - strictEqual( - finalRewardInfo.total_reward.toNumber(), - initRewardInfo.total_reward.toNumber() - ); - } else { - strictEqual(finalRewardInfo.reward.toString(), amount.toString()); - const periodDuration = - (Math.floor( - Math.floor( - (Date.parse(finalRewardInfo.last_update_time) - - Date.parse(initRewardInfo.period_finish)) / - 1000 - ) / 10 - ) + - 1) * - 10; - strictEqual( - finalRewardInfo.reward_per_sec.toString(), - initRewardInfo.reward - .multipliedBy(accuracy) - .div(periodDuration) - .integerValue(BigNumber.ROUND_DOWN) - .toString() - ); - strictEqual( - finalRewardInfo.total_reward.toString(), - finalRewardInfo.reward_per_sec - .multipliedBy(periodDuration) - .div(accuracy) - .integerValue(BigNumber.ROUND_DOWN) - .plus(initRewardInfo.total_reward) - .toString() - ); + function defaultSuccessCase(decription, sender, wait, amount) { + it(decription, async function () { + await context.updateActor(sender); + await context.pairs[0].updateStorage(); + const initRewardInfo = context.pairs[0].storage; + const initTotalSupply = context.pairs[0].storage.total_supply; + if (wait) { + await bakeBlocks(wait); + } + await context.pairs[0].sendReward(amount); + await context.pairs[0].updateStorage(); + const finalRewardInfo = context.pairs[0].storage; const accumulatedReward = new BigNumber(1) .multipliedBy( Math.floor( - (Date.parse(initRewardInfo.period_finish) - + (Date.parse(finalRewardInfo.last_update_time) - Date.parse(initRewardInfo.last_update_time)) / 1000 ) ) .multipliedBy(initRewardInfo.reward_per_sec); - const accumulatedThisCycleReward = new BigNumber(1) - .multipliedBy( - Math.floor( - (Date.parse(finalRewardInfo.last_update_time) - - Date.parse(initRewardInfo.period_finish)) / - 1000 - ) - ) - .multipliedBy(finalRewardInfo.reward_per_sec); - strictEqual( - finalRewardInfo.reward_per_share.toString(), - initRewardInfo.reward_per_share - .plus( - accumulatedReward - .div(initRewardInfo.total_supply) - .integerValue(BigNumber.ROUND_DOWN) + if (initRewardInfo.period_finish == finalRewardInfo.period_finish) { + strictEqual( + finalRewardInfo.reward.toString(), + initRewardInfo.reward.plus(amount).toString() + ); + + strictEqual( + finalRewardInfo.reward.toNumber(), + initRewardInfo.reward.toNumber() + amount + ); + strictEqual( + finalRewardInfo.reward_per_share.toString(), + initRewardInfo.reward_per_share + .plus( + accumulatedReward + .div(initRewardInfo.total_supply) + .integerValue(BigNumber.ROUND_DOWN) + ) + .toString() + ); + strictEqual( + finalRewardInfo.total_reward.toNumber(), + initRewardInfo.total_reward.toNumber() + ); + } else { + strictEqual(finalRewardInfo.reward.toString(), amount.toString()); + const periodDuration = + (Math.floor( + Math.floor( + (Date.parse(finalRewardInfo.last_update_time) - + Date.parse(initRewardInfo.period_finish)) / + 1000 + ) / 10 + ) + + 1) * + 10; + strictEqual( + finalRewardInfo.reward_per_sec.toString(), + initRewardInfo.reward + .multipliedBy(accuracy) + .div(periodDuration) + .integerValue(BigNumber.ROUND_DOWN) + .toString() + ); + strictEqual( + finalRewardInfo.total_reward.toString(), + finalRewardInfo.reward_per_sec + .multipliedBy(periodDuration) + .div(accuracy) + .integerValue(BigNumber.ROUND_DOWN) + .plus(initRewardInfo.total_reward) + .toString() + ); + const accumulatedReward = new BigNumber(1) + .multipliedBy( + Math.floor( + (Date.parse(initRewardInfo.period_finish) - + Date.parse(initRewardInfo.last_update_time)) / + 1000 + ) ) - .plus( - accumulatedThisCycleReward - .div(initRewardInfo.total_supply) - .integerValue(BigNumber.ROUND_DOWN) + .multipliedBy(initRewardInfo.reward_per_sec); + const accumulatedThisCycleReward = new BigNumber(1) + .multipliedBy( + Math.floor( + (Date.parse(finalRewardInfo.last_update_time) - + Date.parse(initRewardInfo.period_finish)) / + 1000 + ) ) - .toString() - ); - } - }); - } - - function defaultFailCase(decription, sender, amount, errorMsg) { - it(decription, async function () { - await context.updateActor(sender); - await rejects(context.pairs[0].sendReward(amount), (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; + .multipliedBy(finalRewardInfo.reward_per_sec); + strictEqual( + finalRewardInfo.reward_per_share.toString(), + initRewardInfo.reward_per_share + .plus( + accumulatedReward + .div(initRewardInfo.total_supply) + .integerValue(BigNumber.ROUND_DOWN) + ) + .plus( + accumulatedThisCycleReward + .div(initRewardInfo.total_supply) + .integerValue(BigNumber.ROUND_DOWN) + ) + .toString() + ); + } }); - }); - } + } - describe("Test the rewards assessment in case of differrent shares", () => { - defaultSuccessCase("success in case of some shares", "alice", 0, 1000); - describe("", async function () { - before(async function () { - await context.updateActor("alice"); - await context.pairs[0].divestLiquidity(1, 1, 100); + function defaultFailCase(decription, sender, amount, errorMsg) { + it(decription, async function () { + await context.updateActor(sender); + await rejects(context.pairs[0].sendReward(amount), (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + }); }); - defaultFailCase("revert in case of no shares", "alice", 1000, "DIV by 0"); - after(async function () { - await context.updateActor("alice"); - await context.pairs[0].initializeExchange(100, 100); + } + + describe("Test the rewards assessment in case of differrent shares", () => { + defaultSuccessCase("success in case of some shares", "alice", 0, 1000); + describe("", async function () { + before(async function () { + await context.updateActor("alice"); + await context.pairs[0].divestLiquidity(1, 1, 100); + }); + defaultFailCase( + "revert in case of no shares", + "alice", + 1000, + "DIV by 0" + ); + after(async function () { + await context.updateActor("alice"); + await context.pairs[0].initializeExchange(100, 100); + }); }); }); - }); - describe("Test the rewards assessment in case of differrent reward sent", () => { - defaultSuccessCase("success in case of some xtz sent", "alice", 0, 1000); - defaultSuccessCase("success in case of no xtz sent", "alice", 0, 0); - }); + describe("Test the rewards assessment in case of differrent reward sent", () => { + defaultSuccessCase("success in case of some xtz sent", "alice", 0, 1000); + defaultSuccessCase("success in case of no xtz sent", "alice", 0, 0); + }); - describe("Test the total rewards assessments in case of different periods", () => { - defaultSuccessCase( - "success in case of before period finished", - "alice", - 0, - 1000 - ); - defaultSuccessCase( - "success in case of after period finished", - "alice", - 10, - 1000 - ); - defaultSuccessCase( - "success in case of in the middle of the second period", - "alice", - 0, - 1000 - ); + describe("Test the total rewards assessments in case of different periods", () => { + defaultSuccessCase( + "success in case of before period finished", + "alice", + 0, + 1000 + ); + defaultSuccessCase( + "success in case of after period finished", + "alice", + 10, + 1000 + ); + defaultSuccessCase( + "success in case of in the middle of the second period", + "alice", + 0, + 1000 + ); + }); }); -}); +} diff --git a/test/DivestLiquidity.spec.ts b/test/DivestLiquidity.spec.ts index 03566339..afd95b59 100644 --- a/test/DivestLiquidity.spec.ts +++ b/test/DivestLiquidity.spec.ts @@ -3,480 +3,491 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; +if (standard !== "FA2FA12") { + contract("DivestLiquidity()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + const receivedTezAmount: number = 200; + const receivedTokenAmount: number = 20000; + const burntShares: number = 200; -contract("DivestLiquidity()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; - const receivedTezAmount: number = 200; - const receivedTokenAmount: number = 20000; - const burntShares: number = 200; - - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(1, "tez_to_token"); - await context.setDexFactoryFunction(2, "token_to_tez"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - pairAddress = await context.createPair(); - tokenAddress = await context.pairs[0].contract.address; - }); - - describe("Test if the diivestment is allowed", () => { - const initToken = 1000000; - const initTez = 10000; - - it("revert in case no liquidity is provided", async function () { - await context.pairs[0].divestLiquidity(1, 1, initTez); - await rejects( - context.pairs[0].divestLiquidity(1, 1, burntShares), - (err) => { - ok(err.message == "Dex/not-launched", "Error message mismatch"); - return true; - } - ); - }); - - it("success in case the exchange is launched", async function () { - await context.pairs[0].initializeExchange(initToken, initTez); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].divestLiquidity(1, 1, burntShares); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], - }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() + receivedTokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() + receivedTezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual(pairTokenBalance.toNumber(), initToken - receivedTokenAmount); - strictEqual(pairTezBalance.toNumber(), initTez - receivedTezAmount); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initTez - burntShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initTez - burntShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initTez - receivedTezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initToken - receivedTokenAmount - ); + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(1, "tez_to_token"); + await context.setDexFactoryFunction(2, "token_to_tez"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + pairAddress = await context.createPair(); + tokenAddress = await context.pairs[0].contract.address; }); - }); - describe("Test various burnt shares", () => { - before(async () => {}); + describe("Test if the diivestment is allowed", () => { + const initToken = 1000000; + const initTez = 10000; - it("revert in case of 0 burnt shares", async function () { - await rejects(context.pairs[0].divestLiquidity(1, 1, 0), (err) => { - ok(err.message == "Dex/zero-burn-shares", "Error message mismatch"); - return true; + it("revert in case no liquidity is provided", async function () { + await context.pairs[0].divestLiquidity(1, 1, initTez); + await rejects( + context.pairs[0].divestLiquidity(1, 1, burntShares), + (err) => { + ok(err.message == "Dex/not-launched", "Error message mismatch"); + return true; + } + ); }); - }); - it("revert in case of too high expected burnt shares", async function () { - await rejects(context.pairs[0].divestLiquidity(1, 1, 20000), (err) => { - ok(err.message == "Dex/insufficient-shares", "Error message mismatch"); - return true; + it("success in case the exchange is launched", async function () { + await context.pairs[0].initializeExchange(initToken, initTez); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].divestLiquidity(1, 1, burntShares); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() + receivedTokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() + receivedTezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initToken - receivedTokenAmount + ); + strictEqual(pairTezBalance.toNumber(), initTez - receivedTezAmount); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initTez - burntShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initTez - burntShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initTez - receivedTezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initToken - receivedTokenAmount + ); }); }); - it("success in case of burnt shares of 1", async function () { - const minBurntShares = 10; - const minReceivedTezAmount: number = 10; - const minReceivedTokenAmount: number = 1000; - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].divestLiquidity(1, 1, minBurntShares); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], - }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.ledger[aliceAddress].balance.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - }); + describe("Test various burnt shares", () => { + before(async () => {}); - it("success in case the medium burnt shares", async function () { - const minBurntShares = 100; - const minReceivedTezAmount: number = 100; - const minReceivedTokenAmount: number = 10000; - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].divestLiquidity(1, 1, minBurntShares); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("revert in case of 0 burnt shares", async function () { + await rejects(context.pairs[0].divestLiquidity(1, 1, 0), (err) => { + ok(err.message == "Dex/zero-burn-shares", "Error message mismatch"); + return true; + }); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.ledger[aliceAddress].balance.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - }); - it("success in case of exact burnt shares", async function () { - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const initialStorage = await context.pairs[0].storage; - const minBurntShares = initialStorage.ledger[ - aliceAddress - ].balance.toNumber(); - const minReceivedTezAmount: number = minBurntShares; - const minReceivedTokenAmount: number = minBurntShares * 100; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].divestLiquidity(1, 1, minBurntShares); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("revert in case of too high expected burnt shares", async function () { + await rejects(context.pairs[0].divestLiquidity(1, 1, 20000), (err) => { + ok( + err.message == "Dex/insufficient-shares", + "Error message mismatch" + ); + return true; + }); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - 0 - ); - strictEqual(context.pairs[0].storage.total_supply.toNumber(), 0); - strictEqual(context.pairs[0].storage.tez_pool.toNumber(), 0); - strictEqual(context.pairs[0].storage.token_pool.toNumber(), 0); - }); - }); - describe("Test calculated received amount", () => { - it("revert in case of calculated tez are zero", async function () { - const initToken = 1000000; - const initTez = 100; - const share = 1; - await context.pairs[0].initializeExchange(initToken, initTez); - await context.pairs[0].tokenToTezPayment(100000, 1, bobAddress); - await rejects(context.pairs[0].divestLiquidity(1, 1, share), (err) => { - ok(err.message == "Dex/high-expectation", "Error message mismatch"); - return true; + it("success in case of burnt shares of 1", async function () { + const minBurntShares = 10; + const minReceivedTezAmount: number = 10; + const minReceivedTokenAmount: number = 1000; + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].divestLiquidity(1, 1, minBurntShares); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() + minReceivedTezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.ledger[aliceAddress].balance.toNumber() - + minBurntShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() - minBurntShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); }); - }); - it("revert in case of calculated tokens are zero", async function () { - const initTez = 1000000; - const initToken = 100; - await context.pairs[0].divestLiquidity(1, 1, initToken); - await context.pairs[0].initializeExchange(initToken, initTez); - await context.pairs[0].tezToTokenPayment(1, 100000, bobAddress); - const share = 1; - await rejects(context.pairs[0].divestLiquidity(1, 1, share), (err) => { - ok(err.message == "Dex/high-expectation", "Error message mismatch"); - return true; + it("success in case the medium burnt shares", async function () { + const minBurntShares = 100; + const minReceivedTezAmount: number = 100; + const minReceivedTokenAmount: number = 10000; + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].divestLiquidity(1, 1, minBurntShares); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() + minReceivedTezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.ledger[aliceAddress].balance.toNumber() - + minBurntShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() - minBurntShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); }); - }); - }); - describe("Test expected amount when", () => { - before(async () => { - const initTez = 1000000; - const initToken = 100; - await context.pairs[0].divestLiquidity(1, 1, initTez); - await context.pairs[0].initializeExchange(initToken, initTez); + it("success in case of exact burnt shares", async function () { + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const initialStorage = await context.pairs[0].storage; + const minBurntShares = + initialStorage.ledger[aliceAddress].balance.toNumber(); + const minReceivedTezAmount: number = minBurntShares; + const minReceivedTokenAmount: number = minBurntShares * 100; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].divestLiquidity(1, 1, minBurntShares); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() + minReceivedTezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + 0 + ); + strictEqual(context.pairs[0].storage.total_supply.toNumber(), 0); + strictEqual(context.pairs[0].storage.tez_pool.toNumber(), 0); + strictEqual(context.pairs[0].storage.token_pool.toNumber(), 0); + }); }); - it("revert in case of expected tez are higher", async function () { - const share = 100; - await rejects( - context.pairs[0].divestLiquidity(1, 100000000, share), - (err) => { + describe("Test calculated received amount", () => { + it("revert in case of calculated tez are zero", async function () { + const initToken = 1000000; + const initTez = 100; + const share = 1; + await context.pairs[0].initializeExchange(initToken, initTez); + await context.pairs[0].tokenToTezPayment(100000, 1, bobAddress); + await rejects(context.pairs[0].divestLiquidity(1, 1, share), (err) => { ok(err.message == "Dex/high-expectation", "Error message mismatch"); return true; - } - ); - }); + }); + }); - it("revert in case of expected tokens are higher", async function () { - const share = 100; - await rejects( - context.pairs[0].divestLiquidity(100000000, 1, share), - (err) => { + it("revert in case of calculated tokens are zero", async function () { + const initTez = 1000000; + const initToken = 100; + await context.pairs[0].divestLiquidity(1, 1, initToken); + await context.pairs[0].initializeExchange(initToken, initTez); + await context.pairs[0].tezToTokenPayment(1, 100000, bobAddress); + const share = 1; + await rejects(context.pairs[0].divestLiquidity(1, 1, share), (err) => { ok(err.message == "Dex/high-expectation", "Error message mismatch"); return true; - } - ); + }); + }); }); - it("revert in case of expected tokens are 0", async function () { - const share = 1; - await rejects(context.pairs[0].divestLiquidity(0, 1, share), (err) => { - ok(err.message == "Dex/dust-output", "Error message mismatch"); - return true; + describe("Test expected amount when", () => { + before(async () => { + const initTez = 1000000; + const initToken = 100; + await context.pairs[0].divestLiquidity(1, 1, initTez); + await context.pairs[0].initializeExchange(initToken, initTez); }); - }); - it("revert in case of expected tez are 0", async function () { - const share = 1; - await rejects(context.pairs[0].divestLiquidity(1, 0, share), (err) => { - ok(err.message == "Dex/dust-output", "Error message mismatch"); - return true; + it("revert in case of expected tez are higher", async function () { + const share = 100; + await rejects( + context.pairs[0].divestLiquidity(1, 100000000, share), + (err) => { + ok(err.message == "Dex/high-expectation", "Error message mismatch"); + return true; + } + ); }); - }); - it("success in case the of the expected amount smaller than calculated", async function () { - const expectedTez = 100000; - const expectedToken = 10; - const minBurntShares = 500000; - const minReceivedTezAmount: number = 500000; - const minReceivedTokenAmount: number = 50; - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].divestLiquidity( - expectedToken, - expectedTez, - minBurntShares - ); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("revert in case of expected tokens are higher", async function () { + const share = 100; + await rejects( + context.pairs[0].divestLiquidity(100000000, 1, share), + (err) => { + ok(err.message == "Dex/high-expectation", "Error message mismatch"); + return true; + } + ); + }); + + it("revert in case of expected tokens are 0", async function () { + const share = 1; + await rejects(context.pairs[0].divestLiquidity(0, 1, share), (err) => { + ok(err.message == "Dex/dust-output", "Error message mismatch"); + return true; + }); + }); + + it("revert in case of expected tez are 0", async function () { + const share = 1; + await rejects(context.pairs[0].divestLiquidity(1, 0, share), (err) => { + ok(err.message == "Dex/dust-output", "Error message mismatch"); + return true; + }); + }); + + it("success in case the of the expected amount smaller than calculated", async function () { + const expectedTez = 100000; + const expectedToken = 10; + const minBurntShares = 500000; + const minReceivedTezAmount: number = 500000; + const minReceivedTokenAmount: number = 50; + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].divestLiquidity( + expectedToken, + expectedTez, + minBurntShares + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() + minReceivedTezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.ledger[aliceAddress].balance.toNumber() - + minBurntShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() - minBurntShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.ledger[aliceAddress].balance.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - }); - it("success in case the of the exact expected tez and tokens", async function () { - const minBurntShares = 500000; - const minReceivedTezAmount: number = 500000; - const minReceivedTokenAmount: number = 50; - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].divestLiquidity( - minReceivedTokenAmount, - minReceivedTezAmount, - minBurntShares - ); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("success in case the of the exact expected tez and tokens", async function () { + const minBurntShares = 500000; + const minReceivedTezAmount: number = 500000; + const minReceivedTokenAmount: number = 50; + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].divestLiquidity( + minReceivedTokenAmount, + minReceivedTezAmount, + minBurntShares + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() + minReceivedTezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.ledger[aliceAddress].balance.toNumber() - + minBurntShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() - minBurntShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() - minReceivedTezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() - minReceivedTokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() + minReceivedTokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() + minReceivedTezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.ledger[aliceAddress].balance.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() - minBurntShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() - minReceivedTezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() - minReceivedTokenAmount - ); }); }); -}); +} diff --git a/test/DivestTTLiquidity.spec.ts b/test/DivestTTLiquidity.spec.ts index e2f72589..9fd8e44d 100644 --- a/test/DivestTTLiquidity.spec.ts +++ b/test/DivestTTLiquidity.spec.ts @@ -3,6 +3,7 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("DivestTTLiquidity()", function () { let context: TTContext; @@ -25,7 +26,7 @@ contract("DivestTTLiquidity()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; @@ -68,9 +69,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -176,9 +176,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -268,9 +267,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -360,9 +358,8 @@ contract("DivestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo diff --git a/test/InitializeExchangeTest.spec.ts b/test/InitializeExchangeTest.spec.ts index 58f8cd8a..b21fd990 100644 --- a/test/InitializeExchangeTest.spec.ts +++ b/test/InitializeExchangeTest.spec.ts @@ -2,256 +2,282 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("InitializeExchange()", function () { - let context: Context; - let aliceAddress: string = accounts.alice.pkh; - const tezAmount: number = 10000; - const tokenAmount: number = 1000; +if (standard !== "FA2FA12") { + contract("InitializeExchange()", function () { + let context: Context; + let aliceAddress: string = accounts.alice.pkh; + const tezAmount: number = 10000; + const tokenAmount: number = 1000; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - }); - - it("should have an empty token list after deployment", async function () { - await context.factory.updateStorage(); - strictEqual(context.factory.storage.token_list.length, 0); - }); - - describe("Test initialize during the deployment", () => { - let tokenAddress: string; before(async () => { - tokenAddress = await context.createToken(); - }); - - it("revert in case the amount of tokens isn't approved", async function () { - await rejects( - context.factory.launchExchange( - tokenAddress, - tokenAmount, - tezAmount, - false - ), - (err) => { - ok( - err.message == "FA2_NOT_OPERATOR" || - err.message == "NotEnoughAllowance", - "Error message mismatch" - ); - return true; - } - ); + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(5, "divest_liquidity"); }); - it("revert in case the amount of XTZ is zero", async function () { - await rejects( - context.createPair({ - tokenAddress, - tezAmount: 0, - tokenAmount, - }), - (err) => { - strictEqual(err.message, "Dex/not-allowed", "Error message mismatch"); - return true; - } - ); + it("should have an empty token list after deployment", async function () { + await context.factory.updateStorage(); + strictEqual(context.factory.storage.token_list.length, 0); }); - it("revert in case the amount of tokens is zero", async function () { - await rejects( - context.createPair({ - tokenAddress, - tezAmount, - tokenAmount: 0, - }), - (err) => { - strictEqual(err.message, "Dex/not-allowed", "Error message mismatch"); - return true; - } - ); - }); + describe("Test initialize during the deployment", () => { + let tokenAddress: string; + before(async () => { + tokenAddress = await context.createToken(); + }); - it("success in case the pair doesn't exist", async function () { - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - const pairAddress = await context.createPair({ - tokenAddress, - tezAmount, - tokenAmount, + it("revert in case the amount of tokens isn't approved", async function () { + await rejects( + context.factory.launchExchange( + tokenAddress, + tokenAmount, + tezAmount, + false + ), + (err) => { + ok( + err.message == "FA2_NOT_OPERATOR" || + err.message == "NotEnoughAllowance", + "Error message mismatch" + ); + return true; + } + ); }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + + it("revert in case the amount of XTZ is zero", async function () { + await rejects( + context.createPair({ + tokenAddress, + tezAmount: 0, + tokenAmount, + }), + (err) => { + strictEqual( + err.message, + "Dex/not-allowed", + "Error message mismatch" + ); + return true; + } + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual(pairTokenBalance.toNumber(), tokenAmount); - strictEqual(pairTezBalance.toNumber(), tezAmount); - strictEqual(context.factory.storage.token_list.length, 1); - notStrictEqual( - context.factory.storage.token_to_exchange[ - context.factory.storage.token_list[0] - ], - null - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - tezAmount - ); - strictEqual(context.pairs[0].storage.total_supply.toNumber(), tezAmount); - strictEqual(context.pairs[0].storage.tez_pool.toNumber(), tezAmount); - strictEqual(context.pairs[0].storage.token_pool.toNumber(), tokenAmount); - strictEqual(context.pairs[0].storage.token_address, tokenAddress); - }); + it("revert in case the amount of tokens is zero", async function () { + await rejects( + context.createPair({ + tokenAddress, + tezAmount, + tokenAmount: 0, + }), + (err) => { + strictEqual( + err.message, + "Dex/not-allowed", + "Error message mismatch" + ); + return true; + } + ); + }); - it("revert in case the pair exists", async function () { - const tokenAddress = context.factory.storage.token_list[0]; - await rejects( - context.createPair({ + it("success in case the pair doesn't exist", async function () { + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const pairAddress = await context.createPair({ tokenAddress, tezAmount, tokenAmount, - }), - (err) => { - strictEqual( - err.message, - "Factory/exchange-launched", - "Error message mismatch" - ); - return true; - } - ); - }); - }); + }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); - describe("Test initialize after liquidity withdrawn when", () => { - it("revert in case liquidity isn't zero", async function () { - await rejects( - context.pairs[0].initializeExchange(tokenAmount, tezAmount), - (err) => { - strictEqual( - err.message, - "Dex/non-zero-reserves", - "Error message mismatch" - ); - return true; - } - ); - }); + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual(pairTokenBalance.toNumber(), tokenAmount); + strictEqual(pairTezBalance.toNumber(), tezAmount); + strictEqual(context.factory.storage.token_list.length, 1); + notStrictEqual( + context.factory.storage.token_to_exchange[ + context.factory.storage.token_list[0] + ], + null + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + tezAmount + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + tezAmount + ); + strictEqual(context.pairs[0].storage.tez_pool.toNumber(), tezAmount); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + tokenAmount + ); + strictEqual(context.pairs[0].storage.token_address, tokenAddress); + }); - it("revert in case the amount of tokens isn't approved", async function () { - await context.pairs[0].divestLiquidity(1, 1, tezAmount); - await context.pairs[0].approveToken(0, context.pairs[0].contract.address); - await rejects( - context.pairs[0].initializeExchange(tokenAmount, tezAmount, false), - (err) => { - ok( - err.message == "FA2_NOT_OPERATOR" || - err.message == "NotEnoughAllowance", - "Error message mismatch" - ); - return true; - } - ); + it("revert in case the pair exists", async function () { + const tokenAddress = context.factory.storage.token_list[0]; + await rejects( + context.createPair({ + tokenAddress, + tezAmount, + tokenAmount, + }), + (err) => { + strictEqual( + err.message, + "Factory/exchange-launched", + "Error message mismatch" + ); + return true; + } + ); + }); }); - it("success in case liquidity is zero", async function () { - const tokenAddress = context.tokens[0].contract.address; - const pairAddress = context.pairs[0].contract.address; - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].initializeExchange(tokenAmount, tezAmount); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + describe("Test initialize after liquidity withdrawn when", () => { + it("revert in case liquidity isn't zero", async function () { + await rejects( + context.pairs[0].initializeExchange(tokenAmount, tezAmount), + (err) => { + strictEqual( + err.message, + "Dex/non-zero-reserves", + "Error message mismatch" + ); + return true; + } + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual(pairTokenBalance.toNumber(), tokenAmount); - strictEqual(pairTezBalance.toNumber(), tezAmount); - strictEqual(context.factory.storage.token_list.length, 1); - notStrictEqual( - context.factory.storage.token_to_exchange[ - context.factory.storage.token_list[0] - ], - null - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - tezAmount - ); - strictEqual(context.pairs[0].storage.total_supply.toNumber(), tezAmount); - strictEqual(context.pairs[0].storage.tez_pool.toNumber(), tezAmount); - strictEqual(context.pairs[0].storage.token_pool.toNumber(), tokenAmount); - strictEqual(context.pairs[0].storage.token_address, tokenAddress); - }); + it("revert in case the amount of tokens isn't approved", async function () { + await context.pairs[0].divestLiquidity(1, 1, tezAmount); + await context.pairs[0].approveToken( + 0, + context.pairs[0].contract.address + ); + await rejects( + context.pairs[0].initializeExchange(tokenAmount, tezAmount, false), + (err) => { + ok( + err.message == "FA2_NOT_OPERATOR" || + err.message == "NotEnoughAllowance", + "Error message mismatch" + ); + return true; + } + ); + }); - it("revert in case the amount of token is zero", async function () { - await rejects( - context.pairs[0].initializeExchange(0, tezAmount), - (err) => { - strictEqual( - err.message, - "Dex/non-zero-reserves", - "Error message mismatch" - ); - return true; - } - ); - }); + it("success in case liquidity is zero", async function () { + const tokenAddress = context.tokens[0].contract.address; + const pairAddress = context.pairs[0].contract.address; + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].initializeExchange(tokenAmount, tezAmount); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual(pairTokenBalance.toNumber(), tokenAmount); + strictEqual(pairTezBalance.toNumber(), tezAmount); + strictEqual(context.factory.storage.token_list.length, 1); + notStrictEqual( + context.factory.storage.token_to_exchange[ + context.factory.storage.token_list[0] + ], + null + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + tezAmount + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + tezAmount + ); + strictEqual(context.pairs[0].storage.tez_pool.toNumber(), tezAmount); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + tokenAmount + ); + strictEqual(context.pairs[0].storage.token_address, tokenAddress); + }); - it("revert in case the amount of XTZ is zero", async function () { - await rejects( - context.pairs[0].initializeExchange(tokenAmount, 0), - (err) => { - strictEqual( - err.message, - "Dex/non-zero-reserves", - "Error message mismatch" - ); - return true; - } - ); + it("revert in case the amount of token is zero", async function () { + await rejects( + context.pairs[0].initializeExchange(0, tezAmount), + (err) => { + strictEqual( + err.message, + "Dex/non-zero-reserves", + "Error message mismatch" + ); + return true; + } + ); + }); + + it("revert in case the amount of XTZ is zero", async function () { + await rejects( + context.pairs[0].initializeExchange(tokenAmount, 0), + (err) => { + strictEqual( + err.message, + "Dex/non-zero-reserves", + "Error message mismatch" + ); + return true; + } + ); + }); }); }); -}); +} diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index 70e92539..abaa7650 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -2,6 +2,7 @@ import { TTContext } from "./helpers/ttContext"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("InitializeTTExchange()", function () { let context: TTContext; @@ -26,7 +27,7 @@ contract("InitializeTTExchange()", function () { before(async () => { tokenAAddress = await context.createToken(); tokenBAddress = await context.createToken(); - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/InvestLiquidity.spec.ts b/test/InvestLiquidity.spec.ts index 5095fb34..305c2090 100644 --- a/test/InvestLiquidity.spec.ts +++ b/test/InvestLiquidity.spec.ts @@ -2,327 +2,338 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("InvestLiquidity()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; - const tezAmount: number = 1000; - const tokenAmount: number = 100000; - const newShares: number = 1000; +if (standard !== "FA2FA12") { + contract("InvestLiquidity()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + const tezAmount: number = 1000; + const tokenAmount: number = 100000; + const newShares: number = 1000; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(1, "tez_to_token"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - pairAddress = await context.createPair(); - tokenAddress = await context.pairs[0].contract.address; - }); + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(1, "tez_to_token"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + pairAddress = await context.createPair(); + tokenAddress = await context.pairs[0].contract.address; + }); - describe("Test if the investment is allowed", () => { - const initToken = 1000000; - const initTez = 10000; + describe("Test if the investment is allowed", () => { + const initToken = 1000000; + const initTez = 10000; - before(async () => {}); + before(async () => {}); - it("revert in case no liquidity is provided", async function () { - await context.pairs[0].divestLiquidity(1, 1, initTez); - await rejects( - context.pairs[0].investLiquidity(tokenAmount, tezAmount, newShares), - (err) => { - ok(err.message == "Dex/not-launched", "Error message mismatch"); - return true; - } - ); - }); + it("revert in case no liquidity is provided", async function () { + await context.pairs[0].divestLiquidity(1, 1, initTez); + await rejects( + context.pairs[0].investLiquidity(tokenAmount, tezAmount, newShares), + (err) => { + ok(err.message == "Dex/not-launched", "Error message mismatch"); + return true; + } + ); + }); - it("success in case the exchange is launched", async function () { - await context.pairs[0].initializeExchange(initToken, initTez); - await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].investLiquidity(tokenAmount, tezAmount, newShares); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("success in case the exchange is launched", async function () { + await context.pairs[0].initializeExchange(initToken, initTez); + await context.tokens[0].updateStorage({ ledger: [aliceAddress] }); + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].investLiquidity( + tokenAmount, + tezAmount, + newShares + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual(pairTokenBalance.toNumber(), initToken + tokenAmount); + strictEqual(pairTezBalance.toNumber(), initTez + tezAmount); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initTez + newShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initTez + newShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initTez + tezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initToken + tokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual(pairTokenBalance.toNumber(), initToken + tokenAmount); - strictEqual(pairTezBalance.toNumber(), initTez + tezAmount); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initTez + newShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initTez + newShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initTez + tezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initToken + tokenAmount - ); }); - }); - describe("Test various min shared", () => { - before(async () => {}); + describe("Test various min shared", () => { + before(async () => {}); - it("success in case of min shares of 1", async function () { - await context.pairs[0].updateStorage(); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].investLiquidity(tokenAmount, tezAmount, 1); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("success in case of min shares of 1", async function () { + await context.pairs[0].updateStorage(); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].investLiquidity(tokenAmount, tezAmount, 1); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - }); - it("success in case of exact min shares", async function () { - await context.pairs[0].updateStorage(); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].investLiquidity(tokenAmount, tezAmount, 100); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("success in case of exact min shares", async function () { + await context.pairs[0].updateStorage(); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].investLiquidity(tokenAmount, tezAmount, 100); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - }); - it("success in case of medium min shares", async function () { - await context.pairs[0].updateStorage(); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].investLiquidity(tokenAmount, tezAmount, 50); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("success in case of medium min shares", async function () { + await context.pairs[0].updateStorage(); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].investLiquidity(tokenAmount, tezAmount, 50); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); }); - }); - describe("Test purchased shares", () => { - before(async () => {}); + describe("Test purchased shares", () => { + before(async () => {}); - it("success in case of more then 0 tokens purchesed", async function () { - await context.pairs[0].updateStorage(); - const initialStorage = await context.pairs[0].storage; - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenBalance = ( - (await context.tokens[0].storage.ledger[aliceAddress]) || - defaultAccountInfo - ).balance; - await context.pairs[0].investLiquidity(tokenAmount, tezAmount, newShares); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, pairAddress], + it("success in case of more then 0 tokens purchesed", async function () { + await context.pairs[0].updateStorage(); + const initialStorage = await context.pairs[0].storage; + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenBalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + await context.pairs[0].investLiquidity( + tokenAmount, + tezAmount, + newShares + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + aliceInitTokenBalance.toNumber() - tokenAmount, + aliceFinalTokenBalance.toNumber() + ); + ok( + aliceInitTezBalance.toNumber() - tezAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); + strictEqual( + pairTezBalance.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.total_supply.toNumber(), + initialStorage.total_supply.toNumber() + newShares + ); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + initialStorage.tez_pool.toNumber() + tezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + initialStorage.token_pool.toNumber() + tokenAmount + ); }); - await context.pairs[0].updateStorage({ ledger: [aliceAddress] }); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceFinalTokenBalance = await context.tokens[0].storage.ledger[ - aliceAddress - ].balance; - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - aliceInitTokenBalance.toNumber() - tokenAmount, - aliceFinalTokenBalance.toNumber() - ); - ok( - aliceInitTezBalance.toNumber() - tezAmount >= - aliceFinalTezBalance.toNumber() - ); - strictEqual( - pairTokenBalance.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - strictEqual( - pairTezBalance.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.ledger[aliceAddress].balance.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.total_supply.toNumber(), - initialStorage.total_supply.toNumber() + newShares - ); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - initialStorage.tez_pool.toNumber() + tezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - initialStorage.token_pool.toNumber() + tokenAmount - ); - }); - it("revert in case of 0 purchased shares", async function () { - await context.pairs[0].tezToTokenPayment(1, 100, bobAddress); - await rejects(context.pairs[0].investLiquidity(1, 1, 1), (err) => { - ok(err.message == "Dex/wrong-params", "Error message mismatch"); - return true; + it("revert in case of 0 purchased shares", async function () { + await context.pairs[0].tezToTokenPayment(1, 100, bobAddress); + await rejects(context.pairs[0].investLiquidity(1, 1, 1), (err) => { + ok(err.message == "Dex/wrong-params", "Error message mismatch"); + return true; + }); }); }); }); -}); +} diff --git a/test/InvestTTLiquidity.spec.ts b/test/InvestTTLiquidity.spec.ts index b3fc1465..cb543535 100644 --- a/test/InvestTTLiquidity.spec.ts +++ b/test/InvestTTLiquidity.spec.ts @@ -2,6 +2,7 @@ import { TTContext } from "./helpers/ttContext"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("InvestTTLiquidity()", function () { let context: TTContext; @@ -22,7 +23,7 @@ contract("InvestTTLiquidity()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; @@ -64,9 +65,8 @@ contract("InvestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -160,9 +160,8 @@ contract("InvestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -247,9 +246,8 @@ contract("InvestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo @@ -343,9 +341,8 @@ contract("InvestTTLiquidity()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo diff --git a/test/MetadataStorage.spec.ts b/test/MetadataStorage.spec.ts index 56464083..4ab20577 100644 --- a/test/MetadataStorage.spec.ts +++ b/test/MetadataStorage.spec.ts @@ -4,114 +4,118 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { prepareProviderOptions } from "./helpers/utils"; import { MichelsonMap } from "@taquito/michelson-encoder"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; const CMetadataStorage = artifacts.require("MetadataStorage"); +if (standard !== "FA2FA12") { + contract("MetadataStorage()", function () { + let metadataStorage: Metadata; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + const carolAddress: string = accounts.carol.pkh; -contract("MetadataStorage()", function () { - let metadataStorage: Metadata; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; - const carolAddress: string = accounts.carol.pkh; - - before(async () => { - metadataStorage = await Metadata.init( - (await CMetadataStorage.deployed()).address - ); - }); + before(async () => { + metadataStorage = await Metadata.init( + ( + await CMetadataStorage.deployed() + ).address + ); + }); - function updateOwnerSuccessCase(decription, sender, owner, add) { - it(decription, async function () { - let config = await prepareProviderOptions(sender); - tezos.setProvider(config); - await metadataStorage.updateStorage({}); - const initOwners = metadataStorage.storage.owners; + function updateOwnerSuccessCase(decription, sender, owner, add) { + it(decription, async function () { + let config = await prepareProviderOptions(sender); + tezos.setProvider(config); + await metadataStorage.updateStorage({}); + const initOwners = metadataStorage.storage.owners; - await metadataStorage.updateOwners(add, owner); - await metadataStorage.updateStorage({}); - const updatedOwners = metadataStorage.storage.owners; - strictEqual(updatedOwners.includes(owner), add); - }); - } + await metadataStorage.updateOwners(add, owner); + await metadataStorage.updateStorage({}); + const updatedOwners = metadataStorage.storage.owners; + strictEqual(updatedOwners.includes(owner), add); + }); + } - function updateMetadataSuccessCase(decription, sender, metadata) { - it(decription, async function () { - let config = await prepareProviderOptions(sender); - tezos.setProvider(config); - await metadataStorage.updateMetadata(metadata); - }); - } + function updateMetadataSuccessCase(decription, sender, metadata) { + it(decription, async function () { + let config = await prepareProviderOptions(sender); + tezos.setProvider(config); + await metadataStorage.updateMetadata(metadata); + }); + } - function matadataFailCase(decription, sender, act, errorMsg) { - it(decription, async function () { - let config = await prepareProviderOptions(sender); - tezos.setProvider(config); - await rejects(act(), (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; + function matadataFailCase(decription, sender, act, errorMsg) { + it(decription, async function () { + let config = await prepareProviderOptions(sender); + tezos.setProvider(config); + await rejects(act(), (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + }); }); - }); - } + } - describe("Test update user's status permission", () => { - matadataFailCase( - "revert in case of updating by an unprivileged user", - "bob", - async () => metadataStorage.updateOwners(true, bobAddress), - "MetadataStorage/permision-denied" - ); - updateOwnerSuccessCase( - "success in case of updating owner by one of the owners", - "alice", - bobAddress, - true - ); - }); + describe("Test update user's status permission", () => { + matadataFailCase( + "revert in case of updating by an unprivileged user", + "bob", + async () => metadataStorage.updateOwners(true, bobAddress), + "MetadataStorage/permision-denied" + ); + updateOwnerSuccessCase( + "success in case of updating owner by one of the owners", + "alice", + bobAddress, + true + ); + }); - describe("Test update metadata permission", () => { - matadataFailCase( - "revert in case of updating by an unprivileged user", - "carol", - async () => - metadataStorage.updateMetadata( - MichelsonMap.fromLiteral({ - some: Buffer.from("new", "ascii").toString("hex"), - }) - ), - "MetadataStorage/permision-denied" - ); - updateMetadataSuccessCase( - "success in case of updating metadata by one of the owners", - "alice", - MichelsonMap.fromLiteral({ - some: Buffer.from("new", "ascii").toString("hex"), - }) - ); - }); + describe("Test update metadata permission", () => { + matadataFailCase( + "revert in case of updating by an unprivileged user", + "carol", + async () => + metadataStorage.updateMetadata( + MichelsonMap.fromLiteral({ + some: Buffer.from("new", "ascii").toString("hex"), + }) + ), + "MetadataStorage/permision-denied" + ); + updateMetadataSuccessCase( + "success in case of updating metadata by one of the owners", + "alice", + MichelsonMap.fromLiteral({ + some: Buffer.from("new", "ascii").toString("hex"), + }) + ); + }); - describe("Test owner status update", () => { - updateOwnerSuccessCase( - "success in case of adding existed owner", - "alice", - bobAddress, - true - ); - updateOwnerSuccessCase( - "success in case of removing unexisted owner", - "alice", - carolAddress, - false - ); - updateOwnerSuccessCase( - "success in case of adding unexisted owner", - "alice", - carolAddress, - true - ); - updateOwnerSuccessCase( - "success in case of removing existed owner", - "alice", - carolAddress, - false - ); + describe("Test owner status update", () => { + updateOwnerSuccessCase( + "success in case of adding existed owner", + "alice", + bobAddress, + true + ); + updateOwnerSuccessCase( + "success in case of removing unexisted owner", + "alice", + carolAddress, + false + ); + updateOwnerSuccessCase( + "success in case of adding unexisted owner", + "alice", + carolAddress, + true + ); + updateOwnerSuccessCase( + "success in case of removing existed owner", + "alice", + carolAddress, + false + ); + }); }); -}); +} diff --git a/test/SellToken.spec.ts b/test/SellToken.spec.ts index fb7baad7..686d395f 100644 --- a/test/SellToken.spec.ts +++ b/test/SellToken.spec.ts @@ -3,6 +3,7 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("SellToken()", function () { let context: TTContext; @@ -21,7 +22,7 @@ contract("SellToken()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; @@ -49,9 +50,8 @@ contract("SellToken()", function () { tokens: ["0"], pairs: ["0"], }); - const aliceInitShares = context.dex.storage.ledger[ - aliceAddress - ].balance.toNumber(); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo diff --git a/test/SetFunctionsTest.spec.ts b/test/SetFunctionsTest.spec.ts index a180e317..165fe69c 100644 --- a/test/SetFunctionsTest.spec.ts +++ b/test/SetFunctionsTest.spec.ts @@ -1,102 +1,115 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("SetXFunctions()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; +if (standard !== "FA2FA12") { + contract("SetXFunctions()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; - before(async () => { - context = await Context.init([], false, "alice", false); - pairAddress = await context.createPair(); - tokenAddress = await context.pairs[0].contract.address; - }); + before(async () => { + context = await Context.init([], false, "alice", false); + pairAddress = await context.createPair(); + tokenAddress = await context.pairs[0].contract.address; + }); - describe("Test function with different intention can be set", () => { - it("success in case of setting all exchange functions to Factory", async function () { - // ensure no functions are stored - await context.factory.updateStorage(); - Object.values(context.factory.storage.dex_lambdas).forEach((value) => { - strictEqual(value, null); - }); + describe("Test function with different intention can be set", () => { + it("success in case of setting all exchange functions to Factory", async function () { + // ensure no functions are stored + await context.factory.updateStorage(); + Object.values(context.factory.storage.dex_lambdas).forEach((value) => { + strictEqual(value, null); + }); - // set all Dex functions - await context.setDexFactoryFunctions(); + // set all Dex functions + await context.setDexFactoryFunctions(); - // ensure code added - Object.values(context.factory.storage.dex_lambdas).forEach((value) => { - notStrictEqual(value, null); + // ensure code added + Object.values(context.factory.storage.dex_lambdas).forEach((value) => { + notStrictEqual(value, null); + }); }); - }); - it("success in case of setting all token functions to Factory", async function () { - // ensure no functions are stored - await context.factory.updateStorage(); - Object.values(context.factory.storage.token_lambdas).forEach((value) => { - strictEqual(value, null); - }); + it("success in case of setting all token functions to Factory", async function () { + // ensure no functions are stored + await context.factory.updateStorage(); + Object.values(context.factory.storage.token_lambdas).forEach( + (value) => { + strictEqual(value, null); + } + ); - // set all Token functions - await context.setTokenFactoryFunctions(); + // set all Token functions + await context.setTokenFactoryFunctions(); - // ensure code added - Object.values(context.factory.storage.token_lambdas).forEach((value) => { - notStrictEqual(value, null); + // ensure code added + Object.values(context.factory.storage.token_lambdas).forEach( + (value) => { + notStrictEqual(value, null); + } + ); }); }); - }); - describe("Test function replacement", () => { - it("revert replacement of Dex functions to Factory", async function () { - await rejects( - context.factory.setDexFunction(1, "initialize_exchange"), - (err) => { - strictEqual( - err.message, - "Factory/function-set", - "Error message mismatch" - ); - return true; - } - ); - }); + describe("Test function replacement", () => { + it("revert replacement of Dex functions to Factory", async function () { + await rejects( + context.factory.setDexFunction(1, "initialize_exchange"), + (err) => { + strictEqual( + err.message, + "Factory/function-set", + "Error message mismatch" + ); + return true; + } + ); + }); - it("revert replacement of Token functions to Factory", async function () { - await rejects(context.factory.setTokenFunction(1, "transfer"), (err) => { - strictEqual( - err.message, - "Factory/function-set", - "Error message mismatch" + it("revert replacement of Token functions to Factory", async function () { + await rejects( + context.factory.setTokenFunction(1, "transfer"), + (err) => { + strictEqual( + err.message, + "Factory/function-set", + "Error message mismatch" + ); + return true; + } ); - return true; }); }); - }); - describe("Test functions count", () => { - it("revert adding more than 9 exchange functions to Factory", async function () { - await rejects( - context.factory.setDexFunction(9, "initialize_exchange"), - (err) => { - strictEqual( - err.message, - "Factory/wrong-index", - "Error message mismatch" - ); - return true; - } - ); - }); + describe("Test functions count", () => { + it("revert adding more than 9 exchange functions to Factory", async function () { + await rejects( + context.factory.setDexFunction(9, "initialize_exchange"), + (err) => { + strictEqual( + err.message, + "Factory/wrong-index", + "Error message mismatch" + ); + return true; + } + ); + }); - it("revert adding more than 5 token functions to Factory", async function () { - await rejects(context.factory.setTokenFunction(5, "transfer"), (err) => { - strictEqual( - err.message, - "Factory/wrong-index", - "Error message mismatch" + it("revert adding more than 5 token functions to Factory", async function () { + await rejects( + context.factory.setTokenFunction(5, "transfer"), + (err) => { + strictEqual( + err.message, + "Factory/wrong-index", + "Error message mismatch" + ); + return true; + } ); - return true; }); }); }); -}); +} diff --git a/test/TezToTokenPayment.spec.ts b/test/TezToTokenPayment.spec.ts index 57d2a0be..b3d53faa 100644 --- a/test/TezToTokenPayment.spec.ts +++ b/test/TezToTokenPayment.spec.ts @@ -2,186 +2,193 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("TezToTokenPayment()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; +if (standard !== "FA2FA12") { + contract("TezToTokenPayment()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(1, "tez_to_token"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - pairAddress = await context.createPair({ - tezAmount: 100, - tokenAmount: 100, + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(1, "tez_to_token"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + pairAddress = await context.createPair({ + tezAmount: 100, + tokenAmount: 100, + }); + tokenAddress = await context.pairs[0].contract.address; }); - tokenAddress = await context.pairs[0].contract.address; - }); - function tezToTokenSuccessCase( - decription, - xtzAmount, - tokensAmount, - tokensLeftover - ) { - it(decription, async function () { - const pairAddress = context.pairs[0].contract.address; - await context.tokens[0].updateStorage({ - ledger: [bobAddress, aliceAddress, pairAddress], + function tezToTokenSuccessCase( + decription, + xtzAmount, + tokensAmount, + tokensLeftover + ) { + it(decription, async function () { + const pairAddress = context.pairs[0].contract.address; + await context.tokens[0].updateStorage({ + ledger: [bobAddress, aliceAddress, pairAddress], + }); + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const aliceInitTokenLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceInitTokenBalance = aliceInitTokenLedger + ? aliceInitTokenLedger.balance + : new BigNumber(0); + const bobInitTokenLedger = await context.tokens[0].storage.ledger[ + bobAddress + ]; + const bobInitTokenBalance = bobInitTokenLedger + ? bobInitTokenLedger.balance + : new BigNumber(0); + const prevPairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const prevPairTezBalance = await tezos.tz.getBalance(pairAddress); + await context.pairs[0].updateStorage(); + const prevStorage = context.pairs[0].storage; + await context.tokens[0].updateStorage({ + ledger: [bobAddress, aliceAddress], + }); + await context.pairs[0].tezToTokenPayment( + tokensAmount, + xtzAmount, + bobAddress + ); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, bobAddress, pairAddress], + }); + const aliceFinalTokenLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceFinalTokenBalance = aliceFinalTokenLedger + ? aliceFinalTokenLedger.balance + : new BigNumber(0); + const bobFinalTokenLedger = await context.tokens[0].storage.ledger[ + bobAddress + ]; + const bobFinalTokenBalance = bobFinalTokenLedger + ? bobFinalTokenLedger.balance + : new BigNumber(0); + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + bobInitTokenBalance.toNumber() + tokensAmount + tokensLeftover, + bobFinalTokenBalance.toNumber() + ); + strictEqual( + aliceInitTokenBalance.toNumber(), + aliceFinalTokenBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + prevPairTokenBalance.toNumber() - tokensAmount - tokensLeftover + ); + ok( + aliceInitTezBalance.toNumber() + xtzAmount >= + aliceFinalTezBalance.toNumber() + ); + strictEqual( + pairTezBalance.toNumber(), + prevPairTezBalance.toNumber() + xtzAmount + ); + await context.pairs[0].updateStorage(); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + prevStorage.tez_pool.toNumber() + xtzAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + prevStorage.token_pool.toNumber() - tokensAmount - tokensLeftover + ); }); - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const aliceInitTokenLedger = await context.tokens[0].storage.ledger[ - aliceAddress - ]; - const aliceInitTokenBalance = aliceInitTokenLedger - ? aliceInitTokenLedger.balance - : new BigNumber(0); - const bobInitTokenLedger = await context.tokens[0].storage.ledger[ - bobAddress - ]; - const bobInitTokenBalance = bobInitTokenLedger - ? bobInitTokenLedger.balance - : new BigNumber(0); - const prevPairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const prevPairTezBalance = await tezos.tz.getBalance(pairAddress); - await context.pairs[0].updateStorage(); - const prevStorage = context.pairs[0].storage; - await context.tokens[0].updateStorage({ - ledger: [bobAddress, aliceAddress], + } + + function tezToTokenFailCase(decription, xtzAmount, tokensAmount, errorMsg) { + it(decription, async function () { + await rejects( + context.pairs[0].tezToTokenPayment( + tokensAmount, + xtzAmount, + bobAddress + ), + (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + } + ); }); - await context.pairs[0].tezToTokenPayment( - tokensAmount, - xtzAmount, - bobAddress + } + + describe("Test different amount of XTZ to be swapped", () => { + tezToTokenFailCase( + "revert in case of 0 XTZ to be swapped", + 0, + 1, + "Dex/zero-amount-in" ); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, bobAddress, pairAddress], - }); - const aliceFinalTokenLedger = await context.tokens[0].storage.ledger[ - aliceAddress - ]; - const aliceFinalTokenBalance = aliceFinalTokenLedger - ? aliceFinalTokenLedger.balance - : new BigNumber(0); - const bobFinalTokenLedger = await context.tokens[0].storage.ledger[ - bobAddress - ]; - const bobFinalTokenBalance = bobFinalTokenLedger - ? bobFinalTokenLedger.balance - : new BigNumber(0); - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - bobInitTokenBalance.toNumber() + tokensAmount + tokensLeftover, - bobFinalTokenBalance.toNumber() + tezToTokenFailCase( + "revert in case of 100% of reserves to be swapped", + 100, + 1, + "Dex/high-out" ); - strictEqual( - aliceInitTokenBalance.toNumber(), - aliceFinalTokenBalance.toNumber() + tezToTokenFailCase( + "revert in case of 10000% of reserves to be swapped", + 10000, + 1, + "Dex/high-out" ); - strictEqual( - pairTokenBalance.toNumber(), - prevPairTokenBalance.toNumber() - tokensAmount - tokensLeftover + tezToTokenFailCase( + "revert in case of 1% of reserves to be swapped", + 1, + 1, + "Dex/wrong-min-out" ); - ok( - aliceInitTezBalance.toNumber() + xtzAmount >= - aliceFinalTezBalance.toNumber() + tezToTokenSuccessCase( + "success in case of ~30% of reserves to be swapped", + 31, + 23, + 0 ); - strictEqual( - pairTezBalance.toNumber(), - prevPairTezBalance.toNumber() + xtzAmount + }); + + describe("Test different minimal desirable output amount", () => { + tezToTokenFailCase( + "reevert in case of 0 tokens expected", + 10, + 0, + "Dex/zero-min-amount-out" ); - await context.pairs[0].updateStorage(); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - prevStorage.tez_pool.toNumber() + xtzAmount + tezToTokenFailCase( + "revert in case of too many tokens expected", + 10, + 7, + "Dex/wrong-min-out" ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - prevStorage.token_pool.toNumber() - tokensAmount - tokensLeftover + tezToTokenSuccessCase( + "success in case of exact amount of tokens expected", + 10, + 5, + 0 ); - }); - } - - function tezToTokenFailCase(decription, xtzAmount, tokensAmount, errorMsg) { - it(decription, async function () { - await rejects( - context.pairs[0].tezToTokenPayment(tokensAmount, xtzAmount, bobAddress), - (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; - } + tezToTokenSuccessCase( + "success in case of smaller amount of tokens expected", + 10, + 3, + 1 ); }); - } - - describe("Test different amount of XTZ to be swapped", () => { - tezToTokenFailCase( - "revert in case of 0 XTZ to be swapped", - 0, - 1, - "Dex/zero-amount-in" - ); - tezToTokenFailCase( - "revert in case of 100% of reserves to be swapped", - 100, - 1, - "Dex/high-out" - ); - tezToTokenFailCase( - "revert in case of 10000% of reserves to be swapped", - 10000, - 1, - "Dex/high-out" - ); - tezToTokenFailCase( - "revert in case of 1% of reserves to be swapped", - 1, - 1, - "Dex/wrong-min-out" - ); - tezToTokenSuccessCase( - "success in case of ~30% of reserves to be swapped", - 31, - 23, - 0 - ); - }); - - describe("Test different minimal desirable output amount", () => { - tezToTokenFailCase( - "reevert in case of 0 tokens expected", - 10, - 0, - "Dex/zero-min-amount-out" - ); - tezToTokenFailCase( - "revert in case of too many tokens expected", - 10, - 7, - "Dex/wrong-min-out" - ); - tezToTokenSuccessCase( - "success in case of exact amount of tokens expected", - 10, - 5, - 0 - ); - tezToTokenSuccessCase( - "success in case of smaller amount of tokens expected", - 10, - 3, - 1 - ); }); -}); +} diff --git a/test/TokenToTezPayment.spec.ts b/test/TokenToTezPayment.spec.ts index 5a92778c..db656f54 100644 --- a/test/TokenToTezPayment.spec.ts +++ b/test/TokenToTezPayment.spec.ts @@ -2,182 +2,189 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("TokenToTezPayment()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; +if (standard !== "FA2FA12") { + contract("TokenToTezPayment()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(2, "token_to_tez"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - pairAddress = await context.createPair({ - tezAmount: 100, - tokenAmount: 100, + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(2, "token_to_tez"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + pairAddress = await context.createPair({ + tezAmount: 100, + tokenAmount: 100, + }); + tokenAddress = await context.pairs[0].contract.address; }); - tokenAddress = await context.pairs[0].contract.address; - }); - function tokenToTezSuccessCase( - decription, - xtzAmount, - tokensAmount, - tezLeftover - ) { - it(decription, async function () { - const pairAddress = context.pairs[0].contract.address; - await context.tokens[0].updateStorage({ - ledger: [bobAddress, aliceAddress, pairAddress], - }); - const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); - const bobInitTezBalance = await tezos.tz.getBalance(bobAddress); - const aliceInitTokenLedger = await context.tokens[0].storage.ledger[ - aliceAddress - ]; - const aliceInitTokenBalance = aliceInitTokenLedger - ? aliceInitTokenLedger.balance - : new BigNumber(0); - const prevPairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const prevPairTezBalance = await tezos.tz.getBalance(pairAddress); - await context.pairs[0].updateStorage(); - const prevStorage = context.pairs[0].storage; - await context.tokens[0].updateStorage({ - ledger: [bobAddress, aliceAddress], + function tokenToTezSuccessCase( + decription, + xtzAmount, + tokensAmount, + tezLeftover + ) { + it(decription, async function () { + const pairAddress = context.pairs[0].contract.address; + await context.tokens[0].updateStorage({ + ledger: [bobAddress, aliceAddress, pairAddress], + }); + const aliceInitTezBalance = await tezos.tz.getBalance(aliceAddress); + const bobInitTezBalance = await tezos.tz.getBalance(bobAddress); + const aliceInitTokenLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceInitTokenBalance = aliceInitTokenLedger + ? aliceInitTokenLedger.balance + : new BigNumber(0); + const prevPairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const prevPairTezBalance = await tezos.tz.getBalance(pairAddress); + await context.pairs[0].updateStorage(); + const prevStorage = context.pairs[0].storage; + await context.tokens[0].updateStorage({ + ledger: [bobAddress, aliceAddress], + }); + await context.pairs[0].tokenToTezPayment( + tokensAmount, + xtzAmount, + bobAddress + ); + const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); + const bobFinalTezBalance = await tezos.tz.getBalance(bobAddress); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, bobAddress, pairAddress], + }); + const aliceFinalTokenLedger = await context.tokens[0].storage.ledger[ + aliceAddress + ]; + const aliceFinalTokenBalance = aliceFinalTokenLedger + ? aliceFinalTokenLedger.balance + : new BigNumber(0); + const bobFinalTokenLedger = await context.tokens[0].storage.ledger[ + bobAddress + ]; + const bobFinalTokenBalance = bobFinalTokenLedger + ? bobFinalTokenLedger.balance + : new BigNumber(0); + const pairTokenBalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTezBalance = await tezos.tz.getBalance(pairAddress); + strictEqual( + bobInitTezBalance.toNumber() + xtzAmount, + bobFinalTezBalance.toNumber() + ); + strictEqual( + aliceInitTokenBalance.toNumber() - tokensAmount, + aliceFinalTokenBalance.toNumber() + ); + strictEqual( + pairTokenBalance.toNumber(), + prevPairTokenBalance.toNumber() + tokensAmount + ); + ok(aliceInitTezBalance.toNumber() > aliceFinalTezBalance.toNumber()); + strictEqual( + pairTezBalance.toNumber(), + prevPairTezBalance.toNumber() - xtzAmount - tezLeftover + ); + await context.pairs[0].updateStorage(); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + prevStorage.tez_pool.toNumber() - xtzAmount - tezLeftover + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + prevStorage.token_pool.toNumber() + tokensAmount + ); }); - await context.pairs[0].tokenToTezPayment( - tokensAmount, - xtzAmount, - bobAddress - ); - const aliceFinalTezBalance = await tezos.tz.getBalance(aliceAddress); - const bobFinalTezBalance = await tezos.tz.getBalance(bobAddress); - await context.tokens[0].updateStorage({ - ledger: [aliceAddress, bobAddress, pairAddress], + } + + function tokenToTezFailCase(decription, xtzAmount, tokensAmount, errorMsg) { + it(decription, async function () { + await rejects( + context.pairs[0].tokenToTezPayment( + tokensAmount, + xtzAmount, + bobAddress + ), + (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + } + ); }); - const aliceFinalTokenLedger = await context.tokens[0].storage.ledger[ - aliceAddress - ]; - const aliceFinalTokenBalance = aliceFinalTokenLedger - ? aliceFinalTokenLedger.balance - : new BigNumber(0); - const bobFinalTokenLedger = await context.tokens[0].storage.ledger[ - bobAddress - ]; - const bobFinalTokenBalance = bobFinalTokenLedger - ? bobFinalTokenLedger.balance - : new BigNumber(0); - const pairTokenBalance = await context.tokens[0].storage.ledger[ - pairAddress - ].balance; - const pairTezBalance = await tezos.tz.getBalance(pairAddress); - strictEqual( - bobInitTezBalance.toNumber() + xtzAmount, - bobFinalTezBalance.toNumber() - ); - strictEqual( - aliceInitTokenBalance.toNumber() - tokensAmount, - aliceFinalTokenBalance.toNumber() + } + + describe("Test different amount of tokens to be swapped", () => { + tokenToTezFailCase( + "revert in case of 0 tokens to be swapped", + 1, + 0, + "Dex/zero-amount-in" ); - strictEqual( - pairTokenBalance.toNumber(), - prevPairTokenBalance.toNumber() + tokensAmount + tokenToTezFailCase( + "revert in case of 100% of reserves to be swapped", + 1, + 100, + "Dex/high-out" ); - ok(aliceInitTezBalance.toNumber() > aliceFinalTezBalance.toNumber()); - strictEqual( - pairTezBalance.toNumber(), - prevPairTezBalance.toNumber() - xtzAmount - tezLeftover + + tokenToTezFailCase( + "revert in case of 10000% of reserves to be swapped", + 1, + 10000, + "Dex/high-out" ); - await context.pairs[0].updateStorage(); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - prevStorage.tez_pool.toNumber() - xtzAmount - tezLeftover + + tokenToTezFailCase( + "revert in case of 1% of reserves to be swapped", + 1, + 1, + "Dex/wrong-min-out" ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - prevStorage.token_pool.toNumber() + tokensAmount + + tokenToTezSuccessCase( + "success in case of ~30% of reserves to be swapped", + 23, + 31, + 0 ); }); - } - function tokenToTezFailCase(decription, xtzAmount, tokensAmount, errorMsg) { - it(decription, async function () { - await rejects( - context.pairs[0].tokenToTezPayment(tokensAmount, xtzAmount, bobAddress), - (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; - } + describe("Test different minimal desirable output amount", () => { + tokenToTezFailCase( + "reevert in case of 0 XTZ expected", + 0, + 10, + "Dex/zero-min-amount-out" + ); + tokenToTezFailCase( + "revert in case of too many XTZ expected", + 10, + 10, + "Dex/wrong-min-out" + ); + tokenToTezSuccessCase( + "success in case of exact amount of XTZ expected", + 5, + 11, + 0 + ); + tokenToTezSuccessCase( + "success in case of smaller amount of XTZ expected", + 4, + 10, + 0 ); }); - } - - describe("Test different amount of tokens to be swapped", () => { - tokenToTezFailCase( - "revert in case of 0 tokens to be swapped", - 1, - 0, - "Dex/zero-amount-in" - ); - tokenToTezFailCase( - "revert in case of 100% of reserves to be swapped", - 1, - 100, - "Dex/high-out" - ); - - tokenToTezFailCase( - "revert in case of 10000% of reserves to be swapped", - 1, - 10000, - "Dex/high-out" - ); - - tokenToTezFailCase( - "revert in case of 1% of reserves to be swapped", - 1, - 1, - "Dex/wrong-min-out" - ); - - tokenToTezSuccessCase( - "success in case of ~30% of reserves to be swapped", - 23, - 31, - 0 - ); - }); - - describe("Test different minimal desirable output amount", () => { - tokenToTezFailCase( - "reevert in case of 0 XTZ expected", - 0, - 10, - "Dex/zero-min-amount-out" - ); - tokenToTezFailCase( - "revert in case of too many XTZ expected", - 10, - 10, - "Dex/wrong-min-out" - ); - tokenToTezSuccessCase( - "success in case of exact amount of XTZ expected", - 5, - 11, - 0 - ); - tokenToTezSuccessCase( - "success in case of smaller amount of XTZ expected", - 4, - 10, - 0 - ); }); -}); +} diff --git a/test/TokenToTokenPayment.spec.ts b/test/TokenToTokenPayment.spec.ts index e2c5ffaf..6dc94a55 100644 --- a/test/TokenToTokenPayment.spec.ts +++ b/test/TokenToTokenPayment.spec.ts @@ -1,170 +1,173 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("TokenToTokenPayment()", function () { - let context: Context; - let tokenAddress: string; - const tezInitAmount0 = 10000; - const tezInitAmount1 = 20000; - const tokenInitAmount0 = 1000000; - const tokenInitAmount1 = 10000; +if (standard !== "FA2FA12") { + contract("TokenToTokenPayment()", function () { + let context: Context; + let tokenAddress: string; + const tezInitAmount0 = 10000; + const tezInitAmount1 = 20000; + const tokenInitAmount0 = 1000000; + const tokenInitAmount1 = 10000; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(1, "tez_to_token"); - await context.setDexFactoryFunction(2, "token_to_tez"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - await context.createPairs([ - { tezAmount: tezInitAmount0, tokenAmount: tokenInitAmount0 }, - { tezAmount: tezInitAmount1, tokenAmount: tokenInitAmount1 }, - ]); - tokenAddress = await context.pairs[0].contract.address; - }); + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(1, "tez_to_token"); + await context.setDexFactoryFunction(2, "token_to_tez"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + await context.createPairs([ + { tezAmount: tezInitAmount0, tokenAmount: tokenInitAmount0 }, + { tezAmount: tezInitAmount1, tokenAmount: tokenInitAmount1 }, + ]); + tokenAddress = await context.pairs[0].contract.address; + }); - it("should exchnge token to token and update dex state", async function () { - this.timeout(5000000); - let tokenAmount = 1000; - let middleTezAmount = 9; - let minTokensOut = 4; + it("should exchnge token to token and update dex state", async function () { + this.timeout(5000000); + let tokenAmount = 1000; + let middleTezAmount = 9; + let minTokensOut = 4; - let firstDexContract = context.pairs[0].contract; - let secondDexContract = context.pairs[1].contract; - let aliceAddress = await tezos.signer.publicKeyHash(); + let firstDexContract = context.pairs[0].contract; + let secondDexContract = context.pairs[1].contract; + let aliceAddress = await tezos.signer.publicKeyHash(); - // update keys - await context.updateActor("carol"); - let carolAddress = await tezos.signer.publicKeyHash(); - await context.updateActor("bob"); - let bobAddress = await tezos.signer.publicKeyHash(); - await context.updateActor(); + // update keys + await context.updateActor("carol"); + let carolAddress = await tezos.signer.publicKeyHash(); + await context.updateActor("bob"); + let bobAddress = await tezos.signer.publicKeyHash(); + await context.updateActor(); - // send tokens to bob - await context.tokens[0].transfer(aliceAddress, bobAddress, tokenAmount); - await context.updateActor("bob"); + // send tokens to bob + await context.tokens[0].transfer(aliceAddress, bobAddress, tokenAmount); + await context.updateActor("bob"); - // check initial balance - let bobInitTezBalance = await tezos.tz.getBalance(bobAddress); - await context.tokens[0].updateStorage({ ledger: [bobAddress] }); - await context.tokens[1].updateStorage({ - ledger: [bobAddress, carolAddress], - }); - let bobInitFirstTokenLedger = await context.tokens[0].storage.ledger[ - bobAddress - ]; - let bobInitSecondTokenLedger = await context.tokens[1].storage.ledger[ - bobAddress - ]; - let bobInitFirstTokenBalance = bobInitFirstTokenLedger - ? bobInitFirstTokenLedger.balance - : new BigNumber(0); - let bobInitSecondTokenBalance = bobInitSecondTokenLedger - ? bobInitSecondTokenLedger.balance - : new BigNumber(0); - let carolInitSecondTokenLedger = await context.tokens[1].storage.ledger[ - carolAddress - ]; - let carolInitSecondTokenBalance = carolInitSecondTokenLedger - ? carolInitSecondTokenLedger.balance - : new BigNumber(0); + // check initial balance + let bobInitTezBalance = await tezos.tz.getBalance(bobAddress); + await context.tokens[0].updateStorage({ ledger: [bobAddress] }); + await context.tokens[1].updateStorage({ + ledger: [bobAddress, carolAddress], + }); + let bobInitFirstTokenLedger = await context.tokens[0].storage.ledger[ + bobAddress + ]; + let bobInitSecondTokenLedger = await context.tokens[1].storage.ledger[ + bobAddress + ]; + let bobInitFirstTokenBalance = bobInitFirstTokenLedger + ? bobInitFirstTokenLedger.balance + : new BigNumber(0); + let bobInitSecondTokenBalance = bobInitSecondTokenLedger + ? bobInitSecondTokenLedger.balance + : new BigNumber(0); + let carolInitSecondTokenLedger = await context.tokens[1].storage.ledger[ + carolAddress + ]; + let carolInitSecondTokenBalance = carolInitSecondTokenLedger + ? carolInitSecondTokenLedger.balance + : new BigNumber(0); - // swap tokens liquidity - await context.pairs[0].tokenToTokenPayment( - tokenAmount, - minTokensOut, - secondDexContract, - middleTezAmount, - carolAddress - ); + // swap tokens liquidity + await context.pairs[0].tokenToTokenPayment( + tokenAmount, + minTokensOut, + secondDexContract, + middleTezAmount, + carolAddress + ); - // checks - let bobFinalTezBalance = await tezos.tz.getBalance(bobAddress); - await context.tokens[0].updateStorage({ - ledger: [bobAddress, firstDexContract.address], - }); - await context.tokens[1].updateStorage({ - ledger: [bobAddress, carolAddress, secondDexContract.address], - }); - let bobFinalFirstTokenBalance = await context.tokens[0].storage.ledger[ - bobAddress - ].balance; - let bobFinalSecondTokenLedger = await context.tokens[1].storage.ledger[ - bobAddress - ]; - let bobFinalSecondTokenBalance = bobFinalSecondTokenLedger - ? bobFinalSecondTokenLedger.balance - : new BigNumber(0); + // checks + let bobFinalTezBalance = await tezos.tz.getBalance(bobAddress); + await context.tokens[0].updateStorage({ + ledger: [bobAddress, firstDexContract.address], + }); + await context.tokens[1].updateStorage({ + ledger: [bobAddress, carolAddress, secondDexContract.address], + }); + let bobFinalFirstTokenBalance = await context.tokens[0].storage.ledger[ + bobAddress + ].balance; + let bobFinalSecondTokenLedger = await context.tokens[1].storage.ledger[ + bobAddress + ]; + let bobFinalSecondTokenBalance = bobFinalSecondTokenLedger + ? bobFinalSecondTokenLedger.balance + : new BigNumber(0); - let carolFinalSecondTokenBalance = await context.tokens[1].storage.ledger[ - carolAddress - ].balance; + let carolFinalSecondTokenBalance = await context.tokens[1].storage.ledger[ + carolAddress + ].balance; - let firstPairTokenBalance = await context.tokens[0].storage.ledger[ - firstDexContract.address - ].balance; - let firstPairTezBalance = await tezos.tz.getBalance( - firstDexContract.address - ); - let secondPairTokenBalance = await context.tokens[1].storage.ledger[ - secondDexContract.address - ].balance; - let secondPairTezBalance = await tezos.tz.getBalance( - secondDexContract.address - ); + let firstPairTokenBalance = await context.tokens[0].storage.ledger[ + firstDexContract.address + ].balance; + let firstPairTezBalance = await tezos.tz.getBalance( + firstDexContract.address + ); + let secondPairTokenBalance = await context.tokens[1].storage.ledger[ + secondDexContract.address + ].balance; + let secondPairTezBalance = await tezos.tz.getBalance( + secondDexContract.address + ); - // 1. check tez balances - ok(bobInitTezBalance.toNumber() > bobFinalTezBalance.toNumber()); - strictEqual( - firstPairTezBalance.toNumber(), - tezInitAmount0 - middleTezAmount - ); - strictEqual( - secondPairTezBalance.toNumber(), - tezInitAmount1 + middleTezAmount - ); + // 1. check tez balances + ok(bobInitTezBalance.toNumber() > bobFinalTezBalance.toNumber()); + strictEqual( + firstPairTezBalance.toNumber(), + tezInitAmount0 - middleTezAmount + ); + strictEqual( + secondPairTezBalance.toNumber(), + tezInitAmount1 + middleTezAmount + ); - // 2. check tokens balances - strictEqual( - bobInitFirstTokenBalance.toNumber() - tokenAmount, - bobFinalFirstTokenBalance.toNumber() - ); - strictEqual( - bobInitSecondTokenBalance.toNumber(), - bobFinalSecondTokenBalance.toNumber() - ); - strictEqual( - carolInitSecondTokenBalance.toNumber() + minTokensOut, - carolFinalSecondTokenBalance.toNumber() - ); - strictEqual( - firstPairTokenBalance.toNumber(), - tokenInitAmount0 + tokenAmount - ); - strictEqual( - secondPairTokenBalance.toNumber(), - tokenInitAmount1 - minTokensOut - ); + // 2. check tokens balances + strictEqual( + bobInitFirstTokenBalance.toNumber() - tokenAmount, + bobFinalFirstTokenBalance.toNumber() + ); + strictEqual( + bobInitSecondTokenBalance.toNumber(), + bobFinalSecondTokenBalance.toNumber() + ); + strictEqual( + carolInitSecondTokenBalance.toNumber() + minTokensOut, + carolFinalSecondTokenBalance.toNumber() + ); + strictEqual( + firstPairTokenBalance.toNumber(), + tokenInitAmount0 + tokenAmount + ); + strictEqual( + secondPairTokenBalance.toNumber(), + tokenInitAmount1 - minTokensOut + ); - // 3. new pairs state - await context.pairs[0].updateStorage(); - await context.pairs[1].updateStorage(); - strictEqual( - context.pairs[0].storage.tez_pool.toNumber(), - tezInitAmount0 - middleTezAmount - ); - strictEqual( - context.pairs[0].storage.token_pool.toNumber(), - tokenInitAmount0 + tokenAmount - ); - strictEqual( - context.pairs[1].storage.tez_pool.toNumber(), - tezInitAmount1 + middleTezAmount - ); - strictEqual( - context.pairs[1].storage.token_pool.toNumber(), - tezInitAmount0 - minTokensOut - ); + // 3. new pairs state + await context.pairs[0].updateStorage(); + await context.pairs[1].updateStorage(); + strictEqual( + context.pairs[0].storage.tez_pool.toNumber(), + tezInitAmount0 - middleTezAmount + ); + strictEqual( + context.pairs[0].storage.token_pool.toNumber(), + tokenInitAmount0 + tokenAmount + ); + strictEqual( + context.pairs[1].storage.tez_pool.toNumber(), + tezInitAmount1 + middleTezAmount + ); + strictEqual( + context.pairs[1].storage.token_pool.toNumber(), + tezInitAmount0 - minTokensOut + ); + }); }); -}); +} diff --git a/test/Veto.spec.ts b/test/Veto.spec.ts index 7f2275d3..f84f0487 100644 --- a/test/Veto.spec.ts +++ b/test/Veto.spec.ts @@ -4,267 +4,271 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("Veto()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; - const carolAddress: string = accounts.carol.pkh; +if (standard !== "FA2FA12") { + contract("Veto()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + const carolAddress: string = accounts.carol.pkh; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(6, "vote"); - await context.setDexFactoryFunction(7, "veto"); - await context.factory.setTokenFunction(0, "transfer"); - await context.factory.setTokenFunction( - 1, - standard == "FA2" ? "update_operators" : "approve" - ); - pairAddress = await context.createPair(); - tokenAddress = await context.pairs[0].contract.address; - }); + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(6, "vote"); + await context.setDexFactoryFunction(7, "veto"); + await context.factory.setTokenFunction(0, "transfer"); + await context.factory.setTokenFunction( + 1, + standard == "FA2" ? "update_operators" : "approve" + ); + pairAddress = await context.createPair(); + tokenAddress = await context.pairs[0].contract.address; + }); - function vetoSuccessCase( - decription, - sender, - voter, - vetor, - candidate, - vote, - veto - ) { - it(decription, async function () { - if (vote) { - await context.updateActor(voter); - const voterAddress = accounts[voter].pkh; - await context.pairs[0].vote(voterAddress, candidate, vote); - } - await context.updateActor(sender); - await context.pairs[0].updateStorage(); - const prevDelegated = context.pairs[0].storage.current_delegated; - await context.pairs[0].updateStorage({ - ledger: [vetor], - voters: [vetor], - vetos: [prevDelegated], + function vetoSuccessCase( + decription, + sender, + voter, + vetor, + candidate, + vote, + veto + ) { + it(decription, async function () { + if (vote) { + await context.updateActor(voter); + const voterAddress = accounts[voter].pkh; + await context.pairs[0].vote(voterAddress, candidate, vote); + } + await context.updateActor(sender); + await context.pairs[0].updateStorage(); + const prevDelegated = context.pairs[0].storage.current_delegated; + await context.pairs[0].updateStorage({ + ledger: [vetor], + voters: [vetor], + vetos: [prevDelegated], + }); + const voterInitVoteInfo = context.pairs[0].storage.voters[vetor] || { + candidate: undefined, + vote: new BigNumber(0), + veto: new BigNumber(0), + }; + const voterInitSharesInfo = context.pairs[0].storage.ledger[vetor] || { + balance: new BigNumber(0), + frozen_balance: new BigNumber(0), + allowances: {}, + }; + const voterInitCandidateVotes = + context.pairs[0].storage.votes[candidate] || new BigNumber(0); + const initVotes = context.pairs[0].storage.total_votes; + const initVeto = context.pairs[0].storage.veto; + const prevCurrentCandidate = context.pairs[0].storage.current_candidate; + await context.pairs[0].veto(vetor, veto); + await context.pairs[0].updateStorage({ + ledger: [vetor], + voters: [vetor], + vetos: [candidate, prevDelegated], + }); + const voterFinalVoteInfo = context.pairs[0].storage.voters[vetor] || { + candidate: undefined, + vote: new BigNumber(0), + veto: new BigNumber(0), + }; + const voterFinalSharesInfo = context.pairs[0].storage.ledger[vetor] || { + balance: new BigNumber(0), + frozen_balance: new BigNumber(0), + allowances: {}, + }; + const prevDelegateFinalVetos = context.pairs[0].storage.vetos[ + prevDelegated + ] + ? Date.parse(context.pairs[0].storage.vetos[prevDelegated]) / 1000 + : 0; + const finalVeto = context.pairs[0].storage.veto; + const finalCurrentCandidate = + context.pairs[0].storage.current_candidate; + const finalCurrentDelegated = + context.pairs[0].storage.current_delegated; + strictEqual( + voterFinalSharesInfo.balance.toNumber(), + voterInitSharesInfo.balance.plus(voterInitVoteInfo.veto).toNumber() - + veto + ); + strictEqual( + voterFinalSharesInfo.frozen_balance.toNumber(), + voterInitSharesInfo.frozen_balance + .minus(voterInitVoteInfo.veto) + .toNumber() + veto + ); + if ( + initVeto + .minus(voterInitVoteInfo.veto) + .plus(new BigNumber(veto)) + .gt(initVotes.div(new BigNumber(3))) + ) { + notStrictEqual(prevDelegateFinalVetos, 0); + strictEqual(finalVeto.toNumber(), 0); + strictEqual(finalCurrentCandidate, null); + strictEqual( + finalCurrentDelegated, + prevCurrentCandidate == prevDelegated ? null : prevCurrentCandidate + ); + } else { + strictEqual(prevDelegateFinalVetos, 0); + strictEqual(finalCurrentCandidate, prevCurrentCandidate); + strictEqual(finalCurrentDelegated, prevDelegated); + } + strictEqual(voterFinalVoteInfo.veto.toNumber(), veto); }); - const voterInitVoteInfo = context.pairs[0].storage.voters[vetor] || { - candidate: undefined, - vote: new BigNumber(0), - veto: new BigNumber(0), - }; - const voterInitSharesInfo = context.pairs[0].storage.ledger[vetor] || { - balance: new BigNumber(0), - frozen_balance: new BigNumber(0), - allowances: {}, - }; - const voterInitCandidateVotes = - context.pairs[0].storage.votes[candidate] || new BigNumber(0); - const initVotes = context.pairs[0].storage.total_votes; - const initVeto = context.pairs[0].storage.veto; - const prevCurrentCandidate = context.pairs[0].storage.current_candidate; - await context.pairs[0].veto(vetor, veto); - await context.pairs[0].updateStorage({ - ledger: [vetor], - voters: [vetor], - vetos: [candidate, prevDelegated], + } + + function vetoFailCase(decription, sender, vetor, value, errorMsg) { + it(decription, async function () { + await context.updateActor(sender); + await rejects(context.pairs[0].veto(vetor, value), (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + }); }); - const voterFinalVoteInfo = context.pairs[0].storage.voters[vetor] || { - candidate: undefined, - vote: new BigNumber(0), - veto: new BigNumber(0), - }; - const voterFinalSharesInfo = context.pairs[0].storage.ledger[vetor] || { - balance: new BigNumber(0), - frozen_balance: new BigNumber(0), - allowances: {}, - }; - const prevDelegateFinalVetos = context.pairs[0].storage.vetos[ - prevDelegated - ] - ? Date.parse(context.pairs[0].storage.vetos[prevDelegated]) / 1000 - : 0; - const finalVeto = context.pairs[0].storage.veto; - const finalCurrentCandidate = context.pairs[0].storage.current_candidate; - const finalCurrentDelegated = context.pairs[0].storage.current_delegated; - strictEqual( - voterFinalSharesInfo.balance.toNumber(), - voterInitSharesInfo.balance.plus(voterInitVoteInfo.veto).toNumber() - - veto + } + + describe("Test the user's veto power", () => { + vetoSuccessCase( + "success in case of enough liquid shares", + "alice", + "alice", + aliceAddress, + bobAddress, + 9000, + 500 ); - strictEqual( - voterFinalSharesInfo.frozen_balance.toNumber(), - voterInitSharesInfo.frozen_balance - .minus(voterInitVoteInfo.veto) - .toNumber() + veto + vetoSuccessCase( + "success in case of exactly equal to liquid balance", + "alice", + "alice", + aliceAddress, + carolAddress, + 1000, + 9000 + ); + vetoSuccessCase( + "success in case of enough shaes after delegate removal", + "alice", + "alice", + aliceAddress, + bobAddress, + 1000, + 100 + ); + vetoSuccessCase( + "success in case of 0 shares", + "alice", + "alice", + aliceAddress, + aliceAddress, + 0, + 0 + ); + vetoFailCase( + "revert in case of more than liquid shares", + "alice", + aliceAddress, + 20000, + "Dex/not-enough-balance" + ); + vetoFailCase( + "revert in case of no shares", + "bob", + bobAddress, + 0, + "Dex/no-shares" ); - if ( - initVeto - .minus(voterInitVoteInfo.veto) - .plus(new BigNumber(veto)) - .gt(initVotes.div(new BigNumber(3))) - ) { - notStrictEqual(prevDelegateFinalVetos, 0); - strictEqual(finalVeto.toNumber(), 0); - strictEqual(finalCurrentCandidate, null); - strictEqual( - finalCurrentDelegated, - prevCurrentCandidate == prevDelegated ? null : prevCurrentCandidate - ); - } else { - strictEqual(prevDelegateFinalVetos, 0); - strictEqual(finalCurrentCandidate, prevCurrentCandidate); - strictEqual(finalCurrentDelegated, prevDelegated); - } - strictEqual(voterFinalVoteInfo.veto.toNumber(), veto); }); - } - function vetoFailCase(decription, sender, vetor, value, errorMsg) { - it(decription, async function () { - await context.updateActor(sender); - await rejects(context.pairs[0].veto(vetor, value), (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; + describe("Test veto permissions", () => { + before(async () => { + await context.updateActor("alice"); + await context.pairs[0].approve(bobAddress, 5000); }); + vetoSuccessCase( + "success in case of veto by the user", + "alice", + "alice", + aliceAddress, + bobAddress, + 3000, + 500 + ); + vetoSuccessCase( + "success in case of veto be the approved user", + "bob", + "alice", + aliceAddress, + bobAddress, + 0, + 100 + ); + vetoFailCase( + "revert in case of not approved user", + "carol", + aliceAddress, + 100, + "Dex/not-enough-allowance" + ); }); - } - - describe("Test the user's veto power", () => { - vetoSuccessCase( - "success in case of enough liquid shares", - "alice", - "alice", - aliceAddress, - bobAddress, - 9000, - 500 - ); - vetoSuccessCase( - "success in case of exactly equal to liquid balance", - "alice", - "alice", - aliceAddress, - carolAddress, - 1000, - 9000 - ); - vetoSuccessCase( - "success in case of enough shaes after delegate removal", - "alice", - "alice", - aliceAddress, - bobAddress, - 1000, - 100 - ); - vetoSuccessCase( - "success in case of 0 shares", - "alice", - "alice", - aliceAddress, - aliceAddress, - 0, - 0 - ); - vetoFailCase( - "revert in case of more than liquid shares", - "alice", - aliceAddress, - 20000, - "Dex/not-enough-balance" - ); - vetoFailCase( - "revert in case of no shares", - "bob", - bobAddress, - 0, - "Dex/no-shares" - ); - }); - describe("Test veto permissions", () => { - before(async () => { - await context.updateActor("alice"); - await context.pairs[0].approve(bobAddress, 5000); + describe("Test different delegates", () => { + vetoSuccessCase( + "success in case of veto against delegate", + "alice", + "alice", + aliceAddress, + bobAddress, + 1000, + 4000 + ); + vetoSuccessCase( + "success in case of veto against no delegate", + "alice", + "alice", + aliceAddress, + bobAddress, + 0, + 4000 + ); }); - vetoSuccessCase( - "success in case of veto by the user", - "alice", - "alice", - aliceAddress, - bobAddress, - 3000, - 500 - ); - vetoSuccessCase( - "success in case of veto be the approved user", - "bob", - "alice", - aliceAddress, - bobAddress, - 0, - 100 - ); - vetoFailCase( - "revert in case of not approved user", - "carol", - aliceAddress, - 100, - "Dex/not-enough-allowance" - ); - }); - - describe("Test different delegates", () => { - vetoSuccessCase( - "success in case of veto against delegate", - "alice", - "alice", - aliceAddress, - bobAddress, - 1000, - 4000 - ); - vetoSuccessCase( - "success in case of veto against no delegate", - "alice", - "alice", - aliceAddress, - bobAddress, - 0, - 4000 - ); - }); - describe("Test the veto limit", () => { - vetoSuccessCase( - "success in case of too little votes for veto", - "alice", - "alice", - aliceAddress, - aliceAddress, - 3000, - 10 - ); - vetoSuccessCase( - "success in case of exctly 1/3 of votes", - "alice", - "alice", - aliceAddress, - aliceAddress, - 3000, - 1000 - ); - vetoSuccessCase( - "success in case of enough votes for veto", - "alice", - "alice", - aliceAddress, - aliceAddress, - 3000, - 2000 - ); + describe("Test the veto limit", () => { + vetoSuccessCase( + "success in case of too little votes for veto", + "alice", + "alice", + aliceAddress, + aliceAddress, + 3000, + 10 + ); + vetoSuccessCase( + "success in case of exctly 1/3 of votes", + "alice", + "alice", + aliceAddress, + aliceAddress, + 3000, + 1000 + ); + vetoSuccessCase( + "success in case of enough votes for veto", + "alice", + "alice", + aliceAddress, + aliceAddress, + 3000, + 2000 + ); + }); }); -}); +} diff --git a/test/Vote.spec.ts b/test/Vote.spec.ts index aad9f08a..86d1f07e 100644 --- a/test/Vote.spec.ts +++ b/test/Vote.spec.ts @@ -4,311 +4,322 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("Vote()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; - const carolAddress: string = accounts.carol.pkh; +if (standard !== "FA2FA12") { + contract("Vote()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; + const carolAddress: string = accounts.carol.pkh; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - await context.setDexFactoryFunction(6, "vote"); - await context.setDexFactoryFunction(7, "veto"); - await context.factory.setTokenFunction(0, "transfer"); - await context.factory.setTokenFunction( - 1, - standard == "FA2" ? "update_operators" : "approve" - ); - pairAddress = await context.createPair(); - tokenAddress = await context.pairs[0].contract.address; - }); + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + await context.setDexFactoryFunction(6, "vote"); + await context.setDexFactoryFunction(7, "veto"); + await context.factory.setTokenFunction(0, "transfer"); + await context.factory.setTokenFunction( + 1, + standard == "FA2" ? "update_operators" : "approve" + ); + pairAddress = await context.createPair(); + tokenAddress = await context.pairs[0].contract.address; + }); - function voteSuccessCase(decription, sender, voter, candidate, value) { - it(decription, async function () { - await context.updateActor(sender); - const senderAddress: string = accounts[sender].pkh; - await context.pairs[0].updateStorage({ - ledger: [senderAddress, voter], - voters: [senderAddress, voter], - votes: [candidate], - }); - const voterInitVoteInfo = context.pairs[0].storage.voters[voter] || { - candidate: undefined, - vote: new BigNumber(0), - veto: new BigNumber(0), - }; - const voterInitSharesInfo = context.pairs[0].storage.ledger[voter] || { - balance: new BigNumber(0), - frozen_balance: new BigNumber(0), - allowances: {}, - }; - const voterInitCandidateVotes = - context.pairs[0].storage.votes[candidate] || new BigNumber(0); - const initVotes = context.pairs[0].storage.total_votes; - await context.pairs[0].vote(voter, candidate, value); - const prevDelegated = context.pairs[0].storage.current_delegated; - await context.pairs[0].updateStorage({ - ledger: [senderAddress, voter], - voters: [senderAddress, voter], - votes: [candidate, prevDelegated], + function voteSuccessCase(decription, sender, voter, candidate, value) { + it(decription, async function () { + await context.updateActor(sender); + const senderAddress: string = accounts[sender].pkh; + await context.pairs[0].updateStorage({ + ledger: [senderAddress, voter], + voters: [senderAddress, voter], + votes: [candidate], + }); + const voterInitVoteInfo = context.pairs[0].storage.voters[voter] || { + candidate: undefined, + vote: new BigNumber(0), + veto: new BigNumber(0), + }; + const voterInitSharesInfo = context.pairs[0].storage.ledger[voter] || { + balance: new BigNumber(0), + frozen_balance: new BigNumber(0), + allowances: {}, + }; + const voterInitCandidateVotes = + context.pairs[0].storage.votes[candidate] || new BigNumber(0); + const initVotes = context.pairs[0].storage.total_votes; + await context.pairs[0].vote(voter, candidate, value); + const prevDelegated = context.pairs[0].storage.current_delegated; + await context.pairs[0].updateStorage({ + ledger: [senderAddress, voter], + voters: [senderAddress, voter], + votes: [candidate, prevDelegated], + }); + + const voterFinalVoteInfo = context.pairs[0].storage.voters[voter] || { + candidate: undefined, + vote: new BigNumber(0), + veto: new BigNumber(0), + }; + const voterFinalSharesInfo = context.pairs[0].storage.ledger[voter] || { + balance: new BigNumber(0), + frozen_balance: new BigNumber(0), + allowances: {}, + }; + const prevDelegateFinalCandidateVotes = + context.pairs[0].storage.votes[prevDelegated] || new BigNumber(0); + const voterFinalCandidateVotes = + context.pairs[0].storage.votes[candidate] || new BigNumber(0); + const finalVotes = context.pairs[0].storage.total_votes; + const finalCurrentCandidate = + context.pairs[0].storage.current_candidate; + const finalCurrentDelegated = + context.pairs[0].storage.current_delegated; + + strictEqual( + voterFinalSharesInfo.balance.toNumber(), + voterInitSharesInfo.balance.plus(voterInitVoteInfo.vote).toNumber() - + value + ); + strictEqual( + voterFinalSharesInfo.frozen_balance.toNumber(), + voterInitSharesInfo.frozen_balance + .minus(voterInitVoteInfo.vote) + .toNumber() + value + ); + strictEqual(voterFinalVoteInfo.candidate, value ? candidate : null); + strictEqual(voterFinalVoteInfo.vote.toNumber(), value); + + if (value) { + if (voterInitVoteInfo.candidate == candidate) { + strictEqual( + voterFinalCandidateVotes.toNumber(), + voterInitCandidateVotes.minus(voterInitVoteInfo.vote).toNumber() + + value + ); + } else { + strictEqual( + voterFinalCandidateVotes.toNumber(), + voterInitCandidateVotes.toNumber() + value + ); + } + } + strictEqual( + initVotes.minus(voterInitVoteInfo.vote).toNumber() + value, + finalVotes.toNumber() + ); + if (value > prevDelegateFinalCandidateVotes.toNumber()) + strictEqual(finalCurrentDelegated, candidate); }); + } - const voterFinalVoteInfo = context.pairs[0].storage.voters[voter] || { - candidate: undefined, - vote: new BigNumber(0), - veto: new BigNumber(0), - }; - const voterFinalSharesInfo = context.pairs[0].storage.ledger[voter] || { - balance: new BigNumber(0), - frozen_balance: new BigNumber(0), - allowances: {}, - }; - const prevDelegateFinalCandidateVotes = - context.pairs[0].storage.votes[prevDelegated] || new BigNumber(0); - const voterFinalCandidateVotes = - context.pairs[0].storage.votes[candidate] || new BigNumber(0); - const finalVotes = context.pairs[0].storage.total_votes; - const finalCurrentCandidate = context.pairs[0].storage.current_candidate; - const finalCurrentDelegated = context.pairs[0].storage.current_delegated; + function voteFailCase( + decription, + sender, + voter, + candidate, + value, + errorMsg + ) { + it(decription, async function () { + await context.updateActor(sender); + await rejects(context.pairs[0].vote(voter, candidate, value), (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + }); + }); + } - strictEqual( - voterFinalSharesInfo.balance.toNumber(), - voterInitSharesInfo.balance.plus(voterInitVoteInfo.vote).toNumber() - - value + describe("Test the user's vote power", () => { + voteSuccessCase( + "success in case of enough liquid shares", + "alice", + aliceAddress, + aliceAddress, + 1000 ); - strictEqual( - voterFinalSharesInfo.frozen_balance.toNumber(), - voterInitSharesInfo.frozen_balance - .minus(voterInitVoteInfo.vote) - .toNumber() + value + voteSuccessCase( + "success in case of exactly equal to liquid balance", + "alice", + aliceAddress, + aliceAddress, + 10000 ); - strictEqual(voterFinalVoteInfo.candidate, value ? candidate : null); - strictEqual(voterFinalVoteInfo.vote.toNumber(), value); - - if (value) { - if (voterInitVoteInfo.candidate == candidate) { - strictEqual( - voterFinalCandidateVotes.toNumber(), - voterInitCandidateVotes.minus(voterInitVoteInfo.vote).toNumber() + - value - ); - } else { - strictEqual( - voterFinalCandidateVotes.toNumber(), - voterInitCandidateVotes.toNumber() + value - ); - } - } - strictEqual( - initVotes.minus(voterInitVoteInfo.vote).toNumber() + value, - finalVotes.toNumber() + voteSuccessCase( + "success in case of enough liquid shares for revoting", + "alice", + aliceAddress, + bobAddress, + 1000 + ); + voteSuccessCase( + "success in case of enough liquid shares for revoting", + "alice", + aliceAddress, + bobAddress, + 0 + ); + voteSuccessCase( + "success in case of 0 shares with no candidate", + "alice", + aliceAddress, + bobAddress, + 0 + ); + voteFailCase( + "revert in case of more than liquid shares", + "alice", + aliceAddress, + aliceAddress, + 20000, + "Dex/not-enough-balance" + ); + voteFailCase( + "revert in case of no shares", + "bob", + bobAddress, + bobAddress, + 0, + "Dex/no-shares" ); - if (value > prevDelegateFinalCandidateVotes.toNumber()) - strictEqual(finalCurrentDelegated, candidate); }); - } - function voteFailCase(decription, sender, voter, candidate, value, errorMsg) { - it(decription, async function () { - await context.updateActor(sender); - await rejects(context.pairs[0].vote(voter, candidate, value), (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; + describe("Test voting permissions", () => { + before(async () => { + await context.updateActor("alice"); + await context.pairs[0].approve(bobAddress, 5000); }); + voteFailCase( + "revert in case of voting by the unapproved user", + "alice", + bobAddress, + bobAddress, + 0, + "Dex/no-shares" + ); + voteSuccessCase( + "success in case of vote by user", + "alice", + aliceAddress, + aliceAddress, + 1000 + ); + voteSuccessCase( + "success in case of vote by approved user", + "bob", + aliceAddress, + bobAddress, + 5000 + ); }); - } - describe("Test the user's vote power", () => { - voteSuccessCase( - "success in case of enough liquid shares", - "alice", - aliceAddress, - aliceAddress, - 1000 - ); - voteSuccessCase( - "success in case of exactly equal to liquid balance", - "alice", - aliceAddress, - aliceAddress, - 10000 - ); - voteSuccessCase( - "success in case of enough liquid shares for revoting", - "alice", - aliceAddress, - bobAddress, - 1000 - ); - voteSuccessCase( - "success in case of enough liquid shares for revoting", - "alice", - aliceAddress, - bobAddress, - 0 - ); - voteSuccessCase( - "success in case of 0 shares with no candidate", - "alice", - aliceAddress, - bobAddress, - 0 - ); - voteFailCase( - "revert in case of more than liquid shares", - "alice", - aliceAddress, - aliceAddress, - 20000, - "Dex/not-enough-balance" - ); - voteFailCase( - "revert in case of no shares", - "bob", - bobAddress, - bobAddress, - 0, - "Dex/no-shares" - ); - }); - - describe("Test voting permissions", () => { - before(async () => { - await context.updateActor("alice"); - await context.pairs[0].approve(bobAddress, 5000); - }); - voteFailCase( - "revert in case of voting by the unapproved user", - "alice", - bobAddress, - bobAddress, - 0, - "Dex/no-shares" - ); - voteSuccessCase( - "success in case of vote by user", - "alice", - aliceAddress, - aliceAddress, - 1000 - ); - voteSuccessCase( - "success in case of vote by approved user", - "bob", - aliceAddress, - bobAddress, - 5000 - ); - }); + describe("Test different candidates", () => { + before(async () => { + await context.updateActor("alice"); + await context.pairs[0].transfer(aliceAddress, bobAddress, 5000); + }); + voteSuccessCase( + "success in case of voting for new candidate", + "alice", + aliceAddress, + aliceAddress, + 1000 + ); + voteSuccessCase( + "success in case of voting for candidate with votes", + "bob", + bobAddress, + bobAddress, + 4000 + ); + voteFailCase( + "revert in case of voting for unregistered candidate with power that won't makes him delegate", + "alice", + aliceAddress, + "tz1gaRjULm9qy4D83VCvb4ABWLKpz4XXfqjx", + 1000, + "(permanent) proto.008-PtEdo2Zk.contract.manager.unregistered_delegate" + ); + voteFailCase( + "revert in case of voting for unregistered candidate with power that makes him delegate", + "alice", + aliceAddress, + "tz1gaRjULm9qy4D83VCvb4ABWLKpz4XXfqjx", + 5000, + "(permanent) proto.008-PtEdo2Zk.contract.manager.unregistered_delegate" + ); - describe("Test different candidates", () => { - before(async () => { - await context.updateActor("alice"); - await context.pairs[0].transfer(aliceAddress, bobAddress, 5000); + describe("", async function () { + before(async function () { + await context.updateActor("bob"); + await context.pairs[0].vote(bobAddress, carolAddress, 2000); + await context.updateActor("alice"); + await context.pairs[0].veto(aliceAddress, 2000); + }); + voteFailCase( + "revert in case of voting for banned candidate", + "bob", + bobAddress, + carolAddress, + 3000, + "Dex/veto-candidate" + ); + }); }); - voteSuccessCase( - "success in case of voting for new candidate", - "alice", - aliceAddress, - aliceAddress, - 1000 - ); - voteSuccessCase( - "success in case of voting for candidate with votes", - "bob", - bobAddress, - bobAddress, - 4000 - ); - voteFailCase( - "revert in case of voting for unregistered candidate with power that won't makes him delegate", - "alice", - aliceAddress, - "tz1gaRjULm9qy4D83VCvb4ABWLKpz4XXfqjx", - 1000, - "(permanent) proto.008-PtEdo2Zk.contract.manager.unregistered_delegate" - ); - voteFailCase( - "revert in case of voting for unregistered candidate with power that makes him delegate", - "alice", - aliceAddress, - "tz1gaRjULm9qy4D83VCvb4ABWLKpz4XXfqjx", - 5000, - "(permanent) proto.008-PtEdo2Zk.contract.manager.unregistered_delegate" - ); - describe("", async function () { - before(async function () { - await context.updateActor("bob"); - await context.pairs[0].vote(bobAddress, carolAddress, 2000); + describe("Test candidate replacement", () => { + before(async () => { await context.updateActor("alice"); - await context.pairs[0].veto(aliceAddress, 2000); + await context.flushPairs(); + pairAddress = await context.createPair(); + tokenAddress = await context.pairs[0].contract.address; + await context.pairs[0].transfer(aliceAddress, bobAddress, 5000); }); - voteFailCase( - "revert in case of voting for banned candidate", + voteSuccessCase( + "success in case of voting for new candidate if no delegate", + "alice", + aliceAddress, + aliceAddress, + 1000 + ); + voteSuccessCase( + "success in case of voting for new candidate if there is delegate with lower votes", "bob", bobAddress, + bobAddress, + 4000 + ); + voteSuccessCase( + "success in case of voting for new candidate if delegate with higher votes", + "alice", + aliceAddress, carolAddress, - 3000, - "Dex/veto-candidate" + 1000 + ); + voteSuccessCase( + "success in case of voting for new candidate if there is delegate with the same votes", + "alice", + aliceAddress, + aliceAddress, + 4000 + ); + voteSuccessCase( + "success in case of removing votes for delegate", + "bob", + bobAddress, + bobAddress, + 2000 + ); + voteSuccessCase( + "success in case of voting for the delegate", + "alice", + aliceAddress, + bobAddress, + 4300 ); }); }); - - describe("Test candidate replacement", () => { - before(async () => { - await context.updateActor("alice"); - await context.flushPairs(); - pairAddress = await context.createPair(); - tokenAddress = await context.pairs[0].contract.address; - await context.pairs[0].transfer(aliceAddress, bobAddress, 5000); - }); - voteSuccessCase( - "success in case of voting for new candidate if no delegate", - "alice", - aliceAddress, - aliceAddress, - 1000 - ); - voteSuccessCase( - "success in case of voting for new candidate if there is delegate with lower votes", - "bob", - bobAddress, - bobAddress, - 4000 - ); - voteSuccessCase( - "success in case of voting for new candidate if delegate with higher votes", - "alice", - aliceAddress, - carolAddress, - 1000 - ); - voteSuccessCase( - "success in case of voting for new candidate if there is delegate with the same votes", - "alice", - aliceAddress, - aliceAddress, - 4000 - ); - voteSuccessCase( - "success in case of removing votes for delegate", - "bob", - bobAddress, - bobAddress, - 2000 - ); - voteSuccessCase( - "success in case of voting for the delegate", - "alice", - aliceAddress, - bobAddress, - 4300 - ); - }); -}); +} diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 46306365..105f7c1a 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -2,6 +2,7 @@ const standard = process.env.EXCHANGE_TOKEN_STANDARD; import { TTDex as TTDexFA12 } from "./ttdexFA12"; import { TTDex as TTDexFA2 } from "./ttdexFA2"; +import { TTDex as TTDexFA2FA12 } from "./ttdexFA2FA12"; import { TokenFA12 } from "./tokenFA12"; import { prepareProviderOptions } from "./utils"; @@ -15,15 +16,26 @@ import { TezosToolkit } from "@taquito/taquito"; import BigNumber from "bignumber.js"; let tokenStorage, CDex, CToken; -type Dex = TTDexFA12 | TTDexFA2; -if (standard == "FA2") { - tokenStorage = tokenFA2Storage; - CDex = artifacts.require("TTDexFA2"); - CToken = artifacts.require("TokenFA2"); -} else { - tokenStorage = tokenFA12Storage; - CDex = artifacts.require("TTDexFA12"); - CToken = artifacts.require("TokenFA12"); +type Dex = TTDexFA12 | TTDexFA2 | TTDexFA2FA12; +const CTokenFA12 = artifacts.require("TokenFA12"); +const CTokenFA2 = artifacts.require("TokenFA2"); + +switch (standard) { + case "FA2": + tokenStorage = tokenFA2Storage; + CDex = artifacts.require("TTDexFA2"); + CToken = artifacts.require("TokenFA2"); + break; + case "FA12": + tokenStorage = tokenFA12Storage; + CDex = artifacts.require("TTDexFA12"); + CToken = artifacts.require("TokenFA12"); + break; + case "FA2FA12": + tokenStorage = tokenFA12Storage; + CDex = artifacts.require("TTDexFA2FA12"); + CToken = artifacts.require("TokenFA2"); + break; } export class TTContext { @@ -60,10 +72,18 @@ export class TTContext { let dexInstance = useDeployedDex ? await CDex.deployed() : await CDex.new(dexStorage); - let dex = - standard === "FA2" - ? await TTDexFA2.init(dexInstance.address.toString()) - : await TTDexFA12.init(dexInstance.address.toString()); + let dex; + switch (standard) { + case "FA2": + dex = await TTDexFA2.init(dexInstance.address.toString()); + break; + case "FA12": + dex = await TTDexFA12.init(dexInstance.address.toString()); + break; + case "FA2FA12": + dex = await TTDexFA2FA12.init(dexInstance.address.toString()); + break; + } let context = new TTContext(dex, []); if (setDexFunctions) { @@ -85,15 +105,19 @@ export class TTContext { await this.updateActor(); } - async createToken(): Promise { - let tokenInstance = await CToken.new(tokenStorage); - let tokenAddress = tokenInstance.address.toString(); - this.tokens.push( - standard === "FA2" - ? await TokenFA2.init(tokenAddress) - : await TokenFA12.init(tokenAddress) - ); - return tokenAddress; + async createToken(type = null): Promise { + if (!type) type = standard; + if (type == "FA2") { + let tokenInstance = await CTokenFA2.new(tokenFA2Storage); + let tokenAddress = tokenInstance.address.toString(); + this.tokens.push(await TokenFA2.init(tokenAddress)); + return tokenAddress; + } else { + let tokenInstance = await CTokenFA12.new(tokenFA12Storage); + let tokenAddress = tokenInstance.address.toString(); + this.tokens.push(await TokenFA12.init(tokenAddress)); + return tokenAddress; + } } async setDexFunctions(): Promise { @@ -149,10 +173,15 @@ export class TTContext { } ): Promise { pairConfig.tokenAAddress = - pairConfig.tokenAAddress || (await this.createToken()); + pairConfig.tokenAAddress || + (await this.createToken(standard == "FA2FA12" ? "FA2" : standard)); pairConfig.tokenBAddress = - pairConfig.tokenBAddress || (await this.createToken()); - if (pairConfig.tokenAAddress > pairConfig.tokenBAddress) { + pairConfig.tokenBAddress || + (await this.createToken(standard == "FA2FA12" ? "FA12" : standard)); + if ( + standard !== "FA2FA12" && + pairConfig.tokenAAddress > pairConfig.tokenBAddress + ) { const tmp = pairConfig.tokenAAddress; pairConfig.tokenAAddress = pairConfig.tokenBAddress; pairConfig.tokenBAddress = tmp; diff --git a/test/helpers/ttdexFA2FA12.ts b/test/helpers/ttdexFA2FA12.ts index 74d41dff..ba7d8fc7 100644 --- a/test/helpers/ttdexFA2FA12.ts +++ b/test/helpers/ttdexFA2FA12.ts @@ -83,19 +83,17 @@ export class TTDex extends TokenFA2 { tokenAAmount: number, tokenBAmount: number, tokenAid: BigNumber = new BigNumber(0), - tokenBid: BigNumber = new BigNumber(0), approve: boolean = true ): Promise { if (approve) { - await this.approveToken( + await this.approveFA2Token( tokenAAddress, tokenAid, tokenAAmount, this.contract.address ); - await this.approveToken( + await this.approveFA12Token( tokenBAddress, - tokenBid, tokenBAmount, this.contract.address ); @@ -106,7 +104,6 @@ export class TTDex extends TokenFA2 { tokenAAddress, tokenAid, tokenBAddress, - tokenBid, tokenAAmount, tokenBAmount ) @@ -122,16 +119,28 @@ export class TTDex extends TokenFA2 { amountIn: number, minAmountOut: number, receiver: string, - tokenAid: BigNumber = new BigNumber(0), - tokenBid: BigNumber = new BigNumber(0) + tokenAid: BigNumber = new BigNumber(0) ): Promise { + if (opType == "buy") { + await this.approveFA12Token( + tokenBAddress, + amountIn, + this.contract.address + ); + } else { + await this.approveFA2Token( + tokenAAddress, + tokenAid, + amountIn, + this.contract.address + ); + } const operation = await this.contract.methods .use( "tokenToTokenPayment", tokenAAddress, tokenAid, tokenBAddress, - tokenBid, opType, null, amountIn, @@ -151,15 +160,14 @@ export class TTDex extends TokenFA2 { ): Promise { await this.updateStorage({ tokens: [pairId] }); let pair = this.storage.tokens[pairId]; - await this.approveToken( + await this.approveFA2Token( pair.token_a_address, pair.token_a_id, tokenAAmount, this.contract.address ); - await this.approveToken( + await this.approveFA12Token( pair.token_b_address, - pair.token_b_id, tokenBAmount, this.contract.address ); @@ -169,7 +177,6 @@ export class TTDex extends TokenFA2 { pair.token_a_address, pair.token_a_id, pair.token_b_address, - pair.token_b_id, tokenAAmount, tokenBAmount, minShares @@ -193,7 +200,6 @@ export class TTDex extends TokenFA2 { pair.token_a_address, pair.token_a_id, pair.token_b_address, - pair.token_b_id, tokenAAmount, tokenBAmount, sharesBurned @@ -203,7 +209,7 @@ export class TTDex extends TokenFA2 { return operation; } - async approveToken( + async approveFA2Token( tokenAddress: string, tokenId: BigNumber, tokenAmount: number, @@ -226,6 +232,18 @@ export class TTDex extends TokenFA2 { return operation; } + async approveFA12Token( + tokenAddress: string, + tokenAmount: number, + address: string + ): Promise { + await this.updateStorage(); + let token = await tezos.contract.at(tokenAddress); + let operation = await token.methods.approve(address, tokenAmount).send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + async setDexFunction(index: number, lambdaName: string): Promise { let ligo = getLigo(true); const stdout = execSync( diff --git a/test/storage/TTFunctions.ts b/test/storage/TTFunctions.ts index 3d516a2c..bff5c8b0 100644 --- a/test/storage/TTFunctions.ts +++ b/test/storage/TTFunctions.ts @@ -34,5 +34,6 @@ export let tokenFunctions = [ ]; module.exports.tokenFunctions = { FA12: tokenFunctions, + FA2FA12: tokenFunctions, FA2: tokenFunctions, }; diff --git a/truffle.d.ts b/truffle.d.ts index e2bee27c..c70a09c1 100644 --- a/truffle.d.ts +++ b/truffle.d.ts @@ -28,6 +28,7 @@ declare interface Artifacts { require(name: "TestFactoryFA2"): Contract; require(name: "DexFA12"): Contract; require(name: "DexFA2"): Contract; + require(name: "TTDexFA2FA12"): Contract; require(name: "TTDexFA12"): Contract; require(name: "TTDexFA2"): Contract; } From 65be8e41dcd3b31933f62ba4dec5d35ac1333410 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 17:22:58 +0300 Subject: [PATCH 16/63] this.skip --- test/BuyToken.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/BuyToken.spec.ts b/test/BuyToken.spec.ts index f83f4109..75050c0f 100644 --- a/test/BuyToken.spec.ts +++ b/test/BuyToken.spec.ts @@ -6,9 +6,6 @@ import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("BuyToken()", function () { - if (standard === "FA2FA12") { - this.skip(); - } let context: TTContext; const tokenAAmount: number = 100000; const tokenBAmount: number = 1000; From 29fe81eeb402c234b84e9889ea9129b2b7399447 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 17:50:53 +0300 Subject: [PATCH 17/63] update tests --- test/TokenAToTokenB.spec.ts | 3 ++- test/TokenBToTokenA.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/TokenAToTokenB.spec.ts b/test/TokenAToTokenB.spec.ts index 1f6c7674..c8ba1706 100644 --- a/test/TokenAToTokenB.spec.ts +++ b/test/TokenAToTokenB.spec.ts @@ -3,6 +3,7 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("TokenAToTokenB()", function () { let context: TTContext; @@ -22,7 +23,7 @@ contract("TokenAToTokenB()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/TokenBToTokenA.spec.ts b/test/TokenBToTokenA.spec.ts index f9d462ab..ad865965 100644 --- a/test/TokenBToTokenA.spec.ts +++ b/test/TokenBToTokenA.spec.ts @@ -3,6 +3,7 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; contract("TokenBToTokenA()", function () { let context: TTContext; @@ -22,7 +23,7 @@ contract("TokenBToTokenA()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; From 126ca05fa84cee6e1acf5933300ecf1944c4e770 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 17:52:38 +0300 Subject: [PATCH 18/63] update CI --- .github/workflows/master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 9a177a55..f912736b 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - standard: ["FA12", "FA2"] + standard: ["FA12", "FA2", "FA2FA12"] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 From 41c46858b779e20d51298511031e4797bd220b7b Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 18:05:21 +0300 Subject: [PATCH 19/63] fix test --- migrations/3_baker_registry_migration.js | 2 - test/InitializeTTExchangeTest.spec.ts | 2 +- test/RewardDistribution.spec.ts | 602 ++++++++++++----------- 3 files changed, 304 insertions(+), 302 deletions(-) diff --git a/migrations/3_baker_registry_migration.js b/migrations/3_baker_registry_migration.js index 3f175748..22dea056 100644 --- a/migrations/3_baker_registry_migration.js +++ b/migrations/3_baker_registry_migration.js @@ -2,8 +2,6 @@ const BakerRegistry = artifacts.require("BakerRegistry"); const { MichelsonMap } = require("@taquito/michelson-encoder"); module.exports = async (deployer) => { - const standard = process.env.EXCHANGE_TOKEN_STANDARD; - if (!["FA2", "FA12"].includes(standard)) return; await deployer.deploy(BakerRegistry, MichelsonMap.fromLiteral({})); const bakerRegistryInstance = await BakerRegistry.deployed(); console.log(`BakerRegistry address: ${bakerRegistryInstance.address}`); diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index abaa7650..5d3e9503 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -27,7 +27,7 @@ contract("InitializeTTExchange()", function () { before(async () => { tokenAAddress = await context.createToken(); tokenBAddress = await context.createToken(); - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/RewardDistribution.spec.ts b/test/RewardDistribution.spec.ts index a769369d..cf791e93 100644 --- a/test/RewardDistribution.spec.ts +++ b/test/RewardDistribution.spec.ts @@ -6,135 +6,93 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo, accuracy } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("RewardsDistribution()", function () { - let context: Context; - let tokenAddress: string; - let pairAddress: string; - const aliceAddress: string = accounts.alice.pkh; - const bobAddress: string = accounts.bob.pkh; +if (standard !== "FA2FA12") { + contract("RewardsDistribution()", function () { + let context: Context; + let tokenAddress: string; + let pairAddress: string; + const aliceAddress: string = accounts.alice.pkh; + const bobAddress: string = accounts.bob.pkh; - before(async () => { - context = await Context.init([], false, "alice", false); - await context.setDexFactoryFunction(0, "initialize_exchange"); - await context.setDexFactoryFunction(3, "withdraw_profit"); - await context.setDexFactoryFunction(4, "invest_liquidity"); - await context.setDexFactoryFunction(5, "divest_liquidity"); - await context.setDexFactoryFunction(6, "vote"); - await context.setDexFactoryFunction(7, "veto"); - await context.setDexFactoryFunction(8, "receive_reward"); - await context.factory.setTokenFunction(0, "transfer"); - await context.factory.setTokenFunction( - 1, - standard == "FA2" ? "update_operators" : "approve" - ); - pairAddress = await context.createPair({ - tezAmount: 100, - tokenAmount: 100, - }); - tokenAddress = await context.pairs[0].contract.address; - await context.tokens[0].transfer(aliceAddress, bobAddress, 1000000); - await context.pairs[0].sendReward(1000); - }); - - function defaultSuccessCase( - decription, - sender, - wait, - action, - rewardWithdrawn = false, - receiver = undefined - ) { - it(decription, async function () { - await context.updateActor(sender); - const senderAddress: string = accounts[sender].pkh; - receiver = receiver || senderAddress; - const aliceInitTezBalance = new BigNumber( - await tezos.tz.getBalance(receiver) + before(async () => { + context = await Context.init([], false, "alice", false); + await context.setDexFactoryFunction(0, "initialize_exchange"); + await context.setDexFactoryFunction(3, "withdraw_profit"); + await context.setDexFactoryFunction(4, "invest_liquidity"); + await context.setDexFactoryFunction(5, "divest_liquidity"); + await context.setDexFactoryFunction(6, "vote"); + await context.setDexFactoryFunction(7, "veto"); + await context.setDexFactoryFunction(8, "receive_reward"); + await context.factory.setTokenFunction(0, "transfer"); + await context.factory.setTokenFunction( + 1, + standard == "FA2" ? "update_operators" : "approve" ); - await context.pairs[0].updateStorage({ - ledger: [senderAddress], - user_rewards: [senderAddress], + pairAddress = await context.createPair({ + tezAmount: 100, + tokenAmount: 100, }); - const initRewardInfo = context.pairs[0].storage; - const initRecord = - (await context.pairs[0].storage.ledger[senderAddress]) || - defaultAccountInfo; - const initTotalSupply = context.pairs[0].storage.total_supply; - const initUserRewards = context.pairs[0].storage.user_rewards[ - senderAddress - ] || { - reward: new BigNumber(0), - reward_paid: new BigNumber(0), - }; - if (wait) { - await bakeBlocks(wait); - } - const op = await action(); - const fee = - receiver == senderAddress ? await calculateFee([op], senderAddress) : 0; - await context.pairs[0].updateStorage({ - user_rewards: [senderAddress], - ledger: [senderAddress], - }); - const finalRewardInfo = context.pairs[0].storage; - const finalTotalSupply = context.pairs[0].storage.total_supply; - const finalUserRewards = context.pairs[0].storage.user_rewards[ - senderAddress - ] || { - reward: new BigNumber(0), - reward_paid: new BigNumber(0), - }; - const finalRecord = - (await context.pairs[0].storage.ledger[senderAddress]) || - defaultAccountInfo; - const aliceFinalTezBalance = new BigNumber( - await tezos.tz.getBalance(receiver) - ); - const accumulatedReward = new BigNumber(1) - .multipliedBy( - Math.floor( - (Date.parse(finalRewardInfo.last_update_time) - - Date.parse(initRewardInfo.last_update_time)) / - 1000 - ) - ) - .multipliedBy(initRewardInfo.reward_per_sec); - const expectedUserReward = initUserRewards.reward.plus( - initRecord.balance - .plus(initRecord.frozen_balance) - .multipliedBy(finalRewardInfo.reward_per_share) - .minus(initUserRewards.reward_paid) - ); - const expectedRewardPaid = finalRecord.balance - .plus(finalRecord.frozen_balance) - .multipliedBy(finalRewardInfo.reward_per_share); - strictEqual( - finalUserRewards.reward_paid.toString(), - expectedRewardPaid.toString() - ); - if (rewardWithdrawn) { - ok(finalUserRewards.reward.lt(new BigNumber("1000000000000000"))); - const realReward = expectedUserReward - .div(accuracy) - .integerValue(BigNumber.ROUND_DOWN); - ok( - aliceFinalTezBalance - .plus(fee) - .minus(aliceInitTezBalance) - .eq(realReward) - ); - } else { - strictEqual( - finalUserRewards.reward.toString(), - expectedUserReward.toString() + tokenAddress = await context.pairs[0].contract.address; + await context.tokens[0].transfer(aliceAddress, bobAddress, 1000000); + await context.pairs[0].sendReward(1000); + }); + + function defaultSuccessCase( + decription, + sender, + wait, + action, + rewardWithdrawn = false, + receiver = undefined + ) { + it(decription, async function () { + await context.updateActor(sender); + const senderAddress: string = accounts[sender].pkh; + receiver = receiver || senderAddress; + const aliceInitTezBalance = new BigNumber( + await tezos.tz.getBalance(receiver) ); - ok( - aliceFinalTezBalance - .minus(aliceInitTezBalance) - .lte(expectedUserReward) + await context.pairs[0].updateStorage({ + ledger: [senderAddress], + user_rewards: [senderAddress], + }); + const initRewardInfo = context.pairs[0].storage; + const initRecord = + (await context.pairs[0].storage.ledger[senderAddress]) || + defaultAccountInfo; + const initTotalSupply = context.pairs[0].storage.total_supply; + const initUserRewards = context.pairs[0].storage.user_rewards[ + senderAddress + ] || { + reward: new BigNumber(0), + reward_paid: new BigNumber(0), + }; + if (wait) { + await bakeBlocks(wait); + } + const op = await action(); + const fee = + receiver == senderAddress + ? await calculateFee([op], senderAddress) + : 0; + await context.pairs[0].updateStorage({ + user_rewards: [senderAddress], + ledger: [senderAddress], + }); + const finalRewardInfo = context.pairs[0].storage; + const finalTotalSupply = context.pairs[0].storage.total_supply; + const finalUserRewards = context.pairs[0].storage.user_rewards[ + senderAddress + ] || { + reward: new BigNumber(0), + reward_paid: new BigNumber(0), + }; + const finalRecord = + (await context.pairs[0].storage.ledger[senderAddress]) || + defaultAccountInfo; + const aliceFinalTezBalance = new BigNumber( + await tezos.tz.getBalance(receiver) ); - } - if (initRewardInfo.period_finish == finalRewardInfo.period_finish) { const accumulatedReward = new BigNumber(1) .multipliedBy( Math.floor( @@ -144,185 +102,193 @@ contract("RewardsDistribution()", function () { ) ) .multipliedBy(initRewardInfo.reward_per_sec); - strictEqual( - finalRewardInfo.reward.toNumber(), - initRewardInfo.reward.toNumber() - ); - strictEqual( - finalRewardInfo.total_reward.toNumber(), - initRewardInfo.total_reward.toNumber() + const expectedUserReward = initUserRewards.reward.plus( + initRecord.balance + .plus(initRecord.frozen_balance) + .multipliedBy(finalRewardInfo.reward_per_share) + .minus(initUserRewards.reward_paid) ); + const expectedRewardPaid = finalRecord.balance + .plus(finalRecord.frozen_balance) + .multipliedBy(finalRewardInfo.reward_per_share); strictEqual( - finalRewardInfo.reward_per_share.toString(), - initRewardInfo.reward_per_share - .plus( - accumulatedReward - .div(initRewardInfo.total_supply) - .integerValue(BigNumber.ROUND_DOWN) - ) - .toString() + finalUserRewards.reward_paid.toString(), + expectedRewardPaid.toString() ); - } else { - strictEqual(finalRewardInfo.reward.toString(), "0"); - const periodDuration = - (Math.floor( - Math.floor( - (Date.parse(finalRewardInfo.last_update_time) - - Date.parse(initRewardInfo.period_finish)) / - 1000 - ) / 10 - ) + - 1) * - 10; - strictEqual( - finalRewardInfo.total_reward.toString(), - finalRewardInfo.reward_per_sec - .multipliedBy(periodDuration) + if (rewardWithdrawn) { + ok(finalUserRewards.reward.lt(new BigNumber("1000000000000000"))); + const realReward = expectedUserReward .div(accuracy) - .integerValue(BigNumber.ROUND_DOWN) - .plus(initRewardInfo.total_reward) - .toString() - ); - strictEqual( - finalRewardInfo.reward_per_sec.toString(), - initRewardInfo.reward - .multipliedBy(accuracy) - .div(periodDuration) - .integerValue(BigNumber.ROUND_DOWN) - .toString() - ); - const accumulatedReward = new BigNumber(1) - .multipliedBy( - Math.floor( - (Date.parse(initRewardInfo.period_finish) - - Date.parse(initRewardInfo.last_update_time)) / - 1000 + .integerValue(BigNumber.ROUND_DOWN); + ok( + aliceFinalTezBalance + .plus(fee) + .minus(aliceInitTezBalance) + .eq(realReward) + ); + } else { + strictEqual( + finalUserRewards.reward.toString(), + expectedUserReward.toString() + ); + ok( + aliceFinalTezBalance + .minus(aliceInitTezBalance) + .lte(expectedUserReward) + ); + } + if (initRewardInfo.period_finish == finalRewardInfo.period_finish) { + const accumulatedReward = new BigNumber(1) + .multipliedBy( + Math.floor( + (Date.parse(finalRewardInfo.last_update_time) - + Date.parse(initRewardInfo.last_update_time)) / + 1000 + ) ) - ) - .multipliedBy(initRewardInfo.reward_per_sec); - const accumulatedThisCycleReward = new BigNumber(1) - .multipliedBy( - Math.floor( - (Date.parse(finalRewardInfo.last_update_time) - - Date.parse(initRewardInfo.period_finish)) / - 1000 + .multipliedBy(initRewardInfo.reward_per_sec); + strictEqual( + finalRewardInfo.reward.toNumber(), + initRewardInfo.reward.toNumber() + ); + strictEqual( + finalRewardInfo.total_reward.toNumber(), + initRewardInfo.total_reward.toNumber() + ); + strictEqual( + finalRewardInfo.reward_per_share.toString(), + initRewardInfo.reward_per_share + .plus( + accumulatedReward + .div(initRewardInfo.total_supply) + .integerValue(BigNumber.ROUND_DOWN) + ) + .toString() + ); + } else { + strictEqual(finalRewardInfo.reward.toString(), "0"); + const periodDuration = + (Math.floor( + Math.floor( + (Date.parse(finalRewardInfo.last_update_time) - + Date.parse(initRewardInfo.period_finish)) / + 1000 + ) / 10 + ) + + 1) * + 10; + strictEqual( + finalRewardInfo.total_reward.toString(), + finalRewardInfo.reward_per_sec + .multipliedBy(periodDuration) + .div(accuracy) + .integerValue(BigNumber.ROUND_DOWN) + .plus(initRewardInfo.total_reward) + .toString() + ); + strictEqual( + finalRewardInfo.reward_per_sec.toString(), + initRewardInfo.reward + .multipliedBy(accuracy) + .div(periodDuration) + .integerValue(BigNumber.ROUND_DOWN) + .toString() + ); + const accumulatedReward = new BigNumber(1) + .multipliedBy( + Math.floor( + (Date.parse(initRewardInfo.period_finish) - + Date.parse(initRewardInfo.last_update_time)) / + 1000 + ) ) - ) - .multipliedBy(finalRewardInfo.reward_per_sec); - strictEqual( - finalRewardInfo.reward_per_share.toString(), - initRewardInfo.reward_per_share - .plus( - accumulatedReward - .div(initRewardInfo.total_supply) - .integerValue(BigNumber.ROUND_DOWN) - ) - .plus( - accumulatedThisCycleReward - .div(initRewardInfo.total_supply) - .integerValue(BigNumber.ROUND_DOWN) + .multipliedBy(initRewardInfo.reward_per_sec); + const accumulatedThisCycleReward = new BigNumber(1) + .multipliedBy( + Math.floor( + (Date.parse(finalRewardInfo.last_update_time) - + Date.parse(initRewardInfo.period_finish)) / + 1000 + ) ) - .toString() - ); - } - }); - } - - function defaultFailCase(decription, sender, amount, errorMsg) { - it(decription, async function () { - await context.updateActor(sender); - await rejects(context.pairs[0].sendReward(amount), (err) => { - ok(err.message == errorMsg, "Error message mismatch"); - return true; + .multipliedBy(finalRewardInfo.reward_per_sec); + strictEqual( + finalRewardInfo.reward_per_share.toString(), + initRewardInfo.reward_per_share + .plus( + accumulatedReward + .div(initRewardInfo.total_supply) + .integerValue(BigNumber.ROUND_DOWN) + ) + .plus( + accumulatedThisCycleReward + .div(initRewardInfo.total_supply) + .integerValue(BigNumber.ROUND_DOWN) + ) + .toString() + ); + } }); - }); - } + } - describe("Test the loyalty assessment", () => { - defaultSuccessCase( - "success in case of update liquidity", - "alice", - 0, - function () { - return context.pairs[0].withdrawProfit(aliceAddress); - }, - true - ); - defaultSuccessCase( - "success in case of user makes new investment", - "bob", - 0, - function () { - return context.pairs[0].investLiquidity(100, 100, 50); - } - ); - defaultSuccessCase( - "success in case of user withdraws shares", - "alice", - 0, - function () { - return context.pairs[0].divestLiquidity(10, 10, 10); - } - ); - defaultSuccessCase( - "success in case of user transfers shares", - "alice", - 0, - function () { - return context.pairs[0].transfer(aliceAddress, bobAddress, 10); - } - ); - }); + function defaultFailCase(decription, sender, amount, errorMsg) { + it(decription, async function () { + await context.updateActor(sender); + await rejects(context.pairs[0].sendReward(amount), (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + }); + }); + } - describe("Test the user's reward distribution", () => { - defaultSuccessCase( - "success in case of reward is claimed before period finished", - "alice", - 0, - function () { - return context.pairs[0].withdrawProfit(aliceAddress); - }, - true - ); - defaultSuccessCase( - "success in case of reward is claimed after period finished", - "bob", - 7, - function () { - return context.pairs[0].withdrawProfit(aliceAddress); - }, - true, - accounts["alice"].pkh - ); - defaultSuccessCase( - "success in case of reward is claimed in the middle of the second period", - "alice", - 3, - function () { - return context.pairs[0].withdrawProfit(bobAddress); - }, - true, - accounts["bob"].pkh - ); - }); + describe("Test the loyalty assessment", () => { + defaultSuccessCase( + "success in case of update liquidity", + "alice", + 0, + function () { + return context.pairs[0].withdrawProfit(aliceAddress); + }, + true + ); + defaultSuccessCase( + "success in case of user makes new investment", + "bob", + 0, + function () { + return context.pairs[0].investLiquidity(100, 100, 50); + } + ); + defaultSuccessCase( + "success in case of user withdraws shares", + "alice", + 0, + function () { + return context.pairs[0].divestLiquidity(10, 10, 10); + } + ); + defaultSuccessCase( + "success in case of user transfers shares", + "alice", + 0, + function () { + return context.pairs[0].transfer(aliceAddress, bobAddress, 10); + } + ); + }); - describe("Test the user's reward distribution with different total rewards", () => { - defaultSuccessCase( - "success in case of no reward", - "alice", - 0, - function () { - return context.pairs[0].withdrawProfit(aliceAddress); - }, - true - ); - describe("", async function () { - before(async function () { - await context.updateActor("bob"); - await context.pairs[0].sendReward(2000); - }); + describe("Test the user's reward distribution", () => { defaultSuccessCase( - "success in case of reward is accumulated", + "success in case of reward is claimed before period finished", + "alice", + 0, + function () { + return context.pairs[0].withdrawProfit(aliceAddress); + }, + true + ); + defaultSuccessCase( + "success in case of reward is claimed after period finished", "bob", 7, function () { @@ -331,16 +297,10 @@ contract("RewardsDistribution()", function () { true, accounts["alice"].pkh ); - }); - describe("", async function () { - before(async function () { - await context.updateActor("alice"); - await context.pairs[0].divestLiquidity(1, 1, 80); - }); defaultSuccessCase( "success in case of reward is claimed in the middle of the second period", "alice", - 0, + 3, function () { return context.pairs[0].withdrawProfit(bobAddress); }, @@ -348,5 +308,49 @@ contract("RewardsDistribution()", function () { accounts["bob"].pkh ); }); + + describe("Test the user's reward distribution with different total rewards", () => { + defaultSuccessCase( + "success in case of no reward", + "alice", + 0, + function () { + return context.pairs[0].withdrawProfit(aliceAddress); + }, + true + ); + describe("", async function () { + before(async function () { + await context.updateActor("bob"); + await context.pairs[0].sendReward(2000); + }); + defaultSuccessCase( + "success in case of reward is accumulated", + "bob", + 7, + function () { + return context.pairs[0].withdrawProfit(aliceAddress); + }, + true, + accounts["alice"].pkh + ); + }); + describe("", async function () { + before(async function () { + await context.updateActor("alice"); + await context.pairs[0].divestLiquidity(1, 1, 80); + }); + defaultSuccessCase( + "success in case of reward is claimed in the middle of the second period", + "alice", + 0, + function () { + return context.pairs[0].withdrawProfit(bobAddress); + }, + true, + accounts["bob"].pkh + ); + }); + }); }); -}); +} From acbc7244ad468d20351fc8bcb4eacb13fc57b674 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 18:11:44 +0300 Subject: [PATCH 20/63] fix order for FA2FA12 pairs --- test/InitializeTTExchangeTest.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index 5d3e9503..8811d2e1 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -27,7 +27,7 @@ contract("InitializeTTExchange()", function () { before(async () => { tokenAAddress = await context.createToken(); tokenBAddress = await context.createToken(); - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; @@ -168,7 +168,7 @@ contract("InitializeTTExchange()", function () { before(async () => { tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (tokenAAddress > tokenBAddress) { + if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { tokenBAddress = context.tokens[0].contract.address; tokenAAddress = context.tokens[1].contract.address; } From 2943c6f0f2e830d9eef7a56189a94a28f01189f6 Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 4 Jun 2021 18:29:00 +0300 Subject: [PATCH 21/63] fix initialize fa2/fa12 exchange test --- test/InitializeTTExchangeTest.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index 8811d2e1..7f169e6d 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -25,8 +25,12 @@ contract("InitializeTTExchange()", function () { let tokenAAddress: string; let tokenBAddress: string; before(async () => { - tokenAAddress = await context.createToken(); - tokenBAddress = await context.createToken(); + tokenAAddress = await context.createToken( + standard == "FA2FA12" ? "FA2" : standard + ); + tokenBAddress = await context.createToken( + standard == "FA2FA12" ? "FA12" : standard + ); if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; From 4619dd6e5d5e2cd2e3822f11d1492fa2b6c84215 Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 12:19:18 +0300 Subject: [PATCH 22/63] merge token to token of diff standards into 1 dex --- contracts/partials/ITTDex.ligo | 24 +- contracts/partials/TTMethodDex.ligo | 580 ++++++++++++++++++---------- 2 files changed, 380 insertions(+), 224 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 27f56387..19cc4449 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -8,21 +8,20 @@ type account_info is record [ allowances : set (address); (* accounts allowed to act on behalf of the user *) ] -#if FA2_STANDARD_ENABLED -type token_transfer_params is list (transfer_param) -type token_identifier is record [ +type token_transfer_params_fa2 is list (transfer_param) +type token_identifier_fa2 is record [ token_address : address; token_id : nat; ] -#if FA2FA12_STANDARD_ENABLED type token_transfer_params_fa12 is michelson_pair(address, "from", michelson_pair(address, "to", nat, "value"), "") type token_identifier_fa12 is address type transfer_type_fa12 is TransferTypeFA12 of token_transfer_params_fa12 -#endif -#else -type token_transfer_params is michelson_pair(address, "from", michelson_pair(address, "to", nat, "value"), "") -type token_identifier is address -#endif +type transfer_type_fa2 is TransferTypeFA2 of token_transfer_params_fa2 + +type pair_type is +| Fa12 +| Fa2 +| Mixed type pair_info is record [ token_a_pool : nat; (* tez reserves in the pool *) @@ -33,13 +32,9 @@ type pair_info is record [ type tokens_info is record [ token_a_address : address; token_b_address : address; -#if FA2_STANDARD_ENABLED token_a_id : nat; -#if FA2FA12_STANDARD_ENABLED -#else token_b_id : nat; -#endif -#endif + standard : pair_type; ] type token_pair is bytes @@ -147,6 +142,5 @@ type full_return is list (operation) * full_dex_storage const fee_rate : nat = 333n; (* exchange fee rate distributed among the liquidity providers *) -type transfer_type is TransferType of token_transfer_params const token_func_count : nat = 2n; diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 3e7427fe..b51a422a 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -27,9 +27,8 @@ function get_pair (const key : tokens_info; const s : dex_storage) : (pair_info } with (pair, token_id) (* Helper function to prepare the token transfer *) -#if FA2_STANDARD_ENABLED -function wrap_transfer_trx(const owner : address; const receiver : address; const value : nat; const token_id : nat) : transfer_type is - TransferType(list[ +function wrap_fa2_transfer_trx(const owner : address; const receiver : address; const value : nat; const token_id : nat) : transfer_type_fa2 is + TransferTypeFA2(list[ record[ from_ = owner; txs = list [ record [ @@ -39,29 +38,22 @@ function wrap_transfer_trx(const owner : address; const receiver : address; cons ] ] ] ]) -#if FA2FA12_STANDARD_ENABLED + function wrap_fa12_transfer_trx(const owner : address; const receiver : address; const value : nat) : transfer_type_fa12 is TransferTypeFA12(owner, (receiver, value)) -#endif -#else -function wrap_transfer_trx(const owner : address; const receiver : address; const value : nat) : transfer_type is - TransferType(owner, (receiver, value)) -#endif (* Helper function to get token contract *) -function get_token_contract(const token_address : address) : contract(transfer_type) is - case (Tezos.get_entrypoint_opt("%transfer", token_address) : option(contract(transfer_type))) of +function get_fa2_token_contract(const token_address : address) : contract(transfer_type_fa2) is + case (Tezos.get_entrypoint_opt("%transfer", token_address) : option(contract(transfer_type_fa2))) of Some(contr) -> contr - | None -> (failwith("Dex/not-token") : contract(transfer_type)) + | None -> (failwith("Dex/not-token") : contract(transfer_type_fa2)) end; -#if FA2FA12_STANDARD_ENABLED function get_fa12_token_contract(const token_address : address) : contract(transfer_type_fa12) is case (Tezos.get_entrypoint_opt("%transfer", token_address) : option(contract(transfer_type_fa12))) of Some(contr) -> contr | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) end; -#endif #include "../partials/TTMethodFA2.ligo" @@ -72,12 +64,10 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con case p of | InitializeExchange(params) -> { (* check preconditions *) -#if FA2FA12_STANDARD_ENABLED -#else - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + failwith("Dex/wrong-token-id") else skip; -#endif + (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); const pair : pair_info = res.0; @@ -121,42 +111,77 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con s.tokens[token_id] := params.pair; (* prepare operations to get initial liquidity *) - operations := list[ - Tezos.transaction( - wrap_transfer_trx(Tezos.sender, - this, - params.token_a_in -#if FA2_STANDARD_ENABLED - , params.pair.token_a_id -#endif - ), - 0mutez, - get_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( -#if FA2FA12_STANDARD_ENABLED - wrap_fa12_transfer_trx( -#else - wrap_transfer_trx( -#endif - Tezos.sender, - this, - params.token_b_in -#if FA2_STANDARD_ENABLED -#if FA2FA12_STANDARD_ENABLED -#else - , params.pair.token_b_id -#endif -#endif + case params.pair.standard of + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx(Tezos.sender, + this, + params.token_a_in), + 0mutez, + get_fa12_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx(Tezos.sender, + this, + params.token_b_in ), - 0mutez, -#if FA2FA12_STANDARD_ENABLED - get_fa12_token_contract( -#else - get_token_contract( -#endif - params.pair.token_b_address) - )]; + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx(Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_b_address) + )]; + } + | Mixed -> { + operations :=list[ + Tezos.transaction( + wrap_fa2_transfer_trx(Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id + ), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.token_b_in + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + + } + end; } | TokenToTokenPayment(n) -> skip | InvestLiquidity(n) -> skip @@ -172,13 +197,10 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this case p of | InitializeExchange(n) -> skip | TokenToTokenPayment(params) -> { -#if FA2FA12_STANDARD_ENABLED -#else (* check preconditions *) - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + failwith("Dex/wrong-token-id") else skip; -#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -221,43 +243,80 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) - - operations := list[ - Tezos.transaction( - wrap_transfer_trx(Tezos.sender, - this, - params.amount_in -#if FA2_STANDARD_ENABLED - , params.pair.token_a_id -#endif + case params.pair.standard of + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in), + 0mutez, + get_fa12_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + this, + params.receiver, + token_b_out ), - 0mutez, - get_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( -#if FA2FA12_STANDARD_ENABLED - wrap_fa12_transfer_trx( -#else - wrap_transfer_trx( -#endif - this, - params.receiver, - token_b_out -#if FA2_STANDARD_ENABLED -#if FA2FA12_STANDARD_ENABLED -#else - , params.pair.token_b_id -#endif -#endif - ), - 0mutez, -#if FA2FA12_STANDARD_ENABLED - get_fa12_token_contract( -#else - get_token_contract( -#endif - params.pair.token_b_address) - )]; + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa2_transfer_trx( + this, + params.receiver, + token_b_out, + params.pair.token_b_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_b_address) + )]; + } + | Mixed -> { + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + params.pair.token_a_id + ), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + this, + params.receiver, + token_b_out + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + } + end; } | Buy -> { (* calculate amount out *) @@ -281,42 +340,80 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) - operations := list[ - Tezos.transaction( -#if FA2FA12_STANDARD_ENABLED - wrap_fa12_transfer_trx( -#else - wrap_transfer_trx( -#endif - Tezos.sender, - this, - params.amount_in -#if FA2_STANDARD_ENABLED -#if FA2FA12_STANDARD_ENABLED -#else - , params.pair.token_b_id -#endif -#endif - ), - 0mutez, -#if FA2FA12_STANDARD_ENABLED - get_fa12_token_contract( -#else - get_token_contract( -#endif - params.pair.token_b_address) - ); - Tezos.transaction( - wrap_transfer_trx(this, - params.receiver, - token_a_out -#if FA2_STANDARD_ENABLED - , params.pair.token_a_id -#endif + case params.pair.standard of + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in), + 0mutez, + get_fa12_token_contract(params.pair.token_b_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + this, + params.receiver, + token_a_out ), - 0mutez, - get_token_contract(params.pair.token_a_address) - )]; + 0mutez, + get_fa12_token_contract( + params.pair.token_a_address) + )]; + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_id), + 0mutez, + get_fa2_token_contract(params.pair.token_b_address) + ); + Tezos.transaction( + wrap_fa2_transfer_trx( + this, + params.receiver, + token_a_out, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_a_address) + )]; + } + | Mixed -> { + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_id + ), + 0mutez, + get_fa2_token_contract(params.pair.token_b_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + this, + params.receiver, + token_a_out + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_a_address) + )]; + } + end; } end; s.pairs[token_id] := pair; @@ -334,13 +431,10 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th | InitializeExchange(n) -> skip | TokenToTokenPayment(n) -> skip | InvestLiquidity(params) -> { -#if FA2FA12_STANDARD_ENABLED -#else (* check preconditions *) - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + failwith("Dex/wrong-token-id") else skip; -#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -401,42 +495,76 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th s.pairs[token_id] := pair; (* prepare operations to get initial liquidity *) - operations := list[ - Tezos.transaction( - wrap_transfer_trx(Tezos.sender, - this, - tokens_a_required -#if FA2_STANDARD_ENABLED - , params.pair.token_a_id -#endif + case params.pair.standard of + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx(Tezos.sender, + this, + params.token_a_in), + 0mutez, + get_fa12_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx(Tezos.sender, + this, + params.token_b_in ), - 0mutez, - get_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( -#if FA2FA12_STANDARD_ENABLED + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx(Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_b_address) + )]; + } + | Mixed -> { + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx(Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id + ), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( wrap_fa12_transfer_trx( -#else - wrap_transfer_trx( -#endif - Tezos.sender, - this, - tokens_b_required -#if FA2_STANDARD_ENABLED -#if FA2FA12_STANDARD_ENABLED -#else - , params.pair.token_b_id -#endif -#endif - ), - 0mutez, -#if FA2FA12_STANDARD_ENABLED - get_fa12_token_contract( -#else - get_token_contract( -#endif - params.pair.token_b_address) - )]; + Tezos.sender, + this, + params.token_b_in + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )] + } + end; } | DivestLiquidity(n) -> skip end @@ -451,13 +579,10 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenPayment(n) -> skip | InvestLiquidity(n) -> skip | DivestLiquidity(params) -> { -#if FA2FA12_STANDARD_ENABLED -#else (* check preconditions *) - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + failwith("Dex/wrong-token-id") else skip; -#endif (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -514,43 +639,80 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th s.pairs[token_id] := pair; (* prepare operations with XTZ and tokens to user *) - operations := list [ - transaction( - wrap_transfer_trx(this, - Tezos.sender, - token_a_divested -#if FA2_STANDARD_ENABLED - , params.pair.token_a_id -#endif - ), - 0mutez, - get_token_contract(params.pair.token_a_address) - ); - transaction( -#if FA2FA12_STANDARD_ENABLED + case params.pair.standard of + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( wrap_fa12_transfer_trx( -#else - wrap_transfer_trx( -#endif - this, - Tezos.sender, - token_b_divested -#if FA2_STANDARD_ENABLED -#if FA2FA12_STANDARD_ENABLED -#else - , params.pair.token_b_id -#endif -#endif + this, + Tezos.sender, + token_a_divested), + 0mutez, + get_fa12_token_contract( + params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + this, + Tezos.sender, + token_b_divested ), - 0mutez, -#if FA2FA12_STANDARD_ENABLED - get_fa12_token_contract( -#else - get_token_contract( -#endif - params.pair.token_b_address) - ); - ]; + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + this, + Tezos.sender, + token_a_divested, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa2_transfer_trx( + this, + Tezos.sender, + token_b_divested, + params.pair.token_b_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_b_address) + )]; + } + | Mixed -> { + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + this, + Tezos.sender, + token_a_divested, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract(params.pair.token_a_address) + ); + Tezos.transaction( + wrap_fa12_transfer_trx( + this, + Tezos.sender, + token_b_divested), + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )]; + } + end; } end } with (operations, s) From ece146af459a2d9c2a87eed23648a23d80305074 Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 14:59:35 +0300 Subject: [PATCH 23/63] fixes & updated tests for token to token --- .github/workflows/master.yml | 2 +- contracts/main/{TTDexFA2.ligo => TTDex.ligo} | 0 contracts/main/TTDexFA12.ligo | 17 -- contracts/main/TTDexFA2FA12.ligo | 19 -- contracts/partials/TTMethodDex.ligo | 14 +- migrations/2_deploy_token_to_token_dex.js | 16 +- test/BuyToken.spec.ts | 4 +- test/Default.spec.ts | 2 +- test/DivestLiquidity.spec.ts | 2 +- test/DivestTTLiquidity.spec.ts | 2 +- test/InitializeExchangeTest.spec.ts | 2 +- test/InitializeTTExchangeTest.spec.ts | 8 +- test/InvestLiquidity.spec.ts | 2 +- test/InvestTTLiquidity.spec.ts | 2 +- test/MetadataStorage.spec.ts | 2 +- test/RewardDistribution.spec.ts | 2 +- test/SellToken.spec.ts | 2 +- test/SetFunctionsTest.spec.ts | 2 +- test/TezToTokenPayment.spec.ts | 2 +- test/TokenAToTokenB.spec.ts | 2 +- test/TokenBToTokenA.spec.ts | 2 +- test/TokenToTezPayment.spec.ts | 2 +- test/TokenToTokenPayment.spec.ts | 2 +- test/Veto.spec.ts | 2 +- test/Vote.spec.ts | 2 +- test/helpers/ttContext.ts | 43 +--- test/helpers/ttdexFA12.ts | 238 ------------------- test/helpers/ttdexFA2.ts | 138 ++++++++--- test/helpers/types.ts | 1 + test/storage/TTFunctions.ts | 2 +- truffle.d.ts | 2 +- 31 files changed, 159 insertions(+), 379 deletions(-) rename contracts/main/{TTDexFA2.ligo => TTDex.ligo} (100%) delete mode 100644 contracts/main/TTDexFA12.ligo delete mode 100644 contracts/main/TTDexFA2FA12.ligo delete mode 100644 test/helpers/ttdexFA12.ts diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index f912736b..dfb2168b 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - standard: ["FA12", "FA2", "FA2FA12"] + standard: ["FA12", "FA2", "MIXED"] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 diff --git a/contracts/main/TTDexFA2.ligo b/contracts/main/TTDex.ligo similarity index 100% rename from contracts/main/TTDexFA2.ligo rename to contracts/main/TTDex.ligo diff --git a/contracts/main/TTDexFA12.ligo b/contracts/main/TTDexFA12.ligo deleted file mode 100644 index 6f2d22a3..00000000 --- a/contracts/main/TTDexFA12.ligo +++ /dev/null @@ -1,17 +0,0 @@ -#define TOKEN_TO_TOKEN_ENABLED -#include "../partials/TTDex.ligo" -#include "../partials/TTMethodDex.ligo" - -(* DexFA12 - Contract for exchanges for XTZ - FA1.2 token pair *) -function main (const p : full_action; const s : full_dex_storage) : full_return is - block { - const this: address = Tezos.self_address; - } with case p of - | Use(params) -> call_dex(params, this, s) - | Transfer(params) -> call_token(ITransfer(params), this, 0n, s) - | Balance_of(params) -> call_token(IBalance_of(params), this, 2n, s) - | Update_operators(params) -> call_token(IUpdate_operators(params), this, 1n, s) - | Get_reserves(params) -> get_reserves(params, s) - | SetDexFunction(params) -> ((nil:list(operation)), if params.index > 3n then (failwith("Dex/wrong-index") : full_dex_storage) else set_dex_function(params.index, params.func, s)) - | SetTokenFunction(params) -> ((nil:list(operation)), if params.index > 2n then (failwith("Dex/wrong-index") : full_dex_storage) else set_token_function(params.index, params.func, s)) - end diff --git a/contracts/main/TTDexFA2FA12.ligo b/contracts/main/TTDexFA2FA12.ligo deleted file mode 100644 index 27bc1ea8..00000000 --- a/contracts/main/TTDexFA2FA12.ligo +++ /dev/null @@ -1,19 +0,0 @@ -#define FA2_STANDARD_ENABLED -#define FA2FA12_STANDARD_ENABLED -#define TOKEN_TO_TOKEN_ENABLED -#include "../partials/TTDex.ligo" -#include "../partials/TTMethodDex.ligo" - -(* DexFA2 - Contract for exchanges for XTZ - FA2 token pair *) -function main (const p : full_action; const s : full_dex_storage) : full_return is - block { - const this: address = Tezos.self_address; - } with case p of - | Use(params) -> call_dex(params, this, s) - | Transfer(params) -> call_token(ITransfer(params), this, 0n, s) - | Balance_of(params) -> call_token(IBalance_of(params), this, 2n, s) - | Update_operators(params) -> call_token(IUpdate_operators(params), this, 1n, s) - | Get_reserves(params) -> get_reserves(params, s) - | SetDexFunction(params) -> ((nil:list(operation)), if params.index > 3n then (failwith("Dex/wrong-index") : full_dex_storage) else set_dex_function(params.index, params.func, s)) - | SetTokenFunction(params) -> ((nil:list(operation)), if params.index > 2n then (failwith("Dex/wrong-index") : full_dex_storage) else set_token_function(params.index, params.func, s)) - end diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index b51a422a..62d4cfdc 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -393,23 +393,23 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this | Mixed -> { operations := list[ Tezos.transaction( - wrap_fa2_transfer_trx( + wrap_fa12_transfer_trx( Tezos.sender, this, - params.amount_in, - params.pair.token_b_id + params.amount_in ), 0mutez, - get_fa2_token_contract(params.pair.token_b_address) + get_fa12_token_contract(params.pair.token_b_address) ); Tezos.transaction( - wrap_fa12_transfer_trx( + wrap_fa2_transfer_trx( this, params.receiver, - token_a_out + token_a_out, + params.pair.token_a_id ), 0mutez, - get_fa12_token_contract( + get_fa2_token_contract( params.pair.token_a_address) )]; } diff --git a/migrations/2_deploy_token_to_token_dex.js b/migrations/2_deploy_token_to_token_dex.js index e448f259..e82d698a 100644 --- a/migrations/2_deploy_token_to_token_dex.js +++ b/migrations/2_deploy_token_to_token_dex.js @@ -1,6 +1,6 @@ const standard = process.env.EXCHANGE_TOKEN_STANDARD; -const usedStandard = standard == "FA2FA12" ? "FA2" : standard; -const TTDex = artifacts.require("TTDex" + standard); +const usedStandard = standard == "MIXED" ? "FA2" : standard; +const TTDex = artifacts.require("TTDex"); const MetadataStorage = artifacts.require("MetadataStorage"); const dexStorage = require("../storage/TTDex"); const { TezosToolkit } = require("@taquito/taquito"); @@ -49,7 +49,7 @@ module.exports = async (deployer, network, accounts) => { for (dexFunction of dexFunctions) { const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetDexFunction(record index =${dexFunction.index}n; func = ${dexFunction.name}; end)'`, + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex.ligo main 'SetDexFunction(record index =${dexFunction.index}n; func = ${dexFunction.name}; end)'`, { maxBuffer: 1024 * 500 } ); const operation = await tezos.contract.transfer({ @@ -64,7 +64,7 @@ module.exports = async (deployer, network, accounts) => { } for (tokenFunction of tokenFunctions[standard]) { const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetTokenFunction(record index =${tokenFunction.index}n; func = ${tokenFunction.name}; end)'`, + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex.ligo main 'SetTokenFunction(record index =${tokenFunction.index}n; func = ${tokenFunction.name}; end)'`, { maxBuffer: 1024 * 500 } ); const operation = await tezos.contract.transfer({ @@ -80,12 +80,12 @@ module.exports = async (deployer, network, accounts) => { if (network !== "mainnet") { let token0Instance = await tezos.contract.at( - standard == "FA2FA12" + standard == "MIXED" ? (await TokenFA2.new(tokenFA2Storage)).address.toString() : (await Token.new(tokenStorage)).address.toString() ); let token1Instance = await tezos.contract.at( - standard == "FA2FA12" + standard == "MIXED" ? (await TokenFA12.new(tokenFA12Storage)).address.toString() : (await Token.new(tokenStorage)).address.toString() ); @@ -132,7 +132,7 @@ module.exports = async (deployer, network, accounts) => { ]) .send(); await operation.confirmation(); - if (standard == "FA2FA12") { + if (standard == "MIXED") { operation = await token1Instance.methods .approve(dexInstance.address.toString(), initialTokenAmount) .send(); @@ -151,7 +151,7 @@ module.exports = async (deployer, network, accounts) => { } await operation.confirmation(); - if (standard === "FA2FA12") { + if (standard === "MIXED") { operation = await dex.methods .use( "initializeExchange", diff --git a/test/BuyToken.spec.ts b/test/BuyToken.spec.ts index a9b0d88b..99df8f7f 100644 --- a/test/BuyToken.spec.ts +++ b/test/BuyToken.spec.ts @@ -5,7 +5,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("BuyToken()", function () { +contract.only("BuyToken()", function () { let context: TTContext; const tokenAAmount: number = 100000; const tokenBAmount: number = 1000; @@ -22,7 +22,7 @@ contract("BuyToken()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/Default.spec.ts b/test/Default.spec.ts index e892a0d1..0938feb4 100644 --- a/test/Default.spec.ts +++ b/test/Default.spec.ts @@ -6,7 +6,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo, accuracy } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("Default()", function () { let context: Context; let tokenAddress: string; diff --git a/test/DivestLiquidity.spec.ts b/test/DivestLiquidity.spec.ts index afd95b59..7aa38ca2 100644 --- a/test/DivestLiquidity.spec.ts +++ b/test/DivestLiquidity.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("DivestLiquidity()", function () { let context: Context; let tokenAddress: string; diff --git a/test/DivestTTLiquidity.spec.ts b/test/DivestTTLiquidity.spec.ts index f3fa35ca..96ca8477 100644 --- a/test/DivestTTLiquidity.spec.ts +++ b/test/DivestTTLiquidity.spec.ts @@ -26,7 +26,7 @@ contract("DivestTTLiquidity()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/InitializeExchangeTest.spec.ts b/test/InitializeExchangeTest.spec.ts index b21fd990..79651189 100644 --- a/test/InitializeExchangeTest.spec.ts +++ b/test/InitializeExchangeTest.spec.ts @@ -4,7 +4,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("InitializeExchange()", function () { let context: Context; let aliceAddress: string = accounts.alice.pkh; diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index 7f169e6d..4b572cd3 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -26,12 +26,12 @@ contract("InitializeTTExchange()", function () { let tokenBAddress: string; before(async () => { tokenAAddress = await context.createToken( - standard == "FA2FA12" ? "FA2" : standard + standard == "MIXED" ? "FA2" : standard ); tokenBAddress = await context.createToken( - standard == "FA2FA12" ? "FA12" : standard + standard == "MIXED" ? "FA12" : standard ); - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; @@ -172,7 +172,7 @@ contract("InitializeTTExchange()", function () { before(async () => { tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { tokenBAddress = context.tokens[0].contract.address; tokenAAddress = context.tokens[1].contract.address; } diff --git a/test/InvestLiquidity.spec.ts b/test/InvestLiquidity.spec.ts index 305c2090..80eca69d 100644 --- a/test/InvestLiquidity.spec.ts +++ b/test/InvestLiquidity.spec.ts @@ -4,7 +4,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("InvestLiquidity()", function () { let context: Context; let tokenAddress: string; diff --git a/test/InvestTTLiquidity.spec.ts b/test/InvestTTLiquidity.spec.ts index cb543535..231d7709 100644 --- a/test/InvestTTLiquidity.spec.ts +++ b/test/InvestTTLiquidity.spec.ts @@ -23,7 +23,7 @@ contract("InvestTTLiquidity()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/MetadataStorage.spec.ts b/test/MetadataStorage.spec.ts index 4ab20577..39086a75 100644 --- a/test/MetadataStorage.spec.ts +++ b/test/MetadataStorage.spec.ts @@ -7,7 +7,7 @@ import { MichelsonMap } from "@taquito/michelson-encoder"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; const CMetadataStorage = artifacts.require("MetadataStorage"); -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("MetadataStorage()", function () { let metadataStorage: Metadata; const aliceAddress: string = accounts.alice.pkh; diff --git a/test/RewardDistribution.spec.ts b/test/RewardDistribution.spec.ts index cf791e93..ddc01271 100644 --- a/test/RewardDistribution.spec.ts +++ b/test/RewardDistribution.spec.ts @@ -6,7 +6,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo, accuracy } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("RewardsDistribution()", function () { let context: Context; let tokenAddress: string; diff --git a/test/SellToken.spec.ts b/test/SellToken.spec.ts index b7792c3b..fa281c34 100644 --- a/test/SellToken.spec.ts +++ b/test/SellToken.spec.ts @@ -22,7 +22,7 @@ contract("SellToken()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/SetFunctionsTest.spec.ts b/test/SetFunctionsTest.spec.ts index 165fe69c..f1e22d4c 100644 --- a/test/SetFunctionsTest.spec.ts +++ b/test/SetFunctionsTest.spec.ts @@ -2,7 +2,7 @@ import { Context } from "./helpers/context"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("SetXFunctions()", function () { let context: Context; let tokenAddress: string; diff --git a/test/TezToTokenPayment.spec.ts b/test/TezToTokenPayment.spec.ts index b3d53faa..0318d172 100644 --- a/test/TezToTokenPayment.spec.ts +++ b/test/TezToTokenPayment.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("TezToTokenPayment()", function () { let context: Context; let tokenAddress: string; diff --git a/test/TokenAToTokenB.spec.ts b/test/TokenAToTokenB.spec.ts index c8ba1706..6ba0fb6d 100644 --- a/test/TokenAToTokenB.spec.ts +++ b/test/TokenAToTokenB.spec.ts @@ -23,7 +23,7 @@ contract("TokenAToTokenB()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/TokenBToTokenA.spec.ts b/test/TokenBToTokenA.spec.ts index ad865965..afc0c4fa 100644 --- a/test/TokenBToTokenA.spec.ts +++ b/test/TokenBToTokenA.spec.ts @@ -23,7 +23,7 @@ contract("TokenBToTokenA()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "FA2FA12" && tokenAAddress > tokenBAddress) { + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { const tmp = context.tokens[0]; context.tokens[0] = context.tokens[1]; context.tokens[1] = tmp; diff --git a/test/TokenToTezPayment.spec.ts b/test/TokenToTezPayment.spec.ts index db656f54..693b45c6 100644 --- a/test/TokenToTezPayment.spec.ts +++ b/test/TokenToTezPayment.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("TokenToTezPayment()", function () { let context: Context; let tokenAddress: string; diff --git a/test/TokenToTokenPayment.spec.ts b/test/TokenToTokenPayment.spec.ts index 6dc94a55..a67d1943 100644 --- a/test/TokenToTokenPayment.spec.ts +++ b/test/TokenToTokenPayment.spec.ts @@ -3,7 +3,7 @@ import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import BigNumber from "bignumber.js"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("TokenToTokenPayment()", function () { let context: Context; let tokenAddress: string; diff --git a/test/Veto.spec.ts b/test/Veto.spec.ts index f84f0487..ead877e6 100644 --- a/test/Veto.spec.ts +++ b/test/Veto.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("Veto()", function () { let context: Context; let tokenAddress: string; diff --git a/test/Vote.spec.ts b/test/Vote.spec.ts index 86d1f07e..bb2b7218 100644 --- a/test/Vote.spec.ts +++ b/test/Vote.spec.ts @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js"; import accounts from "./accounts/accounts"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -if (standard !== "FA2FA12") { +if (standard !== "MIXED") { contract("Vote()", function () { let context: Context; let tokenAddress: string; diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 105f7c1a..58ddf697 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -1,8 +1,6 @@ const standard = process.env.EXCHANGE_TOKEN_STANDARD; -import { TTDex as TTDexFA12 } from "./ttdexFA12"; import { TTDex as TTDexFA2 } from "./ttdexFA2"; -import { TTDex as TTDexFA2FA12 } from "./ttdexFA2FA12"; import { TokenFA12 } from "./tokenFA12"; import { prepareProviderOptions } from "./utils"; @@ -15,28 +13,10 @@ import { Token } from "./token"; import { TezosToolkit } from "@taquito/taquito"; import BigNumber from "bignumber.js"; -let tokenStorage, CDex, CToken; -type Dex = TTDexFA12 | TTDexFA2 | TTDexFA2FA12; +type Dex = TTDexFA2; const CTokenFA12 = artifacts.require("TokenFA12"); const CTokenFA2 = artifacts.require("TokenFA2"); - -switch (standard) { - case "FA2": - tokenStorage = tokenFA2Storage; - CDex = artifacts.require("TTDexFA2"); - CToken = artifacts.require("TokenFA2"); - break; - case "FA12": - tokenStorage = tokenFA12Storage; - CDex = artifacts.require("TTDexFA12"); - CToken = artifacts.require("TokenFA12"); - break; - case "FA2FA12": - tokenStorage = tokenFA12Storage; - CDex = artifacts.require("TTDexFA2FA12"); - CToken = artifacts.require("TokenFA2"); - break; -} +const CDex = artifacts.require("TTDex"); export class TTContext { public dex: Dex; @@ -72,18 +52,7 @@ export class TTContext { let dexInstance = useDeployedDex ? await CDex.deployed() : await CDex.new(dexStorage); - let dex; - switch (standard) { - case "FA2": - dex = await TTDexFA2.init(dexInstance.address.toString()); - break; - case "FA12": - dex = await TTDexFA12.init(dexInstance.address.toString()); - break; - case "FA2FA12": - dex = await TTDexFA2FA12.init(dexInstance.address.toString()); - break; - } + const dex = await TTDexFA2.init(dexInstance.address.toString()); let context = new TTContext(dex, []); if (setDexFunctions) { @@ -174,12 +143,12 @@ export class TTContext { ): Promise { pairConfig.tokenAAddress = pairConfig.tokenAAddress || - (await this.createToken(standard == "FA2FA12" ? "FA2" : standard)); + (await this.createToken(standard == "MIXED" ? "FA2" : standard)); pairConfig.tokenBAddress = pairConfig.tokenBAddress || - (await this.createToken(standard == "FA2FA12" ? "FA12" : standard)); + (await this.createToken(standard == "MIXED" ? "FA12" : standard)); if ( - standard !== "FA2FA12" && + standard !== "MIXED" && pairConfig.tokenAAddress > pairConfig.tokenBAddress ) { const tmp = pairConfig.tokenAAddress; diff --git a/test/helpers/ttdexFA12.ts b/test/helpers/ttdexFA12.ts deleted file mode 100644 index 47c6d56d..00000000 --- a/test/helpers/ttdexFA12.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { ContractAbstraction, ContractProvider } from "@taquito/taquito"; -import { BatchOperation } from "@taquito/taquito/dist/types/operations/batch-operation"; -import { TransactionOperation } from "@taquito/taquito/dist/types/operations/transaction-operation"; -import { BigNumber } from "bignumber.js"; -import { TokenFA2 } from "./tokenFA2"; -import { TTDexStorage } from "./types"; -import { getLigo } from "./utils"; -import { execSync } from "child_process"; -import { confirmOperation } from "./confirmation"; -const standard = process.env.EXCHANGE_TOKEN_STANDARD; - -export class TTDex extends TokenFA2 { - public contract: ContractAbstraction; - public storage: TTDexStorage; - - constructor(contract: ContractAbstraction) { - super(contract); - } - - static async init(dexAddress: string): Promise { - return new TTDex(await tezos.contract.at(dexAddress)); - } - - async updateStorage( - maps: { - tokens?: string[]; - token_to_id?: string[]; - pairs?: string[]; - ledger?: any[]; - dex_lambdas?: number[]; - token_lambdas?: number[]; - } = {} - ): Promise { - const storage: any = await this.contract.storage(); - this.storage = { - pairs_count: storage.storage.pairs_count, - tokens: {}, - token_to_id: {}, - pairs: {}, - ledger: {}, - dex_lambdas: {}, - token_lambdas: {}, - }; - for (let key in maps) { - if (["dex_lambdas", "token_lambdas"].includes(key)) continue; - this.storage[key] = await maps[key].reduce(async (prev, current) => { - try { - return { - ...(await prev), - [key == "ledger" ? current[0] : current]: await storage.storage[ - key - ].get(current), - }; - } catch (ex) { - return { - ...(await prev), - }; - } - }, Promise.resolve({})); - } - for (let key in maps) { - if (!["dex_lambdas", "token_lambdas"].includes(key)) continue; - this[key] = await maps[key].reduce(async (prev, current) => { - try { - return { - ...(await prev), - [current]: await storage[key].get(current.toString()), - }; - } catch (ex) { - return { - ...(await prev), - }; - } - }, Promise.resolve({})); - } - } - - async initializeExchange( - tokenAAddress: string, - tokenBAddress: string, - tokenAAmount: number, - tokenBAmount: number, - approve: boolean = true - ): Promise { - if (approve) { - await this.approveToken( - tokenAAddress, - tokenAAmount, - this.contract.address - ); - await this.approveToken( - tokenBAddress, - tokenBAmount, - this.contract.address - ); - } - const operation = await this.contract.methods - .use( - "initializeExchange", - tokenAAddress, - tokenBAddress, - tokenAAmount, - tokenBAmount - ) - .send(); - await confirmOperation(tezos, operation.hash); - return operation; - } - - async tokenToTokenPayment( - tokenAAddress: string, - tokenBAddress: string, - opType: string, - amountIn: number, - minAmountOut: number, - receiver: string - ): Promise { - if (opType == "buy") { - await this.approveToken(tokenBAddress, amountIn, this.contract.address); - } else { - await this.approveToken(tokenAAddress, amountIn, this.contract.address); - } - const operation = await this.contract.methods - .use( - "tokenToTokenPayment", - tokenAAddress, - tokenBAddress, - opType, - null, - amountIn, - minAmountOut, - receiver - ) - .send(); - await confirmOperation(tezos, operation.hash); - return operation; - } - - async investLiquidity( - pairId: string, - tokenAAmount: number, - tokenBAmount: number, - minShares: number - ): Promise { - await this.updateStorage({ tokens: [pairId] }); - let pair = this.storage.tokens[pairId]; - await this.approveToken( - pair.token_a_address, - tokenAAmount, - this.contract.address - ); - await this.approveToken( - pair.token_b_address, - tokenBAmount, - this.contract.address - ); - const operation = await this.contract.methods - .use( - "investLiquidity", - pair.token_a_address, - pair.token_b_address, - tokenAAmount, - tokenBAmount, - minShares - ) - .send(); - await confirmOperation(tezos, operation.hash); - return operation; - } - - async divestLiquidity( - pairId: string, - tokenAAmount: number, - tokenBAmount: number, - sharesBurned: number - ): Promise { - await this.updateStorage({ tokens: [pairId] }); - let pair = this.storage.tokens[pairId]; - const operation = await this.contract.methods - .use( - "divestLiquidity", - pair.token_a_address, - pair.token_b_address, - tokenAAmount, - tokenBAmount, - sharesBurned - ) - .send(); - await confirmOperation(tezos, operation.hash); - return operation; - } - - async approveToken( - tokenAddress: string, - tokenAmount: number, - address: string - ): Promise { - await this.updateStorage(); - let token = await tezos.contract.at(tokenAddress); - let operation = await token.methods.approve(address, tokenAmount).send(); - await confirmOperation(tezos, operation.hash); - return operation; - } - - async setDexFunction(index: number, lambdaName: string): Promise { - let ligo = getLigo(true); - const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetDexFunction(record index =${index}n; func = ${lambdaName}; end)'`, - { maxBuffer: 1024 * 500 } - ); - const operation = await tezos.contract.transfer({ - to: this.contract.address, - amount: 0, - parameter: { - entrypoint: "setDexFunction", - value: JSON.parse(stdout.toString()).args[0].args[0].args[0], - }, - }); - await confirmOperation(tezos, operation.hash); - } - - async setTokenFunction(index: number, lambdaName: string): Promise { - let ligo = getLigo(true); - const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetTokenFunction(record index =${index}n; func = ${lambdaName}; end)'`, - { maxBuffer: 1024 * 500 } - ); - const operation = await tezos.contract.transfer({ - to: this.contract.address, - amount: 0, - parameter: { - entrypoint: "setTokenFunction", - value: JSON.parse(stdout.toString()).args[0].args[0].args[0], - }, - }); - await confirmOperation(tezos, operation.hash); - } -} diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index 74d41dff..87cc8d61 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -87,22 +87,40 @@ export class TTDex extends TokenFA2 { approve: boolean = true ): Promise { if (approve) { - await this.approveToken( - tokenAAddress, - tokenAid, - tokenAAmount, - this.contract.address - ); - await this.approveToken( - tokenBAddress, - tokenBid, - tokenBAmount, - this.contract.address - ); + if (["FA2", "MIXED"].includes(standard)) { + await this.approveFA2Token( + tokenAAddress, + tokenAid, + tokenAAmount, + this.contract.address + ); + } else { + await this.approveFA12Token( + tokenAAddress, + tokenAAmount, + this.contract.address + ); + } + if ("FA2" == standard) { + await this.approveFA2Token( + tokenBAddress, + tokenBid, + tokenBAmount, + this.contract.address + ); + } else { + await this.approveFA12Token( + tokenBAddress, + tokenBAmount, + this.contract.address + ); + } } const operation = await this.contract.methods .use( "initializeExchange", + standard.toLowerCase(), + null, tokenAAddress, tokenAid, tokenBAddress, @@ -125,9 +143,42 @@ export class TTDex extends TokenFA2 { tokenAid: BigNumber = new BigNumber(0), tokenBid: BigNumber = new BigNumber(0) ): Promise { + if (opType == "buy") { + if (["FA2"].includes(standard)) { + await this.approveFA2Token( + tokenBAddress, + tokenBid, + amountIn, + this.contract.address + ); + } else { + await this.approveFA12Token( + tokenBAddress, + amountIn, + this.contract.address + ); + } + } else { + if (["FA2", "MIXED"].includes(standard)) { + await this.approveFA2Token( + tokenAAddress, + tokenAid, + amountIn, + this.contract.address + ); + } else { + await this.approveFA12Token( + tokenAAddress, + amountIn, + this.contract.address + ); + } + } const operation = await this.contract.methods .use( "tokenToTokenPayment", + standard.toLowerCase(), + null, tokenAAddress, tokenAid, tokenBAddress, @@ -151,21 +202,40 @@ export class TTDex extends TokenFA2 { ): Promise { await this.updateStorage({ tokens: [pairId] }); let pair = this.storage.tokens[pairId]; - await this.approveToken( - pair.token_a_address, - pair.token_a_id, - tokenAAmount, - this.contract.address - ); - await this.approveToken( - pair.token_b_address, - pair.token_b_id, - tokenBAmount, - this.contract.address - ); + if (["FA2", "MIXED"].includes(standard)) { + await this.approveFA2Token( + pair.token_a_address, + pair.token_a_id, + + tokenAAmount, + this.contract.address + ); + } else { + await this.approveFA12Token( + pair.token_a_address, + tokenAAmount, + this.contract.address + ); + } + if ("FA2" == standard) { + await this.approveFA2Token( + pair.token_b_address, + pair.token_b_id, + tokenBAmount, + this.contract.address + ); + } else { + await this.approveFA12Token( + pair.token_b_address, + tokenBAmount, + this.contract.address + ); + } const operation = await this.contract.methods .use( "investLiquidity", + pair.standard, + null, pair.token_a_address, pair.token_a_id, pair.token_b_address, @@ -190,6 +260,8 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "divestLiquidity", + pair.standard, + null, pair.token_a_address, pair.token_a_id, pair.token_b_address, @@ -203,7 +275,7 @@ export class TTDex extends TokenFA2 { return operation; } - async approveToken( + async approveFA2Token( tokenAddress: string, tokenId: BigNumber, tokenAmount: number, @@ -226,10 +298,22 @@ export class TTDex extends TokenFA2 { return operation; } + async approveFA12Token( + tokenAddress: string, + tokenAmount: number, + address: string + ): Promise { + await this.updateStorage(); + let token = await tezos.contract.at(tokenAddress); + let operation = await token.methods.approve(address, tokenAmount).send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + async setDexFunction(index: number, lambdaName: string): Promise { let ligo = getLigo(true); const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetDexFunction(record index =${index}n; func = ${lambdaName}; end)'`, + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex.ligo main 'SetDexFunction(record index =${index}n; func = ${lambdaName}; end)'`, { maxBuffer: 1024 * 500 } ); const operation = await tezos.contract.transfer({ @@ -246,7 +330,7 @@ export class TTDex extends TokenFA2 { async setTokenFunction(index: number, lambdaName: string): Promise { let ligo = getLigo(true); const stdout = execSync( - `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex${standard}.ligo main 'SetTokenFunction(record index =${index}n; func = ${lambdaName}; end)'`, + `${ligo} compile-parameter --michelson-format=json $PWD/contracts/main/TTDex.ligo main 'SetTokenFunction(record index =${index}n; func = ${lambdaName}; end)'`, { maxBuffer: 1024 * 500 } ); const operation = await tezos.contract.transfer({ diff --git a/test/helpers/types.ts b/test/helpers/types.ts index 1237c078..ac2daeb9 100644 --- a/test/helpers/types.ts +++ b/test/helpers/types.ts @@ -5,6 +5,7 @@ export declare type TokenInfo = { token_b_address: string; token_a_id?: BigNumber; token_b_id?: BigNumber; + standard: string; }; export declare type PairInfo = { diff --git a/test/storage/TTFunctions.ts b/test/storage/TTFunctions.ts index bff5c8b0..79e147fc 100644 --- a/test/storage/TTFunctions.ts +++ b/test/storage/TTFunctions.ts @@ -34,6 +34,6 @@ export let tokenFunctions = [ ]; module.exports.tokenFunctions = { FA12: tokenFunctions, - FA2FA12: tokenFunctions, + MIXED: tokenFunctions, FA2: tokenFunctions, }; diff --git a/truffle.d.ts b/truffle.d.ts index c70a09c1..031a7ac5 100644 --- a/truffle.d.ts +++ b/truffle.d.ts @@ -29,8 +29,8 @@ declare interface Artifacts { require(name: "DexFA12"): Contract; require(name: "DexFA2"): Contract; require(name: "TTDexFA2FA12"): Contract; + require(name: "TTDex"): Contract; require(name: "TTDexFA12"): Contract; - require(name: "TTDexFA2"): Contract; } declare interface TokenContractInstance { From 601051cabb0fd3ce01a8f7707e3d251d01d64752 Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 14:59:51 +0300 Subject: [PATCH 24/63] minor fixes --- scripts/cli.js | 2 +- storage/TTFunctions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/cli.js b/scripts/cli.js index c596fc89..dada158e 100755 --- a/scripts/cli.js +++ b/scripts/cli.js @@ -73,7 +73,7 @@ program .option("-j, --no-json", "The format of output file") .option("-g, --no-dockerized_ligo", "Switch global ligo") .action(function (options) { - if (process.env.EXCHANGE_TOKEN_STANDARD == "FA2FA12") return; + if (process.env.EXCHANGE_TOKEN_STANDARD == "MIXED") return; let contractName = `./main/Dex${process.env.EXCHANGE_TOKEN_STANDARD}`; exec("mkdir -p " + options.output_dir); if (contractName === "*") { diff --git a/storage/TTFunctions.js b/storage/TTFunctions.js index 8557e415..f193ca42 100644 --- a/storage/TTFunctions.js +++ b/storage/TTFunctions.js @@ -34,6 +34,6 @@ let tokenFunctions = [ ]; module.exports.tokenFunctions = { FA12: tokenFunctions, - FA2FA12: tokenFunctions, + MIXED: tokenFunctions, FA2: tokenFunctions, }; From 777dc9329661533574b10ecdbecab6eb39e2b584 Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 15:00:19 +0300 Subject: [PATCH 25/63] remove only --- test/BuyToken.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BuyToken.spec.ts b/test/BuyToken.spec.ts index 99df8f7f..ad04f928 100644 --- a/test/BuyToken.spec.ts +++ b/test/BuyToken.spec.ts @@ -5,7 +5,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract.only("BuyToken()", function () { +contract("BuyToken()", function () { let context: TTContext; const tokenAAmount: number = 100000; const tokenBAmount: number = 1000; From 2c5f76a1f7aa54cab7f8f4e749016946b0b6b2b8 Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 15:20:19 +0300 Subject: [PATCH 26/63] minor fixes --- test/helpers/ttdexFA2.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index 87cc8d61..7b614bdf 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -234,7 +234,7 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "investLiquidity", - pair.standard, + standard.toLowerCase(), null, pair.token_a_address, pair.token_a_id, @@ -260,7 +260,7 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "divestLiquidity", - pair.standard, + standard.toLowerCase(), null, pair.token_a_address, pair.token_a_id, From a21a74d3e16e759601f46df181d89ff57f28a315 Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 16:03:07 +0300 Subject: [PATCH 27/63] fix invest liquidity --- contracts/partials/ITTDex.ligo | 16 ++ contracts/partials/TTMethodDex.ligo | 245 +++++++++++++++++++++++++++- 2 files changed, 255 insertions(+), 6 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 19cc4449..f994b603 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -49,6 +49,22 @@ type dex_storage is record [ ] type swap_type is Buy | Sell +type swap_slice_type is record [ + swap : tokens_info; + operation : swap_type; +] + + +(* Entrypoint arguments *) +type token_to_token_route_params is + [@layout:comb] + record [ + swaps : list(swap_slice_type); + amount_in : nat; (* amount of tokens to be exchanged *) + min_amount_out : nat; (* min amount of XTZ received to accept exchange *) + receiver : address; (* tokens receiver *) + ] + (* Entrypoint arguments *) type token_to_token_payment_params is [@layout:comb] diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 62d4cfdc..9efe49c4 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -423,6 +423,239 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this end } with (operations, s) +// (* Exchange tokens to tez, note: tokens should be approved before the operation *) +// function internal_token_to_token_swap (const swap : swap_slice_type; const amount_in : nat; const s : dex_storage) : (nat * dex_storage) is +// block { +// var operations : list(operation) := list[]; +// case p of +// | InitializeExchange(n) -> skip +// | TokenToTokenPayment(params) -> { +// (* check preconditions *) +// if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then +// failwith("Dex/wrong-token-id") +// else skip; + +// (* get par info*) +// const res : (pair_info * nat) = get_pair(params.pair, s); +// const pair : pair_info = res.0; +// const token_id : nat = res.1; + +// (* ensure there is liquidity *) +// if pair.token_a_pool * pair.token_b_pool > 0n then +// skip +// else failwith("Dex/not-launched"); + +// if params.amount_in > 0n (* non-zero amount of tokens exchanged *) +// then skip +// else failwith ("Dex/zero-amount-in"); + +// if params.min_amount_out > 0n (* non-zero amount of tokens exchanged *) +// then skip +// else failwith ("Dex/zero-min-amount-out"); + +// case params.operation of +// | Sell -> { +// (* calculate amount out *) +// const token_a_in_with_fee : nat = params.amount_in * 997n; +// const numerator : nat = token_a_in_with_fee * pair.token_b_pool; +// const denominator : nat = pair.token_a_pool * 1000n + token_a_in_with_fee; + +// (* calculate swapped token amount *) +// const token_b_out : nat = numerator / denominator; + +// (* ensure requirements *) +// if token_b_out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) +// then skip else failwith("Dex/wrong-min-out"); + +// (* ensure requirements *) +// if token_b_out <= pair.token_b_pool / 3n (* the price impact isn't to high *) +// then { +// (* update XTZ pool *) +// pair.token_b_pool := abs(pair.token_b_pool - token_b_out); +// pair.token_a_pool := pair.token_a_pool + params.amount_in; +// } else failwith("Dex/high-out"); + +// (* prepare operations to withdraw user's tokens and transfer XTZ *) +// case params.pair.standard of +// | Fa12 -> { +// if params.pair.token_a_address > params.pair.token_b_address then +// failwith("Dex/wrong-pair") +// else skip; +// operations := list[ +// Tezos.transaction( +// wrap_fa12_transfer_trx( +// Tezos.sender, +// this, +// params.amount_in), +// 0mutez, +// get_fa12_token_contract(params.pair.token_a_address) +// ); +// Tezos.transaction( +// wrap_fa12_transfer_trx( +// this, +// params.receiver, +// token_b_out +// ), +// 0mutez, +// get_fa12_token_contract( +// params.pair.token_b_address) +// )]; +// } +// | Fa2 -> { +// if params.pair.token_a_address > params.pair.token_b_address then +// failwith("Dex/wrong-pair") +// else skip; +// operations := list[ +// Tezos.transaction( +// wrap_fa2_transfer_trx( +// Tezos.sender, +// this, +// params.amount_in, +// params.pair.token_a_id), +// 0mutez, +// get_fa2_token_contract(params.pair.token_a_address) +// ); +// Tezos.transaction( +// wrap_fa2_transfer_trx( +// this, +// params.receiver, +// token_b_out, +// params.pair.token_b_id), +// 0mutez, +// get_fa2_token_contract( +// params.pair.token_b_address) +// )]; +// } +// | Mixed -> { +// operations := list[ +// Tezos.transaction( +// wrap_fa2_transfer_trx( +// Tezos.sender, +// this, +// params.amount_in, +// params.pair.token_a_id +// ), +// 0mutez, +// get_fa2_token_contract(params.pair.token_a_address) +// ); +// Tezos.transaction( +// wrap_fa12_transfer_trx( +// this, +// params.receiver, +// token_b_out +// ), +// 0mutez, +// get_fa12_token_contract( +// params.pair.token_b_address) +// )]; +// } +// end; +// } +// | Buy -> { +// (* calculate amount out *) +// const token_b_in_with_fee : nat = params.amount_in * 997n; +// const numerator : nat = token_b_in_with_fee * pair.token_a_pool; +// const denominator : nat = pair.token_b_pool * 1000n + token_b_in_with_fee; + +// (* calculate swapped token amount *) +// const token_a_out : nat = numerator / denominator; + +// (* ensure requirements *) +// if token_a_out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) +// then skip else failwith("Dex/wrong-min-out"); + +// (* ensure requirements *) +// if token_a_out <= pair.token_a_pool / 3n (* the price impact isn't to high *) +// then { +// (* update XTZ pool *) +// pair.token_a_pool := abs(pair.token_a_pool - token_a_out); +// pair.token_b_pool := pair.token_b_pool + params.amount_in; +// } else failwith("Dex/high-out"); + +// (* prepare operations to withdraw user's tokens and transfer XTZ *) +// case params.pair.standard of +// | Fa12 -> { +// if params.pair.token_a_address > params.pair.token_b_address then +// failwith("Dex/wrong-pair") +// else skip; +// operations := list[ +// Tezos.transaction( +// wrap_fa12_transfer_trx( +// Tezos.sender, +// this, +// params.amount_in), +// 0mutez, +// get_fa12_token_contract(params.pair.token_b_address) +// ); +// Tezos.transaction( +// wrap_fa12_transfer_trx( +// this, +// params.receiver, +// token_a_out +// ), +// 0mutez, +// get_fa12_token_contract( +// params.pair.token_a_address) +// )]; +// } +// | Fa2 -> { +// if params.pair.token_a_address > params.pair.token_b_address then +// failwith("Dex/wrong-pair") +// else skip; +// operations := list[ +// Tezos.transaction( +// wrap_fa2_transfer_trx( +// Tezos.sender, +// this, +// params.amount_in, +// params.pair.token_b_id), +// 0mutez, +// get_fa2_token_contract(params.pair.token_b_address) +// ); +// Tezos.transaction( +// wrap_fa2_transfer_trx( +// this, +// params.receiver, +// token_a_out, +// params.pair.token_a_id), +// 0mutez, +// get_fa2_token_contract( +// params.pair.token_a_address) +// )]; +// } +// | Mixed -> { +// operations := list[ +// Tezos.transaction( +// wrap_fa12_transfer_trx( +// Tezos.sender, +// this, +// params.amount_in +// ), +// 0mutez, +// get_fa12_token_contract(params.pair.token_b_address) +// ); +// Tezos.transaction( +// wrap_fa2_transfer_trx( +// this, +// params.receiver, +// token_a_out, +// params.pair.token_a_id +// ), +// 0mutez, +// get_fa2_token_contract( +// params.pair.token_a_address) +// )]; +// } +// end; +// } +// end; +// s.pairs[token_id] := pair; +// } +// | InvestLiquidity(n) -> skip +// | DivestLiquidity(n) -> skip +// end +// } with (operations, s) + (* Provide liquidity (both tokens and tez) to the pool, note: tokens should be approved before the operation *) function invest_liquidity (const p : dex_action; const s : dex_storage; const this: address) : return is block { @@ -504,14 +737,14 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th Tezos.transaction( wrap_fa12_transfer_trx(Tezos.sender, this, - params.token_a_in), + tokens_a_required), 0mutez, get_fa12_token_contract(params.pair.token_a_address) ); Tezos.transaction( wrap_fa12_transfer_trx(Tezos.sender, this, - params.token_b_in + tokens_b_required ), 0mutez, get_fa12_token_contract( @@ -526,7 +759,7 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th Tezos.transaction( wrap_fa2_transfer_trx(Tezos.sender, this, - params.token_a_in, + tokens_a_required, params.pair.token_a_id), 0mutez, get_fa2_token_contract(params.pair.token_a_address) @@ -535,7 +768,7 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th wrap_fa2_transfer_trx( Tezos.sender, this, - params.token_b_in, + tokens_b_required, params.pair.token_b_id), 0mutez, get_fa2_token_contract( @@ -547,7 +780,7 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th Tezos.transaction( wrap_fa2_transfer_trx(Tezos.sender, this, - params.token_a_in, + tokens_a_required, params.pair.token_a_id ), 0mutez, @@ -557,7 +790,7 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th wrap_fa12_transfer_trx( Tezos.sender, this, - params.token_b_in + tokens_b_required ), 0mutez, get_fa12_token_contract( From bafc703c9e440b8e8bc78b281d1259d40676cc9c Mon Sep 17 00:00:00 2001 From: kstasi Date: Mon, 7 Jun 2021 17:02:09 +0300 Subject: [PATCH 28/63] router method draft --- contracts/partials/ITTDex.ligo | 17 +- contracts/partials/TTDex.ligo | 5 +- contracts/partials/TTMethodDex.ligo | 428 +++++++++++++--------------- storage/TTFunctions.js | 7 +- test/storage/TTFunctions.ts | 7 +- 5 files changed, 221 insertions(+), 243 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index f994b603..53729833 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -50,10 +50,16 @@ type dex_storage is record [ type swap_type is Buy | Sell type swap_slice_type is record [ - swap : tokens_info; + pair : tokens_info; operation : swap_type; ] +type internal_swap_type is record [ + s : dex_storage; + amount_in : nat; + token_address : address; + token_id : nat; +] (* Entrypoint arguments *) type token_to_token_route_params is @@ -103,10 +109,11 @@ type divest_liquidity_params is ] type dex_action is -| InitializeExchange of initialize_exchange_params (* sets initial liquidity *) -| TokenToTokenPayment of token_to_token_payment_params (* exchanges XTZ to tokens and sends them to receiver *) -| InvestLiquidity of invest_liquidity_params (* mints min shares after investing tokens and XTZ *) -| DivestLiquidity of divest_liquidity_params (* burns shares and sends tokens and XTZ to the owner *) +| InitializeExchange of initialize_exchange_params (* sets initial liquidity *) +| TokenToTokenRoutePayment of token_to_token_route_params (* exchanges XTZ to tokens and sends them to receiver *) +| TokenToTokenPayment of token_to_token_payment_params (* exchanges XTZ to tokens and sends them to receiver *) +| InvestLiquidity of invest_liquidity_params (* mints min shares after investing tokens and XTZ *) +| DivestLiquidity of divest_liquidity_params (* burns shares and sends tokens and XTZ to the owner *) type use_params is dex_action type get_reserves_params is record [ diff --git a/contracts/partials/TTDex.ligo b/contracts/partials/TTDex.ligo index e5dc36dc..03325058 100644 --- a/contracts/partials/TTDex.ligo +++ b/contracts/partials/TTDex.ligo @@ -16,8 +16,9 @@ block { const idx : nat = case p of | InitializeExchange(n) -> 0n | TokenToTokenPayment(n) -> 1n - | InvestLiquidity(n) -> 2n - | DivestLiquidity(n) -> 3n + | TokenToTokenRoutePayment(n) -> 2n + | InvestLiquidity(n) -> 3n + | DivestLiquidity(n) -> 4n end; const res : return = case s.dex_lambdas[idx] of Some(f) -> f(p, s.storage, this) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 9efe49c4..4ec9b499 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -184,6 +184,7 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con end; } | TokenToTokenPayment(n) -> skip + | TokenToTokenRoutePayment(n) -> skip | InvestLiquidity(n) -> skip | DivestLiquidity(n) -> skip @@ -196,6 +197,7 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this var operations : list(operation) := list[]; case p of | InitializeExchange(n) -> skip + | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(params) -> { (* check preconditions *) if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then @@ -423,238 +425,198 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this end } with (operations, s) -// (* Exchange tokens to tez, note: tokens should be approved before the operation *) -// function internal_token_to_token_swap (const swap : swap_slice_type; const amount_in : nat; const s : dex_storage) : (nat * dex_storage) is -// block { -// var operations : list(operation) := list[]; -// case p of -// | InitializeExchange(n) -> skip -// | TokenToTokenPayment(params) -> { -// (* check preconditions *) -// if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then -// failwith("Dex/wrong-token-id") -// else skip; - -// (* get par info*) -// const res : (pair_info * nat) = get_pair(params.pair, s); -// const pair : pair_info = res.0; -// const token_id : nat = res.1; - -// (* ensure there is liquidity *) -// if pair.token_a_pool * pair.token_b_pool > 0n then -// skip -// else failwith("Dex/not-launched"); - -// if params.amount_in > 0n (* non-zero amount of tokens exchanged *) -// then skip -// else failwith ("Dex/zero-amount-in"); - -// if params.min_amount_out > 0n (* non-zero amount of tokens exchanged *) -// then skip -// else failwith ("Dex/zero-min-amount-out"); - -// case params.operation of -// | Sell -> { -// (* calculate amount out *) -// const token_a_in_with_fee : nat = params.amount_in * 997n; -// const numerator : nat = token_a_in_with_fee * pair.token_b_pool; -// const denominator : nat = pair.token_a_pool * 1000n + token_a_in_with_fee; - -// (* calculate swapped token amount *) -// const token_b_out : nat = numerator / denominator; - -// (* ensure requirements *) -// if token_b_out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) -// then skip else failwith("Dex/wrong-min-out"); - -// (* ensure requirements *) -// if token_b_out <= pair.token_b_pool / 3n (* the price impact isn't to high *) -// then { -// (* update XTZ pool *) -// pair.token_b_pool := abs(pair.token_b_pool - token_b_out); -// pair.token_a_pool := pair.token_a_pool + params.amount_in; -// } else failwith("Dex/high-out"); - -// (* prepare operations to withdraw user's tokens and transfer XTZ *) -// case params.pair.standard of -// | Fa12 -> { -// if params.pair.token_a_address > params.pair.token_b_address then -// failwith("Dex/wrong-pair") -// else skip; -// operations := list[ -// Tezos.transaction( -// wrap_fa12_transfer_trx( -// Tezos.sender, -// this, -// params.amount_in), -// 0mutez, -// get_fa12_token_contract(params.pair.token_a_address) -// ); -// Tezos.transaction( -// wrap_fa12_transfer_trx( -// this, -// params.receiver, -// token_b_out -// ), -// 0mutez, -// get_fa12_token_contract( -// params.pair.token_b_address) -// )]; -// } -// | Fa2 -> { -// if params.pair.token_a_address > params.pair.token_b_address then -// failwith("Dex/wrong-pair") -// else skip; -// operations := list[ -// Tezos.transaction( -// wrap_fa2_transfer_trx( -// Tezos.sender, -// this, -// params.amount_in, -// params.pair.token_a_id), -// 0mutez, -// get_fa2_token_contract(params.pair.token_a_address) -// ); -// Tezos.transaction( -// wrap_fa2_transfer_trx( -// this, -// params.receiver, -// token_b_out, -// params.pair.token_b_id), -// 0mutez, -// get_fa2_token_contract( -// params.pair.token_b_address) -// )]; -// } -// | Mixed -> { -// operations := list[ -// Tezos.transaction( -// wrap_fa2_transfer_trx( -// Tezos.sender, -// this, -// params.amount_in, -// params.pair.token_a_id -// ), -// 0mutez, -// get_fa2_token_contract(params.pair.token_a_address) -// ); -// Tezos.transaction( -// wrap_fa12_transfer_trx( -// this, -// params.receiver, -// token_b_out -// ), -// 0mutez, -// get_fa12_token_contract( -// params.pair.token_b_address) -// )]; -// } -// end; -// } -// | Buy -> { -// (* calculate amount out *) -// const token_b_in_with_fee : nat = params.amount_in * 997n; -// const numerator : nat = token_b_in_with_fee * pair.token_a_pool; -// const denominator : nat = pair.token_b_pool * 1000n + token_b_in_with_fee; - -// (* calculate swapped token amount *) -// const token_a_out : nat = numerator / denominator; - -// (* ensure requirements *) -// if token_a_out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) -// then skip else failwith("Dex/wrong-min-out"); - -// (* ensure requirements *) -// if token_a_out <= pair.token_a_pool / 3n (* the price impact isn't to high *) -// then { -// (* update XTZ pool *) -// pair.token_a_pool := abs(pair.token_a_pool - token_a_out); -// pair.token_b_pool := pair.token_b_pool + params.amount_in; -// } else failwith("Dex/high-out"); - -// (* prepare operations to withdraw user's tokens and transfer XTZ *) -// case params.pair.standard of -// | Fa12 -> { -// if params.pair.token_a_address > params.pair.token_b_address then -// failwith("Dex/wrong-pair") -// else skip; -// operations := list[ -// Tezos.transaction( -// wrap_fa12_transfer_trx( -// Tezos.sender, -// this, -// params.amount_in), -// 0mutez, -// get_fa12_token_contract(params.pair.token_b_address) -// ); -// Tezos.transaction( -// wrap_fa12_transfer_trx( -// this, -// params.receiver, -// token_a_out -// ), -// 0mutez, -// get_fa12_token_contract( -// params.pair.token_a_address) -// )]; -// } -// | Fa2 -> { -// if params.pair.token_a_address > params.pair.token_b_address then -// failwith("Dex/wrong-pair") -// else skip; -// operations := list[ -// Tezos.transaction( -// wrap_fa2_transfer_trx( -// Tezos.sender, -// this, -// params.amount_in, -// params.pair.token_b_id), -// 0mutez, -// get_fa2_token_contract(params.pair.token_b_address) -// ); -// Tezos.transaction( -// wrap_fa2_transfer_trx( -// this, -// params.receiver, -// token_a_out, -// params.pair.token_a_id), -// 0mutez, -// get_fa2_token_contract( -// params.pair.token_a_address) -// )]; -// } -// | Mixed -> { -// operations := list[ -// Tezos.transaction( -// wrap_fa12_transfer_trx( -// Tezos.sender, -// this, -// params.amount_in -// ), -// 0mutez, -// get_fa12_token_contract(params.pair.token_b_address) -// ); -// Tezos.transaction( -// wrap_fa2_transfer_trx( -// this, -// params.receiver, -// token_a_out, -// params.pair.token_a_id -// ), -// 0mutez, -// get_fa2_token_contract( -// params.pair.token_a_address) -// )]; -// } -// end; -// } -// end; -// s.pairs[token_id] := pair; -// } -// | InvestLiquidity(n) -> skip -// | DivestLiquidity(n) -> skip -// end -// } with (operations, s) +(* Exchange tokens to tez, note: tokens should be approved before the operation *) +function internal_token_to_token_swap (const tmp : internal_swap_type; const params : swap_slice_type ) : internal_swap_type is + block { + (* check preconditions *) + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + failwith("Dex/wrong-token-id") + else skip; + + (* get par info*) + const res : (pair_info * nat) = get_pair(params.pair, tmp.s); + const pair : pair_info = res.0; + const token_id : nat = res.1; + + (* ensure there is liquidity *) + if pair.token_a_pool * pair.token_b_pool > 0n then + skip + else failwith("Dex/not-launched"); + + if tmp.amount_in > 0n (* non-zero amount of tokens exchanged *) + then skip + else failwith ("Dex/zero-amount-in"); + + case params.operation of + | Sell -> { + (* calculate amount out *) + const token_a_in_with_fee : nat = tmp.amount_in * 997n; + const numerator : nat = token_a_in_with_fee * pair.token_b_pool; + const denominator : nat = pair.token_a_pool * 1000n + token_a_in_with_fee; + + (* calculate swapped token amount *) + const token_b_out : nat = numerator / denominator; + + (* ensure requirements *) + if token_b_out <= pair.token_b_pool / 3n (* the price impact isn't to high *) + then { + (* update XTZ pool *) + pair.token_b_pool := abs(pair.token_b_pool - token_b_out); + pair.token_a_pool := pair.token_a_pool + tmp.amount_in; + } else failwith("Dex/high-out"); + tmp.amount_in := token_b_out; + + (* prepare operations to withdraw user's tokens and transfer XTZ *) + case params.pair.standard of + | Fa12 -> if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip + | Fa2 -> if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip + | Mixed -> skip + end; + } + | Buy -> { + (* calculate amount out *) + const token_b_in_with_fee : nat = tmp.amount_in * 997n; + const numerator : nat = token_b_in_with_fee * pair.token_a_pool; + const denominator : nat = pair.token_b_pool * 1000n + token_b_in_with_fee; + + (* calculate swapped token amount *) + const token_a_out : nat = numerator / denominator; + + (* ensure requirements *) + if token_a_out <= pair.token_a_pool / 3n (* the price impact isn't to high *) + then { + (* update XTZ pool *) + pair.token_a_pool := abs(pair.token_a_pool - token_a_out); + pair.token_b_pool := pair.token_b_pool + tmp.amount_in; + } else failwith("Dex/high-out"); + + tmp.amount_in := token_a_out; + (* prepare operations to withdraw user's tokens and transfer XTZ *) + case params.pair.standard of + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; + } + | Mixed -> skip + end; + } + end; + tmp.s.pairs[token_id] := pair; + } with tmp + +(* Exchange tokens to tez, note: tokens should be approved before the operation *) +function token_to_token (const p : dex_action; const s : dex_storage; const this : address) : return is + block { + var operations : list(operation) := list[]; + case p of + | InitializeExchange(n) -> skip + | TokenToTokenPayment(n) -> skip + | TokenToTokenRoutePayment(params) -> { + if params.amount_in > 0n (* non-zero amount of tokens exchanged *) + then skip + else failwith ("Dex/zero-amount-in"); + + if params.min_amount_out > 0n (* non-zero amount of tokens exchanged *) + then skip + else failwith ("Dex/zero-min-amount-out"); + + const tmp : internal_swap_type = List.fold( + internal_token_to_token_swap, + params.swaps, + record [ + s = s; + amount_in = params.amount_in; + token_address = ("tz1ZZZZZZZZZZZZZZZZZZZZZZZZZZZZNkiRg" : address); + token_id = 0n; + ] + ); + s := tmp.s; + + // const swap : tokens_info = List.head_opt(); + // case swap.pair.standard of + // | Fa12 -> { + // operations := list[ + // // Tezos.transaction( + // // wrap_fa12_transfer_trx( + // // Tezos.sender, + // // this, + // // params.amount_in), + // // 0mutez, + // // get_fa12_token_contract(params.pair.token_a_address) + // // ); + // Tezos.transaction( + // wrap_fa12_transfer_trx( + // this, + // params.receiver, + // tmp.amount_in + // ), + // 0mutez, + // get_fa12_token_contract( + // tmp.token_address) + // )]; + // } + // | Fa2 -> { + // operations := list[ + // // Tezos.transaction( + // // wrap_fa2_transfer_trx( + // // Tezos.sender, + // // this, + // // params.amount_in, + // // params.pair.token_a_id), + // // 0mutez, + // // get_fa2_token_contract(params.pair.token_a_address) + // // ); + // Tezos.transaction( + // wrap_fa2_transfer_trx( + // this, + // params.receiver, + // tmp.amount_in, + // token.token_id), + // 0mutez, + // get_fa2_token_contract( + // tmp.token_address) + // )]; + // } + // | Mixed -> { + // operations := list[ + // // Tezos.transaction( + // // wrap_fa2_transfer_trx( + // // Tezos.sender, + // // this, + // // params.amount_in, + // // params.pair.token_id + // // ), + // // 0mutez, + // // get_fa2_token_contract(params.pair.token_a_address) + // // ); + // Tezos.transaction( + // wrap_fa12_transfer_trx( + // this, + // params.receiver, + // tmp.amount_in + // ), + // 0mutez, + // get_fa12_token_contract( + // tmp.token_address) + // )]; + // } + // end; + + } + | InvestLiquidity(n) -> skip + | DivestLiquidity(n) -> skip + end + } with (operations, s) (* Provide liquidity (both tokens and tez) to the pool, note: tokens should be approved before the operation *) function invest_liquidity (const p : dex_action; const s : dex_storage; const this: address) : return is @@ -662,6 +624,7 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th var operations: list(operation) := list[]; case p of | InitializeExchange(n) -> skip + | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(n) -> skip | InvestLiquidity(params) -> { (* check preconditions *) @@ -810,6 +773,7 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th case p of | InitializeExchange(token_amount) -> skip | TokenToTokenPayment(n) -> skip + | TokenToTokenRoutePayment(n) -> skip | InvestLiquidity(n) -> skip | DivestLiquidity(params) -> { (* check preconditions *) diff --git a/storage/TTFunctions.js b/storage/TTFunctions.js index f193ca42..190c4807 100644 --- a/storage/TTFunctions.js +++ b/storage/TTFunctions.js @@ -7,13 +7,16 @@ module.exports.dexFunctions = [ index: 1, name: "token_to_token", }, - { index: 2, - name: "invest_liquidity", + name: "token_to_token_route", }, { index: 3, + name: "invest_liquidity", + }, + { + index: 4, name: "divest_liquidity", }, ]; diff --git a/test/storage/TTFunctions.ts b/test/storage/TTFunctions.ts index 79e147fc..7aab2093 100644 --- a/test/storage/TTFunctions.ts +++ b/test/storage/TTFunctions.ts @@ -7,13 +7,16 @@ export let dexFunctions = [ index: 1, name: "token_to_token", }, - { index: 2, - name: "invest_liquidity", + name: "token_to_token_route", }, { index: 3, + name: "invest_liquidity", + }, + { + index: 4, name: "divest_liquidity", }, ]; From 4a0338baaf1353523b8fd9338d2594afa5feaa7a Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 8 Jun 2021 10:30:24 +0300 Subject: [PATCH 29/63] routerr implemented --- contracts/partials/ITTDex.ligo | 5 +- contracts/partials/TTMethodDex.ligo | 236 ++++++++++++++++++---------- 2 files changed, 160 insertions(+), 81 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 53729833..89df625b 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -57,8 +57,9 @@ type swap_slice_type is record [ type internal_swap_type is record [ s : dex_storage; amount_in : nat; - token_address : address; - token_id : nat; + operation : option(operation); + sender : address; + receiver : address; ] (* Entrypoint arguments *) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 4ec9b499..a625f483 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -468,13 +468,48 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par (* prepare operations to withdraw user's tokens and transfer XTZ *) case params.pair.standard of - | Fa12 -> if params.pair.token_a_address > params.pair.token_b_address then + | Fa12 -> { + if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") - else skip - | Fa2 -> if params.pair.token_a_address > params.pair.token_b_address then + else skip; + tmp.operation := Some(Tezos.transaction( + wrap_fa12_transfer_trx( + tmp.sender, + tmp.receiver, + token_b_out + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )); + } + | Fa2 -> { + if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") - else skip - | Mixed -> skip + else skip; + tmp.operation := Some(Tezos.transaction( + wrap_fa2_transfer_trx( + tmp.sender, + tmp.receiver, + token_b_out, + params.pair.token_b_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_b_address) + )); + } + | Mixed -> { + tmp.operation := Some(Tezos.transaction( + wrap_fa12_transfer_trx( + tmp.sender, + tmp.receiver, + token_b_out + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_b_address) + )); + } end; } | Buy -> { @@ -501,13 +536,45 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; + tmp.operation := Some(Tezos.transaction( + wrap_fa12_transfer_trx( + tmp.sender, + tmp.receiver, + token_a_out + ), + 0mutez, + get_fa12_token_contract( + params.pair.token_a_address) + )); } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; + tmp.operation := Some(Tezos.transaction( + wrap_fa2_transfer_trx( + tmp.sender, + tmp.receiver, + token_a_out, + params.pair.token_a_id), + 0mutez, + get_fa2_token_contract( + params.pair.token_a_address) + )); } - | Mixed -> skip + | Mixed -> { + tmp.operation := Some(Tezos.transaction( + wrap_fa2_transfer_trx( + tmp.sender, + tmp.receiver, + token_a_out, + params.pair.token_a_id + ), + 0mutez, + get_fa2_token_contract( + params.pair.token_a_address) + )); + } end; } end; @@ -515,13 +582,17 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par } with tmp (* Exchange tokens to tez, note: tokens should be approved before the operation *) -function token_to_token (const p : dex_action; const s : dex_storage; const this : address) : return is +function token_to_token_route (const p : dex_action; const s : dex_storage; const this : address) : return is block { var operations : list(operation) := list[]; case p of | InitializeExchange(n) -> skip | TokenToTokenPayment(n) -> skip | TokenToTokenRoutePayment(params) -> { + if List.size(params.swaps) > 1n (* non-zero amount of tokens exchanged *) + then skip + else failwith ("Dex/too-few-swaps"); + if params.amount_in > 0n (* non-zero amount of tokens exchanged *) then skip else failwith ("Dex/zero-amount-in"); @@ -536,82 +607,89 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this record [ s = s; amount_in = params.amount_in; - token_address = ("tz1ZZZZZZZZZZZZZZZZZZZZZZZZZZZZNkiRg" : address); - token_id = 0n; + operation = (None : option(operation)); + sender = this; + receiver = params.receiver; ] ); s := tmp.s; - // const swap : tokens_info = List.head_opt(); - // case swap.pair.standard of - // | Fa12 -> { - // operations := list[ - // // Tezos.transaction( - // // wrap_fa12_transfer_trx( - // // Tezos.sender, - // // this, - // // params.amount_in), - // // 0mutez, - // // get_fa12_token_contract(params.pair.token_a_address) - // // ); - // Tezos.transaction( - // wrap_fa12_transfer_trx( - // this, - // params.receiver, - // tmp.amount_in - // ), - // 0mutez, - // get_fa12_token_contract( - // tmp.token_address) - // )]; - // } - // | Fa2 -> { - // operations := list[ - // // Tezos.transaction( - // // wrap_fa2_transfer_trx( - // // Tezos.sender, - // // this, - // // params.amount_in, - // // params.pair.token_a_id), - // // 0mutez, - // // get_fa2_token_contract(params.pair.token_a_address) - // // ); - // Tezos.transaction( - // wrap_fa2_transfer_trx( - // this, - // params.receiver, - // tmp.amount_in, - // token.token_id), - // 0mutez, - // get_fa2_token_contract( - // tmp.token_address) - // )]; - // } - // | Mixed -> { - // operations := list[ - // // Tezos.transaction( - // // wrap_fa2_transfer_trx( - // // Tezos.sender, - // // this, - // // params.amount_in, - // // params.pair.token_id - // // ), - // // 0mutez, - // // get_fa2_token_contract(params.pair.token_a_address) - // // ); - // Tezos.transaction( - // wrap_fa12_transfer_trx( - // this, - // params.receiver, - // tmp.amount_in - // ), - // 0mutez, - // get_fa12_token_contract( - // tmp.token_address) - // )]; - // } - // end; - + if tmp.amount_in > params.min_amount_out (* non-zero amount of tokens exchanged *) + then skip + else failwith ("Dex/wrong-min-out"); + + (* collect the operations to execute *) + const first_swap : swap_slice_type = case List.head_opt(params.swaps) of + | Some(swap) -> swap + | None -> (failwith("Dex/zero-swaps") : swap_slice_type) + end; + const last_operation : operation = case tmp.operation of + | Some(o) -> o + | None -> (failwith("Dex/too-few-swaps") : operation) + end; + + case first_swap.pair.standard of + | Fa12 -> { + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in + ), + 0mutez, + get_fa12_token_contract( + case first_swap.operation of + | Sell -> first_swap.pair.token_a_address + | Buy -> first_swap.pair.token_b_address + end + ))]; + } + | Fa2 -> { + operations := list[ + case first_swap.operation of + | Sell -> Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_a_id), + 0mutez, + get_fa2_token_contract(first_swap.pair.token_a_address)) + | Buy -> Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_b_id), + 0mutez, + get_fa2_token_contract(first_swap.pair.token_b_address)) + end + ] + } + | Mixed -> { + operations := list[ + case first_swap.operation of + | Sell -> Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_a_id), + 0mutez, + get_fa2_token_contract(first_swap.pair.token_a_address)) + | Buy -> Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in), + 0mutez, + get_fa12_token_contract(first_swap.pair.token_b_address)) + end + ] + } + end; + operations := last_operation # operations; } | InvestLiquidity(n) -> skip | DivestLiquidity(n) -> skip From 3ada5eea356d32315a2eab558f1373f76cde2fc7 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 8 Jun 2021 10:43:34 +0300 Subject: [PATCH 30/63] fix wrong index --- contracts/main/TTDex.ligo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/main/TTDex.ligo b/contracts/main/TTDex.ligo index df5cd555..90f2116f 100644 --- a/contracts/main/TTDex.ligo +++ b/contracts/main/TTDex.ligo @@ -13,6 +13,6 @@ function main (const p : full_action; const s : full_dex_storage) : full_return | Balance_of(params) -> call_token(IBalance_of(params), this, 2n, s) | Update_operators(params) -> call_token(IUpdate_operators(params), this, 1n, s) | Get_reserves(params) -> get_reserves(params, s) - | SetDexFunction(params) -> ((nil:list(operation)), if params.index > 3n then (failwith("Dex/wrong-index") : full_dex_storage) else set_dex_function(params.index, params.func, s)) + | SetDexFunction(params) -> ((nil:list(operation)), if params.index > 4n then (failwith("Dex/wrong-index") : full_dex_storage) else set_dex_function(params.index, params.func, s)) | SetTokenFunction(params) -> ((nil:list(operation)), if params.index > 2n then (failwith("Dex/wrong-index") : full_dex_storage) else set_token_function(params.index, params.func, s)) end From be7b823d00e6731e39a7a2c32c162b77cab4bccf Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 8 Jun 2021 11:02:55 +0300 Subject: [PATCH 31/63] fix test --- test/InitializeTTExchangeTest.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index 4b572cd3..a486744e 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -13,7 +13,7 @@ contract("InitializeTTExchange()", function () { before(async () => { context = await TTContext.init([], false, "alice", false); await context.setDexFunction(0, "initialize_exchange"); - await context.setDexFunction(3, "divest_liquidity"); + await context.setDexFunction(4, "divest_liquidity"); }); it("should have an empty token list after deployment", async function () { From b8131e47a20e5bcf875085fa7a9e67080f223d34 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 8 Jun 2021 14:49:17 +0300 Subject: [PATCH 32/63] router test draft --- contracts/partials/TTMethodDex.ligo | 43 ++--- test/BuyTokenWithRouter.spec.ts | 271 ++++++++++++++++++++++++++++ test/helpers/ttdexFA2.ts | 50 ++++- test/helpers/types.ts | 9 +- truffle.d.ts | 1 - 5 files changed, 350 insertions(+), 24 deletions(-) create mode 100644 test/BuyTokenWithRouter.spec.ts diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index a625f483..662b1065 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -601,32 +601,11 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons then skip else failwith ("Dex/zero-min-amount-out"); - const tmp : internal_swap_type = List.fold( - internal_token_to_token_swap, - params.swaps, - record [ - s = s; - amount_in = params.amount_in; - operation = (None : option(operation)); - sender = this; - receiver = params.receiver; - ] - ); - s := tmp.s; - - if tmp.amount_in > params.min_amount_out (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/wrong-min-out"); - (* collect the operations to execute *) const first_swap : swap_slice_type = case List.head_opt(params.swaps) of | Some(swap) -> swap | None -> (failwith("Dex/zero-swaps") : swap_slice_type) end; - const last_operation : operation = case tmp.operation of - | Some(o) -> o - | None -> (failwith("Dex/too-few-swaps") : operation) - end; case first_swap.pair.standard of | Fa12 -> { @@ -689,6 +668,28 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons ] } end; + + const tmp : internal_swap_type = List.fold( + internal_token_to_token_swap, + params.swaps, + record [ + s = s; + amount_in = params.amount_in; + operation = (None : option(operation)); + sender = this; + receiver = params.receiver; + ] + ); + s := tmp.s; + + if tmp.amount_in > params.min_amount_out (* non-zero amount of tokens exchanged *) + then skip + else failwith ("Dex/wrong-min-out"); + + const last_operation : operation = case tmp.operation of + | Some(o) -> o + | None -> (failwith("Dex/too-few-swaps") : operation) + end; operations := last_operation # operations; } | InvestLiquidity(n) -> skip diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts new file mode 100644 index 00000000..979d44d0 --- /dev/null +++ b/test/BuyTokenWithRouter.spec.ts @@ -0,0 +1,271 @@ +import { TTContext } from "./helpers/ttContext"; +import { strictEqual, ok, notStrictEqual, rejects } from "assert"; +import BigNumber from "bignumber.js"; +import accounts from "./accounts/accounts"; +import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; +import { Parser } from "@taquito/michel-codec"; + +export const michelParser = new Parser(); + +contract.only("BuyTokenWithRoute()", function () { + let context: TTContext; + const tokenAAmount: number = 100000; + const tokenBAmount: number = 10000; + const tokenCAmount: number = 10000; + const aliceAddress: string = accounts.alice.pkh; + let tokenAAddress; + let tokenBAddress; + let tokenCAddress; + let reverseOrder; + + before(async () => { + context = await TTContext.init([], false, "alice", false); + await context.setAllDexFunctions(); + await context.createPair({ + tokenAAmount, + tokenBAmount, + }); + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { + const tmp = context.tokens[0]; + context.tokens[0] = context.tokens[1]; + context.tokens[1] = tmp; + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + } + await context.createPair({ + tokenAAmount: tokenCAmount, + tokenBAddress, + tokenBAmount: tokenBAmount, + }); + tokenCAddress = context.tokens[2].contract.address; + reverseOrder = standard != "MIXED" && tokenCAddress > tokenBAddress; + }); + + function tokenToTokenSuccessCase( + decription, + amountIn, + amountOut, + tokensLeftover + ) { + it(decription, async function () { + const pairAddress = context.dex.contract.address; + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[2].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const aliceInitShares = + context.dex.storage.ledger[aliceAddress].balance.toNumber(); + const aliceInitTokenABalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const aliceInitTokenBBalance = ( + (await context.tokens[2].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const pairInitTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairInitTokenBBalance = await context.tokens[2].storage.ledger[ + pairAddress + ].balance; + const initDexPair = context.dex.storage.pairs[0]; + await context.dex.tokenToTokenRoutePayment( + [ + { + pair: { + token_a_address: tokenAAddress, + token_b_address: tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { buy: null }, + }, + { + pair: { + token_a_address: reverseOrder ? tokenBAddress : tokenCAddress, + token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { buy: null }, + }, + ], + amountIn, + amountOut, + aliceAddress + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[2].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0"], + }); + const finalDexPair = context.dex.storage.pairs[0]; + const aliceFinalTokenABalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const aliceFinalTokenBBalance = await context.tokens[2].storage.ledger[ + aliceAddress + ].balance; + const pairTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTokenBBalance = await context.tokens[2].storage.ledger[ + pairAddress + ].balance; + console.log( + aliceInitTokenBBalance.toNumber(), + amountIn, + aliceFinalTokenBBalance.toNumber() + ); + strictEqual( + aliceInitTokenBBalance.toNumber() - amountIn, + aliceFinalTokenBBalance.toNumber() + ); + strictEqual( + aliceInitTokenABalance.toNumber() + amountOut + tokensLeftover, + aliceFinalTokenABalance.toNumber() + ); + strictEqual( + pairInitTokenABalance.toNumber() - amountOut - tokensLeftover, + pairTokenABalance.toNumber() + ); + strictEqual( + pairInitTokenBBalance.toNumber() + amountIn, + pairTokenBBalance.toNumber() + ); + strictEqual( + finalDexPair.token_a_pool.toNumber(), + initDexPair.token_a_pool.toNumber() - amountOut - tokensLeftover + ); + strictEqual( + finalDexPair.token_b_pool.toNumber(), + initDexPair.token_b_pool.toNumber() + amountIn + ); + }); + } + + function tokenToTokenFailCase(decription, amountIn, amountOut, errorMsg) { + it(decription, async function () { + await rejects( + context.dex.tokenToTokenRoutePayment( + [ + { + pair: { + token_a_address: tokenAAddress, + token_b_address: tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { buy: null }, + }, + { + pair: { + token_a_address: reverseOrder ? tokenBAddress : tokenCAddress, + token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { buy: null }, + }, + ], + amountIn, + amountOut, + aliceAddress + ), + (err) => { + console.log(err.message); + ok(err.message == errorMsg, "Error message mismatch"); + return true; + } + ); + }); + } + + describe("Test different amount of tokens to be swapped", () => { + tokenToTokenFailCase( + "revert in case of 0 tokens to be swapped", + 0, + 1, + "Dex/zero-amount-in" + ); + tokenToTokenFailCase( + "revert in case of 100% of reserves to be swapped", + 1000, + 1000, + "Dex/high-out" + ); + tokenToTokenFailCase( + "revert in case of 10000% of reserves to be swapped", + 100000, + 1, + "Dex/high-out" + ); + + tokenToTokenSuccessCase( + "success in case of 1% of reserves to be swapped", + 1, + 1, + 0 + ); + // tokenToTokenSuccessCase( + // "success in case of ~30% of reserves to be swapped", + // 300, + // 22983, + // 0 + // ); + }); + + // describe("Test different minimal desirable output amount", () => { + // tokenToTokenFailCase( + // "reevert in case of 0 tokens expected", + // 10, + // 0, + // "Dex/zero-min-amount-out" + // ); + // tokenToTokenFailCase( + // "revert in case of too many tokens expected", + // 10, + // 70000, + // "Dex/wrong-min-out" + // ); + // tokenToTokenSuccessCase( + // "success in case of exact amount of tokens expected", + // 5, + // 293, + // 0 + // ); + // tokenToTokenSuccessCase( + // "success in case of smaller amount of tokens expected", + // 10, + // 500, + // 80 + // ); + // }); +}); diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index 7b614bdf..45207588 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -3,7 +3,7 @@ import { BatchOperation } from "@taquito/taquito/dist/types/operations/batch-ope import { TransactionOperation } from "@taquito/taquito/dist/types/operations/transaction-operation"; import { BigNumber } from "bignumber.js"; import { TokenFA2 } from "./tokenFA2"; -import { TTDexStorage } from "./types"; +import { TTDexStorage, SwapSliceType } from "./types"; import { getLigo } from "./utils"; import { execSync } from "child_process"; import { confirmOperation } from "./confirmation"; @@ -133,6 +133,54 @@ export class TTDex extends TokenFA2 { return operation; } + async tokenToTokenRoutePayment( + swaps: SwapSliceType[], + amountIn: number, + minAmountOut: number, + receiver: string, + tokenAid: BigNumber = new BigNumber(0), + tokenBid: BigNumber = new BigNumber(0) + ): Promise { + let firstSwap = swaps[0]; + if (Object.keys(firstSwap.operation)[0] == "buy") { + if (["FA2"].includes(standard)) { + await this.approveFA2Token( + firstSwap.pair.token_b_address, + tokenBid, + amountIn, + this.contract.address + ); + } else { + await this.approveFA12Token( + firstSwap.pair.token_b_address, + amountIn, + this.contract.address + ); + } + } else { + if (["FA2"].includes(standard)) { + await this.approveFA2Token( + firstSwap.pair.token_a_address, + tokenAid, + amountIn, + this.contract.address + ); + } else { + await this.approveFA12Token( + firstSwap.pair.token_a_address, + amountIn, + this.contract.address + ); + } + } + + const operation = await this.contract.methods + .use("tokenToTokenRoutePayment", swaps, amountIn, minAmountOut, receiver) + .send(); + await confirmOperation(tezos, operation.hash); + return operation; + } + async tokenToTokenPayment( tokenAAddress: string, tokenBAddress: string, diff --git a/test/helpers/types.ts b/test/helpers/types.ts index ac2daeb9..6cd85bfd 100644 --- a/test/helpers/types.ts +++ b/test/helpers/types.ts @@ -5,7 +5,9 @@ export declare type TokenInfo = { token_b_address: string; token_a_id?: BigNumber; token_b_id?: BigNumber; - standard: string; + standard: { + [key: string]: any; + }; }; export declare type PairInfo = { @@ -14,6 +16,11 @@ export declare type PairInfo = { total_supply: BigNumber; }; +export declare type SwapSliceType = { + pair: TokenInfo; + operation: { [key: string]: any }; +}; + export declare type TTDexStorage = { dex_lambdas: { [key: string]: any }; token_lambdas: { [key: string]: any }; diff --git a/truffle.d.ts b/truffle.d.ts index 031a7ac5..11b082ac 100644 --- a/truffle.d.ts +++ b/truffle.d.ts @@ -28,7 +28,6 @@ declare interface Artifacts { require(name: "TestFactoryFA2"): Contract; require(name: "DexFA12"): Contract; require(name: "DexFA2"): Contract; - require(name: "TTDexFA2FA12"): Contract; require(name: "TTDex"): Contract; require(name: "TTDexFA12"): Contract; } From e17e517356c54684855936dab23ed353e8404735 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 8 Jun 2021 16:56:40 +0300 Subject: [PATCH 33/63] fixes --- contracts/partials/ITTDex.ligo | 2 + contracts/partials/TTMethodDex.ligo | 133 +++++++++++++++++++--------- test/BuyTokenWithRouter.spec.ts | 20 ++--- 3 files changed, 102 insertions(+), 53 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 89df625b..abc7529c 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -57,6 +57,8 @@ type swap_slice_type is record [ type internal_swap_type is record [ s : dex_storage; amount_in : nat; + token_address_in : address; + token_id_in : nat; operation : option(operation); sender : address; receiver : address; diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 662b1065..b3284a6b 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -449,6 +449,10 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par case params.operation of | Sell -> { + if params.pair.token_a_address = tmp.token_address_in and params.pair.token_a_id = tmp.token_id_in then + skip + else failwith("Dex/wrong-route"); + (* calculate amount out *) const token_a_in_with_fee : nat = tmp.amount_in * 997n; const numerator : nat = token_a_in_with_fee * pair.token_b_pool; @@ -465,6 +469,8 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par pair.token_a_pool := pair.token_a_pool + tmp.amount_in; } else failwith("Dex/high-out"); tmp.amount_in := token_b_out; + tmp.token_address_in := params.pair.token_b_address; + tmp.token_id_in := params.pair.token_b_id; (* prepare operations to withdraw user's tokens and transfer XTZ *) case params.pair.standard of @@ -513,6 +519,10 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par end; } | Buy -> { + if params.pair.token_b_address = tmp.token_address_in and params.pair.token_b_id = tmp.token_id_in then + skip + else failwith("Dex/wrong-route"); + (* calculate amount out *) const token_b_in_with_fee : nat = tmp.amount_in * 997n; const numerator : nat = token_b_in_with_fee * pair.token_a_pool; @@ -530,6 +540,9 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par } else failwith("Dex/high-out"); tmp.amount_in := token_a_out; + tmp.token_address_in := params. + pair.token_a_address; + tmp.token_id_in := params.pair.token_a_id; (* prepare operations to withdraw user's tokens and transfer XTZ *) case params.pair.standard of | Fa12 -> { @@ -607,65 +620,97 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons | None -> (failwith("Dex/zero-swaps") : swap_slice_type) end; + (* declare helper variables *) + var token_id_in : nat := first_swap.pair.token_a_id; + var token_address_in : address := first_swap.pair.token_a_address; + case first_swap.pair.standard of | Fa12 -> { - operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in - ), - 0mutez, - get_fa12_token_contract( - case first_swap.operation of - | Sell -> first_swap.pair.token_a_address - | Buy -> first_swap.pair.token_b_address - end - ))]; + case first_swap.operation of + | Sell -> { + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in + ), + 0mutez, + get_fa12_token_contract( + first_swap.pair.token_a_address + ))]; + } + | Buy -> { + token_id_in := first_swap.pair.token_b_id; + token_address_in := first_swap.pair.token_b_address; + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in + ), + 0mutez, + get_fa12_token_contract( + first_swap.pair.token_b_address + ))]; + } + end } | Fa2 -> { - operations := list[ - case first_swap.operation of - | Sell -> Tezos.transaction( + case first_swap.operation of + | Sell -> { + operations := list[ + Tezos.transaction( wrap_fa2_transfer_trx( Tezos.sender, this, params.amount_in, first_swap.pair.token_a_id), 0mutez, - get_fa2_token_contract(first_swap.pair.token_a_address)) - | Buy -> Tezos.transaction( - wrap_fa2_transfer_trx( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_b_id), - 0mutez, - get_fa2_token_contract(first_swap.pair.token_b_address)) - end - ] - } - | Mixed -> { - operations := list[ - case first_swap.operation of - | Sell -> Tezos.transaction( + get_fa2_token_contract(first_swap.pair.token_a_address))] + } + | Buy -> { + token_id_in := first_swap.pair.token_b_id; + token_address_in := first_swap.pair.token_b_address; + operations := list[ + Tezos.transaction( wrap_fa2_transfer_trx( Tezos.sender, this, params.amount_in, - first_swap.pair.token_a_id), + first_swap.pair.token_b_id), 0mutez, - get_fa2_token_contract(first_swap.pair.token_a_address)) - | Buy -> Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in), - 0mutez, - get_fa12_token_contract(first_swap.pair.token_b_address)) + get_fa2_token_contract(first_swap.pair.token_b_address))] + } + end + } + | Mixed -> { + case first_swap.operation of + | Sell -> { + operations := list[ + Tezos.transaction( + wrap_fa2_transfer_trx( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_a_id), + 0mutez, + get_fa2_token_contract(first_swap.pair.token_a_address))] + } + | Buy -> { + token_id_in := first_swap.pair.token_b_id; + token_address_in := first_swap.pair.token_b_address; + operations := list[ + Tezos.transaction( + wrap_fa12_transfer_trx( + Tezos.sender, + this, + params.amount_in), + 0mutez, + get_fa12_token_contract(first_swap.pair.token_b_address))] + } end - ] } end; @@ -678,6 +723,8 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons operation = (None : option(operation)); sender = this; receiver = params.receiver; + token_id_in = token_id_in; + token_address_in = token_address_in; ] ); s := tmp.s; diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index 979d44d0..f7d64bfe 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -36,12 +36,12 @@ contract.only("BuyTokenWithRoute()", function () { tokenBAddress = context.tokens[1].contract.address; } await context.createPair({ - tokenAAmount: tokenCAmount, - tokenBAddress, - tokenBAmount: tokenBAmount, + tokenAAmount: tokenAAmount, + tokenAAddress, + tokenBAmount: tokenCAmount, }); tokenCAddress = context.tokens[2].contract.address; - reverseOrder = standard != "MIXED" && tokenCAddress > tokenBAddress; + reverseOrder = standard != "MIXED" && tokenCAddress > tokenAAddress; }); function tokenToTokenSuccessCase( @@ -97,13 +97,13 @@ contract.only("BuyTokenWithRoute()", function () { }, { pair: { - token_a_address: reverseOrder ? tokenBAddress : tokenCAddress, - token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, + token_a_address: reverseOrder ? tokenAAddress : tokenCAddress, + token_b_address: reverseOrder ? tokenCAddress : tokenAAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), standard: { [standard.toLowerCase()]: null }, }, - operation: { buy: null }, + operation: { sell: null }, }, ], amountIn, @@ -186,13 +186,13 @@ contract.only("BuyTokenWithRoute()", function () { }, { pair: { - token_a_address: reverseOrder ? tokenBAddress : tokenCAddress, - token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, + token_a_address: reverseOrder ? tokenAAddress : tokenCAddress, + token_b_address: reverseOrder ? tokenCAddress : tokenAAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), standard: { [standard.toLowerCase()]: null }, }, - operation: { buy: null }, + operation: { sell: null }, }, ], amountIn, From e7b9514e165615faf7cf141823aaf62814e7b1fa Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 12:42:45 +0300 Subject: [PATCH 34/63] router buy tested --- contracts/partials/TTMethodDex.ligo | 2 +- test/BuyTokenWithRouter.spec.ts | 144 ++++++++++++++++------------ test/helpers/ttdexFA2.ts | 1 - 3 files changed, 84 insertions(+), 63 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index b3284a6b..1e60858e 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -729,7 +729,7 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons ); s := tmp.s; - if tmp.amount_in > params.min_amount_out (* non-zero amount of tokens exchanged *) + if tmp.amount_in >= params.min_amount_out (* non-zero amount of tokens exchanged *) then skip else failwith ("Dex/wrong-min-out"); diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index f7d64bfe..284cec1d 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -64,25 +64,31 @@ contract.only("BuyTokenWithRoute()", function () { await context.dex.updateStorage({ ledger: [[aliceAddress, 0]], tokens: ["0"], - pairs: ["0"], + pairs: ["0", "1"], }); - const aliceInitShares = - context.dex.storage.ledger[aliceAddress].balance.toNumber(); const aliceInitTokenABalance = ( (await context.tokens[0].storage.ledger[aliceAddress]) || defaultAccountInfo ).balance; const aliceInitTokenBBalance = ( + (await context.tokens[1].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const aliceInitTokenСBalance = ( (await context.tokens[2].storage.ledger[aliceAddress]) || defaultAccountInfo ).balance; const pairInitTokenABalance = await context.tokens[0].storage.ledger[ pairAddress ].balance; - const pairInitTokenBBalance = await context.tokens[2].storage.ledger[ + const pairInitTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const pairInitTokenСBalance = await context.tokens[2].storage.ledger[ pairAddress ].balance; - const initDexPair = context.dex.storage.pairs[0]; + const initDexPair0 = context.dex.storage.pairs[0]; + const initDexPair1 = context.dex.storage.pairs[1]; await context.dex.tokenToTokenRoutePayment( [ { @@ -97,8 +103,8 @@ contract.only("BuyTokenWithRoute()", function () { }, { pair: { - token_a_address: reverseOrder ? tokenAAddress : tokenCAddress, - token_b_address: reverseOrder ? tokenCAddress : tokenAAddress, + token_a_address: reverseOrder ? tokenCAddress : tokenAAddress, + token_b_address: reverseOrder ? tokenAAddress : tokenCAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), standard: { [standard.toLowerCase()]: null }, @@ -122,50 +128,67 @@ contract.only("BuyTokenWithRoute()", function () { await context.dex.updateStorage({ ledger: [[aliceAddress, 0]], tokens: ["0"], - pairs: ["0"], + pairs: ["0", "1"], }); - const finalDexPair = context.dex.storage.pairs[0]; + const finalDexPair0 = context.dex.storage.pairs[0]; + const finalDexPair1 = context.dex.storage.pairs[1]; const aliceFinalTokenABalance = await context.tokens[0].storage.ledger[ aliceAddress ].balance; - const aliceFinalTokenBBalance = await context.tokens[2].storage.ledger[ + const aliceFinalTokenBBalance = await context.tokens[1].storage.ledger[ + aliceAddress + ].balance; + const aliceFinalTokenСBalance = await context.tokens[2].storage.ledger[ aliceAddress ].balance; const pairTokenABalance = await context.tokens[0].storage.ledger[ pairAddress ].balance; - const pairTokenBBalance = await context.tokens[2].storage.ledger[ + const pairTokenBBalance = await context.tokens[1].storage.ledger[ pairAddress ].balance; - console.log( - aliceInitTokenBBalance.toNumber(), - amountIn, - aliceFinalTokenBBalance.toNumber() + const pairTokenСBalance = await context.tokens[2].storage.ledger[ + pairAddress + ].balance; + const internalBalanceChange0 = + initDexPair0.token_a_pool.toNumber() - + finalDexPair0.token_a_pool.toNumber(); + const internalBalanceChange1 = + finalDexPair1.token_a_pool.toNumber() - + initDexPair1.token_a_pool.toNumber(); + strictEqual( + aliceInitTokenABalance.toNumber(), + aliceFinalTokenABalance.toNumber() ); strictEqual( aliceInitTokenBBalance.toNumber() - amountIn, aliceFinalTokenBBalance.toNumber() ); strictEqual( - aliceInitTokenABalance.toNumber() + amountOut + tokensLeftover, - aliceFinalTokenABalance.toNumber() + aliceInitTokenСBalance.toNumber() + amountOut + tokensLeftover, + aliceFinalTokenСBalance.toNumber() ); strictEqual( - pairInitTokenABalance.toNumber() - amountOut - tokensLeftover, + pairInitTokenABalance.toNumber(), pairTokenABalance.toNumber() ); + strictEqual( + pairInitTokenСBalance.toNumber() - amountOut - tokensLeftover, + pairTokenСBalance.toNumber() + ); strictEqual( pairInitTokenBBalance.toNumber() + amountIn, pairTokenBBalance.toNumber() ); strictEqual( - finalDexPair.token_a_pool.toNumber(), - initDexPair.token_a_pool.toNumber() - amountOut - tokensLeftover + finalDexPair0.token_b_pool.toNumber(), + initDexPair0.token_b_pool.toNumber() + amountIn ); strictEqual( - finalDexPair.token_b_pool.toNumber(), - initDexPair.token_b_pool.toNumber() + amountIn + finalDexPair1.token_b_pool.toNumber(), + initDexPair1.token_b_pool.toNumber() - amountOut - tokensLeftover ); + strictEqual(internalBalanceChange0, internalBalanceChange1); }); } @@ -186,8 +209,8 @@ contract.only("BuyTokenWithRoute()", function () { }, { pair: { - token_a_address: reverseOrder ? tokenAAddress : tokenCAddress, - token_b_address: reverseOrder ? tokenCAddress : tokenAAddress, + token_a_address: reverseOrder ? tokenCAddress : tokenAAddress, + token_b_address: reverseOrder ? tokenAAddress : tokenCAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), standard: { [standard.toLowerCase()]: null }, @@ -200,7 +223,6 @@ contract.only("BuyTokenWithRoute()", function () { aliceAddress ), (err) => { - console.log(err.message); ok(err.message == errorMsg, "Error message mismatch"); return true; } @@ -217,8 +239,8 @@ contract.only("BuyTokenWithRoute()", function () { ); tokenToTokenFailCase( "revert in case of 100% of reserves to be swapped", - 1000, - 1000, + 10000, + 1, "Dex/high-out" ); tokenToTokenFailCase( @@ -230,42 +252,42 @@ contract.only("BuyTokenWithRoute()", function () { tokenToTokenSuccessCase( "success in case of 1% of reserves to be swapped", - 1, - 1, + 11, + 10, + 0 + ); + tokenToTokenSuccessCase( + "success in case of ~30% of reserves to be swapped", + 300, + 280, 0 ); - // tokenToTokenSuccessCase( - // "success in case of ~30% of reserves to be swapped", - // 300, - // 22983, - // 0 - // ); }); - // describe("Test different minimal desirable output amount", () => { - // tokenToTokenFailCase( - // "reevert in case of 0 tokens expected", - // 10, - // 0, - // "Dex/zero-min-amount-out" - // ); - // tokenToTokenFailCase( - // "revert in case of too many tokens expected", - // 10, - // 70000, - // "Dex/wrong-min-out" - // ); - // tokenToTokenSuccessCase( - // "success in case of exact amount of tokens expected", - // 5, - // 293, - // 0 - // ); - // tokenToTokenSuccessCase( - // "success in case of smaller amount of tokens expected", - // 10, - // 500, - // 80 - // ); - // }); + describe("Test different minimal desirable output amount", () => { + tokenToTokenFailCase( + "reevert in case of 0 tokens expected", + 10, + 0, + "Dex/zero-min-amount-out" + ); + tokenToTokenFailCase( + "revert in case of too many tokens expected", + 10, + 70000, + "Dex/wrong-min-out" + ); + tokenToTokenSuccessCase( + "success in case of exact amount of tokens expected", + 5, + 2, + 0 + ); + tokenToTokenSuccessCase( + "success in case of smaller amount of tokens expected", + 10, + 5, + 1 + ); + }); }); diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index 45207588..e43131dc 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -173,7 +173,6 @@ export class TTDex extends TokenFA2 { ); } } - const operation = await this.contract.methods .use("tokenToTokenRoutePayment", swaps, amountIn, minAmountOut, receiver) .send(); From 64c71d8bc92aa41de5ee780b577f187fc7cb9f89 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 12:48:06 +0300 Subject: [PATCH 35/63] test updated --- test/BuyTokenWithRouter.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index 284cec1d..e7c10bbe 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -280,13 +280,13 @@ contract.only("BuyTokenWithRoute()", function () { tokenToTokenSuccessCase( "success in case of exact amount of tokens expected", 5, - 2, + 4, 0 ); tokenToTokenSuccessCase( "success in case of smaller amount of tokens expected", 10, - 5, + 7, 1 ); }); From 321df80b93764690a0958902be2bfd6e590cc703 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 12:55:50 +0300 Subject: [PATCH 36/63] fix test --- test/BuyTokenWithRouter.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index e7c10bbe..b96c08db 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -41,7 +41,7 @@ contract.only("BuyTokenWithRoute()", function () { tokenBAmount: tokenCAmount, }); tokenCAddress = context.tokens[2].contract.address; - reverseOrder = standard != "MIXED" && tokenCAddress > tokenAAddress; + reverseOrder = standard != "MIXED" && tokenAAddress > tokenCAddress; }); function tokenToTokenSuccessCase( From 182c3779d64bbdc2dc433b147344e49a67b21696 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 14:50:45 +0300 Subject: [PATCH 37/63] add router tests --- test/BuyTokenWithRouter.spec.ts | 2 +- test/SellTokenWithRoute.spec.ts | 289 ++++++++++++++++++++++++++++++++ test/helpers/ttdexFA2.ts | 2 +- 3 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 test/SellTokenWithRoute.spec.ts diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index b96c08db..8047a7bf 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -8,7 +8,7 @@ import { Parser } from "@taquito/michel-codec"; export const michelParser = new Parser(); -contract.only("BuyTokenWithRoute()", function () { +contract("BuyTokenWithRoute()", function () { let context: TTContext; const tokenAAmount: number = 100000; const tokenBAmount: number = 10000; diff --git a/test/SellTokenWithRoute.spec.ts b/test/SellTokenWithRoute.spec.ts new file mode 100644 index 00000000..4983ed98 --- /dev/null +++ b/test/SellTokenWithRoute.spec.ts @@ -0,0 +1,289 @@ +import { TTContext } from "./helpers/ttContext"; +import { strictEqual, ok, notStrictEqual, rejects } from "assert"; +import BigNumber from "bignumber.js"; +import accounts from "./accounts/accounts"; +import { defaultAccountInfo } from "./constants"; +const standard = process.env.EXCHANGE_TOKEN_STANDARD; + +contract.only("SellTokenWithRoute()", function () { + let context: TTContext; + const tokenAAmount: number = 100000; + const tokenBAmount: number = 10000; + const tokenCAmount: number = 10000; + const aliceAddress: string = accounts.alice.pkh; + let tokenAAddress; + let tokenBAddress; + let tokenCAddress; + let reverseOrder; + + before(async () => { + context = await TTContext.init([], false, "alice", false); + await context.setAllDexFunctions(); + await context.createPair({ + tokenAAmount, + tokenBAmount, + }); + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + if (standard != "MIXED" && tokenAAddress > tokenBAddress) { + const tmp = context.tokens[0]; + context.tokens[0] = context.tokens[1]; + context.tokens[1] = tmp; + tokenAAddress = context.tokens[0].contract.address; + tokenBAddress = context.tokens[1].contract.address; + } + await context.createPair({ + tokenAAmount: tokenCAmount, + tokenBAddress, + tokenBAmount: tokenBAmount, + }); + tokenCAddress = context.tokens[2].contract.address; + reverseOrder = standard != "MIXED" && tokenCAddress > tokenBAddress; + }); + + function tokenToTokenSuccessCase( + decription, + amountIn, + amountOut, + tokensLeftover + ) { + it(decription, async function () { + const pairAddress = context.dex.contract.address; + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[2].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0", "1"], + }); + const aliceInitTokenABalance = ( + (await context.tokens[0].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const aliceInitTokenBBalance = ( + (await context.tokens[1].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const aliceInitTokenСBalance = ( + (await context.tokens[2].storage.ledger[aliceAddress]) || + defaultAccountInfo + ).balance; + const pairInitTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairInitTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const pairInitTokenСBalance = await context.tokens[2].storage.ledger[ + pairAddress + ].balance; + const initDexPair0 = context.dex.storage.pairs[0]; + const initDexPair1 = context.dex.storage.pairs[1]; + await context.dex.tokenToTokenRoutePayment( + [ + { + pair: { + token_a_address: tokenAAddress, + token_b_address: tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { sell: null }, + }, + { + pair: { + token_a_address: reverseOrder ? tokenBAddress : tokenCAddress, + token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { buy: null }, + }, + ], + amountIn, + amountOut, + aliceAddress + ); + await context.tokens[0].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[1].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.tokens[2].updateStorage({ + ledger: [aliceAddress, pairAddress], + }); + await context.dex.updateStorage({ + ledger: [[aliceAddress, 0]], + tokens: ["0"], + pairs: ["0", "1"], + }); + const finalDexPair0 = context.dex.storage.pairs[0]; + const finalDexPair1 = context.dex.storage.pairs[1]; + const aliceFinalTokenABalance = await context.tokens[0].storage.ledger[ + aliceAddress + ].balance; + const aliceFinalTokenBBalance = await context.tokens[1].storage.ledger[ + aliceAddress + ].balance; + const aliceFinalTokenСBalance = await context.tokens[2].storage.ledger[ + aliceAddress + ].balance; + const pairTokenABalance = await context.tokens[0].storage.ledger[ + pairAddress + ].balance; + const pairTokenBBalance = await context.tokens[1].storage.ledger[ + pairAddress + ].balance; + const pairTokenСBalance = await context.tokens[2].storage.ledger[ + pairAddress + ].balance; + const internalBalanceChange0 = + initDexPair0.token_b_pool.toNumber() - + finalDexPair0.token_b_pool.toNumber(); + const internalBalanceChange1 = + finalDexPair1.token_b_pool.toNumber() - + initDexPair1.token_b_pool.toNumber(); + strictEqual( + aliceInitTokenBBalance.toNumber(), + aliceFinalTokenBBalance.toNumber() + ); + strictEqual( + aliceInitTokenABalance.toNumber() - amountIn, + aliceFinalTokenABalance.toNumber() + ); + strictEqual( + aliceInitTokenСBalance.toNumber() + amountOut + tokensLeftover, + aliceFinalTokenСBalance.toNumber() + ); + strictEqual( + pairInitTokenBBalance.toNumber(), + pairTokenBBalance.toNumber() + ); + strictEqual( + pairInitTokenСBalance.toNumber() - amountOut - tokensLeftover, + pairTokenСBalance.toNumber() + ); + strictEqual( + pairInitTokenABalance.toNumber() + amountIn, + pairTokenABalance.toNumber() + ); + strictEqual( + finalDexPair0.token_a_pool.toNumber(), + initDexPair0.token_a_pool.toNumber() + amountIn + ); + strictEqual( + finalDexPair1.token_a_pool.toNumber(), + initDexPair1.token_a_pool.toNumber() - amountOut - tokensLeftover + ); + strictEqual(internalBalanceChange0, internalBalanceChange1); + }); + } + + function tokenToTokenFailCase(decription, amountIn, amountOut, errorMsg) { + it(decription, async function () { + await rejects( + context.dex.tokenToTokenRoutePayment( + [ + { + pair: { + token_a_address: tokenAAddress, + token_b_address: tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { sell: null }, + }, + { + pair: { + token_a_address: reverseOrder ? tokenBAddress : tokenCAddress, + token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, + token_a_id: new BigNumber(0), + token_b_id: new BigNumber(0), + standard: { [standard.toLowerCase()]: null }, + }, + operation: { buy: null }, + }, + ], + amountIn, + amountOut, + aliceAddress + ), + (err) => { + ok(err.message == errorMsg, "Error message mismatch"); + return true; + } + ); + }); + } + + describe("Test different amount of tokens to be swapped", () => { + tokenToTokenFailCase( + "revert in case of 0 tokens to be swapped", + 0, + 1, + "Dex/zero-amount-in" + ); + tokenToTokenFailCase( + "revert in case of 100% of reserves to be swapped", + 100000, + 300, + "Dex/high-out" + ); + tokenToTokenFailCase( + "revert in case of 10000% of reserves to be swapped", + 100000000, + 1, + "Dex/high-out" + ); + tokenToTokenFailCase( + "revert in case of 1% of reserves to be swapped", + 12, + 1, + "Dex/wrong-min-out" + ); + tokenToTokenSuccessCase( + "success in case of ~30% of reserves to be swapped", + 30000, + 1866, + 0 + ); + }); + + describe("Test different minimal desirable output amount", () => { + tokenToTokenFailCase( + "reevert in case of 0 tokens expected", + 10, + 0, + "Dex/zero-min-amount-out" + ); + tokenToTokenFailCase( + "revert in case of too many tokens expected", + 20, + 7, + "Dex/wrong-min-out" + ); + tokenToTokenSuccessCase( + "success in case of exact amount of tokens expected", + 1000, + 38, + 0 + ); + tokenToTokenSuccessCase( + "success in case of smaller amount of tokens expected", + 1000, + 33, + 4 + ); + }); +}); diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index e43131dc..1180d4e1 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -158,7 +158,7 @@ export class TTDex extends TokenFA2 { ); } } else { - if (["FA2"].includes(standard)) { + if (["FA2", "MIXED"].includes(standard)) { await this.approveFA2Token( firstSwap.pair.token_a_address, tokenAid, From a220d7ba95f2c8b93fed5eb36998c35dffbc1825 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 14:57:21 +0300 Subject: [PATCH 38/63] update test --- test/helpers/ttContext.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 58ddf697..38c61335 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -141,20 +141,21 @@ export class TTContext { tokenBAmount: 1000000, } ): Promise { - pairConfig.tokenAAddress = - pairConfig.tokenAAddress || - (await this.createToken(standard == "MIXED" ? "FA2" : standard)); - pairConfig.tokenBAddress = - pairConfig.tokenBAddress || - (await this.createToken(standard == "MIXED" ? "FA12" : standard)); - if ( - standard !== "MIXED" && - pairConfig.tokenAAddress > pairConfig.tokenBAddress - ) { - const tmp = pairConfig.tokenAAddress; - pairConfig.tokenAAddress = pairConfig.tokenBAddress; - pairConfig.tokenBAddress = tmp; - } + let tokenAAddress; + let tokenBAddress; + do { + tokenAAddress = + pairConfig.tokenAAddress || + (await this.createToken(standard == "MIXED" ? "FA2" : standard)); + tokenBAddress = + pairConfig.tokenBAddress || + (await this.createToken(standard == "MIXED" ? "FA12" : standard)); + } while ( + standard == "MIXED" || + pairConfig.tokenAAddress < pairConfig.tokenBAddress + ); + pairConfig.tokenAAddress = tokenAAddress; + pairConfig.tokenBAddress = tokenBAddress; await this.dex.initializeExchange( pairConfig.tokenAAddress, pairConfig.tokenBAddress, From ae5626bfa91dede9a3e129f61bd9ccdac12feb85 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 15:05:12 +0300 Subject: [PATCH 39/63] fix loop --- test/BuyToken.spec.ts | 7 ------- test/BuyTokenWithRouter.spec.ts | 7 ------- test/DivestTTLiquidity.spec.ts | 7 ------- test/InitializeTTExchangeTest.spec.ts | 7 ------- test/InvestTTLiquidity.spec.ts | 7 ------- test/SellToken.spec.ts | 7 ------- test/SellTokenWithRoute.spec.ts | 7 ------- test/TokenAToTokenB.spec.ts | 7 ------- test/TokenBToTokenA.spec.ts | 7 ------- test/helpers/ttContext.ts | 5 +---- 10 files changed, 1 insertion(+), 67 deletions(-) diff --git a/test/BuyToken.spec.ts b/test/BuyToken.spec.ts index ad04f928..c0a5e1c8 100644 --- a/test/BuyToken.spec.ts +++ b/test/BuyToken.spec.ts @@ -22,13 +22,6 @@ contract("BuyToken()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); function tokenToTokenSuccessCase( diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index 8047a7bf..8ed99bf7 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -28,13 +28,6 @@ contract("BuyTokenWithRoute()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } await context.createPair({ tokenAAmount: tokenAAmount, tokenAAddress, diff --git a/test/DivestTTLiquidity.spec.ts b/test/DivestTTLiquidity.spec.ts index 96ca8477..9ecce765 100644 --- a/test/DivestTTLiquidity.spec.ts +++ b/test/DivestTTLiquidity.spec.ts @@ -26,13 +26,6 @@ contract("DivestTTLiquidity()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); describe("Test if the diivestment is allowed", () => { diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index a486744e..bb6c8083 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -31,13 +31,6 @@ contract("InitializeTTExchange()", function () { tokenBAddress = await context.createToken( standard == "MIXED" ? "FA12" : standard ); - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); it("revert in case the amount of one of A tokens is zero", async function () { diff --git a/test/InvestTTLiquidity.spec.ts b/test/InvestTTLiquidity.spec.ts index 231d7709..5398a80c 100644 --- a/test/InvestTTLiquidity.spec.ts +++ b/test/InvestTTLiquidity.spec.ts @@ -23,13 +23,6 @@ contract("InvestTTLiquidity()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); describe("Test if the investment is allowed", () => { diff --git a/test/SellToken.spec.ts b/test/SellToken.spec.ts index fa281c34..5a029aa8 100644 --- a/test/SellToken.spec.ts +++ b/test/SellToken.spec.ts @@ -22,13 +22,6 @@ contract("SellToken()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); function tokenToTokenSuccessCase( diff --git a/test/SellTokenWithRoute.spec.ts b/test/SellTokenWithRoute.spec.ts index 4983ed98..3ab239b2 100644 --- a/test/SellTokenWithRoute.spec.ts +++ b/test/SellTokenWithRoute.spec.ts @@ -25,13 +25,6 @@ contract.only("SellTokenWithRoute()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } await context.createPair({ tokenAAmount: tokenCAmount, tokenBAddress, diff --git a/test/TokenAToTokenB.spec.ts b/test/TokenAToTokenB.spec.ts index 6ba0fb6d..06cee2b9 100644 --- a/test/TokenAToTokenB.spec.ts +++ b/test/TokenAToTokenB.spec.ts @@ -23,13 +23,6 @@ contract("TokenAToTokenB()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); function tokenAToTokenBSuccessCase( diff --git a/test/TokenBToTokenA.spec.ts b/test/TokenBToTokenA.spec.ts index afc0c4fa..77e30c85 100644 --- a/test/TokenBToTokenA.spec.ts +++ b/test/TokenBToTokenA.spec.ts @@ -23,13 +23,6 @@ contract("TokenBToTokenA()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - if (standard != "MIXED" && tokenAAddress > tokenBAddress) { - const tmp = context.tokens[0]; - context.tokens[0] = context.tokens[1]; - context.tokens[1] = tmp; - tokenAAddress = context.tokens[0].contract.address; - tokenBAddress = context.tokens[1].contract.address; - } }); function tokenAToTokenBSuccessCase( diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 38c61335..67b50d5f 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -150,10 +150,7 @@ export class TTContext { tokenBAddress = pairConfig.tokenBAddress || (await this.createToken(standard == "MIXED" ? "FA12" : standard)); - } while ( - standard == "MIXED" || - pairConfig.tokenAAddress < pairConfig.tokenBAddress - ); + } while (standard == "MIXED" || tokenAAddress < tokenBAddress); pairConfig.tokenAAddress = tokenAAddress; pairConfig.tokenBAddress = tokenBAddress; await this.dex.initializeExchange( From 840472abd336d777f7915e692d65b967ce504f86 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 15:27:30 +0300 Subject: [PATCH 40/63] fix loop --- test/helpers/ttContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 67b50d5f..518ebef5 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -150,7 +150,7 @@ export class TTContext { tokenBAddress = pairConfig.tokenBAddress || (await this.createToken(standard == "MIXED" ? "FA12" : standard)); - } while (standard == "MIXED" || tokenAAddress < tokenBAddress); + } while (standard !== "MIXED" && tokenAAddress > tokenBAddress); pairConfig.tokenAAddress = tokenAAddress; pairConfig.tokenBAddress = tokenBAddress; await this.dex.initializeExchange( From 7c442c5b4f32d8069114f503d7171d0fbc0d8f6a Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 16:08:59 +0300 Subject: [PATCH 41/63] update tests --- test/BuyTokenWithRouter.spec.ts | 13 ++++++++----- test/SellTokenWithRoute.spec.ts | 13 ++++++++----- test/helpers/ttContext.ts | 33 +++++++++++++++++++++++++-------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index 8ed99bf7..655bb8d8 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -28,11 +28,14 @@ contract("BuyTokenWithRoute()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - await context.createPair({ - tokenAAmount: tokenAAmount, - tokenAAddress, - tokenBAmount: tokenCAmount, - }); + await context.createPair( + { + tokenAAmount: tokenAAmount, + tokenAAddress, + tokenBAmount: tokenCAmount, + }, + false + ); tokenCAddress = context.tokens[2].contract.address; reverseOrder = standard != "MIXED" && tokenAAddress > tokenCAddress; }); diff --git a/test/SellTokenWithRoute.spec.ts b/test/SellTokenWithRoute.spec.ts index 3ab239b2..575e6b34 100644 --- a/test/SellTokenWithRoute.spec.ts +++ b/test/SellTokenWithRoute.spec.ts @@ -25,11 +25,14 @@ contract.only("SellTokenWithRoute()", function () { }); tokenAAddress = context.tokens[0].contract.address; tokenBAddress = context.tokens[1].contract.address; - await context.createPair({ - tokenAAmount: tokenCAmount, - tokenBAddress, - tokenBAmount: tokenBAmount, - }); + await context.createPair( + { + tokenAAmount: tokenCAmount, + tokenBAddress, + tokenBAmount: tokenBAmount, + }, + false + ); tokenCAddress = context.tokens[2].contract.address; reverseOrder = standard != "MIXED" && tokenCAddress > tokenBAddress; }); diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 518ebef5..0c4e34b1 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -74,17 +74,17 @@ export class TTContext { await this.updateActor(); } - async createToken(type = null): Promise { + async createToken(type = null, push = true): Promise { if (!type) type = standard; if (type == "FA2") { let tokenInstance = await CTokenFA2.new(tokenFA2Storage); let tokenAddress = tokenInstance.address.toString(); - this.tokens.push(await TokenFA2.init(tokenAddress)); + if (push) this.tokens.push(await TokenFA2.init(tokenAddress)); return tokenAddress; } else { let tokenInstance = await CTokenFA12.new(tokenFA12Storage); let tokenAddress = tokenInstance.address.toString(); - this.tokens.push(await TokenFA12.init(tokenAddress)); + if (push) this.tokens.push(await TokenFA12.init(tokenAddress)); return tokenAddress; } } @@ -139,18 +139,35 @@ export class TTContext { } = { tokenAAmount: 1000000, tokenBAmount: 1000000, - } + }, + allowReplace: boolean = true ): Promise { let tokenAAddress; let tokenBAddress; do { - tokenAAddress = - pairConfig.tokenAAddress || - (await this.createToken(standard == "MIXED" ? "FA2" : standard)); tokenBAddress = pairConfig.tokenBAddress || - (await this.createToken(standard == "MIXED" ? "FA12" : standard)); + (await this.createToken( + standard == "MIXED" ? "FA12" : standard, + false + )); + tokenAAddress = + pairConfig.tokenAAddress || + (await this.createToken(standard == "MIXED" ? "FA2" : standard, false)); + if ( + allowReplace && + standard !== "MIXED" && + tokenAAddress > tokenBAddress + ) { + const tmp = tokenAAddress; + tokenAAddress = tokenBAddress; + tokenBAddress = tmp; + } } while (standard !== "MIXED" && tokenAAddress > tokenBAddress); + if (pairConfig.tokenAAddress != tokenAAddress) + this.tokens.push(await TokenFA2.init(tokenAAddress)); + if (pairConfig.tokenBAddress != tokenBAddress) + this.tokens.push(await TokenFA2.init(tokenBAddress)); pairConfig.tokenAAddress = tokenAAddress; pairConfig.tokenBAddress = tokenBAddress; await this.dex.initializeExchange( From dee46982845596b581b39653a3377dc3d8f8a061 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 16:14:32 +0300 Subject: [PATCH 42/63] uncomment all tests --- test/SellTokenWithRoute.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SellTokenWithRoute.spec.ts b/test/SellTokenWithRoute.spec.ts index 575e6b34..2abec25c 100644 --- a/test/SellTokenWithRoute.spec.ts +++ b/test/SellTokenWithRoute.spec.ts @@ -5,7 +5,7 @@ import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract.only("SellTokenWithRoute()", function () { +contract("SellTokenWithRoute()", function () { let context: TTContext; const tokenAAmount: number = 100000; const tokenBAmount: number = 10000; From 3c55202330d0bcc2d2f947c2067190be7e76881d Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 16:43:04 +0300 Subject: [PATCH 43/63] fix ttContext --- test/InitializeTTExchangeTest.spec.ts | 41 ++++++++++++++++++++++----- test/helpers/ttContext.ts | 26 ++++++++++++++--- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index bb6c8083..180c0004 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -2,9 +2,11 @@ import { TTContext } from "./helpers/ttContext"; import { strictEqual, ok, notStrictEqual, rejects } from "assert"; import accounts from "./accounts/accounts"; import { defaultAccountInfo } from "./constants"; +import { TokenFA2 } from "./helpers/tokenFA2"; +import { TokenFA12 } from "./helpers/tokenFA12"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract("InitializeTTExchange()", function () { +contract.only("InitializeTTExchange()", function () { let context: TTContext; let aliceAddress: string = accounts.alice.pkh; const tokenAAmount: number = 10000; @@ -25,12 +27,37 @@ contract("InitializeTTExchange()", function () { let tokenAAddress: string; let tokenBAddress: string; before(async () => { - tokenAAddress = await context.createToken( - standard == "MIXED" ? "FA2" : standard - ); - tokenBAddress = await context.createToken( - standard == "MIXED" ? "FA12" : standard - ); + do { + tokenBAddress = await context.createToken( + standard == "MIXED" ? "FA12" : standard, + false + ); + tokenAAddress = await context.createToken( + standard == "MIXED" ? "FA2" : standard, + false + ); + if (standard !== "MIXED" && tokenAAddress > tokenBAddress) { + const tmp = tokenAAddress; + tokenAAddress = tokenBAddress; + tokenBAddress = tmp; + } + } while (standard !== "MIXED" && tokenAAddress > tokenBAddress); + switch (standard) { + case "FA2": + context.tokens.push(await TokenFA2.init(tokenAAddress)); + context.tokens.push(await TokenFA2.init(tokenBAddress)); + break; + case "FA12": + context.tokens.push(await TokenFA12.init(tokenAAddress)); + context.tokens.push(await TokenFA12.init(tokenBAddress)); + break; + case "MIXED": + context.tokens.push(await TokenFA2.init(tokenAAddress)); + context.tokens.push(await TokenFA12.init(tokenBAddress)); + break; + default: + break; + } }); it("revert in case the amount of one of A tokens is zero", async function () { diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index 0c4e34b1..df062ecd 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -164,10 +164,28 @@ export class TTContext { tokenBAddress = tmp; } } while (standard !== "MIXED" && tokenAAddress > tokenBAddress); - if (pairConfig.tokenAAddress != tokenAAddress) - this.tokens.push(await TokenFA2.init(tokenAAddress)); - if (pairConfig.tokenBAddress != tokenBAddress) - this.tokens.push(await TokenFA2.init(tokenBAddress)); + switch (standard) { + case "FA2": + if (pairConfig.tokenAAddress != tokenAAddress) + this.tokens.push(await TokenFA2.init(tokenAAddress)); + if (pairConfig.tokenBAddress != tokenBAddress) + this.tokens.push(await TokenFA2.init(tokenBAddress)); + break; + case "FA12": + if (pairConfig.tokenAAddress != tokenAAddress) + this.tokens.push(await TokenFA12.init(tokenAAddress)); + if (pairConfig.tokenBAddress != tokenBAddress) + this.tokens.push(await TokenFA12.init(tokenBAddress)); + break; + case "MIXED": + if (pairConfig.tokenAAddress != tokenAAddress) + this.tokens.push(await TokenFA2.init(tokenAAddress)); + if (pairConfig.tokenBAddress != tokenBAddress) + this.tokens.push(await TokenFA12.init(tokenBAddress)); + break; + default: + break; + } pairConfig.tokenAAddress = tokenAAddress; pairConfig.tokenBAddress = tokenBAddress; await this.dex.initializeExchange( From c32956085d079f61f105e8f7e862deaf51f7522b Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 9 Jun 2021 16:44:12 +0300 Subject: [PATCH 44/63] enable all tests --- test/InitializeTTExchangeTest.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index 180c0004..e722e5af 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -6,7 +6,7 @@ import { TokenFA2 } from "./helpers/tokenFA2"; import { TokenFA12 } from "./helpers/tokenFA12"; const standard = process.env.EXCHANGE_TOKEN_STANDARD; -contract.only("InitializeTTExchange()", function () { +contract("InitializeTTExchange()", function () { let context: TTContext; let aliceAddress: string = accounts.alice.pkh; const tokenAAmount: number = 10000; From 04c265e9d2c48f4eeedd086d52e7b2cd1c7ba5a6 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 15 Jun 2021 14:24:25 +0300 Subject: [PATCH 45/63] code style improvements --- contracts/partials/ITTDex.ligo | 11 +- contracts/partials/TTMethodDex.ligo | 602 +++++++++++----------------- 2 files changed, 246 insertions(+), 367 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index abc7529c..633ee497 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -85,21 +85,12 @@ type token_to_token_payment_params is receiver : address; (* tokens receiver *) ] -type initialize_exchange_params is - [@layout:comb] - record [ - pair : tokens_info; - token_a_in : nat; (* min amount of XTZ received to accept the divestment *) - token_b_in : nat; (* min amount of tokens received to accept the divestment *) - ] - type invest_liquidity_params is [@layout:comb] record [ pair : tokens_info; token_a_in : nat; (* min amount of XTZ received to accept the divestment *) token_b_in : nat; (* min amount of tokens received to accept the divestment *) - shares : nat; (* amount of shares to be burnt *) ] type divest_liquidity_params is @@ -112,7 +103,7 @@ type divest_liquidity_params is ] type dex_action is -| InitializeExchange of initialize_exchange_params (* sets initial liquidity *) +| InitializeExchange of invest_liquidity_params (* sets initial liquidity *) | TokenToTokenRoutePayment of token_to_token_route_params (* exchanges XTZ to tokens and sends them to receiver *) | TokenToTokenPayment of token_to_token_payment_params (* exchanges XTZ to tokens and sends them to receiver *) | InvestLiquidity of invest_liquidity_params (* mints min shares after investing tokens and XTZ *) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 1e60858e..c5d82eff 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -55,6 +55,37 @@ function get_fa12_token_contract(const token_address : address) : contract(trans | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) end; +function transfer_fa2( + const sender_ : address; + const receiver : address; + const amount_ : nat; + const token_id : nat; + const contract_address : address) : operation is + Tezos.transaction( + wrap_fa2_transfer_trx( + sender_, + receiver, + amount_, + token_id), + 0mutez, + get_fa2_token_contract(contract_address) + ); + +function transfer_fa12( + const sender_ : address; + const receiver : address; + const amount_ : nat; + const contract_address : address) : operation is + Tezos.transaction( + wrap_fa12_transfer_trx( + sender_, + receiver, + amount_), + 0mutez, + get_fa12_token_contract(contract_address) + ); + + #include "../partials/TTMethodFA2.ligo" (* Initialize exchange after the previous liquidity was drained *) @@ -117,69 +148,49 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx(Tezos.sender, - this, - params.token_a_in), - 0mutez, - get_fa12_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx(Tezos.sender, - this, - params.token_b_in - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; + transfer_fa12( + Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_address); + transfer_fa12( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_address)]; } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx(Tezos.sender, - this, - params.token_a_in, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa2_transfer_trx( - Tezos.sender, - this, - params.token_b_in, - params.pair.token_b_id), - 0mutez, - get_fa2_token_contract( - params.pair.token_b_address) - )]; + transfer_fa2( + Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa2( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_id, + params.pair.token_b_address); + ]; } | Mixed -> { operations :=list[ - Tezos.transaction( - wrap_fa2_transfer_trx(Tezos.sender, - this, - params.token_a_in, - params.pair.token_a_id - ), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.token_b_in - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; - + transfer_fa2( + Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa12( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_address)]; } end; } @@ -251,72 +262,49 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in), - 0mutez, - get_fa12_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - this, - params.receiver, - token_b_out - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; + transfer_fa12( + Tezos.sender, + this, + params.amount_in, + params.pair.token_a_address); + transfer_fa12( + this, + params.receiver, + token_b_out, + params.pair.token_b_address)]; } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( - Tezos.sender, - this, - params.amount_in, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa2_transfer_trx( - this, - params.receiver, - token_b_out, - params.pair.token_b_id), - 0mutez, - get_fa2_token_contract( - params.pair.token_b_address) - )]; + transfer_fa2( + Tezos.sender, + this, + params.amount_in, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa2( + this, + params.receiver, + token_b_out, + params.pair.token_b_id, + params.pair.token_b_address); + ]; } | Mixed -> { operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( - Tezos.sender, - this, - params.amount_in, - params.pair.token_a_id - ), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - this, - params.receiver, - token_b_out - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; + transfer_fa2( + Tezos.sender, + this, + params.amount_in, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa12( + this, + params.receiver, + token_b_out, + params.pair.token_b_address)]; } end; } @@ -348,39 +336,28 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in), - 0mutez, - get_fa12_token_contract(params.pair.token_b_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - this, - params.receiver, - token_a_out - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_a_address) - )]; + transfer_fa12( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_address); + transfer_fa12( + this, + params.receiver, + token_a_out, + params.pair.token_a_address)]; } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( - Tezos.sender, - this, - params.amount_in, - params.pair.token_b_id), - 0mutez, - get_fa2_token_contract(params.pair.token_b_address) - ); + transfer_fa2( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_id, + params.pair.token_b_address); Tezos.transaction( wrap_fa2_transfer_trx( this, @@ -394,26 +371,18 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this } | Mixed -> { operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in - ), - 0mutez, - get_fa12_token_contract(params.pair.token_b_address) - ); - Tezos.transaction( - wrap_fa2_transfer_trx( - this, - params.receiver, - token_a_out, - params.pair.token_a_id - ), - 0mutez, - get_fa2_token_contract( - params.pair.token_a_address) - )]; + transfer_fa12( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_address); + transfer_fa2( + this, + params.receiver, + token_a_out, + params.pair.token_a_id, + params.pair.token_a_address); + ]; } end; } @@ -478,43 +447,32 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; - tmp.operation := Some(Tezos.transaction( - wrap_fa12_transfer_trx( - tmp.sender, - tmp.receiver, - token_b_out - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )); + tmp.operation := Some( + transfer_fa12( + tmp.sender, + tmp.receiver, + token_b_out, + params.pair.token_b_address)); } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; - tmp.operation := Some(Tezos.transaction( - wrap_fa2_transfer_trx( + tmp.operation := Some( + transfer_fa2( tmp.sender, tmp.receiver, token_b_out, - params.pair.token_b_id), - 0mutez, - get_fa2_token_contract( + params.pair.token_b_id, params.pair.token_b_address) - )); + ); } | Mixed -> { - tmp.operation := Some(Tezos.transaction( - wrap_fa12_transfer_trx( - tmp.sender, - tmp.receiver, - token_b_out - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )); + tmp.operation := Some(transfer_fa12( + tmp.sender, + tmp.receiver, + token_b_out, + params.pair.token_b_address)); } end; } @@ -549,44 +507,35 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; - tmp.operation := Some(Tezos.transaction( - wrap_fa12_transfer_trx( - tmp.sender, - tmp.receiver, - token_a_out - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_a_address) - )); + tmp.operation := Some( + transfer_fa12( + tmp.sender, + tmp.receiver, + token_a_out, + params.pair.token_a_address)); } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; - tmp.operation := Some(Tezos.transaction( - wrap_fa2_transfer_trx( + tmp.operation := Some( + transfer_fa2( tmp.sender, tmp.receiver, token_a_out, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract( + params.pair.token_a_id, params.pair.token_a_address) - )); + ); } | Mixed -> { - tmp.operation := Some(Tezos.transaction( - wrap_fa2_transfer_trx( + tmp.operation := Some( + transfer_fa2( tmp.sender, tmp.receiver, token_a_out, - params.pair.token_a_id - ), - 0mutez, - get_fa2_token_contract( + params.pair.token_a_id, params.pair.token_a_address) - )); + ); } end; } @@ -629,31 +578,21 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons case first_swap.operation of | Sell -> { operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in - ), - 0mutez, - get_fa12_token_contract( - first_swap.pair.token_a_address - ))]; + transfer_fa12( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_a_address)]; } | Buy -> { token_id_in := first_swap.pair.token_b_id; token_address_in := first_swap.pair.token_b_address; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in - ), - 0mutez, - get_fa12_token_contract( - first_swap.pair.token_b_address - ))]; + transfer_fa12( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_b_address)]; } end } @@ -661,27 +600,23 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons case first_swap.operation of | Sell -> { operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( + transfer_fa2( Tezos.sender, this, params.amount_in, - first_swap.pair.token_a_id), - 0mutez, - get_fa2_token_contract(first_swap.pair.token_a_address))] + first_swap.pair.token_a_id, + first_swap.pair.token_a_address)] } | Buy -> { token_id_in := first_swap.pair.token_b_id; token_address_in := first_swap.pair.token_b_address; operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( + transfer_fa2( Tezos.sender, this, params.amount_in, - first_swap.pair.token_b_id), - 0mutez, - get_fa2_token_contract(first_swap.pair.token_b_address))] + first_swap.pair.token_b_id, + first_swap.pair.token_b_address)] } end } @@ -689,26 +624,22 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons case first_swap.operation of | Sell -> { operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_a_id), - 0mutez, - get_fa2_token_contract(first_swap.pair.token_a_address))] + transfer_fa2( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_a_id, + first_swap.pair.token_a_address)] } | Buy -> { token_id_in := first_swap.pair.token_b_id; token_address_in := first_swap.pair.token_b_address; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - params.amount_in), - 0mutez, - get_fa12_token_contract(first_swap.pair.token_b_address))] + transfer_fa12( + Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_b_address)] } end } @@ -823,68 +754,48 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx(Tezos.sender, - this, - tokens_a_required), - 0mutez, - get_fa12_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx(Tezos.sender, - this, - tokens_b_required - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; + transfer_fa12( + Tezos.sender, + this, + tokens_a_required, + params.pair.token_a_address); + transfer_fa12( + Tezos.sender, + this, + tokens_b_required, + params.pair.token_b_address)]; } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx(Tezos.sender, + transfer_fa2( + Tezos.sender, this, tokens_a_required, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa2_transfer_trx( + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa2( Tezos.sender, this, tokens_b_required, - params.pair.token_b_id), - 0mutez, - get_fa2_token_contract( - params.pair.token_b_address) - )]; + params.pair.token_b_id, + params.pair.token_b_address)]; } | Mixed -> { operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx(Tezos.sender, - this, - tokens_a_required, - params.pair.token_a_id - ), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - Tezos.sender, - this, - tokens_b_required - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )] + transfer_fa2( + Tezos.sender, + this, + tokens_a_required, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa12( + Tezos.sender, + this, + tokens_b_required, + params.pair.token_b_address)] } end; } @@ -968,72 +879,49 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa12_transfer_trx( - this, - Tezos.sender, - token_a_divested), - 0mutez, - get_fa12_token_contract( - params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - this, - Tezos.sender, - token_b_divested - ), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; + transfer_fa12( + this, + Tezos.sender, + token_a_divested, + params.pair.token_a_address); + transfer_fa12( + Tezos.sender, + this, + token_b_divested, + params.pair.token_b_address)]; } | Fa2 -> { if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( - this, - Tezos.sender, - token_a_divested, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract( - params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa2_transfer_trx( - this, - Tezos.sender, - token_b_divested, - params.pair.token_b_id), - 0mutez, - get_fa2_token_contract( - params.pair.token_b_address) - )]; + transfer_fa2( + this, + Tezos.sender, + token_a_divested, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa2( + this, + Tezos.sender, + token_b_divested, + params.pair.token_b_id, + params.pair.token_b_address)]; } | Mixed -> { operations := list[ - Tezos.transaction( - wrap_fa2_transfer_trx( - this, - Tezos.sender, - token_a_divested, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract(params.pair.token_a_address) - ); - Tezos.transaction( - wrap_fa12_transfer_trx( - this, - Tezos.sender, - token_b_divested), - 0mutez, - get_fa12_token_contract( - params.pair.token_b_address) - )]; + transfer_fa2( + this, + Tezos.sender, + token_a_divested, + params.pair.token_a_id, + params.pair.token_a_address); + transfer_fa12( + Tezos.sender, + this, + token_b_divested, + params.pair.token_b_address) + ]; } end; } From a9b4ecd481bbc0b799e9f176b9caf0ee22d556a0 Mon Sep 17 00:00:00 2001 From: kstasi Date: Tue, 15 Jun 2021 15:38:12 +0300 Subject: [PATCH 46/63] fix --- contracts/partials/TTMethodDex.ligo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index c5d82eff..af689023 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -885,8 +885,8 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th token_a_divested, params.pair.token_a_address); transfer_fa12( - Tezos.sender, this, + Tezos.sender, token_b_divested, params.pair.token_b_address)]; } @@ -917,8 +917,8 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th params.pair.token_a_id, params.pair.token_a_address); transfer_fa12( - Tezos.sender, this, + Tezos.sender, token_b_divested, params.pair.token_b_address) ]; From f612ba10a2ecbf788b65f0e4a7549024279049e0 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 11:37:39 +0300 Subject: [PATCH 47/63] token to token refactoring --- contracts/partials/ITTDex.ligo | 6 +- contracts/partials/TTMethodDex.ligo | 353 ++++++++++------------------ test/BuyTokenWithRouter.spec.ts | 36 ++- test/SellTokenWithRoute.spec.ts | 36 ++- test/helpers/types.ts | 5 +- 5 files changed, 192 insertions(+), 244 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 633ee497..3433abc9 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -18,10 +18,9 @@ type token_identifier_fa12 is address type transfer_type_fa12 is TransferTypeFA12 of token_transfer_params_fa12 type transfer_type_fa2 is TransferTypeFA2 of token_transfer_params_fa2 -type pair_type is +type token_type is | Fa12 | Fa2 -| Mixed type pair_info is record [ token_a_pool : nat; (* tez reserves in the pool *) @@ -34,7 +33,8 @@ type tokens_info is record [ token_b_address : address; token_a_id : nat; token_b_id : nat; - standard : pair_type; + token_a_type : token_type; + token_b_type : token_type; ] type token_pair is bytes diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index af689023..335429f2 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -95,9 +95,12 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con case p of | InitializeExchange(params) -> { (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -142,55 +145,45 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con s.tokens[token_id] := params.pair; (* prepare operations to get initial liquidity *) - case params.pair.standard of + case params.pair.token_a_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa12( Tezos.sender, this, params.token_a_in, - params.pair.token_a_address); - transfer_fa12( - Tezos.sender, - this, - params.token_b_in, - params.pair.token_b_address)]; + params.pair.token_a_address)]; } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa2( Tezos.sender, this, params.token_a_in, params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa2( + params.pair.token_a_address) + ]; + } + end; + + (* prepare operations to get initial liquidity *) + case params.pair.token_b_type of + | Fa12 -> { + operations := + transfer_fa12( Tezos.sender, this, params.token_b_in, - params.pair.token_b_id, - params.pair.token_b_address); - ]; + params.pair.token_b_address) # operations; } - | Mixed -> { - operations :=list[ + | Fa2 -> { + operations := transfer_fa2( - Tezos.sender, - this, - params.token_a_in, - params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa12( Tezos.sender, this, params.token_b_in, - params.pair.token_b_address)]; + params.pair.token_b_id, + params.pair.token_b_address) # operations; } end; } @@ -211,10 +204,12 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(params) -> { (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; - + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); const pair : pair_info = res.0; @@ -256,55 +251,44 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.standard of + case params.pair.token_a_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa12( Tezos.sender, this, params.amount_in, params.pair.token_a_address); - transfer_fa12( - this, - params.receiver, - token_b_out, - params.pair.token_b_address)]; + ]; } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa2( Tezos.sender, this, params.amount_in, params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa2( + params.pair.token_a_address) + ]; + } + end; + case params.pair.token_b_type of + | Fa12 -> { + operations := + transfer_fa12( this, params.receiver, token_b_out, - params.pair.token_b_id, - params.pair.token_b_address); - ]; + params.pair.token_b_address) # operations; } - | Mixed -> { - operations := list[ + | Fa2 -> { + operations := transfer_fa2( - Tezos.sender, - this, - params.amount_in, - params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa12( this, params.receiver, token_b_out, - params.pair.token_b_address)]; + params.pair.token_b_id, + params.pair.token_b_address) # operations; } end; } @@ -330,60 +314,43 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.standard of + case params.pair.token_a_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ - transfer_fa12( - Tezos.sender, - this, - params.amount_in, - params.pair.token_b_address); transfer_fa12( this, params.receiver, token_a_out, - params.pair.token_a_address)]; + params.pair.token_a_address) + ]; } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; - operations := list[ - transfer_fa2( - Tezos.sender, - this, - params.amount_in, - params.pair.token_b_id, - params.pair.token_b_address); - Tezos.transaction( - wrap_fa2_transfer_trx( - this, - params.receiver, - token_a_out, - params.pair.token_a_id), - 0mutez, - get_fa2_token_contract( - params.pair.token_a_address) - )]; - } - | Mixed -> { operations := list[ - transfer_fa12( - Tezos.sender, - this, - params.amount_in, - params.pair.token_b_address); transfer_fa2( this, params.receiver, token_a_out, params.pair.token_a_id, - params.pair.token_a_address); + params.pair.token_a_address) ]; + } + end; + case params.pair.token_b_type of + | Fa12 -> { + operations := transfer_fa12( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_address) # operations; } + | Fa2 -> { + operations := transfer_fa2( + Tezos.sender, + this, + params.amount_in, + params.pair.token_b_id, + params.pair.token_b_address) # operations; + } end; } end; @@ -398,9 +365,12 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this function internal_token_to_token_swap (const tmp : internal_swap_type; const params : swap_slice_type ) : internal_swap_type is block { (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, tmp.s); @@ -442,11 +412,8 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par tmp.token_id_in := params.pair.token_b_id; (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.standard of + case params.pair.token_b_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; tmp.operation := Some( transfer_fa12( tmp.sender, @@ -455,9 +422,6 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par params.pair.token_b_address)); } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; tmp.operation := Some( transfer_fa2( tmp.sender, @@ -467,13 +431,6 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par params.pair.token_b_address) ); } - | Mixed -> { - tmp.operation := Some(transfer_fa12( - tmp.sender, - tmp.receiver, - token_b_out, - params.pair.token_b_address)); - } end; } | Buy -> { @@ -502,11 +459,8 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par pair.token_a_address; tmp.token_id_in := params.pair.token_a_id; (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.standard of + case params.pair.token_a_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; tmp.operation := Some( transfer_fa12( tmp.sender, @@ -515,9 +469,6 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par params.pair.token_a_address)); } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; tmp.operation := Some( transfer_fa2( tmp.sender, @@ -527,16 +478,6 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par params.pair.token_a_address) ); } - | Mixed -> { - tmp.operation := Some( - transfer_fa2( - tmp.sender, - tmp.receiver, - token_a_out, - params.pair.token_a_id, - params.pair.token_a_address) - ); - } end; } end; @@ -573,10 +514,10 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons var token_id_in : nat := first_swap.pair.token_a_id; var token_address_in : address := first_swap.pair.token_a_address; - case first_swap.pair.standard of - | Fa12 -> { - case first_swap.operation of - | Sell -> { + case first_swap.operation of + | Sell -> { + case first_swap.pair.token_a_type of + | Fa12 -> { operations := list[ transfer_fa12( Tezos.sender, @@ -584,64 +525,39 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons params.amount_in, first_swap.pair.token_a_address)]; } - | Buy -> { - token_id_in := first_swap.pair.token_b_id; - token_address_in := first_swap.pair.token_b_address; + | Fa2 -> { operations := list[ - transfer_fa12( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_b_address)]; - } - end - } - | Fa2 -> { - case first_swap.operation of - | Sell -> { - operations := list[ - transfer_fa2( + transfer_fa2( Tezos.sender, this, params.amount_in, first_swap.pair.token_a_id, first_swap.pair.token_a_address)] } - | Buy -> { + end; + } + | Buy -> { token_id_in := first_swap.pair.token_b_id; token_address_in := first_swap.pair.token_b_address; - operations := list[ - transfer_fa2( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_b_id, - first_swap.pair.token_b_address)] - } - end - } - | Mixed -> { - case first_swap.operation of - | Sell -> { + case first_swap.pair.token_b_type of + | Fa12 -> { operations := list[ - transfer_fa2( + transfer_fa12( Tezos.sender, this, params.amount_in, - first_swap.pair.token_a_id, - first_swap.pair.token_a_address)] + first_swap.pair.token_b_address)]; } - | Buy -> { - token_id_in := first_swap.pair.token_b_id; - token_address_in := first_swap.pair.token_b_address; + | Fa2 -> { operations := list[ - transfer_fa12( + transfer_fa2( Tezos.sender, this, params.amount_in, + first_swap.pair.token_b_id, first_swap.pair.token_b_address)] - } - end + } + end; } end; @@ -685,9 +601,12 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenPayment(n) -> skip | InvestLiquidity(params) -> { (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -748,54 +667,40 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th s.pairs[token_id] := pair; (* prepare operations to get initial liquidity *) - case params.pair.standard of + case params.pair.token_a_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; - operations := list[ + operations := list[ transfer_fa12( Tezos.sender, this, tokens_a_required, - params.pair.token_a_address); - transfer_fa12( - Tezos.sender, - this, - tokens_b_required, - params.pair.token_b_address)]; + params.pair.token_a_address)]; } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa2( Tezos.sender, this, tokens_a_required, params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa2( - Tezos.sender, - this, - tokens_b_required, - params.pair.token_b_id, - params.pair.token_b_address)]; + params.pair.token_a_address);]; } - | Mixed -> { - operations := list[ - transfer_fa2( - Tezos.sender, - this, - tokens_a_required, - params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa12( + end; + case params.pair.token_b_type of + | Fa12 -> { + operations := transfer_fa12( Tezos.sender, this, tokens_b_required, - params.pair.token_b_address)] + params.pair.token_b_address) # operations; + } + | Fa2 -> { + operations := transfer_fa2( + Tezos.sender, + this, + tokens_b_required, + params.pair.token_b_id, + params.pair.token_b_address) # operations; } end; } @@ -814,9 +719,12 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th | InvestLiquidity(n) -> skip | DivestLiquidity(params) -> { (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id > params.pair.token_b_id then + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address then + failwith("Dex/wrong-pair") + else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -873,55 +781,40 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th s.pairs[token_id] := pair; (* prepare operations with XTZ and tokens to user *) - case params.pair.standard of + case params.pair.token_a_type of | Fa12 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa12( this, Tezos.sender, token_a_divested, - params.pair.token_a_address); - transfer_fa12( - this, - Tezos.sender, - token_b_divested, - params.pair.token_b_address)]; + params.pair.token_a_address)]; } | Fa2 -> { - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; operations := list[ transfer_fa2( this, Tezos.sender, token_a_divested, params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa2( + params.pair.token_a_address)]; + } + end; + case params.pair.token_b_type of + | Fa12 -> { + operations := transfer_fa12( this, Tezos.sender, token_b_divested, - params.pair.token_b_id, - params.pair.token_b_address)]; + params.pair.token_b_address) # operations; } - | Mixed -> { - operations := list[ - transfer_fa2( - this, - Tezos.sender, - token_a_divested, - params.pair.token_a_id, - params.pair.token_a_address); - transfer_fa12( + | Fa2 -> { + operations := transfer_fa2( this, Tezos.sender, token_b_divested, - params.pair.token_b_address) - ]; + params.pair.token_b_id, + params.pair.token_b_address) # operations; } end; } diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index 655bb8d8..bc41f72b 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -93,7 +93,14 @@ contract("BuyTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { buy: null }, }, @@ -103,7 +110,14 @@ contract("BuyTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenAAddress : tokenCAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { sell: null }, }, @@ -199,7 +213,14 @@ contract("BuyTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { buy: null }, }, @@ -209,7 +230,14 @@ contract("BuyTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenAAddress : tokenCAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { sell: null }, }, diff --git a/test/SellTokenWithRoute.spec.ts b/test/SellTokenWithRoute.spec.ts index 2abec25c..e036df6b 100644 --- a/test/SellTokenWithRoute.spec.ts +++ b/test/SellTokenWithRoute.spec.ts @@ -90,7 +90,14 @@ contract("SellTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { sell: null }, }, @@ -100,7 +107,14 @@ contract("SellTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { buy: null }, }, @@ -196,7 +210,14 @@ contract("SellTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { sell: null }, }, @@ -206,7 +227,14 @@ contract("SellTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - standard: { [standard.toLowerCase()]: null }, + token_a_type: + standard.toLowerCase() == "mixed" + ? "fa2" + : standard.toLowerCase(), + token_b_type: + standard.toLowerCase() == "mixed" + ? "fa12" + : standard.toLowerCase(), }, operation: { buy: null }, }, diff --git a/test/helpers/types.ts b/test/helpers/types.ts index 6cd85bfd..4cfa72b1 100644 --- a/test/helpers/types.ts +++ b/test/helpers/types.ts @@ -5,9 +5,8 @@ export declare type TokenInfo = { token_b_address: string; token_a_id?: BigNumber; token_b_id?: BigNumber; - standard: { - [key: string]: any; - }; + token_a_type: string; + token_b_type: string; }; export declare type PairInfo = { From afbf19a9f48614ae6bca02f992781f3e0fb6642a Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 11:48:38 +0300 Subject: [PATCH 48/63] update tests --- test/helpers/ttdexFA2.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index 1180d4e1..9adeccbd 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -119,12 +119,13 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "initializeExchange", - standard.toLowerCase(), null, tokenAAddress, tokenAid, + standard.toLowerCase(), tokenBAddress, tokenBid, + standard.toLowerCase(), tokenAAmount, tokenBAmount ) @@ -224,12 +225,13 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "tokenToTokenPayment", - standard.toLowerCase(), null, tokenAAddress, tokenAid, + standard.toLowerCase(), tokenBAddress, tokenBid, + standard.toLowerCase(), opType, null, amountIn, @@ -281,12 +283,13 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "investLiquidity", - standard.toLowerCase(), null, pair.token_a_address, pair.token_a_id, + standard.toLowerCase(), pair.token_b_address, pair.token_b_id, + standard.toLowerCase(), tokenAAmount, tokenBAmount, minShares @@ -311,8 +314,10 @@ export class TTDex extends TokenFA2 { null, pair.token_a_address, pair.token_a_id, + standard.toLowerCase(), pair.token_b_address, pair.token_b_id, + standard.toLowerCase(), tokenAAmount, tokenBAmount, sharesBurned From 768e0857408fa7e04b89c587c31d738251be6660 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 12:02:42 +0300 Subject: [PATCH 49/63] update tests --- test/helpers/ttdexFA2.ts | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/test/helpers/ttdexFA2.ts b/test/helpers/ttdexFA2.ts index 9adeccbd..bd6c4f08 100644 --- a/test/helpers/ttdexFA2.ts +++ b/test/helpers/ttdexFA2.ts @@ -116,16 +116,18 @@ export class TTDex extends TokenFA2 { ); } } + const operation = await this.contract.methods .use( "initializeExchange", - null, tokenAAddress, tokenAid, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa2" : standard.toLowerCase(), + null, tokenBAddress, tokenBid, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa12" : standard.toLowerCase(), + null, tokenAAmount, tokenBAmount ) @@ -225,13 +227,14 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "tokenToTokenPayment", - null, tokenAAddress, tokenAid, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa2" : standard.toLowerCase(), + null, tokenBAddress, tokenBid, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa12" : standard.toLowerCase(), + null, opType, null, amountIn, @@ -283,13 +286,14 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "investLiquidity", - null, pair.token_a_address, pair.token_a_id, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa2" : standard.toLowerCase(), + null, pair.token_b_address, pair.token_b_id, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa12" : standard.toLowerCase(), + null, tokenAAmount, tokenBAmount, minShares @@ -310,14 +314,14 @@ export class TTDex extends TokenFA2 { const operation = await this.contract.methods .use( "divestLiquidity", - standard.toLowerCase(), - null, pair.token_a_address, pair.token_a_id, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa2" : standard.toLowerCase(), + null, pair.token_b_address, pair.token_b_id, - standard.toLowerCase(), + standard.toLowerCase() == "mixed" ? "fa12" : standard.toLowerCase(), + null, tokenAAmount, tokenBAmount, sharesBurned From c5fa5c3acc62b84515776dbe535d6305f6f185df Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 12:53:36 +0300 Subject: [PATCH 50/63] fix order --- test/helpers/ttContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helpers/ttContext.ts b/test/helpers/ttContext.ts index df062ecd..e84ef210 100644 --- a/test/helpers/ttContext.ts +++ b/test/helpers/ttContext.ts @@ -163,7 +163,7 @@ export class TTContext { tokenAAddress = tokenBAddress; tokenBAddress = tmp; } - } while (standard !== "MIXED" && tokenAAddress > tokenBAddress); + } while (tokenAAddress > tokenBAddress); switch (standard) { case "FA2": if (pairConfig.tokenAAddress != tokenAAddress) From 2fc2aee4168772e12a88e605943f0f89044cc281 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 13:48:51 +0300 Subject: [PATCH 51/63] fix test call --- test/BuyTokenWithRouter.spec.ts | 56 +++++++++++++++++++-------------- test/SellTokenWithRoute.spec.ts | 56 +++++++++++++++++++-------------- test/helpers/types.ts | 4 +-- 3 files changed, 66 insertions(+), 50 deletions(-) diff --git a/test/BuyTokenWithRouter.spec.ts b/test/BuyTokenWithRouter.spec.ts index bc41f72b..a4f07fd8 100644 --- a/test/BuyTokenWithRouter.spec.ts +++ b/test/BuyTokenWithRouter.spec.ts @@ -93,14 +93,16 @@ contract("BuyTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { buy: null }, }, @@ -110,14 +112,16 @@ contract("BuyTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenAAddress : tokenCAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { sell: null }, }, @@ -213,14 +217,16 @@ contract("BuyTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { buy: null }, }, @@ -230,14 +236,16 @@ contract("BuyTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenAAddress : tokenCAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { sell: null }, }, diff --git a/test/SellTokenWithRoute.spec.ts b/test/SellTokenWithRoute.spec.ts index e036df6b..204dfbf1 100644 --- a/test/SellTokenWithRoute.spec.ts +++ b/test/SellTokenWithRoute.spec.ts @@ -90,14 +90,16 @@ contract("SellTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { sell: null }, }, @@ -107,14 +109,16 @@ contract("SellTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { buy: null }, }, @@ -210,14 +214,16 @@ contract("SellTokenWithRoute()", function () { token_b_address: tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { sell: null }, }, @@ -227,14 +233,16 @@ contract("SellTokenWithRoute()", function () { token_b_address: reverseOrder ? tokenCAddress : tokenBAddress, token_a_id: new BigNumber(0), token_b_id: new BigNumber(0), - token_a_type: - standard.toLowerCase() == "mixed" + token_a_type: { + [standard.toLowerCase() == "mixed" ? "fa2" - : standard.toLowerCase(), - token_b_type: - standard.toLowerCase() == "mixed" + : standard.toLowerCase()]: null, + }, + token_b_type: { + [standard.toLowerCase() == "mixed" ? "fa12" - : standard.toLowerCase(), + : standard.toLowerCase()]: null, + }, }, operation: { buy: null }, }, diff --git a/test/helpers/types.ts b/test/helpers/types.ts index 4cfa72b1..c8ea857c 100644 --- a/test/helpers/types.ts +++ b/test/helpers/types.ts @@ -5,8 +5,8 @@ export declare type TokenInfo = { token_b_address: string; token_a_id?: BigNumber; token_b_id?: BigNumber; - token_a_type: string; - token_b_type: string; + token_a_type: { [key: string]: any }; + token_b_type: { [key: string]: any }; }; export declare type PairInfo = { From 9757aeb72153893b6ea83569e2d567f050a40ce1 Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 14:37:54 +0300 Subject: [PATCH 52/63] update test --- test/InitializeTTExchangeTest.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InitializeTTExchangeTest.spec.ts b/test/InitializeTTExchangeTest.spec.ts index e722e5af..d60d4a59 100644 --- a/test/InitializeTTExchangeTest.spec.ts +++ b/test/InitializeTTExchangeTest.spec.ts @@ -41,7 +41,7 @@ contract("InitializeTTExchange()", function () { tokenAAddress = tokenBAddress; tokenBAddress = tmp; } - } while (standard !== "MIXED" && tokenAAddress > tokenBAddress); + } while (tokenAAddress > tokenBAddress); switch (standard) { case "FA2": context.tokens.push(await TokenFA2.init(tokenAAddress)); From af3f42ebe56ec6564509ce2d46bb8012861978ec Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 16:17:04 +0300 Subject: [PATCH 53/63] reentracy protection draft --- contracts/main/TTDex.ligo | 1 + contracts/partials/ITTDex.ligo | 2 ++ contracts/partials/TTDex.ligo | 11 +++++++++++ contracts/partials/TTMethodDex.ligo | 24 ++++++++++++++++++++++-- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/contracts/main/TTDex.ligo b/contracts/main/TTDex.ligo index 90f2116f..691ff562 100644 --- a/contracts/main/TTDex.ligo +++ b/contracts/main/TTDex.ligo @@ -13,6 +13,7 @@ function main (const p : full_action; const s : full_dex_storage) : full_return | Balance_of(params) -> call_token(IBalance_of(params), this, 2n, s) | Update_operators(params) -> call_token(IUpdate_operators(params), this, 1n, s) | Get_reserves(params) -> get_reserves(params, s) + | Close -> ((nil:list(operation)), close(s)) | SetDexFunction(params) -> ((nil:list(operation)), if params.index > 4n then (failwith("Dex/wrong-index") : full_dex_storage) else set_dex_function(params.index, params.func, s)) | SetTokenFunction(params) -> ((nil:list(operation)), if params.index > 2n then (failwith("Dex/wrong-index") : full_dex_storage) else set_token_function(params.index, params.func, s)) end diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 3433abc9..0eefe89b 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -41,6 +41,7 @@ type token_pair is bytes (* record for the dex storage *) type dex_storage is record [ + entered : bool; pairs_count : nat; (* total shares count *) tokens : big_map(nat, tokens_info); (* all the tokens list *) token_to_id : big_map(token_pair, nat); (* all the tokens list *) @@ -144,6 +145,7 @@ type full_action is | Balance_of of balance_params | Update_operators of update_operator_params | Get_reserves of get_reserves_params +| Close of unit | SetDexFunction of set_dex_function_params (* sets the dex specific function. Is used before the whole system is launched *) | SetTokenFunction of set_token_function_params (* sets the FA function, is used before the whole system is launched *) diff --git a/contracts/partials/TTDex.ligo b/contracts/partials/TTDex.ligo index 03325058..1c74ef70 100644 --- a/contracts/partials/TTDex.ligo +++ b/contracts/partials/TTDex.ligo @@ -47,6 +47,17 @@ block { s.storage := res.1; } with (res.0, s) +[@inline] function close (const s : full_dex_storage) : full_dex_storage is +block { + if not s.storage.entered then + failwith("Dex/not-entered") + else skip; + if Tezos.sender =/= Tezos.self_address then + failwith("Dex/not-self") + else skip; + s.storage.entered := False; +} with s + (* Return the reserves to the contracts. *) [@inline] function get_reserves (const params : get_reserves_params; const s : full_dex_storage) : full_return is block { diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 335429f2..4cfe66a3 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -94,6 +94,10 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con var operations : list(operation) := list[]; case p of | InitializeExchange(params) -> { + if s.entered then + failwith("Dex/reentrancy") + else s.entered := True; + (* check preconditions *) if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") @@ -169,7 +173,7 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con (* prepare operations to get initial liquidity *) case params.pair.token_b_type of | Fa12 -> { - operations := + operations := transfer_fa12( Tezos.sender, this, @@ -177,7 +181,7 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con params.pair.token_b_address) # operations; } | Fa2 -> { - operations := + operations := transfer_fa2( Tezos.sender, this, @@ -203,6 +207,10 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this | InitializeExchange(n) -> skip | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(params) -> { + if s.entered then + failwith("Dex/reentrancy") + else s.entered := True; + (* check preconditions *) if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") @@ -492,6 +500,10 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons | InitializeExchange(n) -> skip | TokenToTokenPayment(n) -> skip | TokenToTokenRoutePayment(params) -> { + if s.entered then + failwith("Dex/reentrancy") + else s.entered := True; + if List.size(params.swaps) > 1n (* non-zero amount of tokens exchanged *) then skip else failwith ("Dex/too-few-swaps"); @@ -600,6 +612,10 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(n) -> skip | InvestLiquidity(params) -> { + if s.entered then + failwith("Dex/reentrancy") + else s.entered := True; + (* check preconditions *) if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") @@ -718,6 +734,10 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenRoutePayment(n) -> skip | InvestLiquidity(n) -> skip | DivestLiquidity(params) -> { + if s.entered then + failwith("Dex/reentrancy") + else s.entered := True; + (* check preconditions *) if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") From 83b7d91ebebd05d8a3f3105bbb6b7cec67e3b0fd Mon Sep 17 00:00:00 2001 From: kstasi Date: Wed, 16 Jun 2021 17:13:15 +0300 Subject: [PATCH 54/63] add reentrancy protection --- contracts/partials/TTMethodDex.ligo | 113 +++++++++++++++------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 4cfe66a3..1087b24f 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -55,6 +55,12 @@ function get_fa12_token_contract(const token_address : address) : contract(trans | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) end; +function get_close_entrypoint(const contract_address : address) : contract(unit) is + case (Tezos.get_entrypoint_opt("%close", contract_address) : option(contract(unit))) of + Some(contr) -> contr + | None -> (failwith("Dex/no-close-entrypoint") : contract(unit)) + end; + function transfer_fa2( const sender_ : address; const receiver : address; @@ -91,8 +97,12 @@ function transfer_fa12( (* Initialize exchange after the previous liquidity was drained *) function initialize_exchange (const p : dex_action ; const s : dex_storage ; const this: address) : return is block { - var operations : list(operation) := list[]; - case p of + var operations: list(operation) := list[Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) + )]; + case p of | InitializeExchange(params) -> { if s.entered then failwith("Dex/reentrancy") @@ -151,22 +161,21 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con (* prepare operations to get initial liquidity *) case params.pair.token_a_type of | Fa12 -> { - operations := list[ + operations := transfer_fa12( Tezos.sender, this, params.token_a_in, - params.pair.token_a_address)]; + params.pair.token_a_address) # operations; } | Fa2 -> { - operations := list[ + operations := transfer_fa2( Tezos.sender, this, params.token_a_in, params.pair.token_a_id, - params.pair.token_a_address) - ]; + params.pair.token_a_address) # operations; } end; @@ -202,7 +211,11 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con (* Exchange tokens to tez, note: tokens should be approved before the operation *) function token_to_token (const p : dex_action; const s : dex_storage; const this : address) : return is block { - var operations : list(operation) := list[]; + var operations: list(operation) := list[Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) + )]; case p of | InitializeExchange(n) -> skip | TokenToTokenRoutePayment(n) -> skip @@ -261,23 +274,19 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this (* prepare operations to withdraw user's tokens and transfer XTZ *) case params.pair.token_a_type of | Fa12 -> { - operations := list[ - transfer_fa12( + operations := transfer_fa12( Tezos.sender, this, params.amount_in, - params.pair.token_a_address); - ]; + params.pair.token_a_address) # operations; } | Fa2 -> { - operations := list[ - transfer_fa2( + operations := transfer_fa2( Tezos.sender, this, params.amount_in, params.pair.token_a_id, - params.pair.token_a_address) - ]; + params.pair.token_a_address) # operations; } end; case params.pair.token_b_type of @@ -324,23 +333,19 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this (* prepare operations to withdraw user's tokens and transfer XTZ *) case params.pair.token_a_type of | Fa12 -> { - operations := list[ - transfer_fa12( + operations := transfer_fa12( this, params.receiver, token_a_out, - params.pair.token_a_address) - ]; + params.pair.token_a_address) # operations; } | Fa2 -> { - operations := list[ - transfer_fa2( + operations := transfer_fa2( this, params.receiver, token_a_out, params.pair.token_a_id, - params.pair.token_a_address) - ]; + params.pair.token_a_address) # operations; } end; case params.pair.token_b_type of @@ -495,7 +500,11 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par (* Exchange tokens to tez, note: tokens should be approved before the operation *) function token_to_token_route (const p : dex_action; const s : dex_storage; const this : address) : return is block { - var operations : list(operation) := list[]; + var operations: list(operation) := list[Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) + )]; case p of | InitializeExchange(n) -> skip | TokenToTokenPayment(n) -> skip @@ -530,21 +539,19 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons | Sell -> { case first_swap.pair.token_a_type of | Fa12 -> { - operations := list[ - transfer_fa12( + operations := transfer_fa12( Tezos.sender, this, params.amount_in, - first_swap.pair.token_a_address)]; + first_swap.pair.token_a_address) # operations; } | Fa2 -> { - operations := list[ - transfer_fa2( + operations := transfer_fa2( Tezos.sender, this, params.amount_in, first_swap.pair.token_a_id, - first_swap.pair.token_a_address)] + first_swap.pair.token_a_address) # operations; } end; } @@ -553,21 +560,19 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons token_address_in := first_swap.pair.token_b_address; case first_swap.pair.token_b_type of | Fa12 -> { - operations := list[ - transfer_fa12( + operations := transfer_fa12( Tezos.sender, this, params.amount_in, - first_swap.pair.token_b_address)]; + first_swap.pair.token_b_address) # operations; } | Fa2 -> { - operations := list[ - transfer_fa2( + operations := transfer_fa2( Tezos.sender, this, params.amount_in, first_swap.pair.token_b_id, - first_swap.pair.token_b_address)] + first_swap.pair.token_b_address) # operations; } end; } @@ -606,7 +611,11 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons (* Provide liquidity (both tokens and tez) to the pool, note: tokens should be approved before the operation *) function invest_liquidity (const p : dex_action; const s : dex_storage; const this: address) : return is block { - var operations: list(operation) := list[]; + var operations: list(operation) := list[Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) + )]; case p of | InitializeExchange(n) -> skip | TokenToTokenRoutePayment(n) -> skip @@ -685,21 +694,19 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th (* prepare operations to get initial liquidity *) case params.pair.token_a_type of | Fa12 -> { - operations := list[ - transfer_fa12( + operations := transfer_fa12( Tezos.sender, this, tokens_a_required, - params.pair.token_a_address)]; + params.pair.token_a_address) # operations; } | Fa2 -> { - operations := list[ - transfer_fa2( + operations := transfer_fa2( Tezos.sender, this, tokens_a_required, params.pair.token_a_id, - params.pair.token_a_address);]; + params.pair.token_a_address) # operations; } end; case params.pair.token_b_type of @@ -727,8 +734,12 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th (* Remove liquidity (both tokens and tez) from the pool by burning shares *) function divest_liquidity (const p : dex_action; const s : dex_storage; const this: address) : return is block { - var operations: list(operation) := list[]; - case p of + var operations: list(operation) := list[Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) + )]; + case p of | InitializeExchange(token_amount) -> skip | TokenToTokenPayment(n) -> skip | TokenToTokenRoutePayment(n) -> skip @@ -803,21 +814,19 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th (* prepare operations with XTZ and tokens to user *) case params.pair.token_a_type of | Fa12 -> { - operations := list[ - transfer_fa12( + operations := transfer_fa12( this, Tezos.sender, token_a_divested, - params.pair.token_a_address)]; + params.pair.token_a_address) # operations; } | Fa2 -> { - operations := list[ - transfer_fa2( + operations := transfer_fa2( this, Tezos.sender, token_a_divested, params.pair.token_a_id, - params.pair.token_a_address)]; + params.pair.token_a_address) # operations; } end; case params.pair.token_b_type of From baf6630488ccee6b99b887cb1553e5c53572865d Mon Sep 17 00:00:00 2001 From: Julian Konchunas Date: Wed, 16 Jun 2021 17:55:53 +0300 Subject: [PATCH 55/63] Terser token to token function --- contracts/partials/ITTDex.ligo | 12 ++ contracts/partials/TTMethodDex.ligo | 212 ++++++++++++---------------- 2 files changed, 105 insertions(+), 119 deletions(-) diff --git a/contracts/partials/ITTDex.ligo b/contracts/partials/ITTDex.ligo index 0eefe89b..96954cd8 100644 --- a/contracts/partials/ITTDex.ligo +++ b/contracts/partials/ITTDex.ligo @@ -55,6 +55,18 @@ type swap_slice_type is record [ operation : swap_type; ] +type swap_side is record [ + pool : nat; + token : address; + id: nat; + standard: token_type; +] + +type swap_data is record [ + to_: swap_side; + from_: swap_side; +] + type internal_swap_type is record [ s : dex_storage; amount_in : nat; diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 1087b24f..abcd03e2 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -26,6 +26,45 @@ function get_pair (const key : tokens_info; const s : dex_storage) : (pair_info end; } with (pair, token_id) +function form_pools(const from_pool: nat; const to_pool: nat; const supply: nat; const direction: swap_type) : pair_info is + case direction of + Buy -> record [ + token_a_pool = to_pool; + token_b_pool = from_pool; + total_supply = supply; + ] + | Sell -> record [ + token_a_pool = from_pool; + token_b_pool = to_pool; + total_supply = supply; + ] + end; + +function form_swap_data(const pair: pair_info; const swap: tokens_info; const direction: swap_type) : swap_data is + block { + const side_a : swap_side = record [ + pool = pair.token_a_pool; + token = swap.token_a_address; + id = swap.token_a_id; + standard = swap.token_a_type; + ]; + const side_b : swap_side = record [ + pool = pair.token_b_pool; + token = swap.token_b_address; + id = swap.token_b_id; + standard = swap.token_b_type; + ]; + } with case direction of + Sell -> record [ + from_ = side_a; + to_ = side_b; + ] + | Buy -> record [ + from_ = side_b; + to_ = side_a; + ] + end; + (* Helper function to prepare the token transfer *) function wrap_fa2_transfer_trx(const owner : address; const receiver : address; const value : nat; const token_id : nat) : transfer_type_fa2 is TransferTypeFA2(list[ @@ -91,6 +130,17 @@ function transfer_fa12( get_fa12_token_contract(contract_address) ); +function typed_transfer( + const sender_ : address; + const receiver : address; + const amount_ : nat; + const token_id : nat; + const contract_address : address; + const standard: token_type) : operation is + case standard of + Fa12 -> transfer_fa12(sender_, receiver, amount_, contract_address) + | Fa2 -> transfer_fa2(sender_, receiver, amount_, token_id, contract_address) + end; #include "../partials/TTMethodFA2.ligo" @@ -249,125 +299,49 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this then skip else failwith ("Dex/zero-min-amount-out"); - case params.operation of - | Sell -> { - (* calculate amount out *) - const token_a_in_with_fee : nat = params.amount_in * 997n; - const numerator : nat = token_a_in_with_fee * pair.token_b_pool; - const denominator : nat = pair.token_a_pool * 1000n + token_a_in_with_fee; - - (* calculate swapped token amount *) - const token_b_out : nat = numerator / denominator; - - (* ensure requirements *) - if token_b_out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) - then skip else failwith("Dex/wrong-min-out"); - - (* ensure requirements *) - if token_b_out <= pair.token_b_pool / 3n (* the price impact isn't to high *) - then { - (* update XTZ pool *) - pair.token_b_pool := abs(pair.token_b_pool - token_b_out); - pair.token_a_pool := pair.token_a_pool + params.amount_in; - } else failwith("Dex/high-out"); - - (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.token_a_type of - | Fa12 -> { - operations := transfer_fa12( - Tezos.sender, - this, - params.amount_in, - params.pair.token_a_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - Tezos.sender, - this, - params.amount_in, - params.pair.token_a_id, - params.pair.token_a_address) # operations; - } - end; - case params.pair.token_b_type of - | Fa12 -> { - operations := - transfer_fa12( - this, - params.receiver, - token_b_out, - params.pair.token_b_address) # operations; - } - | Fa2 -> { - operations := - transfer_fa2( - this, - params.receiver, - token_b_out, - params.pair.token_b_id, - params.pair.token_b_address) # operations; - } - end; - } - | Buy -> { - (* calculate amount out *) - const token_b_in_with_fee : nat = params.amount_in * 997n; - const numerator : nat = token_b_in_with_fee * pair.token_a_pool; - const denominator : nat = pair.token_b_pool * 1000n + token_b_in_with_fee; - - (* calculate swapped token amount *) - const token_a_out : nat = numerator / denominator; - - (* ensure requirements *) - if token_a_out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) - then skip else failwith("Dex/wrong-min-out"); - - (* ensure requirements *) - if token_a_out <= pair.token_a_pool / 3n (* the price impact isn't to high *) - then { - (* update XTZ pool *) - pair.token_a_pool := abs(pair.token_a_pool - token_a_out); - pair.token_b_pool := pair.token_b_pool + params.amount_in; - } else failwith("Dex/high-out"); - - (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.token_a_type of - | Fa12 -> { - operations := transfer_fa12( - this, - params.receiver, - token_a_out, - params.pair.token_a_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - this, - params.receiver, - token_a_out, - params.pair.token_a_id, - params.pair.token_a_address) # operations; - } - end; - case params.pair.token_b_type of - | Fa12 -> { - operations := transfer_fa12( - Tezos.sender, - this, - params.amount_in, - params.pair.token_b_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - Tezos.sender, - this, - params.amount_in, - params.pair.token_b_id, - params.pair.token_b_address) # operations; - } - end; - } - end; - s.pairs[token_id] := pair; + const swap: swap_data = form_swap_data(pair, params.pair, params.operation); + + (* calculate amount out *) + const from_in_with_fee : nat = params.amount_in * 997n; + const numerator : nat = from_in_with_fee * swap.to_.pool; + const denominator : nat = swap.from_.pool * 1000n + from_in_with_fee; + + (* calculate swapped token amount *) + const out : nat = numerator / denominator; + + (* ensure requirements *) + if out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) + then skip else failwith("Dex/wrong-min-out"); + + (* ensure requirements *) + if out <= swap.to_.pool / 3n (* the price impact isn't too high *) + then { + (* update XTZ pool *) + swap.to_.pool := abs(swap.to_.pool - out); + swap.from_.pool := swap.from_.pool + params.amount_in; + + const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); + s.pairs[token_id] := updated_pair; + + } else failwith("Dex/high-out"); + + (* prepare operations to withdraw user's tokens and transfer XTZ *) + operations := list [ + (* from *) + typed_transfer(Tezos.sender, this, params.amount_in, + swap.from_.id, + swap.from_.token, + swap.from_.standard + ); + (* to *) + typed_transfer(this, params.receiver, out, + swap.to_.id, + swap.to_.token, + swap.to_.standard + ) + ]; + + (* TODO saving pool to storage *) } | InvestLiquidity(n) -> skip | DivestLiquidity(n) -> skip From 85e1260ff893081ae5ffa6159b95177547f40887 Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 11:18:29 +0300 Subject: [PATCH 56/63] fix reentrancy protection --- contracts/partials/TTMethodDex.ligo | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index abcd03e2..63c29eb8 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -322,26 +322,24 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); s.pairs[token_id] := updated_pair; - + } else failwith("Dex/high-out"); (* prepare operations to withdraw user's tokens and transfer XTZ *) - operations := list [ + operations := (* from *) typed_transfer(Tezos.sender, this, params.amount_in, swap.from_.id, swap.from_.token, swap.from_.standard - ); + ) # operations; + operations := (* to *) typed_transfer(this, params.receiver, out, swap.to_.id, swap.to_.token, swap.to_.standard - ) - ]; - - (* TODO saving pool to storage *) + ) # operations; } | InvestLiquidity(n) -> skip | DivestLiquidity(n) -> skip From f1aa72c8954fa5d1c8f0b197641e2061c1fdbd32 Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 11:47:17 +0300 Subject: [PATCH 57/63] token to token refactoring --- contracts/partials/TTMethodDex.ligo | 134 ++++++++-------------------- 1 file changed, 37 insertions(+), 97 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 63c29eb8..a66f06d2 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -219,7 +219,7 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con params.pair.token_a_address) # operations; } | Fa2 -> { - operations := + operations := transfer_fa2( Tezos.sender, this, @@ -371,102 +371,42 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par then skip else failwith ("Dex/zero-amount-in"); - case params.operation of - | Sell -> { - if params.pair.token_a_address = tmp.token_address_in and params.pair.token_a_id = tmp.token_id_in then - skip - else failwith("Dex/wrong-route"); - - (* calculate amount out *) - const token_a_in_with_fee : nat = tmp.amount_in * 997n; - const numerator : nat = token_a_in_with_fee * pair.token_b_pool; - const denominator : nat = pair.token_a_pool * 1000n + token_a_in_with_fee; - - (* calculate swapped token amount *) - const token_b_out : nat = numerator / denominator; - - (* ensure requirements *) - if token_b_out <= pair.token_b_pool / 3n (* the price impact isn't to high *) - then { - (* update XTZ pool *) - pair.token_b_pool := abs(pair.token_b_pool - token_b_out); - pair.token_a_pool := pair.token_a_pool + tmp.amount_in; - } else failwith("Dex/high-out"); - tmp.amount_in := token_b_out; - tmp.token_address_in := params.pair.token_b_address; - tmp.token_id_in := params.pair.token_b_id; - - (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.token_b_type of - | Fa12 -> { - tmp.operation := Some( - transfer_fa12( - tmp.sender, - tmp.receiver, - token_b_out, - params.pair.token_b_address)); - } - | Fa2 -> { - tmp.operation := Some( - transfer_fa2( - tmp.sender, - tmp.receiver, - token_b_out, - params.pair.token_b_id, - params.pair.token_b_address) - ); - } - end; - } - | Buy -> { - if params.pair.token_b_address = tmp.token_address_in and params.pair.token_b_id = tmp.token_id_in then - skip - else failwith("Dex/wrong-route"); - - (* calculate amount out *) - const token_b_in_with_fee : nat = tmp.amount_in * 997n; - const numerator : nat = token_b_in_with_fee * pair.token_a_pool; - const denominator : nat = pair.token_b_pool * 1000n + token_b_in_with_fee; - - (* calculate swapped token amount *) - const token_a_out : nat = numerator / denominator; - - (* ensure requirements *) - if token_a_out <= pair.token_a_pool / 3n (* the price impact isn't to high *) - then { - (* update XTZ pool *) - pair.token_a_pool := abs(pair.token_a_pool - token_a_out); - pair.token_b_pool := pair.token_b_pool + tmp.amount_in; - } else failwith("Dex/high-out"); - - tmp.amount_in := token_a_out; - tmp.token_address_in := params. - pair.token_a_address; - tmp.token_id_in := params.pair.token_a_id; - (* prepare operations to withdraw user's tokens and transfer XTZ *) - case params.pair.token_a_type of - | Fa12 -> { - tmp.operation := Some( - transfer_fa12( - tmp.sender, - tmp.receiver, - token_a_out, - params.pair.token_a_address)); - } - | Fa2 -> { - tmp.operation := Some( - transfer_fa2( - tmp.sender, - tmp.receiver, - token_a_out, - params.pair.token_a_id, - params.pair.token_a_address) - ); - } - end; - } - end; - tmp.s.pairs[token_id] := pair; + const swap: swap_data = form_swap_data(pair, params.pair, params.operation); + + if swap.from_.token = tmp.token_address_in and swap.from_.id = tmp.token_id_in then + skip + else failwith("Dex/wrong-route"); + + (* calculate amount out *) + const from_in_with_fee : nat = tmp.amount_in * 997n; + const numerator : nat = from_in_with_fee * swap.to_.pool; + const denominator : nat = swap.from_.pool * 1000n + from_in_with_fee; + + (* calculate swapped token amount *) + const out : nat = numerator / denominator; + + (* ensure requirements *) + if out <= swap.to_.pool / 3n (* the price impact isn't too high *) + then { + (* update XTZ pool *) + swap.to_.pool := abs(swap.to_.pool - out); + swap.from_.pool := swap.from_.pool + tmp.amount_in; + + tmp.amount_in := out; + tmp.token_address_in := swap.to_.token; + tmp.token_id_in := swap.to_.id; + + const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); + tmp.s.pairs[token_id] := updated_pair; + } else failwith("Dex/high-out"); + + (* prepare operations to withdraw user's tokens and transfer XTZ *) + tmp.operation := Some( + typed_transfer(tmp.sender, tmp.receiver, out, + swap.to_.id, + swap.to_.token, + swap.to_.standard + )); } with tmp (* Exchange tokens to tez, note: tokens should be approved before the operation *) From 858a8893b4e5527b73f1ab7193c36f3b970b86f6 Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 12:06:45 +0300 Subject: [PATCH 58/63] refactoring --- contracts/partials/TTMethodDex.ligo | 214 ++++++++++------------------ 1 file changed, 72 insertions(+), 142 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index a66f06d2..1d8da11f 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -209,46 +209,24 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con s.tokens[token_id] := params.pair; (* prepare operations to get initial liquidity *) - case params.pair.token_a_type of - | Fa12 -> { - operations := - transfer_fa12( - Tezos.sender, - this, - params.token_a_in, - params.pair.token_a_address) # operations; - } - | Fa2 -> { - operations := - transfer_fa2( - Tezos.sender, - this, - params.token_a_in, - params.pair.token_a_id, - params.pair.token_a_address) # operations; - } - end; - - (* prepare operations to get initial liquidity *) - case params.pair.token_b_type of - | Fa12 -> { - operations := - transfer_fa12( - Tezos.sender, - this, - params.token_b_in, - params.pair.token_b_address) # operations; - } - | Fa2 -> { - operations := - transfer_fa2( - Tezos.sender, - this, - params.token_b_in, - params.pair.token_b_id, - params.pair.token_b_address) # operations; - } - end; + operations := + typed_transfer( + Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id, + params.pair.token_a_address, + params.pair.token_a_type + ) # operations; + operations := + typed_transfer( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_id, + params.pair.token_b_address, + params.pair.token_b_type + ) # operations; } | TokenToTokenPayment(n) -> skip | TokenToTokenRoutePayment(n) -> skip @@ -449,44 +427,28 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons case first_swap.operation of | Sell -> { - case first_swap.pair.token_a_type of - | Fa12 -> { - operations := transfer_fa12( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_a_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_a_id, - first_swap.pair.token_a_address) # operations; - } - end; + operations := + (* from *) + typed_transfer(Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_a_id, + first_swap.pair.token_a_address, + first_swap.pair.token_a_type + ) # operations; } | Buy -> { token_id_in := first_swap.pair.token_b_id; token_address_in := first_swap.pair.token_b_address; - case first_swap.pair.token_b_type of - | Fa12 -> { - operations := transfer_fa12( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_b_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - Tezos.sender, - this, - params.amount_in, - first_swap.pair.token_b_id, - first_swap.pair.token_b_address) # operations; - } - end; + operations := + (* from *) + typed_transfer(Tezos.sender, + this, + params.amount_in, + first_swap.pair.token_b_id, + first_swap.pair.token_b_address, + first_swap.pair.token_b_type + ) # operations; } end; @@ -604,40 +566,24 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th s.pairs[token_id] := pair; (* prepare operations to get initial liquidity *) - case params.pair.token_a_type of - | Fa12 -> { - operations := transfer_fa12( - Tezos.sender, - this, - tokens_a_required, - params.pair.token_a_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - Tezos.sender, - this, - tokens_a_required, - params.pair.token_a_id, - params.pair.token_a_address) # operations; - } - end; - case params.pair.token_b_type of - | Fa12 -> { - operations := transfer_fa12( - Tezos.sender, - this, - tokens_b_required, - params.pair.token_b_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - Tezos.sender, - this, - tokens_b_required, - params.pair.token_b_id, - params.pair.token_b_address) # operations; - } - end; + operations := + typed_transfer( + Tezos.sender, + this, + tokens_a_required, + params.pair.token_a_id, + params.pair.token_a_address, + params.pair.token_a_type + ) # operations; + operations := + typed_transfer( + Tezos.sender, + this, + tokens_b_required, + params.pair.token_b_id, + params.pair.token_b_address, + params.pair.token_b_type + ) # operations; } | DivestLiquidity(n) -> skip end @@ -724,40 +670,24 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th s.pairs[token_id] := pair; (* prepare operations with XTZ and tokens to user *) - case params.pair.token_a_type of - | Fa12 -> { - operations := transfer_fa12( - this, - Tezos.sender, - token_a_divested, - params.pair.token_a_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - this, - Tezos.sender, - token_a_divested, - params.pair.token_a_id, - params.pair.token_a_address) # operations; - } - end; - case params.pair.token_b_type of - | Fa12 -> { - operations := transfer_fa12( - this, - Tezos.sender, - token_b_divested, - params.pair.token_b_address) # operations; - } - | Fa2 -> { - operations := transfer_fa2( - this, - Tezos.sender, - token_b_divested, - params.pair.token_b_id, - params.pair.token_b_address) # operations; - } - end; + operations := + typed_transfer( + this, + Tezos.sender, + token_a_divested, + params.pair.token_a_id, + params.pair.token_a_address, + params.pair.token_a_type + ) # operations; + operations := + typed_transfer( + this, + Tezos.sender, + token_b_divested, + params.pair.token_b_id, + params.pair.token_b_address, + params.pair.token_b_type + ) # operations; } end } with (operations, s) From 72b517b400307ae54ae6d1967fc8d95c57a373ae Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 13:45:20 +0300 Subject: [PATCH 59/63] refactoring --- contracts/partials/TTMethodDex.ligo | 247 ++++++++++++---------------- 1 file changed, 107 insertions(+), 140 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 1d8da11f..da672adc 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -154,17 +154,15 @@ function initialize_exchange (const p : dex_action ; const s : dex_storage ; con )]; case p of | InitializeExchange(params) -> { - if s.entered then - failwith("Dex/reentrancy") + if s.entered + then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then - failwith("Dex/wrong-token-id") - else skip; - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -248,38 +246,32 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this | InitializeExchange(n) -> skip | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(params) -> { - if s.entered then - failwith("Dex/reentrancy") + if s.entered + then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then - failwith("Dex/wrong-token-id") - else skip; - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; + (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); const pair : pair_info = res.0; const token_id : nat = res.1; (* ensure there is liquidity *) - if pair.token_a_pool * pair.token_b_pool > 0n then - skip - else failwith("Dex/not-launched"); - - if params.amount_in > 0n (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/zero-amount-in"); - + if pair.token_a_pool * pair.token_b_pool = 0n + then failwith("Dex/not-launched") else skip; + if params.amount_in = 0n (* non-zero amount of tokens exchanged *) + then failwith ("Dex/zero-amount-in") else skip; if params.min_amount_out > 0n (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/zero-min-amount-out"); + then failwith ("Dex/zero-min-amount-out") else skip; - const swap: swap_data = form_swap_data(pair, params.pair, params.operation); (* calculate amount out *) + const swap: swap_data = form_swap_data(pair, params.pair, params.operation); const from_in_with_fee : nat = params.amount_in * 997n; const numerator : nat = from_in_with_fee * swap.to_.pool; const denominator : nat = swap.from_.pool * 1000n + from_in_with_fee; @@ -288,20 +280,17 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this const out : nat = numerator / denominator; (* ensure requirements *) - if out >= params.min_amount_out (* minimal XTZ amount out is sutisfied *) - then skip else failwith("Dex/wrong-min-out"); - - (* ensure requirements *) - if out <= swap.to_.pool / 3n (* the price impact isn't too high *) - then { - (* update XTZ pool *) - swap.to_.pool := abs(swap.to_.pool - out); - swap.from_.pool := swap.from_.pool + params.amount_in; + if out < params.min_amount_out (* minimal XTZ amount out is sutisfied *) + then failwith("Dex/wrong-min-out") else skip; + if out > swap.to_.pool / 3n (* the price impact isn't too high *) + then failwith("Dex/high-out") else skip; - const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); - s.pairs[token_id] := updated_pair; + (* update XTZ pool *) + swap.to_.pool := abs(swap.to_.pool - out); + swap.from_.pool := swap.from_.pool + params.amount_in; - } else failwith("Dex/high-out"); + const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); + s.pairs[token_id] := updated_pair; (* prepare operations to withdraw user's tokens and transfer XTZ *) operations := @@ -328,32 +317,24 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this function internal_token_to_token_swap (const tmp : internal_swap_type; const params : swap_slice_type ) : internal_swap_type is block { (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then - failwith("Dex/wrong-token-id") - else skip; - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, tmp.s); const pair : pair_info = res.0; const token_id : nat = res.1; + const swap: swap_data = form_swap_data(pair, params.pair, params.operation); (* ensure there is liquidity *) - if pair.token_a_pool * pair.token_b_pool > 0n then - skip - else failwith("Dex/not-launched"); - + if pair.token_a_pool * pair.token_b_pool = 0n + then failwith("Dex/not-launched") else skip; if tmp.amount_in > 0n (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/zero-amount-in"); - - const swap: swap_data = form_swap_data(pair, params.pair, params.operation); - - if swap.from_.token = tmp.token_address_in and swap.from_.id = tmp.token_id_in then - skip - else failwith("Dex/wrong-route"); + then failwith ("Dex/zero-amount-in") else skip; + if swap.from_.token = tmp.token_address_in and swap.from_.id = tmp.token_id_in + then failwith("Dex/wrong-route") else skip; (* calculate amount out *) const from_in_with_fee : nat = tmp.amount_in * 997n; @@ -364,21 +345,23 @@ function internal_token_to_token_swap (const tmp : internal_swap_type; const par const out : nat = numerator / denominator; (* ensure requirements *) - if out <= swap.to_.pool / 3n (* the price impact isn't too high *) - then { - (* update XTZ pool *) - swap.to_.pool := abs(swap.to_.pool - out); - swap.from_.pool := swap.from_.pool + tmp.amount_in; + if out > swap.to_.pool / 3n (* the price impact isn't too high *) + then failwith("Dex/high-out") else skip; - tmp.amount_in := out; - tmp.token_address_in := swap.to_.token; - tmp.token_id_in := swap.to_.id; + (* update pools amounts *) + swap.to_.pool := abs(swap.to_.pool - out); + swap.from_.pool := swap.from_.pool + tmp.amount_in; - const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); - tmp.s.pairs[token_id] := updated_pair; - } else failwith("Dex/high-out"); + (* update info for the next hop *) + tmp.amount_in := out; + tmp.token_address_in := swap.to_.token; + tmp.token_id_in := swap.to_.id; - (* prepare operations to withdraw user's tokens and transfer XTZ *) + (* update storage *) + const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); + tmp.s.pairs[token_id] := updated_pair; + + (* prepare operations to withdraw user's tokens *) tmp.operation := Some( typed_transfer(tmp.sender, tmp.receiver, out, swap.to_.id, @@ -399,21 +382,17 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons | InitializeExchange(n) -> skip | TokenToTokenPayment(n) -> skip | TokenToTokenRoutePayment(params) -> { - if s.entered then - failwith("Dex/reentrancy") + if s.entered + then failwith("Dex/reentrancy") else s.entered := True; - if List.size(params.swaps) > 1n (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/too-few-swaps"); - - if params.amount_in > 0n (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/zero-amount-in"); - - if params.min_amount_out > 0n (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/zero-min-amount-out"); + (* validate input params *) + if List.size(params.swaps) < 2n (* non-zero amount of tokens exchanged *) + then failwith ("Dex/too-few-swaps") else skip; + if params.amount_in = 0n (* non-zero amount of tokens exchanged *) + then failwith ("Dex/zero-amount-in") else skip; + if params.min_amount_out = 0n (* non-zero amount of tokens exchanged *) + then failwith ("Dex/zero-min-amount-out") else skip; (* collect the operations to execute *) const first_swap : swap_slice_type = case List.head_opt(params.swaps) of @@ -425,6 +404,7 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons var token_id_in : nat := first_swap.pair.token_a_id; var token_address_in : address := first_swap.pair.token_a_address; + (* prepare operation to withdraw user's tokens *) case first_swap.operation of | Sell -> { operations := @@ -438,8 +418,8 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons ) # operations; } | Buy -> { - token_id_in := first_swap.pair.token_b_id; - token_address_in := first_swap.pair.token_b_address; + token_id_in := first_swap.pair.token_b_id; + token_address_in := first_swap.pair.token_b_address; operations := (* from *) typed_transfer(Tezos.sender, @@ -452,6 +432,7 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons } end; + (* perform internal swaps *) const tmp : internal_swap_type = List.fold( internal_token_to_token_swap, params.swaps, @@ -465,12 +446,15 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons token_address_in = token_address_in; ] ); - s := tmp.s; - if tmp.amount_in >= params.min_amount_out (* non-zero amount of tokens exchanged *) - then skip - else failwith ("Dex/wrong-min-out"); + (* check minimal received *) + if tmp.amount_in < params.min_amount_out (* non-zero amount of tokens exchanged *) + then failwith ("Dex/wrong-min-out") else skip; + + (* update storage*) + s := tmp.s; + (* add token transfer to user's account *) const last_operation : operation = case tmp.operation of | Some(o) -> o | None -> (failwith("Dex/too-few-swaps") : operation) @@ -495,17 +479,15 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenRoutePayment(n) -> skip | TokenToTokenPayment(n) -> skip | InvestLiquidity(params) -> { - if s.entered then - failwith("Dex/reentrancy") + if s.entered + then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then - failwith("Dex/wrong-token-id") - else skip; - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -513,32 +495,27 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th const token_id : nat = res.1; (* ensure there is liquidity *) - if pair.token_a_pool * pair.token_b_pool > 0n then - skip - else failwith("Dex/not-launched"); + if pair.token_a_pool * pair.token_b_pool = 0n + then failwith("Dex/not-launched") else skip; + (* calculate purchased tokens *) const shares_a_purchased : nat = params.token_a_in * pair.total_supply / pair.token_a_pool; const shares_b_purchased : nat = params.token_b_in * pair.total_supply / pair.token_b_pool; const shares_purchased : nat = if shares_a_purchased < shares_b_purchased - then - shares_a_purchased - else - shares_b_purchased; + then shares_a_purchased + else shares_b_purchased; (* ensure *) - if shares_purchased > 0n (* purchsed shares satisfy required minimum *) - then skip - else failwith("Dex/wrong-params"); + if shares_purchased = 0n (* purchsed shares satisfy required minimum *) + then failwith("Dex/wrong-params") else skip; (* calculate tokens to be withdrawn *) const tokens_a_required : nat = shares_purchased * pair.token_a_pool / pair.total_supply; - if shares_purchased * pair.token_a_pool > tokens_a_required * pair.total_supply then - tokens_a_required := tokens_a_required + 1n - else skip; + if shares_purchased * pair.token_a_pool > tokens_a_required * pair.total_supply + then tokens_a_required := tokens_a_required + 1n else skip; const tokens_b_required : nat = shares_purchased * pair.token_b_pool / pair.total_supply; - if shares_purchased * pair.token_b_pool > tokens_b_required * pair.total_supply then - tokens_b_required := tokens_b_required + 1n - else skip; + if shares_purchased * pair.token_b_pool > tokens_b_required * pair.total_supply + then tokens_b_required := tokens_b_required + 1n else skip; (* ensure *) if tokens_a_required = 0n (* providing liquidity won't impact on price *) @@ -603,17 +580,15 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th | TokenToTokenRoutePayment(n) -> skip | InvestLiquidity(n) -> skip | DivestLiquidity(params) -> { - if s.entered then - failwith("Dex/reentrancy") + if s.entered + then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id then - failwith("Dex/wrong-token-id") - else skip; - if params.pair.token_a_address > params.pair.token_b_address then - failwith("Dex/wrong-pair") - else skip; + if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; (* get par info*) const res : (pair_info * nat) = get_pair(params.pair, s); @@ -621,25 +596,19 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th const token_id : nat = res.1; (* ensure pair exist *) - if s.pairs_count = token_id then - failwith("Dex/pair-not-exist") - else skip; - - (* check preconditions *) - if pair.token_a_pool * pair.token_b_pool > 0n then - skip - else failwith("Dex/not-launched"); + if s.pairs_count = token_id + then failwith("Dex/pair-not-exist") else skip; + if pair.token_a_pool * pair.token_b_pool = 0n + then failwith("Dex/not-launched") else skip; var account : account_info := get_account((Tezos.sender, token_id), s); const share : nat = account.balance; (* ensure *) - if params.shares > 0n (* minimal burn's shares are non-zero *) - then skip - else failwith("Dex/zero-burn-shares"); - if params.shares <= share (* burnt shares are lower than liquid balance *) - then skip - else failwith("Dex/insufficient-shares"); + if params.shares = 0n (* minimal burn's shares are non-zero *) + then failwith("Dex/zero-burn-shares") else skip; + if params.shares > share (* burnt shares are lower than liquid balance *) + then failwith("Dex/insufficient-shares") else skip; (* update users shares *) account.balance := abs(share - params.shares); @@ -650,14 +619,12 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th const token_b_divested : nat = pair.token_b_pool * params.shares / pair.total_supply; (* ensure minimal amounts out are non-zero *) - if params.min_token_a_out > 0n and params.min_token_b_out > 0n then - skip - else failwith("Dex/dust-output"); + if params.min_token_a_out = 0n or params.min_token_b_out = 0n + then failwith("Dex/dust-output") else skip; (* ensure minimal amounts are satisfied *) - if token_a_divested >= params.min_token_a_out and token_b_divested >= params.min_token_b_out then - skip - else failwith("Dex/high-expectation"); + if token_a_divested < params.min_token_a_out or token_b_divested < params.min_token_b_out + then failwith("Dex/high-expectation") else skip; (* update total shares *) pair.total_supply := abs(pair.total_supply - params.shares); From a108c218dd051fb0cbcbdb00fc44757981c87509 Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 14:16:43 +0300 Subject: [PATCH 60/63] improve code style --- contracts/partials/TTMethodDex.ligo | 484 ++++++++++++++++------------ 1 file changed, 282 insertions(+), 202 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index da672adc..4f60eacd 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -1,5 +1,7 @@ (* Helper function to get account *) -function get_account (const key : (address * nat); const s : dex_storage) : account_info is +function get_account( + const key : (address * nat); + const s : dex_storage) : account_info is case s.ledger[key] of None -> record [ balance = 0n; @@ -9,7 +11,9 @@ function get_account (const key : (address * nat); const s : dex_storage) : acco end; (* Helper function to get account *) -function get_pair (const key : tokens_info; const s : dex_storage) : (pair_info * nat) is +function get_pair( + const key : tokens_info; + const s : dex_storage) : (pair_info * nat) is block { const token_bytes : token_pair = Bytes.pack(key); const token_id : nat = case s.token_to_id[token_bytes] of @@ -26,21 +30,28 @@ function get_pair (const key : tokens_info; const s : dex_storage) : (pair_info end; } with (pair, token_id) -function form_pools(const from_pool: nat; const to_pool: nat; const supply: nat; const direction: swap_type) : pair_info is +function form_pools( + const from_pool: nat; + const to_pool: nat; + const supply: nat; + const direction: swap_type) : pair_info is case direction of Buy -> record [ token_a_pool = to_pool; token_b_pool = from_pool; total_supply = supply; ] - | Sell -> record [ + | Sell -> record [ token_a_pool = from_pool; token_b_pool = to_pool; total_supply = supply; ] end; -function form_swap_data(const pair: pair_info; const swap: tokens_info; const direction: swap_type) : swap_data is +function form_swap_data( + const pair: pair_info; + const swap: tokens_info; + const direction: swap_type) : swap_data is block { const side_a : swap_side = record [ pool = pair.token_a_pool; @@ -59,14 +70,18 @@ function form_swap_data(const pair: pair_info; const swap: tokens_info; const di from_ = side_a; to_ = side_b; ] - | Buy -> record [ + | Buy -> record [ from_ = side_b; to_ = side_a; ] end; (* Helper function to prepare the token transfer *) -function wrap_fa2_transfer_trx(const owner : address; const receiver : address; const value : nat; const token_id : nat) : transfer_type_fa2 is +function wrap_fa2_transfer_trx( + const owner : address; + const receiver : address; + const value : nat; + const token_id : nat) : transfer_type_fa2 is TransferTypeFA2(list[ record[ from_ = owner; @@ -78,26 +93,37 @@ function wrap_fa2_transfer_trx(const owner : address; const receiver : address; ] ]) -function wrap_fa12_transfer_trx(const owner : address; const receiver : address; const value : nat) : transfer_type_fa12 is +function wrap_fa12_transfer_trx( + const owner : address; + const receiver : address; + const value : nat) : transfer_type_fa12 is TransferTypeFA12(owner, (receiver, value)) (* Helper function to get token contract *) -function get_fa2_token_contract(const token_address : address) : contract(transfer_type_fa2) is - case (Tezos.get_entrypoint_opt("%transfer", token_address) : option(contract(transfer_type_fa2))) of +function get_fa2_token_contract( + const token_address : address) : contract(transfer_type_fa2) is + case (Tezos.get_entrypoint_opt( + "%transfer", + token_address) : option(contract(transfer_type_fa2))) of Some(contr) -> contr - | None -> (failwith("Dex/not-token") : contract(transfer_type_fa2)) + | None -> (failwith("Dex/not-token") : contract(transfer_type_fa2)) end; -function get_fa12_token_contract(const token_address : address) : contract(transfer_type_fa12) is - case (Tezos.get_entrypoint_opt("%transfer", token_address) : option(contract(transfer_type_fa12))) of +function get_fa12_token_contract( + const token_address : address) : contract(transfer_type_fa12) is + case (Tezos.get_entrypoint_opt( + "%transfer", + token_address) : option(contract(transfer_type_fa12))) of Some(contr) -> contr - | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) + | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) end; -function get_close_entrypoint(const contract_address : address) : contract(unit) is - case (Tezos.get_entrypoint_opt("%close", contract_address) : option(contract(unit))) of +function get_close_entrypoint( + const contract_address : address) : contract(unit) is + case (Tezos.get_entrypoint_opt( + "%close", contract_address) : option(contract(unit))) of Some(contr) -> contr - | None -> (failwith("Dex/no-close-entrypoint") : contract(unit)) + | None -> (failwith("Dex/no-close-entrypoint") : contract(unit)) end; function transfer_fa2( @@ -138,120 +164,138 @@ function typed_transfer( const contract_address : address; const standard: token_type) : operation is case standard of - Fa12 -> transfer_fa12(sender_, receiver, amount_, contract_address) - | Fa2 -> transfer_fa2(sender_, receiver, amount_, token_id, contract_address) + Fa12 -> transfer_fa12( + sender_, + receiver, + amount_, + contract_address) + | Fa2 -> transfer_fa2( + sender_, + receiver, + amount_, + token_id, + contract_address) end; #include "../partials/TTMethodFA2.ligo" (* Initialize exchange after the previous liquidity was drained *) -function initialize_exchange (const p : dex_action ; const s : dex_storage ; const this: address) : return is +function initialize_exchange( + const p : dex_action; + const s : dex_storage; + const this: address) : return is block { - var operations: list(operation) := list[Tezos.transaction( - unit, - 0mutez, - get_close_entrypoint(this) + var operations: list(operation) := list[ + Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) )]; case p of - | InitializeExchange(params) -> { - if s.entered - then failwith("Dex/reentrancy") - else s.entered := True; - - (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id - then failwith("Dex/wrong-token-id") else skip; - if params.pair.token_a_address > params.pair.token_b_address - then failwith("Dex/wrong-pair") else skip; - - (* get par info*) - const res : (pair_info * nat) = get_pair(params.pair, s); - const pair : pair_info = res.0; - const token_id : nat = res.1; - - (* update counter if needed *) - if s.pairs_count = token_id then { - s.token_to_id[Bytes.pack(params.pair)] := token_id; - s.pairs_count := s.pairs_count + 1n; - } else skip; - - (* check preconditions *) - if pair.token_a_pool * pair.token_b_pool =/= 0n (* no reserves *) - then failwith("Dex/non-zero-reserves") else skip; - if pair.total_supply =/= 0n (* no shares owned *) - then failwith("Dex/non-zero-shares") else skip; - if params.token_a_in < 1n (* XTZ provided *) - then failwith("Dex/no-token-a") else skip; - if params.token_b_in < 1n (* XTZ provided *) - then failwith("Dex/no-token-b") else skip; - - (* update pool reserves *) - pair.token_a_pool := params.token_a_in; - pair.token_b_pool := params.token_b_in; - - (* calculate initial shares *) - const init_shares : nat = - if params.token_a_in < params.token_b_in then - params.token_a_in - else params.token_b_in; - - (* distribute initial shares *) - s.ledger[(Tezos.sender, token_id)] := record [ - balance = init_shares; - allowances = (set [] : set(address)); - ]; - pair.total_supply := init_shares; - - (* update storage *) - s.pairs[token_id] := pair; - s.tokens[token_id] := params.pair; - - (* prepare operations to get initial liquidity *) - operations := - typed_transfer( - Tezos.sender, - this, - params.token_a_in, - params.pair.token_a_id, - params.pair.token_a_address, - params.pair.token_a_type - ) # operations; - operations := - typed_transfer( - Tezos.sender, - this, - params.token_b_in, - params.pair.token_b_id, - params.pair.token_b_address, - params.pair.token_b_type - ) # operations; - } - | TokenToTokenPayment(n) -> skip - | TokenToTokenRoutePayment(n) -> skip - | InvestLiquidity(n) -> skip - | DivestLiquidity(n) -> skip + InitializeExchange(params) -> { + if s.entered + then failwith("Dex/reentrancy") + else s.entered := True; - end - } with (operations, s) + (* check preconditions *) + if params.pair.token_a_address = params.pair.token_b_address + and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; + + (* get par info*) + const res : (pair_info * nat) = get_pair(params.pair, s); + const pair : pair_info = res.0; + const token_id : nat = res.1; + + (* update counter if needed *) + if s.pairs_count = token_id then { + s.token_to_id[Bytes.pack(params.pair)] := token_id; + s.pairs_count := s.pairs_count + 1n; + } else skip; + + (* check preconditions *) + if pair.token_a_pool * pair.token_b_pool =/= 0n (* no reserves *) + then failwith("Dex/non-zero-reserves") else skip; + if pair.total_supply =/= 0n (* no shares owned *) + then failwith("Dex/non-zero-shares") else skip; + if params.token_a_in < 1n (* XTZ provided *) + then failwith("Dex/no-token-a") else skip; + if params.token_b_in < 1n (* XTZ provided *) + then failwith("Dex/no-token-b") else skip; + + (* update pool reserves *) + pair.token_a_pool := params.token_a_in; + pair.token_b_pool := params.token_b_in; + + (* calculate initial shares *) + const init_shares : nat = + if params.token_a_in < params.token_b_in then + params.token_a_in + else params.token_b_in; + + (* distribute initial shares *) + s.ledger[(Tezos.sender, token_id)] := record [ + balance = init_shares; + allowances = (set [] : set(address)); + ]; + pair.total_supply := init_shares; + + (* update storage *) + s.pairs[token_id] := pair; + s.tokens[token_id] := params.pair; + + (* prepare operations to get initial liquidity *) + operations := + typed_transfer( + Tezos.sender, + this, + params.token_a_in, + params.pair.token_a_id, + params.pair.token_a_address, + params.pair.token_a_type + ) # operations; + operations := + typed_transfer( + Tezos.sender, + this, + params.token_b_in, + params.pair.token_b_id, + params.pair.token_b_address, + params.pair.token_b_type + ) # operations; + } + | TokenToTokenPayment(n) -> skip + | TokenToTokenRoutePayment(n) -> skip + | InvestLiquidity(n) -> skip + | DivestLiquidity(n) -> skip + end +} with (operations, s) (* Exchange tokens to tez, note: tokens should be approved before the operation *) -function token_to_token (const p : dex_action; const s : dex_storage; const this : address) : return is +function token_to_token( + const p : dex_action; + const s : dex_storage; + const this : address) : return is block { - var operations: list(operation) := list[Tezos.transaction( - unit, - 0mutez, - get_close_entrypoint(this) + var operations: list(operation) := list[ + Tezos.transaction( + unit, + 0mutez, + get_close_entrypoint(this) )]; case p of - | InitializeExchange(n) -> skip - | TokenToTokenRoutePayment(n) -> skip - | TokenToTokenPayment(params) -> { + InitializeExchange(n) -> skip + | TokenToTokenRoutePayment(n) -> skip + | TokenToTokenPayment(params) -> { if s.entered then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + if params.pair.token_a_address = params.pair.token_b_address + and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; @@ -271,7 +315,10 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this (* calculate amount out *) - const swap: swap_data = form_swap_data(pair, params.pair, params.operation); + const swap: swap_data = form_swap_data( + pair, + params.pair, + params.operation); const from_in_with_fee : nat = params.amount_in * 997n; const numerator : nat = from_in_with_fee * swap.to_.pool; const denominator : nat = swap.from_.pool * 1000n + from_in_with_fee; @@ -289,7 +336,11 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this swap.to_.pool := abs(swap.to_.pool - out); swap.from_.pool := swap.from_.pool + params.amount_in; - const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); + const updated_pair : pair_info = form_pools( + swap.from_.pool, + swap.to_.pool, + pair.total_supply, + params.operation); s.pairs[token_id] := updated_pair; (* prepare operations to withdraw user's tokens and transfer XTZ *) @@ -308,70 +359,81 @@ function token_to_token (const p : dex_action; const s : dex_storage; const this swap.to_.standard ) # operations; } - | InvestLiquidity(n) -> skip - | DivestLiquidity(n) -> skip + | InvestLiquidity(n) -> skip + | DivestLiquidity(n) -> skip end } with (operations, s) (* Exchange tokens to tez, note: tokens should be approved before the operation *) -function internal_token_to_token_swap (const tmp : internal_swap_type; const params : swap_slice_type ) : internal_swap_type is +function internal_token_to_token_swap( + const tmp : internal_swap_type; + const params : swap_slice_type ) : internal_swap_type is block { - (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id - then failwith("Dex/wrong-token-id") else skip; - if params.pair.token_a_address > params.pair.token_b_address - then failwith("Dex/wrong-pair") else skip; - - (* get par info*) - const res : (pair_info * nat) = get_pair(params.pair, tmp.s); - const pair : pair_info = res.0; - const token_id : nat = res.1; - const swap: swap_data = form_swap_data(pair, params.pair, params.operation); - - (* ensure there is liquidity *) - if pair.token_a_pool * pair.token_b_pool = 0n - then failwith("Dex/not-launched") else skip; - if tmp.amount_in > 0n (* non-zero amount of tokens exchanged *) - then failwith ("Dex/zero-amount-in") else skip; - if swap.from_.token = tmp.token_address_in and swap.from_.id = tmp.token_id_in - then failwith("Dex/wrong-route") else skip; - - (* calculate amount out *) - const from_in_with_fee : nat = tmp.amount_in * 997n; - const numerator : nat = from_in_with_fee * swap.to_.pool; - const denominator : nat = swap.from_.pool * 1000n + from_in_with_fee; - - (* calculate swapped token amount *) - const out : nat = numerator / denominator; - - (* ensure requirements *) - if out > swap.to_.pool / 3n (* the price impact isn't too high *) - then failwith("Dex/high-out") else skip; - - (* update pools amounts *) - swap.to_.pool := abs(swap.to_.pool - out); - swap.from_.pool := swap.from_.pool + tmp.amount_in; - - (* update info for the next hop *) - tmp.amount_in := out; - tmp.token_address_in := swap.to_.token; - tmp.token_id_in := swap.to_.id; - - (* update storage *) - const updated_pair : pair_info = form_pools(swap.from_.pool, swap.to_.pool, pair.total_supply, params.operation); - tmp.s.pairs[token_id] := updated_pair; - - (* prepare operations to withdraw user's tokens *) - tmp.operation := Some( - typed_transfer(tmp.sender, tmp.receiver, out, - swap.to_.id, - swap.to_.token, - swap.to_.standard - )); + (* check preconditions *) + if params.pair.token_a_address = params.pair.token_b_address + and params.pair.token_a_id >= params.pair.token_b_id + then failwith("Dex/wrong-token-id") else skip; + if params.pair.token_a_address > params.pair.token_b_address + then failwith("Dex/wrong-pair") else skip; + + (* get par info*) + const res : (pair_info * nat) = get_pair(params.pair, tmp.s); + const pair : pair_info = res.0; + const token_id : nat = res.1; + const swap: swap_data = form_swap_data(pair, params.pair, params.operation); + + (* ensure there is liquidity *) + if pair.token_a_pool * pair.token_b_pool = 0n + then failwith("Dex/not-launched") else skip; + if tmp.amount_in > 0n (* non-zero amount of tokens exchanged *) + then failwith ("Dex/zero-amount-in") else skip; + if swap.from_.token = tmp.token_address_in + and swap.from_.id = tmp.token_id_in + then failwith("Dex/wrong-route") else skip; + + (* calculate amount out *) + const from_in_with_fee : nat = tmp.amount_in * 997n; + const numerator : nat = from_in_with_fee * swap.to_.pool; + const denominator : nat = swap.from_.pool * 1000n + from_in_with_fee; + + (* calculate swapped token amount *) + const out : nat = numerator / denominator; + + (* ensure requirements *) + if out > swap.to_.pool / 3n (* the price impact isn't too high *) + then failwith("Dex/high-out") else skip; + + (* update pools amounts *) + swap.to_.pool := abs(swap.to_.pool - out); + swap.from_.pool := swap.from_.pool + tmp.amount_in; + + (* update info for the next hop *) + tmp.amount_in := out; + tmp.token_address_in := swap.to_.token; + tmp.token_id_in := swap.to_.id; + + (* update storage *) + const updated_pair : pair_info = form_pools( + swap.from_.pool, + swap.to_.pool, + pair.total_supply, + params.operation); + tmp.s.pairs[token_id] := updated_pair; + + (* prepare operations to withdraw user's tokens *) + tmp.operation := Some( + typed_transfer(tmp.sender, tmp.receiver, out, + swap.to_.id, + swap.to_.token, + swap.to_.standard + )); } with tmp (* Exchange tokens to tez, note: tokens should be approved before the operation *) -function token_to_token_route (const p : dex_action; const s : dex_storage; const this : address) : return is +function token_to_token_route( + const p : dex_action; + const s : dex_storage; + const this : address) : return is block { var operations: list(operation) := list[Tezos.transaction( unit, @@ -379,9 +441,9 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons get_close_entrypoint(this) )]; case p of - | InitializeExchange(n) -> skip - | TokenToTokenPayment(n) -> skip - | TokenToTokenRoutePayment(params) -> { + | InitializeExchange(n) -> skip + | TokenToTokenPayment(n) -> skip + | TokenToTokenRoutePayment(params) -> { if s.entered then failwith("Dex/reentrancy") else s.entered := True; @@ -396,7 +458,7 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons (* collect the operations to execute *) const first_swap : swap_slice_type = case List.head_opt(params.swaps) of - | Some(swap) -> swap + Some(swap) -> swap | None -> (failwith("Dex/zero-swaps") : swap_slice_type) end; @@ -456,18 +518,21 @@ function token_to_token_route (const p : dex_action; const s : dex_storage; cons (* add token transfer to user's account *) const last_operation : operation = case tmp.operation of - | Some(o) -> o + Some(o) -> o | None -> (failwith("Dex/too-few-swaps") : operation) end; operations := last_operation # operations; } - | InvestLiquidity(n) -> skip - | DivestLiquidity(n) -> skip + | InvestLiquidity(n) -> skip + | DivestLiquidity(n) -> skip end } with (operations, s) (* Provide liquidity (both tokens and tez) to the pool, note: tokens should be approved before the operation *) -function invest_liquidity (const p : dex_action; const s : dex_storage; const this: address) : return is +function invest_liquidity( + const p : dex_action; + const s : dex_storage; + const this: address) : return is block { var operations: list(operation) := list[Tezos.transaction( unit, @@ -475,16 +540,17 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th get_close_entrypoint(this) )]; case p of - | InitializeExchange(n) -> skip - | TokenToTokenRoutePayment(n) -> skip - | TokenToTokenPayment(n) -> skip - | InvestLiquidity(params) -> { + | InitializeExchange(n) -> skip + | TokenToTokenRoutePayment(n) -> skip + | TokenToTokenPayment(n) -> skip + | InvestLiquidity(params) -> { if s.entered then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + if params.pair.token_a_address = params.pair.token_b_address + and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; @@ -499,9 +565,12 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th then failwith("Dex/not-launched") else skip; (* calculate purchased tokens *) - const shares_a_purchased : nat = params.token_a_in * pair.total_supply / pair.token_a_pool; - const shares_b_purchased : nat = params.token_b_in * pair.total_supply / pair.token_b_pool; - const shares_purchased : nat = if shares_a_purchased < shares_b_purchased + const shares_a_purchased : nat = + params.token_a_in * pair.total_supply / pair.token_a_pool; + const shares_b_purchased : nat = + params.token_b_in * pair.total_supply / pair.token_b_pool; + const shares_purchased : nat = + if shares_a_purchased < shares_b_purchased then shares_a_purchased else shares_b_purchased; @@ -510,11 +579,15 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th then failwith("Dex/wrong-params") else skip; (* calculate tokens to be withdrawn *) - const tokens_a_required : nat = shares_purchased * pair.token_a_pool / pair.total_supply; - if shares_purchased * pair.token_a_pool > tokens_a_required * pair.total_supply + const tokens_a_required : nat = + shares_purchased * pair.token_a_pool / pair.total_supply; + if shares_purchased * pair.token_a_pool > + tokens_a_required * pair.total_supply then tokens_a_required := tokens_a_required + 1n else skip; - const tokens_b_required : nat = shares_purchased * pair.token_b_pool / pair.total_supply; - if shares_purchased * pair.token_b_pool > tokens_b_required * pair.total_supply + const tokens_b_required : nat = + shares_purchased * pair.token_b_pool / pair.total_supply; + if shares_purchased * pair.token_b_pool > + tokens_b_required * pair.total_supply then tokens_b_required := tokens_b_required + 1n else skip; (* ensure *) @@ -562,12 +635,15 @@ function invest_liquidity (const p : dex_action; const s : dex_storage; const th params.pair.token_b_type ) # operations; } - | DivestLiquidity(n) -> skip + | DivestLiquidity(n) -> skip end } with (operations, s) (* Remove liquidity (both tokens and tez) from the pool by burning shares *) -function divest_liquidity (const p : dex_action; const s : dex_storage; const this: address) : return is +function divest_liquidity( + const p : dex_action; + const s : dex_storage; + const this: address) : return is block { var operations: list(operation) := list[Tezos.transaction( unit, @@ -575,17 +651,18 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th get_close_entrypoint(this) )]; case p of - | InitializeExchange(token_amount) -> skip - | TokenToTokenPayment(n) -> skip - | TokenToTokenRoutePayment(n) -> skip - | InvestLiquidity(n) -> skip - | DivestLiquidity(params) -> { + | InitializeExchange(token_amount) -> skip + | TokenToTokenPayment(n) -> skip + | TokenToTokenRoutePayment(n) -> skip + | InvestLiquidity(n) -> skip + | DivestLiquidity(params) -> { if s.entered then failwith("Dex/reentrancy") else s.entered := True; (* check preconditions *) - if params.pair.token_a_address = params.pair.token_b_address and params.pair.token_a_id >= params.pair.token_b_id + if params.pair.token_a_address = params.pair.token_b_address + and params.pair.token_a_id >= params.pair.token_b_id then failwith("Dex/wrong-token-id") else skip; if params.pair.token_a_address > params.pair.token_b_address then failwith("Dex/wrong-pair") else skip; @@ -607,7 +684,7 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th (* ensure *) if params.shares = 0n (* minimal burn's shares are non-zero *) then failwith("Dex/zero-burn-shares") else skip; - if params.shares > share (* burnt shares are lower than liquid balance *) + if params.shares > share (* burnt shares are lower than balance *) then failwith("Dex/insufficient-shares") else skip; (* update users shares *) @@ -615,15 +692,18 @@ function divest_liquidity (const p : dex_action; const s : dex_storage; const th s.ledger[(Tezos.sender, token_id)] := account; (* calculate amount of token's sent to user *) - const token_a_divested : nat = pair.token_a_pool * params.shares / pair.total_supply; - const token_b_divested : nat = pair.token_b_pool * params.shares / pair.total_supply; + const token_a_divested : nat = + pair.token_a_pool * params.shares / pair.total_supply; + const token_b_divested : nat = + pair.token_b_pool * params.shares / pair.total_supply; (* ensure minimal amounts out are non-zero *) if params.min_token_a_out = 0n or params.min_token_b_out = 0n then failwith("Dex/dust-output") else skip; (* ensure minimal amounts are satisfied *) - if token_a_divested < params.min_token_a_out or token_b_divested < params.min_token_b_out + if token_a_divested < params.min_token_a_out + or token_b_divested < params.min_token_b_out then failwith("Dex/high-expectation") else skip; (* update total shares *) From 1027679ee8b47c283c50f24902f55511f261b674 Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 14:21:37 +0300 Subject: [PATCH 61/63] improve comments --- contracts/partials/TTMethodDex.ligo | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index 4f60eacd..f24b585c 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -10,7 +10,7 @@ function get_account( | Some(instance) -> instance end; -(* Helper function to get account *) +(* Helper function to get token pair *) function get_pair( const key : tokens_info; const s : dex_storage) : (pair_info * nat) is @@ -30,6 +30,7 @@ function get_pair( end; } with (pair, token_id) +(* Helper function to wrap the pair for swap *) function form_pools( const from_pool: nat; const to_pool: nat; @@ -48,6 +49,7 @@ function form_pools( ] end; +(* Helper function to unwrap the pair for swap *) function form_swap_data( const pair: pair_info; const swap: tokens_info; @@ -93,6 +95,7 @@ function wrap_fa2_transfer_trx( ] ]) +(* Helper function to prepare the token transfer *) function wrap_fa12_transfer_trx( const owner : address; const receiver : address; @@ -109,6 +112,7 @@ function get_fa2_token_contract( | None -> (failwith("Dex/not-token") : contract(transfer_type_fa2)) end; +(* Helper function to get token contract *) function get_fa12_token_contract( const token_address : address) : contract(transfer_type_fa12) is case (Tezos.get_entrypoint_opt( @@ -118,6 +122,7 @@ function get_fa12_token_contract( | None -> (failwith("Dex/not-token") : contract(transfer_type_fa12)) end; +(* Helper function to get self reentrancy entrypoint *) function get_close_entrypoint( const contract_address : address) : contract(unit) is case (Tezos.get_entrypoint_opt( @@ -273,7 +278,8 @@ function initialize_exchange( end } with (operations, s) -(* Exchange tokens to tez, note: tokens should be approved before the operation *) +(* Exchange tokens to tokens, +note: tokens should be approved before the operation *) function token_to_token( const p : dex_action; const s : dex_storage; @@ -364,7 +370,7 @@ function token_to_token( end } with (operations, s) -(* Exchange tokens to tez, note: tokens should be approved before the operation *) +(* Intrenal functions for swap hops *) function internal_token_to_token_swap( const tmp : internal_swap_type; const params : swap_slice_type ) : internal_swap_type is @@ -429,7 +435,8 @@ function internal_token_to_token_swap( )); } with tmp -(* Exchange tokens to tez, note: tokens should be approved before the operation *) +(* Exchange tokens to tokens with multiple hops, +note: tokens should be approved before the operation *) function token_to_token_route( const p : dex_action; const s : dex_storage; @@ -509,8 +516,8 @@ function token_to_token_route( ] ); - (* check minimal received *) - if tmp.amount_in < params.min_amount_out (* non-zero amount of tokens exchanged *) + (* check the amount of tokens is sufficient *) + if tmp.amount_in < params.min_amount_out then failwith ("Dex/wrong-min-out") else skip; (* update storage*) @@ -528,7 +535,8 @@ function token_to_token_route( end } with (operations, s) -(* Provide liquidity (both tokens and tez) to the pool, note: tokens should be approved before the operation *) +(* Provide liquidity (both tokens) to the pool, +note: tokens should be approved before the operation *) function invest_liquidity( const p : dex_action; const s : dex_storage; @@ -639,7 +647,7 @@ function invest_liquidity( end } with (operations, s) -(* Remove liquidity (both tokens and tez) from the pool by burning shares *) +(* Remove liquidity (both tokens) from the pool by burning shares *) function divest_liquidity( const p : dex_action; const s : dex_storage; From 340cd9aa4e85067889f5f92cdf4201a395203801 Mon Sep 17 00:00:00 2001 From: kstasi Date: Thu, 17 Jun 2021 14:23:22 +0300 Subject: [PATCH 62/63] fix bug --- contracts/partials/TTMethodDex.ligo | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/partials/TTMethodDex.ligo b/contracts/partials/TTMethodDex.ligo index f24b585c..a4dec575 100644 --- a/contracts/partials/TTMethodDex.ligo +++ b/contracts/partials/TTMethodDex.ligo @@ -316,7 +316,7 @@ function token_to_token( then failwith("Dex/not-launched") else skip; if params.amount_in = 0n (* non-zero amount of tokens exchanged *) then failwith ("Dex/zero-amount-in") else skip; - if params.min_amount_out > 0n (* non-zero amount of tokens exchanged *) + if params.min_amount_out = 0n (* non-zero amount of tokens exchanged *) then failwith ("Dex/zero-min-amount-out") else skip; @@ -391,10 +391,10 @@ function internal_token_to_token_swap( (* ensure there is liquidity *) if pair.token_a_pool * pair.token_b_pool = 0n then failwith("Dex/not-launched") else skip; - if tmp.amount_in > 0n (* non-zero amount of tokens exchanged *) + if tmp.amount_in = 0n (* non-zero amount of tokens exchanged *) then failwith ("Dex/zero-amount-in") else skip; - if swap.from_.token = tmp.token_address_in - and swap.from_.id = tmp.token_id_in + if swap.from_.token =/= tmp.token_address_in + or swap.from_.id =/= tmp.token_id_in then failwith("Dex/wrong-route") else skip; (* calculate amount out *) From a508e5ba773eff11b4e612e0e4fd0c86ab0e4b8c Mon Sep 17 00:00:00 2001 From: kstasi Date: Fri, 18 Jun 2021 12:43:47 +0300 Subject: [PATCH 63/63] update migrations --- Architecture.png | Bin 43097 -> 38073 bytes migrations/2_deploy_token_to_token_dex.js | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Architecture.png b/Architecture.png index 428708fd750bd8072b77dc449550bda38a7bd51b..4c14f8b1f630b9eb93f5ce02c0e52bef2920c1e9 100644 GIT binary patch literal 38073 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 I+U(~40Xm_u7XSbN literal 43097 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 O(|kwI9#n;$r+<#hVNByb}Eg-!8aY>)_5rns`&Q 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 { ? token0Instance.address.toString() : token1Instance.address.toString(), 0, + "fa12", ordered ? token1Instance.address.toString() : token0Instance.address.toString(), - initialTokenAmount, + 0, + "fa2".initialTokenAmount, initialTokenAmount ) .send(); @@ -174,10 +176,12 @@ module.exports = async (deployer, network, accounts) => { ? token0Instance.address.toString() : token1Instance.address.toString(), 0, + standard.toLocaleLowerCase(), ordered ? token1Instance.address.toString() : token0Instance.address.toString(), 0, + standard.toLocaleLowerCase(), initialTokenAmount, initialTokenAmount )