Skip to content

Commit

Permalink
Refactor swap clients (#6505)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Oct 3, 2024
1 parent fc060ae commit c61cc35
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 319 deletions.
35 changes: 4 additions & 31 deletions Cargo.lock

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

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ members = [
"backend/libraries/human_readable",
"backend/libraries/human_readable_derive",
"backend/libraries/icdex_client",
"backend/libraries/icpswap_client",
"backend/libraries/identity_utils",
"backend/libraries/index_store",
"backend/libraries/instruction_counts_log",
Expand All @@ -146,7 +145,6 @@ members = [
"backend/libraries/storage_bucket_client",
"backend/libraries/search",
"backend/libraries/sha256",
"backend/libraries/sonic_client",
"backend/libraries/stable_memory",
"backend/libraries/testing",
"backend/libraries/timer_job_queue",
Expand Down
1 change: 1 addition & 0 deletions backend/canisters/user/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Return owned values from `EventsMap` in prep for switch to stable memory ([#6469](https://github.com/open-chat-labs/open-chat/pull/6469))
- Add serde default attribute in preparation for skipping serialization if default ([#6475](https://github.com/open-chat-labs/open-chat/pull/6475))
- Refactor transfers to differentiate between transfers that failed due to c2c error vs transfer error ([#6500](https://github.com/open-chat-labs/open-chat/pull/6500))
- Refactor ICPSwap and Sonic swap clients ([#6505](https://github.com/open-chat-labs/open-chat/pull/6505))

### Fixed

Expand Down
6 changes: 4 additions & 2 deletions backend/canisters/user/impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ ic-cdk-timers = { workspace = true }
ic-certificate-verification = { workspace = true }
ic-ledger-types = { workspace = true }
ic-stable-structures = { workspace = true }
icpswap_client = { path = "../../../libraries/icpswap_client" }
icpswap_swap_pool_canister = { path = "../../../external_canisters/icpswap_swap_pool/api" }
icpswap_swap_pool_canister_c2c_client = { path = "../../../external_canisters/icpswap_swap_pool/c2c_client" }
icrc_ledger_canister_c2c_client = { path = "../../../external_canisters/icrc_ledger/c2c_client" }
icrc_ledger_canister = { path = "../../../external_canisters/icrc_ledger/api" }
icrc-ledger-types = { workspace = true }
Expand All @@ -64,7 +65,8 @@ serde = { workspace = true }
serde_bytes = { workspace = true }
sns_governance_canister = { path = "../../../external_canisters/sns_governance/api" }
sns_governance_canister_c2c_client = { path = "../../../external_canisters/sns_governance/c2c_client" }
sonic_client = { path = "../../../libraries/sonic_client" }
sonic_canister = { path = "../../../external_canisters/sonic/api" }
sonic_canister_c2c_client = { path = "../../../external_canisters/sonic/c2c_client" }
stable_memory = { path = "../../../libraries/stable_memory" }
storage_bucket_client = { path = "../../../libraries/storage_bucket_client" }
tracing = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion backend/canisters/user/impl/src/model/token_swaps.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use icrc_ledger_types::icrc1::account::Account;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use types::icrc1::Account;
use types::{CanisterId, ExchangeId, TimestampMillis, Timestamped};
use user_canister::token_swap_status::TokenSwapStatus;

Expand Down
116 changes: 113 additions & 3 deletions backend/canisters/user/impl/src/token_swaps/icpswap.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,118 @@
use super::swap_client::SwapClient;
use async_trait::async_trait;
use ic_cdk::api::call::CallResult;
use icpswap_client::ICPSwapClient;
use icrc_ledger_types::icrc1::account::Account;
use candid::Nat;
use ic_cdk::api::call::{CallResult, RejectionCode};
use icpswap_swap_pool_canister::{ICPSwapError, ICPSwapResult};
use ledger_utils::convert_to_subaccount;
use serde::{Deserialize, Serialize};
use types::icrc1::Account;
use types::{CanisterId, TokenInfo};

#[derive(Serialize, Deserialize)]
pub struct ICPSwapClient {
this_canister_id: CanisterId,
swap_canister_id: CanisterId,
token0: TokenInfo,
token1: TokenInfo,
zero_for_one: bool,
}

impl ICPSwapClient {
pub fn new(
this_canister_id: CanisterId,
swap_canister_id: CanisterId,
token0: TokenInfo,
token1: TokenInfo,
zero_for_one: bool,
) -> Self {
ICPSwapClient {
this_canister_id,
swap_canister_id,
token0,
token1,
zero_for_one,
}
}

pub fn deposit_account(&self) -> Account {
Account {
owner: self.swap_canister_id,
subaccount: Some(convert_to_subaccount(&self.this_canister_id).0),
}
}

pub async fn deposit(&self, amount: u128) -> CallResult<u128> {
let token = self.input_token();
let args = icpswap_swap_pool_canister::deposit::Args {
token: token.ledger.to_string(),
amount: amount.into(),
fee: token.fee.into(),
};
match icpswap_swap_pool_canister_c2c_client::deposit(self.swap_canister_id, &args).await? {
ICPSwapResult::Ok(amount_deposited) => Ok(nat_to_u128(amount_deposited)),
ICPSwapResult::Err(error) => Err(convert_error(error)),
}
}

pub async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<Result<u128, String>> {
let args = icpswap_swap_pool_canister::swap::Args {
operator: self.this_canister_id,
amount_in: amount.to_string(),
zero_for_one: self.zero_for_one,
amount_out_minimum: min_amount_out.to_string(),
};
match icpswap_swap_pool_canister_c2c_client::swap(self.swap_canister_id, &args).await? {
ICPSwapResult::Ok(amount_out) => Ok(Ok(nat_to_u128(amount_out))),
ICPSwapResult::Err(error) => Ok(Err(format!("{error:?}"))),
}
}

pub async fn withdraw(&self, successful_swap: bool, amount: u128) -> CallResult<u128> {
let token = if successful_swap { self.output_token() } else { self.input_token() };
withdraw(self.swap_canister_id, token.ledger, amount, token.fee).await
}

fn input_token(&self) -> &TokenInfo {
if self.zero_for_one {
&self.token0
} else {
&self.token1
}
}

fn output_token(&self) -> &TokenInfo {
if self.zero_for_one {
&self.token1
} else {
&self.token0
}
}
}

pub async fn withdraw(
swap_canister_id: CanisterId,
ledger_canister_id: CanisterId,
amount: u128,
fee: u128,
) -> CallResult<u128> {
let args = icpswap_swap_pool_canister::withdraw::Args {
token: ledger_canister_id.to_string(),
amount: amount.into(),
fee: fee.into(),
};
match icpswap_swap_pool_canister_c2c_client::withdraw(swap_canister_id, &args).await? {
ICPSwapResult::Ok(amount_out) => Ok(nat_to_u128(amount_out)),
ICPSwapResult::Err(error) => Err(convert_error(error)),
}
}

fn nat_to_u128(value: Nat) -> u128 {
value.0.try_into().unwrap()
}

fn convert_error(error: ICPSwapError) -> (RejectionCode, String) {
(RejectionCode::Unknown, format!("{error:?}"))
}

#[async_trait]
impl SwapClient for ICPSwapClient {
Expand Down
127 changes: 124 additions & 3 deletions backend/canisters/user/impl/src/token_swaps/sonic.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,129 @@
use super::swap_client::SwapClient;
use async_trait::async_trait;
use ic_cdk::api::call::CallResult;
use icrc_ledger_types::icrc1::account::Account;
use sonic_client::SonicClient;
use candid::{Int, Nat};
use ic_cdk::api::call::{CallResult, RejectionCode};
use serde::{Deserialize, Serialize};
use sonic_canister::SonicResult;
use std::cell::Cell;
use types::icrc1::Account;
use types::{CanisterId, TokenInfo};

thread_local! {
static SUBACCOUNT: Cell<[u8; 32]> = Cell::default();
}

#[derive(Serialize, Deserialize)]
pub struct SonicClient {
this_canister_id: CanisterId,
sonic_canister_id: CanisterId,
token0: TokenInfo,
token1: TokenInfo,
zero_for_one: bool,
}

impl SonicClient {
pub fn new(
this_canister_id: CanisterId,
sonic_canister_id: CanisterId,
token0: TokenInfo,
token1: TokenInfo,
zero_for_one: bool,
) -> Self {
SonicClient {
this_canister_id,
sonic_canister_id,
token0,
token1,
zero_for_one,
}
}

pub async fn deposit_account(&self) -> CallResult<Account> {
retrieve_subaccount(self.sonic_canister_id).await.map(|sa| Account {
owner: self.sonic_canister_id,
subaccount: Some(sa),
})
}

pub async fn deposit(&self, amount: u128) -> CallResult<u128> {
let token = self.input_token();
let args = (token.ledger, amount.saturating_sub(token.fee).into());
match sonic_canister_c2c_client::deposit(self.sonic_canister_id, args).await?.0 {
SonicResult::Ok(amount_deposited) => Ok(nat_to_u128(amount_deposited)),
SonicResult::Err(error) => Err(convert_error(error)),
}
}

pub async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<Result<u128, String>> {
let args = (
Nat::from(amount),
Nat::from(min_amount_out),
vec![self.input_token().ledger.to_string(), self.output_token().ledger.to_string()],
self.this_canister_id,
Int::from(u64::MAX),
);
match sonic_canister_c2c_client::swap_exact_tokens_for_tokens(self.sonic_canister_id, args.clone())
.await?
.0
{
SonicResult::Ok(amount_out) => Ok(Ok(nat_to_u128(amount_out))),
SonicResult::Err(error) => Ok(Err(error)),
}
}

pub async fn withdraw(&self, successful_swap: bool, amount: u128) -> CallResult<u128> {
let token = if successful_swap { self.output_token() } else { self.input_token() };
let amount = if successful_swap { amount } else { amount.saturating_sub(token.fee) };
withdraw(self.sonic_canister_id, token.ledger, amount).await
}

fn input_token(&self) -> &TokenInfo {
if self.zero_for_one {
&self.token0
} else {
&self.token1
}
}

fn output_token(&self) -> &TokenInfo {
if self.zero_for_one {
&self.token1
} else {
&self.token0
}
}
}

pub async fn withdraw(swap_canister_id: CanisterId, ledger_canister_id: CanisterId, amount: u128) -> CallResult<u128> {
let args = (ledger_canister_id, amount.into());
match sonic_canister_c2c_client::withdraw(swap_canister_id, args).await?.0 {
SonicResult::Ok(amount_withdrawn) => Ok(nat_to_u128(amount_withdrawn)),
SonicResult::Err(error) => Err(convert_error(error)),
}
}

async fn retrieve_subaccount(sonic_canister_id: CanisterId) -> CallResult<[u8; 32]> {
let current = SUBACCOUNT.get();
if current != [0; 32] {
Ok(current)
} else {
match sonic_canister_c2c_client::initiate_icrc1_transfer(sonic_canister_id).await {
Ok(sa) => {
SUBACCOUNT.set(sa);
Ok(sa)
}
Err(error) => Err(error),
}
}
}

fn nat_to_u128(value: Nat) -> u128 {
value.0.try_into().unwrap()
}

fn convert_error(error: String) -> (RejectionCode, String) {
(RejectionCode::Unknown, error)
}

#[async_trait]
impl SwapClient for SonicClient {
Expand Down
2 changes: 1 addition & 1 deletion backend/canisters/user/impl/src/token_swaps/swap_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use async_trait::async_trait;
use ic_cdk::api::call::CallResult;
use icrc_ledger_types::icrc1::account::Account;
use types::icrc1::Account;

#[async_trait]
pub trait SwapClient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ async fn reclaim_swap_tokens(args: Args) -> Response {

let result = match args.exchange_id {
ExchangeId::ICPSwap => {
icpswap_client::withdraw(args.swap_canister_id, args.ledger_canister_id, args.amount, args.fee).await
crate::token_swaps::icpswap::withdraw(args.swap_canister_id, args.ledger_canister_id, args.amount, args.fee).await
}
ExchangeId::Sonic => {
crate::token_swaps::sonic::withdraw(args.swap_canister_id, args.ledger_canister_id, args.amount).await
}
ExchangeId::Sonic => sonic_client::withdraw(args.swap_canister_id, args.ledger_canister_id, args.amount).await,
ExchangeId::KongSwap => unimplemented!(),
};

Expand Down
Loading

0 comments on commit c61cc35

Please sign in to comment.