Skip to content

Commit

Permalink
Merge branch 'main' into clippy-auto-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Vovke authored Nov 18, 2024
2 parents 4d003b2 + cf19a81 commit d7149a0
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 64 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to this project will be documented in this file.

## [0.2.7] - 2024-11-18

### 🚀 Features

- Asset Hub transactions with fee currency
- Autofill tip with asset
- Pass asset id into transaction constructor to properly select fee currency

### 🧪 Testing

- Test cases to cover partial withdrawal and Asset Gub transfers

## [0.2.6] - 2024-11-01

### 🚀 Features
Expand Down
19 changes: 18 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
## Preparing development environment

It's possible to mimic to spawn chopsticks instances in parallel for development purposes.
Chopsticks Dockerfile exposes 4 ports (8000, 8500, 9000, 9500), so you can spawn 4 instances of chopsticks and each one of them will look at different RPC.
Chopsticks Dockerfile exposes 4 ports (8000, 8500, 9000, 9500), so you can spawn up to 4 instances of chopsticks and each one of them will look at different RPC (note that those will be different chains).
Note that the RPCs are not real, so the changes made on one chopsticks instance will not affect the others.

1. `cd chopsticks`
2. `docker compose up`, in case you want to just 2 instances edit the docker-compose.yml file
3. start the app with `KALATORI_CONFIG` environment variable pointing to `configs/chopsticks.toml`

## Running tests locally

While having the kalatori app running. You can run the tests locally by running the following command:

```bash
cd tests/kalatori-api-test-suite
yarn
yarn test
```

You can run specific test similarly to the following command:

```bash
cd tests/kalatori-api-test-suite
yarn test -t "should create, repay, and automatically withdraw an order in USDC"
```

## Version Bumping and Release Process

When you make changes that require a new version of the project, follow these steps to bump the version:
Expand Down
25 changes: 12 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "kalatori"
authors = ["Alzymologist Oy <contact@zymologia.fi>"]
version = "0.2.6"
version = "0.2.7"
edition = "2021"
description = "A gateway daemon for Kalatori."
license = "GPL-3.0-or-later"
Expand All @@ -27,10 +27,10 @@ serde = { version = "1", features = ["derive", "rc"] }
tracing = "0.1"
scale-info = "2"
axum-macros = "0.4"
primitive-types = { version = "0.12", features = ["codec"] }
primitive-types = { version = "0.13", features = ["codec"] }
jsonrpsee = { version = "0.24", features = ["ws-client"] }
thiserror = "1"
frame-metadata = "16"
frame-metadata = "17"
const-hex = "1"
codec = { package = "parity-scale-codec", version = "3", features = [
"chain-error",
Expand Down
1 change: 1 addition & 0 deletions src/chain/payout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub async fn payout(
block,
block_number,
0,
currency.asset_id,
)?;

let sign_this = batch_transaction
Expand Down
71 changes: 29 additions & 42 deletions src/chain/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,52 +116,39 @@ pub fn start_chain_watch(
break;
}

match transfer_events(
&client,
&block,
&watcher.metadata,
)
.await {
Ok(events) => {
let mut id_remove_list = Vec::new();
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis() as u64;
let mut id_remove_list = Vec::new();
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis() as u64;

for (id, invoice) in &watched_accounts {
if events.iter().any(|event| was_balance_received_at_account(&invoice.address, &event.0.fields)) {
match invoice.check(&client, &watcher, &block).await {
Ok(true) => {
state.order_paid(id.clone()).await;
id_remove_list.push(id.to_owned());
}
Ok(false) => (),
Err(e) => {
tracing::warn!("account fetch error: {0:?}", e);
}
}
} else if invoice.death.0 >= now {
match invoice.check(&client, &watcher, &block).await {
Ok(paid) => {
if paid {
state.order_paid(id.clone()).await;
}

id_remove_list.push(id.to_owned());
}
Err(e) => {
tracing::warn!("account fetch error: {0:?}", e);
}
}
// Important! There used to be a significant oprimisation that
// watched events and checked only accounts that have tranfers into
// them in given block; this was found to be unreliable: there are
// ways to transfer funds without emitting a transfer event (one
// notable example is through asset exchange procedure directed
// straight into invoice account), and probably even without any
// reliably expected event (through XCM). Thus we just scan all
// accounts, every time. Please submit a PR or an issue if you
// figure out a reliable optimization for this.
for (id, invoice) in &watched_accounts {
match invoice.check(&client, &watcher, &block).await {
Ok(true) => {
state.order_paid(id.clone()).await;
id_remove_list.push(id.to_owned());
},
Ok(false) => {
if invoice.death.0 <= now {
id_remove_list.push(id.to_owned());
}
},
Err(e) => {
tracing::warn!("account fetch error: {0:?}", e);
}
for id in id_remove_list {
watched_accounts.remove(&id);
}
},
Err(e) => {
tracing::warn!("Events fetch error {e} at {}", chain.name);
break;
},
}
}

for id in id_remove_list {
watched_accounts.remove(&id);
};

tracing::debug!("Block {} from {} processed successfully", block.to_string(), chain.name);
}
ChainTrackerRequest::WatchAccount(request) => {
Expand Down
4 changes: 4 additions & 0 deletions src/chain/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub fn construct_batch_transaction(
block: BlockHash,
block_number: u32,
nonce: u32,
asset: Option<u32>,
) -> Result<TransactionToFill, ChainError> {
let mut transaction_to_fill = TransactionToFill::init(&mut (), metadata, genesis_hash.0)?;

Expand Down Expand Up @@ -296,6 +297,9 @@ pub fn construct_batch_transaction(

transaction_to_fill.populate_block_info(Some(block.0), Some(block_number.into()));
transaction_to_fill.populate_nonce(nonce);
if let Some(asset) = asset {
transaction_to_fill.try_default_tip_assets_in_given_asset(&mut (), metadata, asset);
}

for ext in &mut transaction_to_fill.extensions {
if ext.finalize().is_none() {
Expand Down
74 changes: 69 additions & 5 deletions tests/kalatori-api-test-suite/tests/order.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ describe('Order Endpoint Blackbox Tests', () => {
expect(repaidOrderDetails.withdrawal_status).toBe('completed');
}, 100000);

it.skip('should create, repay, and automatically withdraw an order in USDC', async () => {
it('should create, repay, and automatically withdraw an order in USDC', async () => {
const orderId = generateRandomOrderId();
await createOrder(orderId, usdcOrderData);
const orderDetails = await getOrderDetails(orderId);
Expand All @@ -255,7 +255,46 @@ describe('Order Endpoint Blackbox Tests', () => {
expect(repaidOrderDetails.withdrawal_status).toBe('completed');
}, 50000);

it.skip('should not automatically withdraw an order until fully repaid', async () => {
it('should not automatically withdraw DOT order until fully repaid', async () => {
const orderId = generateRandomOrderId();
await createOrder(orderId, dotOrderData);
const orderDetails = await getOrderDetails(orderId);
const paymentAccount = orderDetails.payment_account;
expect(paymentAccount).toBeDefined();

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));

let repaidOrderDetails = await getOrderDetails(orderId);
expect(repaidOrderDetails.payment_status).toBe('pending');
expect(repaidOrderDetails.withdrawal_status).toBe('waiting');

// Full repayment
await transferFunds(
orderDetails.currency.rpc_url,
paymentAccount,
halfAmount+5,
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));

repaidOrderDetails = await getOrderDetails(orderId);
expect(repaidOrderDetails.payment_status).toBe('paid');
expect(repaidOrderDetails.withdrawal_status).toBe('completed');
}, 100000);

it('should not automatically withdraw USDC order until fully repaid', async () => {
const orderId = generateRandomOrderId();
await createOrder(orderId, usdcOrderData);
const orderDetails = await getOrderDetails(orderId);
Expand Down Expand Up @@ -292,9 +331,9 @@ describe('Order Endpoint Blackbox Tests', () => {
repaidOrderDetails = await getOrderDetails(orderId);
expect(repaidOrderDetails.payment_status).toBe('paid');
expect(repaidOrderDetails.withdrawal_status).toBe('completed');
}, 50000);
}, 100000);

it.skip('should not update order if received payment in wrong currency', async () => {
it('should not update order if received payment in wrong currency', async () => {
const orderId = generateRandomOrderId();
await createOrder(orderId, usdcOrderData);
const orderDetails = await getOrderDetails(orderId);
Expand All @@ -317,7 +356,7 @@ describe('Order Endpoint Blackbox Tests', () => {
expect(repaidOrderDetails.withdrawal_status).toBe('waiting');
}, 50000);

it('should be able to force withdraw partially repayed order', async () => {
it('should be able to force withdraw partially repayed DOT order', async () => {
const orderId = generateRandomOrderId();
await createOrder(orderId, dotOrderData);
const orderDetails = await getOrderDetails(orderId);
Expand All @@ -342,6 +381,31 @@ describe('Order Endpoint Blackbox Tests', () => {
expect(forcedOrderDetails.withdrawal_status).toBe('forced');
}, 100000);

it('should be able to force withdraw partially repayed USDC order', async () => {
const orderId = generateRandomOrderId();
await createOrder(orderId, usdcOrderData);
const orderDetails = await getOrderDetails(orderId);
const paymentAccount = orderDetails.payment_account;
expect(paymentAccount).toBeDefined();

await transferFunds(orderDetails.currency.rpc_url, paymentAccount, usdcOrderData.amount/2);

// lets wait for the changes to get propagated on chain and app to catch them
await new Promise(resolve => setTimeout(resolve, 15000));

const partiallyRepaidOrderDetails = await getOrderDetails(orderId);
expect(partiallyRepaidOrderDetails.payment_status).toBe('pending');
expect(partiallyRepaidOrderDetails.withdrawal_status).toBe('waiting');

const response = await request(baseUrl)
.post(`/v2/order/${orderId}/forceWithdrawal`);
expect(response.status).toBe(201);

let forcedOrderDetails = await getOrderDetails(orderId);
expect(forcedOrderDetails.payment_status).toBe('pending');
expect(forcedOrderDetails.withdrawal_status).toBe('forced');
}, 100000);

it('should return 404 for non-existing order on force withdrawal', async () => {
const nonExistingOrderId = 'nonExistingOrder123';
const response = await request(baseUrl)
Expand Down

0 comments on commit d7149a0

Please sign in to comment.