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

Swap backend #448

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
14 changes: 14 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions src/canister/individual_user_template/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ic-icrc1-index-ng.workspace = true
ic-icrc1-index.workspace = true
icrc-ledger-types.workspace = true
hex = "0.4.3"
bigdecimal = "0.4.6"

[dev-dependencies]
test_utils = { workspace = true }
6 changes: 3 additions & 3 deletions src/canister/individual_user_template/src/api/cdao/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod airdrop;
pub mod swap;
mod token;
use std::collections::{HashMap, HashSet, VecDeque};

Expand Down Expand Up @@ -295,9 +296,8 @@ async fn deploy_cdao_sns(
root: root.0,
swap: swap.0,
index: index.0,
airdrop_info: AirdropInfo {
principals_who_successfully_claimed: HashMap::new(),
},
airdrop_info: AirdropInfo { principals_who_successfully_claimed: HashMap::new() },
last_swapped_price: None,
};
CANISTER_DATA.with(|cdata| {
let mut cdata = cdata.borrow_mut();
Expand Down
138 changes: 138 additions & 0 deletions src/canister/individual_user_template/src/api/cdao/swap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use candid::{CandidType, Nat, Principal};
use ic_cdk::api::time;
use ic_cdk_macros::update;
use icrc_ledger_types::icrc2::{allowance::{Allowance, AllowanceArgs}, approve::{ApproveArgs, ApproveError}, transfer_from::{TransferFromArgs, TransferFromError}};
use serde::Deserialize;
use shared_utils::{canister_specific::individual_user_template::types::{cdao::{SwapRequestActions, TokenPairs}, error::SwapError}, common::utils::permissions::is_caller_global_admin};

use crate::{api::profile::get_profile_details_v2::get_profile_details_v2, CANISTER_DATA};

#[derive(CandidType, Deserialize, PartialEq, Eq, Debug)]
struct SupportedStandards{
name: String,
url: String
}

const SWAP_REQUEST_EXPIRY: u64 = 7 * 24 * 60 * 60 * 1_000_000_000; // 1 wk


#[update]
pub async fn swap_request(token_pairs: TokenPairs) -> Result<(), SwapError>{
let TokenPairs{token_a, token_b} = token_pairs;

if !is_icrc2_supported_token(token_a.ledger).await?{
return Err(SwapError::UnsupportedToken);
}

if CANISTER_DATA.with_borrow(|data| data.cdao_canisters.iter().find(|cdao| cdao.ledger == token_b.ledger).cloned()).is_none(){
return Err(SwapError::IsNotTokenCreator);
}


// todo: replace this by a frontend server function instead because this uses the caller to infer the from field in approve...
let previous_approval_amt = get_previous_approval_amount(ic_cdk::caller(), ic_cdk::id(), token_a.ledger).await?;
let allocation_res: (Result<Nat, ApproveError>, ) = ic_cdk::call(token_a.ledger, "icrc2_approve", (ApproveArgs{
from_subaccount: None,
spender: ic_cdk::id().into(),
amount: previous_approval_amt + token_a.amt,
expected_allowance: None,
memo: None,
expires_at: Some(time() + SWAP_REQUEST_EXPIRY),
fee: None,
created_at_time: None
}, )).await?;
// ....


allocation_res.0.map_err(SwapError::ApproveError)?;
// TODO: Push notifications
Ok(())
}

// Creator principal is the caller here
#[update]
pub async fn swap_request_action(op: SwapRequestActions) -> Result<(), SwapError>{
//auth
if ic_cdk::caller() != get_profile_details_v2().principal_id{
return Err(SwapError::Unauthenticated);
}
match op{
SwapRequestActions::Accept { token_pairs, requester } => {
let TokenPairs{token_a, token_b} = token_pairs;

let transfer_res: (Result<Nat, TransferFromError>, ) = ic_cdk::call(token_a.ledger, "icrc2_transfer_from", (TransferFromArgs{
from: requester.into(),
spender_subaccount: None,
to: ic_cdk::caller().into(),
amount: token_a.amt,
fee: None,
memo: None,
created_at_time: None
}, )).await?;
transfer_res.0.map_err(SwapError::TransferFromError)?;

let transfer_res: (Result<Nat, TransferFromError>, ) = ic_cdk::call(token_b.ledger, "icrc2_transfer_from", (TransferFromArgs{
from: ic_cdk::caller().into(),
spender_subaccount: None,
to: requester.into(),
amount: token_b.amt,
fee: None,
memo: None,
created_at_time: None
}, )).await?;
transfer_res.0.map_err(SwapError::TransferFromError)?;

// send push notifs to both parties
// Cannot remove the approval as it is not possible to do so
},
SwapRequestActions::Reject { token_pairs, requester } => {
// Reject and send push notifs to both parties
// Cannot remove the approval as it is not possible to do so
}
}

Ok(())
}

#[update(guard = "is_caller_global_admin")]
fn update_last_swap_price(token_ledger: Principal, price: f64){
CANISTER_DATA.with_borrow_mut(|data| {
if let Some(cdao) = data.cdao_canisters.iter_mut().find(|cdao| cdao.ledger == token_ledger){
cdao.last_swapped_price = Some(price);
}
});
}

#[update]
pub async fn cancel_swap_request(token_pairs: TokenPairs) -> Result<(), SwapError>{
let TokenPairs{token_a, ..} = token_pairs;
let previous_approval_amt = get_previous_approval_amount(ic_cdk::caller(), ic_cdk::id(), token_a.ledger).await?;

let allocation_res: (Result<Nat, ApproveError>, ) = ic_cdk::call(token_a.ledger, "icrc2_approve", (ApproveArgs{
from_subaccount: None,
spender: ic_cdk::id().into(),
amount: previous_approval_amt - token_a.amt, // https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-2/README.md#alice-removes-her-allowance-for-canister-c
expected_allowance: None,
memo: None,
expires_at: Some(time() + SWAP_REQUEST_EXPIRY),
fee: None,
created_at_time: None
}, )).await?;

allocation_res.0.map_err(SwapError::ApproveError)?;
Ok(())
}

async fn get_previous_approval_amount(requester: Principal, spender: Principal, ledger: Principal) -> Result<Nat, SwapError>{
let previous_approval: (Allowance, ) = ic_cdk::call(ledger, "icrc2_allowance", (AllowanceArgs{
account: requester.into(),
spender: spender.into()
}, )).await?;

Ok(previous_approval.0.allowance)
}

async fn is_icrc2_supported_token(token_ledger: Principal) -> Result<bool, SwapError>{
let res: (Vec<SupportedStandards>, ) = ic_cdk::call(token_ledger, "icrc1_supported_standards", ()).await?;
Ok(res.0.iter().any(|v| v.name == "ICRC2"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ic_cdk_macros::query;
use shared_utils::canister_specific::individual_user_template::types::profile::UserProfileDetailsForFrontendV2;

#[query]
fn get_profile_details_v2() -> UserProfileDetailsForFrontendV2 {
pub fn get_profile_details_v2() -> UserProfileDetailsForFrontendV2 {
CANISTER_DATA.with_borrow(|canister_data_ref_cell| {
let profile = canister_data_ref_cell.profile.clone();
let token_balance = &canister_data_ref_cell.my_token_balance;
Expand Down
6 changes: 4 additions & 2 deletions src/canister/individual_user_template/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use icrc_ledger_types::icrc1::transfer::Memo;
use shared_utils::{
canister_specific::individual_user_template::types::{
arg::{FolloweeArg, IndividualUserTemplateInitArgs, PlaceBetArg},
cdao::DeployedCdaoCanisters,
cdao::{DeployedCdaoCanisters, SwapRequestActions, TokenPairs},
device_id::DeviceIdentity,
error::{
BetOnCurrentlyViewingPostError, CdaoDeployError, CdaoTokenError,
FollowAnotherUserProfileError, GetPostsOfUserProfileError, AirdropError
FollowAnotherUserProfileError, GetPostsOfUserProfileError, AirdropError, SwapError
},
follow::{FollowEntryDetail, FollowEntryId},
hot_or_not::{BetDetails, BetOutcomeForBetMaker, BettingStatus, PlacedBetDetail},
Expand All @@ -48,13 +48,15 @@ use shared_utils::{
types::canister_specific::individual_user_template::error_types::{
GetUserUtilityTokenTransactionHistoryError, UpdateProfileSetUniqueUsernameError,
},

};

mod api;
pub mod data_model;
mod util;

thread_local! {

static CANISTER_DATA: RefCell<CanisterData> = RefCell::default();
static SNAPSHOT_DATA: RefCell<Vec<u8>> = RefCell::default();
}
Expand Down
Loading
Loading