Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle case where swap fails due to slippage #4924

Merged
merged 6 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/canisters/user/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `local_user_index_canister_id` to group/community summaries ([#4857](https://github.com/open-chat-labs/open-chat/pull/4857))
- Switch to `c2c_send_message` when sending messages c2c to groups or channels ([#4895](https://github.com/open-chat-labs/open-chat/pull/4895))
- Remove `display_name` from `init` args ([#4910](https://github.com/open-chat-labs/open-chat/pull/4910))
- Handle case where swap fails due to slippage ([#4924](https://github.com/open-chat-labs/open-chat/pull/4924))

## [[2.0.947](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.947-user)] - 2023-11-24

Expand Down
7 changes: 6 additions & 1 deletion backend/canisters/user/api/can.did
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type SwapTokensResponse = variant {
Success : record {
amount_out : nat;
};
SwapFailed;
InternalError : text;
};

Expand Down Expand Up @@ -194,13 +195,17 @@ type TokenSwapStatusResponse = variant {
Err : text;
};
amount_swapped : opt variant {
Ok : nat;
Ok : variant {
Ok : nat;
Err : text;
};
Err : text;
};
withdraw_from_dex : opt variant {
Ok : nat;
Err : text;
};
success : opt bool;
};
NotFound;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ pub struct TokenSwapStatus {
pub deposit_account: SwapSubtask<()>,
pub transfer: SwapSubtask<u64>, // Block Index
pub notify_dex: SwapSubtask<()>,
pub amount_swapped: SwapSubtask<u128>,
pub amount_swapped: SwapSubtask<Result<u128, String>>,
pub withdraw_from_dex: SwapSubtask<u128>,
pub success: Option<bool>,
}

type SwapSubtask<T = ()> = Option<Result<T, String>>;
1 change: 1 addition & 0 deletions backend/canisters/user/api/src/updates/swap_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct ICPSwapArgs {
#[derive(CandidType, Serialize, Deserialize, Debug)]
pub enum Response {
Success(SuccessResult),
SwapFailed,
InternalError(String),
}

Expand Down
5 changes: 3 additions & 2 deletions backend/canisters/user/impl/src/model/token_swaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct TokenSwap {
pub deposit_account: SwapSubtask<Account>,
pub transfer: SwapSubtask<u64>, // Block Index
pub notified_dex_at: SwapSubtask,
pub amount_swapped: SwapSubtask<u128>,
pub amount_swapped: SwapSubtask<Result<u128, String>>,
pub withdrawn_from_dex_at: SwapSubtask<u128>,
pub success: Option<Timestamped<bool>>,
}
Expand Down Expand Up @@ -61,8 +61,9 @@ impl From<TokenSwap> for TokenSwapStatus {
deposit_account: value.deposit_account.map(|a| a.value.map(|_| ())),
transfer: value.transfer.map(|t| t.value),
notify_dex: value.notified_dex_at.map(|t| t.value.map(|_| ())),
amount_swapped: value.amount_swapped.map(|t| t.value),
amount_swapped: value.amount_swapped.as_ref().map(|t| t.value.clone()),
withdraw_from_dex: value.withdrawn_from_dex_at.map(|t| t.value),
success: value.success.map(|t| t.value),
}
}
}
6 changes: 3 additions & 3 deletions backend/canisters/user/impl/src/token_swaps/icpswap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ impl SwapClient for ICPSwapClient {
self.deposit(amount).await.map(|_| ())
}

async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<u128> {
async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<Result<u128, String>> {
self.swap(amount, min_amount_out).await
}

async fn withdraw(&self, amount: u128) -> CallResult<u128> {
self.withdraw(amount).await
async fn withdraw(&self, successful_swap: bool, amount: u128) -> CallResult<u128> {
self.withdraw(successful_swap, amount).await
}
}
4 changes: 2 additions & 2 deletions backend/canisters/user/impl/src/token_swaps/swap_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ use icrc_ledger_types::icrc1::account::Account;
pub trait SwapClient {
async fn deposit_account(&self) -> CallResult<Account>;
async fn deposit(&self, amount: u128) -> CallResult<()>;
async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<u128>;
async fn withdraw(&self, amount: u128) -> CallResult<u128>;
async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<Result<u128, String>>;
async fn withdraw(&self, successful_swap: bool, amount: u128) -> CallResult<u128>;
}
22 changes: 15 additions & 7 deletions backend/canisters/user/impl/src/updates/swap_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ pub(crate) async fn process_token_swap(mut token_swap: TokenSwap, attempt: u32)
}
}

let amount_swapped = if let Some(a) = extract_result(&token_swap.amount_swapped) {
*a
let swap_result = if let Some(a) = extract_result(&token_swap.amount_swapped).cloned() {
a
} else {
match swap_client
.swap(args.input_amount.saturating_sub(args.input_token.fee), args.min_output_amount)
Expand All @@ -123,7 +123,7 @@ pub(crate) async fn process_token_swap(mut token_swap: TokenSwap, attempt: u32)
Ok(a) => {
mutate_state(|state| {
let now = state.env.now();
token_swap.amount_swapped = Some(Timestamped::new(Ok(a), now));
token_swap.amount_swapped = Some(Timestamped::new(Ok(a.clone()), now));
state.data.token_swaps.upsert(token_swap.clone());
});
a
Expand All @@ -141,10 +141,14 @@ pub(crate) async fn process_token_swap(mut token_swap: TokenSwap, attempt: u32)
}
};

let amount_out = amount_swapped.saturating_sub(args.output_token.fee);
let (successful_swap, amount_out) = if let Ok(amount_swapped) = swap_result {
(true, amount_swapped.saturating_sub(args.output_token.fee))
} else {
(false, args.input_amount.saturating_sub(args.input_token.fee))
};

if extract_result(&token_swap.withdrawn_from_dex_at).is_none() {
if let Err(error) = swap_client.withdraw(amount_out).await {
if let Err(error) = swap_client.withdraw(successful_swap, amount_out).await {
let msg = format!("{error:?}");
mutate_state(|state| {
let now = state.env.now();
Expand All @@ -157,13 +161,17 @@ pub(crate) async fn process_token_swap(mut token_swap: TokenSwap, attempt: u32)
mutate_state(|state| {
let now = state.env.now();
token_swap.withdrawn_from_dex_at = Some(Timestamped::new(Ok(amount_out), now));
token_swap.success = Some(Timestamped::new(true, now));
token_swap.success = Some(Timestamped::new(successful_swap, now));
state.data.token_swaps.upsert(token_swap);
});
}
}

Success(SuccessResult { amount_out })
if successful_swap {
Success(SuccessResult { amount_out })
} else {
SwapFailed
}
}

fn build_swap_client(args: &Args, state: &RuntimeState) -> Box<dyn SwapClient> {
Expand Down
10 changes: 5 additions & 5 deletions backend/libraries/icpswap_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,21 @@ impl ICPSwapClient {
}
}

pub async fn swap(&self, amount: u128, min_amount_out: u128) -> CallResult<u128> {
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(nat_to_u128(amount_out)),
ICPSwapResult::Err(error) => Err(convert_error(error)),
ICPSwapResult::Ok(amount_out) => Ok(Ok(nat_to_u128(amount_out))),
ICPSwapResult::Err(error) => Ok(Err(format!("{error:?}"))),
}
}

pub async fn withdraw(&self, amount: u128) -> CallResult<u128> {
let token = self.output_token();
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 args = icpswap_swap_pool_canister::withdraw::Args {
token: token.ledger.to_string(),
amount: amount.into(),
Expand Down
Loading