// SPDX-License-Identifier: Apache-2.0 import expect from 'expect'; import { weight, query, createConnection, deploy, transaction, aliceKeypair, daveKeypair } from './index'; import { ContractPromise } from '@polkadot/api-contract'; import { ApiPromise } from '@polkadot/api'; import { KeyringPair } from '@polkadot/keyring/types'; const MINIMUM_LIQUIDITY = BigInt(1000); const TOTAL_SUPPLY = BigInt(10000e18); describe('UniswapV2Pair', () => { let conn: ApiPromise; let factory: ContractPromise; let pair: ContractPromise; let token0: ContractPromise; let token1: ContractPromise; let alice: KeyringPair; let dave: KeyringPair; beforeEach(async function () { conn = await createConnection(); alice = aliceKeypair(); dave = daveKeypair(); // Upload UniswapV2Pair contract code so that it can instantiated from the factory // there probably is a better way of doing this than deploying a contract. Patches welcome. const pairTmp = await deploy(conn, alice, 'UniswapV2Pair.contract', 0n); const pairAbi = pairTmp.abi; let deploy_contract = await deploy(conn, alice, 'UniswapV2Factory.contract', 10000000000000000n, alice.address); factory = new ContractPromise(conn, deploy_contract.abi, deploy_contract.address); const tokenA_contract = await deploy(conn, alice, 'ERC20.contract', 0n, TOTAL_SUPPLY); const tokenA = new ContractPromise(conn, tokenA_contract.abi, tokenA_contract.address); const tokenB_contract = await deploy(conn, alice, 'ERC20.contract', 0n, TOTAL_SUPPLY); const tokenB = new ContractPromise(conn, tokenB_contract.abi, tokenB_contract.address); let gasLimit = await weight(conn, factory, "createPair", [tokenA.address, tokenB.address]); let tx = factory.tx.createPair({ gasLimit }, tokenA.address, tokenB.address); await transaction(tx, alice); const { output: get_pair } = await query(conn, alice, factory, "getPair", [tokenA.address, tokenB.address]); pair = new ContractPromise(conn, pairAbi, get_pair!.toString()); const { output: token0_address } = await query(conn, alice, pair, "token0"); if (tokenA.address.toString() == token0_address!.toString()) { token0 = tokenA; token1 = tokenB; } else { expect(tokenB.address.toString()).toEqual(token0_address!.toString()); token0 = tokenB; token1 = tokenA; } }); afterEach(async function () { await conn.disconnect(); }); it('mint', async () => { const token0Amount = BigInt(1e18) const token1Amount = BigInt(4e18) let gasLimit = await weight(conn, token0, "transfer", [pair.address, token0Amount]); let tx = token0.tx.transfer({ gasLimit }, pair.address, token0Amount); await transaction(tx, alice); gasLimit = await weight(conn, token1, "transfer", [pair.address, token1Amount]); tx = token1.tx.transfer({ gasLimit }, pair.address, token1Amount); await transaction(tx, alice); const expectedLiquidity = BigInt(2e18) gasLimit = await weight(conn, pair, "mint", [alice.address]); tx = pair.tx.mint({ gasLimit }, alice.address); await transaction(tx, alice); const { output: totalSupply } = await query(conn, alice, pair, "totalSupply"); expect(totalSupply?.eq(expectedLiquidity)).toBeTruthy(); const { output: bal } = await query(conn, alice, pair, "balanceOfAddress", [alice.address]); expect(bal?.eq(expectedLiquidity - MINIMUM_LIQUIDITY)).toBeTruthy(); const { output: bal0 } = await query(conn, alice, token0, "balanceOf", [pair.address]); expect(bal0?.eq(token0Amount)).toBeTruthy(); const { output: bal1 } = await query(conn, alice, token1, "balanceOf", [pair.address]); expect(bal1?.eq(token1Amount)).toBeTruthy(); const { output: reserves } = await query(conn, alice, pair, "getReserves"); // surely there must be a better way. let v: any = reserves; expect(v[0].eq(token0Amount)).toBeTruthy(); expect(v[1].eq(token1Amount)).toBeTruthy(); }) async function addLiquidity(token0Amount: BigInt, token1Amount: BigInt) { let gasLimit = await weight(conn, token0, "transfer", [pair.address, token0Amount]); let tx = token0.tx.transfer({ gasLimit }, pair.address, token0Amount); await transaction(tx, alice); gasLimit = await weight(conn, token1, "transfer", [pair.address, token1Amount]); tx = token1.tx.transfer({ gasLimit }, pair.address, token1Amount); await transaction(tx, alice); gasLimit = await weight(conn, pair, "mint", [alice.address]); tx = pair.tx.mint({ gasLimit }, alice.address); await transaction(tx, alice); } it('swap:token0', async () => { const token0Amount = BigInt(5e18) const token1Amount = BigInt(10e18) await addLiquidity(token0Amount, token1Amount) const swapAmount = BigInt(1e18) const expectedOutputAmount = BigInt(1662497915624478906) let gasLimit = await weight(conn, token0, "transfer", [pair.address, swapAmount]); let tx = token0.tx.transfer({ gasLimit }, pair.address, swapAmount); await transaction(tx, alice); gasLimit = await weight(conn, pair, "swap", [0, expectedOutputAmount, alice.address, '']); tx = pair.tx.swap({ gasLimit }, 0, expectedOutputAmount, alice.address, ''); await transaction(tx, alice); const { output: reserves } = await query(conn, alice, pair, "getReserves"); // surely there must be a better way. let v: any = reserves; expect(v[0].eq(token0Amount + swapAmount)).toBeTruthy(); expect(v[1].eq(token1Amount - expectedOutputAmount)).toBeTruthy(); const { output: bal0 } = await query(conn, alice, token0, "balanceOf", [pair.address]); expect(bal0?.eq(token0Amount + swapAmount)).toBeTruthy(); const { output: bal1 } = await query(conn, alice, token1, "balanceOf", [pair.address]); expect(bal1?.eq(token1Amount - expectedOutputAmount)).toBeTruthy(); const { output: returnTotalSupplyToken0 } = await query(conn, alice, token0, "totalSupply"); const { output: returnTotalSupplyToken1 } = await query(conn, alice, token1, "totalSupply"); const { output: walletBal0 } = await query(conn, alice, token0, "balanceOf", [alice.address]); const { output: walletBal1 } = await query(conn, alice, token1, "balanceOf", [alice.address]); const totalSupplyToken0 = BigInt(returnTotalSupplyToken0!.toString()); const totalSupplyToken1 = BigInt(returnTotalSupplyToken1!.toString()); expect(walletBal0?.eq(totalSupplyToken0 - token0Amount - swapAmount)).toBeTruthy(); expect(walletBal1?.eq(totalSupplyToken1 - token1Amount + expectedOutputAmount)).toBeTruthy(); }) it('swap:token1', async () => { const token0Amount = BigInt(5e18) const token1Amount = BigInt(10e18) await addLiquidity(token0Amount, token1Amount) const swapAmount = BigInt(1e18) const expectedOutputAmount = BigInt(453305446940074565) let gasLimit = await weight(conn, token1, "transfer", [pair.address, swapAmount]); let tx = token1.tx.transfer({ gasLimit }, pair.address, swapAmount); await transaction(tx, alice); gasLimit = await weight(conn, pair, "swap", [expectedOutputAmount, 0, alice.address, '']); tx = pair.tx.swap({ gasLimit }, expectedOutputAmount, 0, alice.address, ''); await transaction(tx, alice); const { output: reserves } = await query(conn, alice, pair, "getReserves"); // surely there must be a better way. let v: any = reserves; expect(v[0].eq(token0Amount - expectedOutputAmount)).toBeTruthy(); expect(v[1].eq(token1Amount + swapAmount)).toBeTruthy(); const { output: bal0 } = await query(conn, alice, token0, "balanceOf", [pair.address]); expect(bal0?.eq(token0Amount - expectedOutputAmount)).toBeTruthy(); const { output: bal1 } = await query(conn, alice, token1, "balanceOf", [pair.address]); expect(bal1?.eq(token1Amount + swapAmount)).toBeTruthy(); const { output: returnTotalSupplyToken0 } = await query(conn, alice, token0, "totalSupply"); const { output: returnTotalSupplyToken1 } = await query(conn, alice, token1, "totalSupply"); const { output: walletBal0 } = await query(conn, alice, token0, "balanceOf", [alice.address]); const { output: walletBal1 } = await query(conn, alice, token1, "balanceOf", [alice.address]); const totalSupplyToken0 = BigInt(returnTotalSupplyToken0!.toString()); const totalSupplyToken1 = BigInt(returnTotalSupplyToken1!.toString()); expect(walletBal0?.eq(totalSupplyToken0 - token0Amount + expectedOutputAmount)).toBeTruthy(); expect(walletBal1?.eq(totalSupplyToken1 - token1Amount - swapAmount)).toBeTruthy(); }) it('burn', async () => { const token0Amount = BigInt(3e18) const token1Amount = BigInt(3e18) await addLiquidity(token0Amount, token1Amount) const expectedLiquidity = BigInt(3e18) let gasLimit = await weight(conn, pair, "transferAddressUint256", [pair.address, expectedLiquidity - MINIMUM_LIQUIDITY]); let tx = pair.tx.transferAddressUint256({ gasLimit }, pair.address, expectedLiquidity - MINIMUM_LIQUIDITY); await transaction(tx, alice); gasLimit = await weight(conn, pair, "burn", [alice.address]); tx = pair.tx.burn({ gasLimit }, alice.address); await transaction(tx, alice); const { output: walletBal0 } = await query(conn, alice, pair, "balanceOfAddress", [alice.address]); expect(walletBal0?.eq(0)).toBeTruthy(); const { output: pairTotalSupply } = await query(conn, alice, pair, "totalSupply"); expect(pairTotalSupply?.eq(MINIMUM_LIQUIDITY)).toBeTruthy(); const { output: token0pairBal } = await query(conn, alice, token0, "balanceOf", [pair.address]); expect(token0pairBal?.eq(1000)).toBeTruthy(); const { output: token1pairBal } = await query(conn, alice, token1, "balanceOf", [pair.address]); expect(token1pairBal?.eq(1000)).toBeTruthy(); const { output: retToken0TotalSupply } = await query(conn, alice, token0, "totalSupply"); const { output: retToken1TotalSupply } = await query(conn, alice, token1, "totalSupply"); const totalSupplyToken0 = BigInt(retToken0TotalSupply!.toString()); const totalSupplyToken1 = BigInt(retToken1TotalSupply!.toString()); const { output: bal0 } = await query(conn, alice, token0, "balanceOf", [alice.address]); expect(bal0?.eq(totalSupplyToken0 - 1000n)).toBeTruthy(); const { output: bal1 } = await query(conn, alice, token1, "balanceOf", [alice.address]); expect(bal1?.eq(totalSupplyToken1 - 1000n)).toBeTruthy(); }) });