diff --git a/Makefile b/Makefile index 22eca8c..9cc3e1a 100644 --- a/Makefile +++ b/Makefile @@ -101,10 +101,20 @@ test-4: install .PHONY: test-a .SILENT: test-a test-a: install - # Call the bounty canister to get the GitHub issue and capture the output + # Call the bounty canister for healthcheck and capture the output @echo "Calling healthcheck on bounty canister..." @TMP_FILE=$$(mktemp); \ dfx canister call bounty healthcheck > $$TMP_FILE; \ echo "healthcheck response:"; \ cat $$TMP_FILE; \ - rm -f $$TMP_FILE \ No newline at end of file + rm -f $$TMP_FILE + +.PHONY: test-a +.SILENT: test-a +test-deposit: install + $(shell make/test/deposit.sh) + +.PHONY: test-a +.SILENT: test-a +test-deposit-direct: install + $(shell make/test/deposit_direct.sh) \ No newline at end of file diff --git a/bounty/bounty.did b/bounty/bounty.did index 23a4fe3..0888f74 100644 --- a/bounty/bounty.did +++ b/bounty/bounty.did @@ -16,5 +16,6 @@ service : (principal, int32) -> { "healthcheck": () -> (text); "accept": (Contributor) -> (); "deposit": () -> (DepositReceipt); - "deposit_direct": (amount: nat) -> (DepositReceipt); + "deposit_direct": (amount: nat64) -> (DepositReceipt); } + diff --git a/bounty/src/api/deposit.rs b/bounty/src/api/deposit.rs index db80ad9..822c419 100644 --- a/bounty/src/api/deposit.rs +++ b/bounty/src/api/deposit.rs @@ -3,15 +3,15 @@ use num_traits::ToPrimitive; use super::*; -use icrc1::{ICRC1, ICRC1_FEE, MAINNET_ICRC1_LEDGER_CANISTER_ID, AllowanceArgs, Account, TransferFromArgs, TransferArg}; +use icrc1::{ + Account, AllowanceArgs, TransferArg, TransferFromArgs, ICRC1, + MAINNET_ICRC1_LEDGER_CANISTER_ID, Tokens +}; -use candid::{CandidType, Nat, Principal}; +use candid::{CandidType, Principal}; -use ic_ledger_types::{Timestamp, Tokens}; use std::convert::From; -use std::time::{SystemTime, UNIX_EPOCH}; - use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, CandidType, PartialEq)] @@ -24,16 +24,15 @@ pub type DepositReceipt = Result; pub async fn deposit_impl() -> DepositReceipt { // FIXME check caller equals the owner who initialized the bounty. let caller = caller(); - let icrc1_token_canister_id = - Principal::from_text(MAINNET_ICRC1_LEDGER_CANISTER_ID) - .expect("Wrong MAINNET_ICRC1_LEDGER_CANISTER_ID"); - + let icrc1_token_canister_id = Principal::from_text(MAINNET_ICRC1_LEDGER_CANISTER_ID) + .expect("Wrong MAINNET_ICRC1_LEDGER_CANISTER_ID"); + return deposit_icrc1(caller, icrc1_token_canister_id).await; } async fn deposit_icrc1( caller: Principal, - icrc1_token_canister_id: Principal + icrc1_token_canister_id: Principal, ) -> Result { let icrc1_token = ICRC1::new(icrc1_token_canister_id); let icrc1_token_fee = icrc1_token.get_fee().await; @@ -42,58 +41,53 @@ async fn deposit_icrc1( // depends on: // > dfx canister call icrc1_ledger_canister icrc2_approve "(record { amount = 100_000; spender = record{owner = principal \"SPENDER_PRINCIPAL\";} })" - let allowance_args = - AllowanceArgs { - account: Account { owner: caller, subaccount : None }, - spender : Account { owner: id(), subaccount : None } - }; + let allowance_args = AllowanceArgs { + account: Account { + owner: caller, + subaccount: None, + }, + spender: Account { + owner: id(), + subaccount: None, + }, + }; let allowance = icrc1_token.allowance(allowance_args).await; - let available = - ToPrimitive::to_u64(&allowance.allowance.0) - .expect("Nat value is too large for u64") - - Tokens::e8s(&icrc1_token_fee); - - let nanoseconds_since_epoch = - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_nanos() as u64; - - let transfer_from_args = - TransferFromArgs{ - // TODO check or FIXME - spender_subaccount: Some(From::from(caller)), - from: Account { - owner: caller, - subaccount : Some(From::from(caller)) - }, - to: Account { - owner : bounty_canister_id, - subaccount : Some(From::from(bounty_canister_id)) - }, - amount: Tokens::from_e8s(available), - fee: Some(Tokens::from_e8s(ICRC1_FEE)), - // TODO enhance memo text - memo: Some(String::from("deposit_icrc1").as_bytes().to_vec()), - created_at_time: Some(Timestamp { timestamp_nanos: nanoseconds_since_epoch}) - }; - + let available = allowance.allowance - icrc1_token_fee.clone(); + let amount = ToPrimitive::to_u64(&available.0).expect("amount: Nat value is too large for u64"); + + let transfer_from_args = TransferFromArgs { + // TODO check or FIXME + spender_subaccount: Some(From::from(caller)), + from: Account { + owner: caller, + subaccount: Some(From::from(caller)), + }, + to: Account { + owner: bounty_canister_id, + subaccount: Some(From::from(bounty_canister_id)), + }, + amount: From::from(amount), + fee: Some(icrc1_token_fee), + // TODO enhance memo text + memo: None, + created_at_time: None, + }; + return icrc1_token .transfer_from(transfer_from_args) .await - .map(|_| Tokens::from_e8s(available)) - .map_err(|error| - DepositErr::TransferFailure{reason: error.to_string()} - ); + .map(|_| From::from(amount)) + .map_err(|error| DepositErr::TransferFailure { + reason: error.to_string(), + }); } -pub async fn deposit_direct_impl(amount: Nat) -> DepositReceipt { +pub async fn deposit_direct_impl(amount: u64) -> DepositReceipt { // FIXME check caller equals the owner who initialized the bounty. let caller = caller(); - let icrc1_ledger_canister_id = - Principal::from_text(MAINNET_ICRC1_LEDGER_CANISTER_ID) - .expect("Wrong MAINNET_ICRC1_LEDGER_CANISTER_ID"); + let icrc1_ledger_canister_id = Principal::from_text(MAINNET_ICRC1_LEDGER_CANISTER_ID) + .expect("Wrong MAINNET_ICRC1_LEDGER_CANISTER_ID"); return deposit_direct_icrc1(caller, icrc1_ledger_canister_id, amount).await; } @@ -101,44 +95,35 @@ pub async fn deposit_direct_impl(amount: Nat) -> DepositReceipt { async fn deposit_direct_icrc1( caller: Principal, icrc1_token_canister_id: Principal, - amount: Nat + amount: u64, ) -> Result { let icrc1_token = ICRC1::new(icrc1_token_canister_id); let icrc1_token_fee = icrc1_token.get_fee().await; let bounty_canister_id = id(); - let available = - ToPrimitive::to_u64(&amount.0) - .expect("Nat value is too large for u64") - - Tokens::e8s(&icrc1_token_fee); - - let nanoseconds_since_epoch = - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_nanos() as u64; - - let transfer_args = - TransferArg{ - // TODO check or FIXME - from_subaccount: Some(From::from(caller)), - to: Account { - owner : bounty_canister_id, - subaccount : Some(From::from(bounty_canister_id)) - }, - amount: Tokens::from_e8s(available), - fee: Some(Tokens::from_e8s(ICRC1_FEE)), - // TODO enhance memo text - memo: Some(String::from("deposit_icrc1").as_bytes().to_vec()), - created_at_time: Some(Timestamp { timestamp_nanos: nanoseconds_since_epoch}) - }; - + let available = amount - icrc1_token_fee.clone(); + let amount = ToPrimitive::to_u64(&available.0).expect("amount: Nat value is too large for u64"); + + let transfer_args = TransferArg { + // TODO check or FIXME + from_subaccount: Some(From::from(caller)), + to: Account { + owner: bounty_canister_id, + subaccount: Some(From::from(bounty_canister_id)), + }, + amount: From::from(amount), + fee: Some(icrc1_token_fee), + // TODO enhance memo text + memo: None, + created_at_time: None, + }; + return icrc1_token .transfer(transfer_args) .await - .map(|_| Tokens::from_e8s(available)) - .map_err(|error| - DepositErr::TransferFailure{reason: error.to_string()} - ); + .map(|_| From::from(amount)) + .map_err(|error| DepositErr::TransferFailure { + reason: error.to_string(), + }); } diff --git a/bounty/src/api/icrc1.rs b/bounty/src/api/icrc1.rs index b54d43c..5866383 100644 --- a/bounty/src/api/icrc1.rs +++ b/bounty/src/api/icrc1.rs @@ -1,12 +1,18 @@ use candid::{CandidType, Nat, Principal}; +use ic_ledger_types::{Memo, Subaccount}; use serde::{Deserialize, Serialize}; -use ic_ledger_types::{Timestamp, Subaccount, Tokens}; use ic_cdk::api::call::call; type BlockIndex = Nat; use std::fmt::{Display, Formatter}; +// pub type Blob = Vec; + +pub type Tokens = Nat; + +pub type Timestamp = u64; + #[derive(Debug, Serialize, Deserialize, CandidType, PartialEq)] pub enum TransferError { BadFee { expected_fee : Tokens }, @@ -31,7 +37,7 @@ impl TransferError { TransferError::TooOld => String::from("Transaction too old"), TransferError::CreatedInFuture { ledger_time } => - format!("Created in the future: {}", ledger_time.timestamp_nanos.to_string()), + format!("Created in the future: {}", ledger_time.to_string()), TransferError::TemporarilyUnavailable => String::from("Ledger temporarily unavailable"), TransferError::Duplicate { duplicate_of } => @@ -71,7 +77,7 @@ impl TransferFromError { TransferFromError::TooOld => String::from("Transaction too old"), TransferFromError::CreatedInFuture { ledger_time } => - format!("Created in the future: {}", ledger_time.timestamp_nanos.to_string()), + format!("Created in the future: {}", ledger_time.to_string()), TransferFromError::TemporarilyUnavailable => String::from("Ledger temporarily unavailable"), TransferFromError::Duplicate { duplicate_of } => @@ -99,7 +105,7 @@ impl Display for TransferFromError { } TransferFromError::TooOld => write!(f, "Transaction too old"), TransferFromError::CreatedInFuture { ledger_time } => { - write!(f, "Created in the future: {}", ledger_time.timestamp_nanos.to_string()) + write!(f, "Created in the future: {}", ledger_time.to_string()) } TransferFromError::TemporarilyUnavailable => { write!(f, "Ledger temporarily unavailable") @@ -116,8 +122,6 @@ impl Display for TransferFromError { pub type TransferFromResult = Result; -pub type Blob = Vec; - #[derive(Debug, Serialize, Deserialize, CandidType)] pub struct Account { pub owner : Principal, @@ -130,7 +134,7 @@ pub struct TransferArg { pub to : Account, pub amount : Tokens, pub fee : Option, - pub memo : Option, + pub memo : Option, pub created_at_time: Option } @@ -141,7 +145,7 @@ pub struct TransferFromArgs { pub to : Account, pub amount : Tokens, pub fee : Option, - pub memo : Option, + pub memo : Option, pub created_at_time: Option } @@ -157,7 +161,6 @@ pub struct Allowance { pub expires_at : Option } -pub const ICRC1_FEE: u64 = 10_000; pub const MAINNET_ICRC1_LEDGER_CANISTER_ID: &str = "mxzaz-hqaaa-aaaar-qaada-cai"; pub struct ICRC1 { @@ -170,18 +173,19 @@ impl ICRC1 { } pub async fn transfer(&self, args: TransferArg) -> TransferResult { - let argument = ( - args.from_subaccount, - args.to, - args.amount, - args.fee, - args.memo, - args.created_at_time - ); let call_result: Result<(TransferResult,), _> = - call(self.principal, "icrc1_transfer", argument).await; - - call_result.unwrap().0 + call(self.principal, "icrc1_transfer", (args,)).await; + match call_result { + Ok((transfer_from_result,)) => { + // Handle the success case + return transfer_from_result; + } + Err(err) => { + // Handle the error case + println!("Transfer failed: {:?}", err); + panic!("Transfer failed: {:?}", err); + } + } } pub async fn transfer_from(&self, args: TransferFromArgs) -> TransferFromResult { @@ -208,8 +212,8 @@ impl ICRC1 { return call_result.unwrap().0; } - pub async fn get_fee(&self) -> Tokens { - let call_result: Result<(Tokens,), _> = + pub async fn get_fee(&self) -> Nat { + let call_result: Result<(Nat,), _> = call(self.principal, "icrc1_fee", ()).await; call_result.unwrap().0 diff --git a/bounty/src/lib.rs b/bounty/src/lib.rs index 084b332..764d6e9 100644 --- a/bounty/src/lib.rs +++ b/bounty/src/lib.rs @@ -1,4 +1,4 @@ -use candid::{Nat, Principal}; +use candid::Principal; mod api { pub mod state; @@ -29,7 +29,7 @@ async fn deposit() -> DepositReceipt { } #[ic_cdk::update] -async fn deposit_direct(amount: Nat) -> DepositReceipt { +async fn deposit_direct(amount: u64) -> DepositReceipt { return deposit_direct_impl(amount).await; } diff --git a/make/install_ledger.sh b/make/install_ledger.sh index 64ecf8e..b2a0636 100755 --- a/make/install_ledger.sh +++ b/make/install_ledger.sh @@ -11,9 +11,11 @@ NUM_OF_BLOCK_TO_ARCHIVE=1000 CYCLE_FOR_ARCHIVE_CREATION=10000000000000 FEATURE_FLAGS=true -MINTER=$(dfx identity get-principal) +# FIXME: create propper identity +MINTER="t2y5w-qp34w-qixaj-s67wp-syrei-5yqse-xbed6-z5nsd-fszmf-izgt2-lqe" dfx identity use default DEFAULT=$(dfx identity get-principal) +# FIXME: create propper identity ARCHIVE_CONTROLLER=$(dfx identity get-principal) dfx canister install icrc1_ledger --mode reinstall --yes --argument "(variant { Init = diff --git a/make/test/deposit.sh b/make/test/deposit.sh new file mode 100755 index 0000000..e1d38c1 --- /dev/null +++ b/make/test/deposit.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +CALLER=$(dfx identity get-principal) +BOUNTY="bd3sg-teaaa-aaaaa-qaaba-cai" + +# Call the bounty canister to deposit direct from caller and capture the output +echo "Calling deposit on bounty canister..." + +# check initial balances +echo "Caller initial balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${CALLER}\"; })" +echo "Bounty initial balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${BOUNTY}\"; })" + +# deposit +dfx canister call icrc1_ledger icrc2_approve "(record { amount = 100_000; spender = record{owner = principal \"${BOUNTY}\";} })" +dfx canister call bounty deposit + +# check final balances +echo "Caller final balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${CALLER}\"; })" +echo "Bounty final balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${BOUNTY}\"; })" diff --git a/make/test/deposit_direct.sh b/make/test/deposit_direct.sh new file mode 100755 index 0000000..effb3a7 --- /dev/null +++ b/make/test/deposit_direct.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +CALLER=$(dfx identity get-principal) +BOUNTY="bd3sg-teaaa-aaaaa-qaaba-cai" + +# Call the bounty canister to deposit direct from caller and capture the output +echo "Calling deposit_direct on bounty canister..." + +# check initial balances +echo "Caller initial balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${CALLER}\"; })" +echo "Bounty initial balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${BOUNTY}\"; })" + +# deposit direct +dfx canister call bounty deposit_direct '(100_000,)' + +# check final balances +echo "Caller final balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${CALLER}\"; })" +echo "Bounty final balance:" +dfx canister call icrc1_index icrc1_balance_of "(record{owner = principal \"${BOUNTY}\"; })"