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

refactor(api): Move code out of lib.rs #12

Merged
merged 7 commits into from
Sep 17, 2024
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
19 changes: 19 additions & 0 deletions src/api/src/caller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Types used primarily by the caller of the payment API.
use candid::{CandidType, Deserialize, Principal};
pub use cycles_ledger_client::Account;

/// How a caller states that they will pay.
#[derive(Debug, CandidType, Deserialize, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum PaymentType {
/// The caller is paying with cycles attached to the call.
///
/// Note: This is not available for ingress messages.
///
/// Note: The API does not require additional arguments to support this payment type.
AttachedCycles,
/// The caller is paying with cycles from their main account on the cycles ledger.
CallerIcrc2Cycles,
/// A patron is paying, on behalf of the caller, from their main account on the cycles ledger.
PatronIcrc2Cycles(Principal),
}
21 changes: 21 additions & 0 deletions src/api/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Payment API error types.
use candid::{CandidType, Deserialize, Principal};
pub use cycles_ledger_client::Account;
use cycles_ledger_client::WithdrawFromError;

#[derive(Debug, CandidType, Deserialize, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum PaymentError {
UnsupportedPaymentType,
LedgerUnreachable {
ledger: Principal,
},
LedgerError {
ledger: Principal,
error: WithdrawFromError,
},
InsufficientFunds {
needed: u64,
available: u64,
},
}
57 changes: 7 additions & 50 deletions src/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,13 @@
use candid::{CandidType, Deserialize, Principal};
use candid::Principal;
pub use cycles_ledger_client::Account;
use cycles_ledger_client::WithdrawFromError;
use serde_bytes::ByteBuf;

#[derive(Debug, CandidType, Deserialize, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum PaymentError {
UnsupportedPaymentType,
LedgerUnreachable {
ledger: Principal,
},
LedgerError {
ledger: Principal,
error: WithdrawFromError,
},
InsufficientFunds {
needed: u64,
available: u64,
},
}

#[derive(Debug, CandidType, Deserialize, Copy, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum PaymentType {
/// The caller is paying with cycles attached to the call.
///
/// Note: This is not available for ingress messages.
///
/// Note: The API does not require additional arguments to support this payment type.
AttachedCycles,
/// The caller is paying with cycles from their main account on the (by default cycles) ledger.
CallerIcrc2,
/// A patron is paying, on behalf of the caller, from their main account on the (by default cycles) ledger.
PatronIcrc2(Principal),
}
pub mod caller;
pub mod error;
pub mod vendor;
pub use caller::PaymentType;
pub use error::PaymentError;
pub use vendor::Icrc2Payer;

pub fn principal2account(principal: &Principal) -> ByteBuf {
// TODO: This is NOT the right way.
Expand All @@ -43,20 +17,3 @@ pub fn principal2account(principal: &Principal) -> ByteBuf {
}
ByteBuf::from(ans)
}

/// User's payment details for an ICRC2 payment.
#[derive(Debug, CandidType, Deserialize, Clone, Eq, PartialEq)]
pub struct Icrc2Payer {
/// The customer's principal and (optionally) subaccount.
///
/// By default, the caller's main account is used.
pub account: Option<Account>,
/// The spender, if different from the payer.
pub spender_subaccount: Option<serde_bytes::ByteBuf>,
/// The ledger canister ID.
///
/// Note: This is included in order to improve error messages if the caller tries to use the wrong ledger.
pub ledger_canister_id: Option<Principal>,
/// Corresponds to the `created_at_time` field in ICRC2.
pub created_at_time: Option<u64>,
}
20 changes: 20 additions & 0 deletions src/api/src/vendor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Types used primartily by the vendor of the payment API.
use candid::{CandidType, Deserialize, Principal};
pub use cycles_ledger_client::Account;

/// User's payment details for an ICRC2 payment.
#[derive(Debug, CandidType, Deserialize, Clone, Eq, PartialEq)]
pub struct Icrc2Payer {
/// The customer's principal and (optionally) subaccount.
///
/// By default, the caller's main account is used.
pub account: Option<Account>,
/// The spender, if different from the payer.
pub spender_subaccount: Option<serde_bytes::ByteBuf>,
/// The ledger canister ID.
///
/// Note: This is included in order to improve error messages if the caller tries to use the wrong ledger.
pub ledger_canister_id: Option<Principal>,
/// Corresponds to the `created_at_time` field in ICRC2.
pub created_at_time: Option<u64>,
}
29 changes: 19 additions & 10 deletions src/example/paid_service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ async fn cost_1000_attached_cycles() -> Result<String, PaymentError> {
/// An API method that requires 1 billion cycles using an ICRC-2 approve with default parameters.
#[update()]
async fn cost_1b_icrc2_from_caller() -> Result<String, PaymentError> {
let mut guard = Icrc2CyclesPaymentGuard::new();
guard.ledger_canister_id = payment_ledger();
let guard = Icrc2CyclesPaymentGuard {
ledger_canister_id: payment_ledger(),
..Icrc2CyclesPaymentGuard::default()
};
guard.deduct(1_000_000_000).await?;
Ok("Yes, you paid 1 billion cycles!".to_string())
}
Expand All @@ -46,16 +48,23 @@ async fn cost_1b(payment: PaymentType) -> Result<String, PaymentError> {
PaymentType::AttachedCycles => {
AttachedCyclesPayment::default().deduct(fee).await?;
}
PaymentType::CallerIcrc2 => {
let mut guard = Icrc2CyclesPaymentGuard::new();
guard.ledger_canister_id = payment_ledger();
PaymentType::CallerIcrc2Cycles => {
let guard = Icrc2CyclesPaymentGuard {
ledger_canister_id: payment_ledger(),
..Icrc2CyclesPaymentGuard::default()
};
guard.deduct(fee).await?;
}
PaymentType::PatronIcrc2(patron) => {
let mut guard = Icrc2CyclesPaymentGuard::new();
guard.ledger_canister_id = payment_ledger();
guard.payer_account.owner = patron;
guard.spender_subaccount = Some(principal2account(&ic_cdk::caller()));
PaymentType::PatronIcrc2Cycles(patron) => {
let guard = Icrc2CyclesPaymentGuard {
ledger_canister_id: payment_ledger(),
payer_account: ic_papi_api::Account {
owner: patron,
subaccount: None,
},
spender_subaccount: Some(principal2account(&ic_cdk::caller())),
..Icrc2CyclesPaymentGuard::default()
};
guard.deduct(fee).await?;
}
_ => return Err(PaymentError::UnsupportedPaymentType),
Expand Down
6 changes: 3 additions & 3 deletions src/example/paid_service/tests/it/icrc2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ fn caller_pays_by_named_icrc2() {
// Call the API
let response: Result<String, PaymentError> = setup
.paid_service
.update(setup.user, api_method, PaymentType::CallerIcrc2)
.update(setup.user, api_method, PaymentType::CallerIcrc2Cycles)
.expect("Failed to call the paid service");
assert_eq!(
response,
Expand All @@ -394,7 +394,7 @@ fn caller_pays_by_named_icrc2() {
.update(
setup.unauthorized_user,
api_method,
PaymentType::CallerIcrc2,
PaymentType::CallerIcrc2Cycles,
)
.expect("Failed to call the paid service");
assert_eq!(
Expand Down Expand Up @@ -439,7 +439,7 @@ fn patron_pays_by_named_icrc2() {
// Ok, now we should be able to make an API call with EITHER an ICRC-2 approve or attached cycles, by declaring the payment type.
// In this test, we will exercise the ICRC-2 approve.
let api_method = "cost_1b";
let payment_arg = PaymentType::PatronIcrc2(setup.user);
let payment_arg = PaymentType::PatronIcrc2Cycles(setup.user);
let api_fee = 1_000_000_000u128;
let repetitions = 3;
// Pre-approve payments
Expand Down
18 changes: 9 additions & 9 deletions src/guard/src/guards/icrc2_cycles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ impl Icrc2CyclesPaymentGuard {
)
.expect("Failed to parse cycles ledger canister ID")
}
/// A default payment guard for ICRC-2 cycles.
pub fn new() -> Self {
}

impl Default for Icrc2CyclesPaymentGuard {
fn default() -> Self {
Self {
payer_account: Self::default_account(),
ledger_canister_id: Self::default_cycles_ledger(),
Expand Down Expand Up @@ -61,16 +63,14 @@ impl PaymentGuard for Icrc2CyclesPaymentGuard {
}
})?
.0
.map_err(|e| {
.map_err(|error| {
eprintln!(
"Failed to withdraw from ledger canister at {}: {e:?}",
"Failed to withdraw from ledger canister at {}: {error:?}",
self.ledger_canister_id
);
match e {
error => PaymentError::LedgerError {
ledger: self.ledger_canister_id,
error,
},
PaymentError::LedgerError {
ledger: self.ledger_canister_id,
error,
}
})
.map(|_| ())
Expand Down
Loading