diff --git a/README.md b/README.md index 5ddd072..4363adc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Client facing frontends can communicate with Kalatori leveraging exposed API des --- ### Download -Download the latest Docker container or x86-64 release from the [GitHub releases page](https://github.com/Alzymologist/Kalatori-backend/releases/latest). +Download the latest Docker container or x86-64 release from the [GitHub releases page](https://github.com/Kalapaja/Kalatori/releases/latest). --- @@ -120,11 +120,11 @@ Kalatori is open-source software licensed under the GPLv3 License. See the [LICE Join the discussion and get support on: - [Kalatori Matrix](https://matrix.to/#/#Kalatori-support:matrix.zymologia.fi) -- [GitHub Discussions](https://github.com/Alzymologist/Kalatori-backend/discussions) +- [GitHub Discussions](https://github.com/Kalapaja/Kalatori/discussions) ### Roadmap -Refer to the Kalatori project [board](https://github.com/orgs/Alzymologist/projects/2) and [milestones](https://github.com/Alzymologist/Kalatori-backend/milestones) for the current roadmap and upcoming features. +Refer to the Kalatori project [board](https://github.com/orgs/Kalapaja/projects/2) and [milestones](https://github.com/Kalapaja/Kalatori/milestones) for the current roadmap and upcoming features. ### Acknowledgments diff --git a/chopsticks/docker-compose.yml b/chopsticks/docker-compose.yml index 4d6db64..101a4d2 100644 --- a/chopsticks/docker-compose.yml +++ b/chopsticks/docker-compose.yml @@ -11,6 +11,8 @@ services: volumes: - ./pd.yml:/app/config.yml command: ["chopsticks", "-c", "/app/config.yml", "-p", "8000", "--addr", "0.0.0.0"] + networks: + - kalatori-network chopsticks-polkadot-2: build: @@ -22,6 +24,8 @@ services: volumes: - ./pd-2.yml:/app/config.yml command: [ "chopsticks", "-c", "/app/config.yml", "-p", "8500", "--addr", "0.0.0.0" ] + networks: + - kalatori-network chopsticks-statemint: build: @@ -33,6 +37,8 @@ services: volumes: - ./pd-ah.yml:/app/config.yml command: ["chopsticks", "-c", "/app/config.yml", "-p", "9000", "--addr", "0.0.0.0"] + networks: + - kalatori-network chopsticks-statemint-2: build: @@ -44,3 +50,10 @@ services: volumes: - ./pd-ah-2.yml:/app/config.yml command: [ "chopsticks", "-c", "/app/config.yml", "-p", "9500", "--addr", "0.0.0.0" ] + networks: + - kalatori-network + + +networks: + kalatori-network: + external: true diff --git a/src/chain/payout.rs b/src/chain/payout.rs index 5c58ee5..cb96289 100644 --- a/src/chain/payout.rs +++ b/src/chain/payout.rs @@ -47,7 +47,7 @@ pub async fn payout( let block = block_hash(&client, None).await?; // TODO should retry instead let block_number = current_block_number(&client, &chain.metadata, &block).await?; let balance = order.balance(&client, &chain, &block).await?; // TODO same - let loss_tolerance = 10000; // TODO: replace with multiple of existential + let loss_tolerance = 20000; // TODO: replace with multiple of existential // TODO: add upper limit for transactions that would require manual intervention // just because it was found to be needed with non-crypto trade, who knows why? let currency = chain diff --git a/tests/kalatori-api-test-suite/src/polkadot.ts b/tests/kalatori-api-test-suite/src/polkadot.ts index 2c51fab..291f865 100644 --- a/tests/kalatori-api-test-suite/src/polkadot.ts +++ b/tests/kalatori-api-test-suite/src/polkadot.ts @@ -1,13 +1,9 @@ import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; import { cryptoWaitReady, decodeAddress, encodeAddress } from '@polkadot/util-crypto'; - -export async function updateAccountBalance(api: ApiPromise, accountAddress: string, amount: bigint): Promise { - const keyring = new Keyring({ type: 'sr25519' }); - const alice = keyring.addFromUri('//Alice'); - - const transfer = api.tx.balances.transfer(accountAddress, amount); - await transfer.signAndSend(alice); -} +import { u32 } from '@polkadot/types'; +import type { AccountInfo } from '@polkadot/types/interfaces/system'; +import type { AssetBalance } from '@polkadot/types/interfaces/assets'; +import { BN } from '@polkadot/util'; export async function connectPolkadot(rpcUrl: string): Promise { const provider = new WsProvider(rpcUrl); @@ -16,6 +12,38 @@ export async function connectPolkadot(rpcUrl: string): Promise { return api; } +export const reverseDecimals = (amount: number, decimals: number): number => { + return amount / Math.pow(10, decimals); +}; + +export async function getDotBalance(rpcUrl: string, paymentAccount: string): Promise { + const provider = new WsProvider(rpcUrl); + const api = await ApiPromise.create({ provider }); + + const accountInfo = await api.query.system.account(paymentAccount); + + // @ts-ignore + const freeBalance = accountInfo.data.free.toBigInt(); + + return Number(freeBalance); +} + +export async function getAssetBalance(rpcUrl: string, paymentAccount: string, assetId: number): Promise { + const provider = new WsProvider(rpcUrl); + const api = await ApiPromise.create({ provider }); + const decodedAccount = decodeAddress(paymentAccount); + + // Query the balance for the specified asset and account + const assetIdU32 = new u32(api.registry, assetId); + const accountInfo = (await api.query.assets.account(assetIdU32, decodedAccount)).toJSON() as { balance: number}; + + if (accountInfo) { + return accountInfo.balance; + } else { + return 0; + } +} + export async function transferFunds(rpcUrl: string, paymentAccount: string, amount: number, assetId?: number) { const provider = new WsProvider(rpcUrl); const api = await ApiPromise.create({ provider }); diff --git a/tests/kalatori-api-test-suite/tests/order.test.ts b/tests/kalatori-api-test-suite/tests/order.test.ts index e9a23ef..baeafbb 100644 --- a/tests/kalatori-api-test-suite/tests/order.test.ts +++ b/tests/kalatori-api-test-suite/tests/order.test.ts @@ -1,6 +1,5 @@ import request from 'supertest'; -import { connectPolkadot, transferFunds } from '../src/polkadot'; -import { ApiPromise } from '@polkadot/api'; +import {getAssetBalance, getDotBalance, reverseDecimals, transferFunds} from '../src/polkadot'; describe('Order Endpoint Blackbox Tests', () => { const baseUrl = process.env.DAEMON_HOST; @@ -14,7 +13,7 @@ describe('Order Endpoint Blackbox Tests', () => { }; const usdcOrderData = { - amount: 1, + amount: 10, currency: 'USDC', callback: 'https://example.com/callback' }; @@ -254,6 +253,9 @@ describe('Order Endpoint Blackbox Tests', () => { expect(repaidOrderDetails.payment_status).toBe('paid'); expect(repaidOrderDetails.withdrawal_status).toBe('completed'); + + const paymentAccountDotBalance = await getDotBalance(orderDetails.currency.rpc_url, paymentAccount); + expect(reverseDecimals(paymentAccountDotBalance,10)).toBe(0); }, 100000); it('should create, repay, and automatically withdraw an order in USDC', async () => { @@ -283,6 +285,9 @@ describe('Order Endpoint Blackbox Tests', () => { expect(repaidOrderDetails.payment_status).toBe('paid'); expect(repaidOrderDetails.withdrawal_status).toBe('completed'); + + const paymentAccountUsdcBalance = await getAssetBalance(orderDetails.currency.rpc_url, paymentAccount, orderDetails.currency.asset_id); + expect(reverseDecimals(paymentAccountUsdcBalance, 6)).toBe(0); }, 50000); it('should not automatically withdraw DOT order until fully repaid', async () => { @@ -305,6 +310,9 @@ describe('Order Endpoint Blackbox Tests', () => { // lets wait for the changes to get propagated on chain and app to catch them await new Promise(resolve => setTimeout(resolve, 15000)); + const halfAmountBalance = await getDotBalance(orderDetails.currency.rpc_url, paymentAccount); + expect(reverseDecimals(halfAmountBalance, 10)).toBe(orderDetails.amount/2); + let repaidOrderDetails = await getOrderDetails(orderId); expect(repaidOrderDetails.payment_status).toBe('pending'); expect(repaidOrderDetails.withdrawal_status).toBe('waiting'); @@ -323,6 +331,9 @@ describe('Order Endpoint Blackbox Tests', () => { repaidOrderDetails = await getOrderDetails(orderId); expect(repaidOrderDetails.payment_status).toBe('paid'); expect(repaidOrderDetails.withdrawal_status).toBe('completed'); + + const paymentAccountDotBalance = await getDotBalance(orderDetails.currency.rpc_url, paymentAccount); + expect(reverseDecimals(paymentAccountDotBalance, 10)).toBe(0); }, 100000); it('should not automatically withdraw USDC order until fully repaid', async () => { @@ -345,6 +356,9 @@ describe('Order Endpoint Blackbox Tests', () => { // lets wait for the changes to get propagated on chain and app to catch them await new Promise(resolve => setTimeout(resolve, 15000)); + const halfAmountBalance = await getAssetBalance(orderDetails.currency.rpc_url, paymentAccount, orderDetails.currency.asset_id); + expect(reverseDecimals(halfAmountBalance, 6)).toBe(halfAmount); + let repaidOrderDetails = await getOrderDetails(orderId); expect(repaidOrderDetails.payment_status).toBe('pending'); expect(repaidOrderDetails.withdrawal_status).toBe('waiting'); @@ -363,6 +377,9 @@ describe('Order Endpoint Blackbox Tests', () => { repaidOrderDetails = await getOrderDetails(orderId); expect(repaidOrderDetails.payment_status).toBe('paid'); expect(repaidOrderDetails.withdrawal_status).toBe('completed'); + + const paymentAccountUsdcBalance = await getAssetBalance(orderDetails.currency.rpc_url, paymentAccount, orderDetails.currency.asset_id); + expect(reverseDecimals(paymentAccountUsdcBalance, 6)).toBe(0); }, 100000); it('should not update order if received payment in wrong currency', async () => { @@ -400,6 +417,9 @@ describe('Order Endpoint Blackbox Tests', () => { // lets wait for the changes to get propagated on chain and app to catch them await new Promise(resolve => setTimeout(resolve, 15000)); + const halfAmountBalance = await getDotBalance(orderDetails.currency.rpc_url, paymentAccount); + expect(reverseDecimals(halfAmountBalance, 10)).toBe(orderDetails.amount/2); + const partiallyRepaidOrderDetails = await getOrderDetails(orderId); expect(partiallyRepaidOrderDetails.payment_status).toBe('pending'); expect(partiallyRepaidOrderDetails.withdrawal_status).toBe('waiting'); @@ -411,6 +431,11 @@ describe('Order Endpoint Blackbox Tests', () => { let forcedOrderDetails = await getOrderDetails(orderId); expect(forcedOrderDetails.payment_status).toBe('pending'); expect(forcedOrderDetails.withdrawal_status).toBe('forced'); + + await new Promise(resolve => setTimeout(resolve, 5000)); + + const paymentAccountDotBalance = await getDotBalance(orderDetails.currency.rpc_url, paymentAccount); + expect(reverseDecimals(paymentAccountDotBalance, 10)).toBe(0); }, 100000); it('should be able to force withdraw partially repayed USDC order', async () => { @@ -420,11 +445,22 @@ describe('Order Endpoint Blackbox Tests', () => { const paymentAccount = orderDetails.payment_account; expect(paymentAccount).toBeDefined(); - await transferFunds(orderDetails.currency.rpc_url, paymentAccount, usdcOrderData.amount/2); + const halfAmount = orderDetails.amount/2; + + // Partial repayment + await transferFunds( + orderDetails.currency.rpc_url, + paymentAccount, + halfAmount, + orderDetails.currency.asset_id + ); // lets wait for the changes to get propagated on chain and app to catch them await new Promise(resolve => setTimeout(resolve, 15000)); + const halfAmountBalance = await getAssetBalance(orderDetails.currency.rpc_url, paymentAccount, orderDetails.currency.asset_id); + expect(reverseDecimals(halfAmountBalance, 6)).toBe(halfAmount); + const partiallyRepaidOrderDetails = await getOrderDetails(orderId); expect(partiallyRepaidOrderDetails.payment_status).toBe('pending'); expect(partiallyRepaidOrderDetails.withdrawal_status).toBe('waiting'); @@ -433,9 +469,15 @@ describe('Order Endpoint Blackbox Tests', () => { .post(`/v2/order/${orderId}/forceWithdrawal`); expect(response.status).toBe(201); + // lets wait for the changes to get propagated on chain and app to catch them + await new Promise(resolve => setTimeout(resolve, 15000)); + let forcedOrderDetails = await getOrderDetails(orderId); expect(forcedOrderDetails.payment_status).toBe('pending'); expect(forcedOrderDetails.withdrawal_status).toBe('forced'); + + const paymentAccountUsdcBalance = await getAssetBalance(orderDetails.currency.rpc_url, paymentAccount, orderDetails.currency.asset_id); + expect(reverseDecimals(paymentAccountUsdcBalance, 6)).toBe(0); }, 100000); it('should return 404 for non-existing order on force withdrawal', async () => {