Skip to content

Commit

Permalink
feat: use cycles ledger in deposit-cycles (#3626)
Browse files Browse the repository at this point in the history
  • Loading branch information
sesi200 authored Mar 5, 2024
1 parent 90fc510 commit dd20e6f
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 38 deletions.
58 changes: 48 additions & 10 deletions e2e/tests-dfx/cycles-ledger.bash
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ add_cycles_ledger_canisters_to_project() {
}

deploy_cycles_ledger() {
assert_command dfx deploy cycles-ledger --specified-id "um5iw-rqaaa-aaaaq-qaaba-cai" --argument '(variant { Init = record { max_transactions_per_request = 100; index_id = null; } })'
assert_command dfx deploy cycles-ledger --specified-id "um5iw-rqaaa-aaaaq-qaaba-cai" --argument '(variant { Init = record { max_blocks_per_request = 100; index_id = null; } })'
assert_command dfx deploy depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000 --specified-id "ul4oc-4iaaa-aaaaq-qaabq-cai"
}

Expand Down Expand Up @@ -355,7 +355,8 @@ current_time_nanoseconds() {
assert_contains "Invalid receiver: $BOB. Make sure the receiver is a canister."
}

@test "top-up" {
@test "top-up and deposit-cycles" {
skip "can't be properly tested with feature flag turned off (CYCLES_LEDGER_ENABLED). TODO(SDK-1331): re-enable this test"
start_and_install_nns

dfx_new
Expand All @@ -376,44 +377,81 @@ current_time_nanoseconds() {
assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver
assert_command dfx canister call depositor deposit "(record {to = record{owner = principal \"$BOB\"; subaccount = opt blob \"$BOB_SUBACCT2_CANDID\"};cycles = 2_700_000_000_000;})" --identity cycle-giver

# shellcheck disable=SC2030
export DFX_DISABLE_AUTO_WALLET=1

# account to canister
assert_command dfx cycles balance --precise --identity bob
assert_eq "2400000000000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_000_000 Cycles"

assert_command dfx cycles top-up e2e_project_backend 100000 --identity bob

assert_command dfx cycles balance --precise --identity bob
assert_eq "2399899900000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_100_000 Cycles"

assert_command dfx canister deposit-cycles 100000 e2e_project_backend --identity bob
assert_command dfx cycles balance --precise --identity bob
assert_eq "2399799800000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_200_000 Cycles"

# subaccount to canister
assert_command dfx cycles balance --precise --identity bob --subaccount "$BOB_SUBACCT1"
assert_eq "2600000000000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_100_000 Cycles"
assert_contains "Balance: 3_100_000_200_000 Cycles"

assert_command dfx cycles top-up e2e_project_backend 300000 --identity bob --from-subaccount "$BOB_SUBACCT1"

assert_command dfx cycles balance --precise --identity bob --subaccount "$BOB_SUBACCT1"
assert_eq "2599899700000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_400_000 Cycles"
assert_contains "Balance: 3_100_000_500_000 Cycles"

assert_command dfx canister deposit-cycles 300000 e2e_project_backend --identity bob --from-subaccount "$BOB_SUBACCT1"
assert_command dfx cycles balance --precise --identity bob --subaccount "$BOB_SUBACCT1"
assert_eq "2599799400000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_800_000 Cycles"

# subaccount to canister - by canister id
assert_command dfx cycles balance --precise --identity bob --subaccount "$BOB_SUBACCT2"
assert_eq "2700000000000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_000_400_000 Cycles"
assert_contains "Balance: 3_100_000_800_000 Cycles"

assert_command dfx cycles top-up "$(dfx canister id e2e_project_backend)" 600000 --identity bob --from-subaccount "$BOB_SUBACCT2"

assert_command dfx cycles balance --precise --identity bob --subaccount "$BOB_SUBACCT2"
assert_eq "2699899400000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_001_000_000 Cycles"
assert_contains "Balance: 3_100_001_400_000 Cycles"

assert_command dfx canister deposit-cycles 600000 "$(dfx canister id e2e_project_backend)" --identity bob --from-subaccount "$BOB_SUBACCT2"
assert_command dfx cycles balance --precise --identity bob --subaccount "$BOB_SUBACCT2"
assert_eq "2699798800000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_002_000_000 Cycles"

# deduplication
t=$(current_time_nanoseconds)
assert_command dfx cycles balance --precise --identity bob
assert_eq "2399799800000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_002_000_000 Cycles"

assert_command dfx canister deposit-cycles 100000 e2e_project_backend --identity bob --created-at-time "$t"
assert_command dfx cycles balance --precise --identity bob
assert_eq "2399699700000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_002_100_000 Cycles"

assert_command dfx canister deposit-cycles 100000 e2e_project_backend --identity bob --created-at-time "$t"
assert_command dfx cycles balance --precise --identity bob
assert_eq "2399699700000 cycles."
assert_command dfx canister status e2e_project_backend
assert_contains "Balance: 3_100_002_100_000 Cycles"
}

@test "top-up deduplication" {
Expand Down Expand Up @@ -550,7 +588,7 @@ current_time_nanoseconds() {

# using dfx canister create
dfx identity use alice
# shellcheck disable=SC2030
# shellcheck disable=SC2030,SC2031
export DFX_DISABLE_AUTO_WALLET=1
t=$(current_time_nanoseconds)
assert_command dfx canister create e2e_project_backend --with-cycles 1T --created-at-time "$t"
Expand Down
2 changes: 1 addition & 1 deletion e2e/utils/cycles-ledger.bash
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CYCLES_LEDGER_VERSION="0.2.8"
CYCLES_LEDGER_VERSION="0.3.0"

build_artifact_url() {
echo "https://github.com/dfinity/cycles-ledger/releases/download/cycles-ledger-v$CYCLES_LEDGER_VERSION/${1}"
Expand Down
103 changes: 89 additions & 14 deletions src/dfx/src/commands/canister/deposit_cycles.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use crate::lib::environment::Environment;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::lib::error::DfxResult;
use crate::lib::identity::wallet::get_or_create_wallet_canister;
use crate::lib::operations::canister;
use crate::lib::operations::cycles_ledger::CYCLES_LEDGER_ENABLED;
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::util::clap::parsers::cycle_amount_parser;
use anyhow::Context;
use crate::lib::{environment::Environment, operations::cycles_ledger};
use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser};
use anyhow::{bail, Context};
use candid::Principal;
use clap::Parser;
use dfx_core::identity::CallSender;
use slog::info;
use icrc_ledger_types::icrc1::account::Subaccount;
use slog::{debug, info, warn};

/// Deposit cycles into the specified canister.
#[derive(Parser)]
Expand All @@ -25,13 +29,26 @@ pub struct DepositCyclesOpts {
/// Deposit cycles to all of the canisters configured in the dfx.json file.
#[arg(long, required_unless_present("canister"))]
all: bool,

/// Use cycles from this subaccount.
//TODO(SDK-1331): unhide
#[arg(long, value_parser = icrc_subaccount_parser, hide = true)]
from_subaccount: Option<Subaccount>,

/// Transaction timestamp, in nanoseconds, for use in controlling transaction deduplication, default is system time.
/// https://internetcomputer.org/docs/current/developer-docs/integrations/icrc-1/#transaction-deduplication-
//TODO(SDK-1331): unhide
#[arg(long, hide = true)]
created_at_time: Option<u64>,
}

async fn deposit_cycles(
env: &dyn Environment,
canister: &str,
call_sender: &CallSender,
cycles: u128,
created_at_time: u64,
from_subaccount: Option<Subaccount>,
) -> DfxResult {
let log = env.get_logger();
let canister_id_store = env.get_canister_id_store()?;
Expand All @@ -40,7 +57,26 @@ async fn deposit_cycles(

info!(log, "Depositing {} cycles onto {}", cycles, canister,);

canister::deposit_cycles(env, canister_id, call_sender, cycles).await?;
match call_sender {
CallSender::SelectedId => {
if !CYCLES_LEDGER_ENABLED {
// should be unreachable
bail!("No wallet configured");
}
cycles_ledger::withdraw(
env.get_agent(),
env.get_logger(),
canister_id,
cycles,
created_at_time,
from_subaccount,
)
.await?;
}
CallSender::Wallet(_) => {
canister::deposit_cycles(env, canister_id, call_sender, cycles).await?
}
};

let status = canister::get_canister_status(env, canister_id, call_sender).await;
if let Ok(status) = status {
Expand All @@ -64,31 +100,70 @@ pub async fn exec(

let proxy_sender;

// choose default wallet if no wallet is specified
if call_sender == &CallSender::SelectedId {
let wallet = get_or_create_wallet_canister(
match get_or_create_wallet_canister(
env,
env.get_network_descriptor(),
env.get_selected_identity().expect("No selected identity"),
)
.await?;
proxy_sender = CallSender::Wallet(*wallet.canister_id_());
call_sender = &proxy_sender;
.await
{
Ok(wallet) => {
proxy_sender = CallSender::Wallet(*wallet.canister_id_());
call_sender = &proxy_sender;
}
Err(err) => {
if CYCLES_LEDGER_ENABLED && matches!(err, crate::lib::identity::wallet::GetOrCreateWalletCanisterError::NoWalletConfigured { .. }) {
debug!(env.get_logger(), "No wallet configured");
} else {
bail!(err)
}
},
}
}

let created_at_time = opts.created_at_time.unwrap_or_else(|| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
if matches!(call_sender, CallSender::SelectedId) {
warn!(
env.get_logger(),
"If you retry this operation, use --created-at-time {}", now
);
}
now
});

// amount has been validated by cycle_amount_validator
let cycles = opts.cycles;

if let Some(canister) = opts.canister.as_deref() {
deposit_cycles(env, canister, call_sender, cycles).await
deposit_cycles(
env,
canister,
call_sender,
cycles,
created_at_time,
opts.from_subaccount,
)
.await
} else if opts.all {
let config = env.get_config_or_anyhow()?;

if let Some(canisters) = &config.get_config().canisters {
for canister in canisters.keys() {
deposit_cycles(env, canister, call_sender, cycles)
.await
.with_context(|| format!("Failed to deposit cycles into {}.", canister))?;
deposit_cycles(
env,
canister,
call_sender,
cycles,
created_at_time,
opts.from_subaccount,
)
.await
.with_context(|| format!("Failed to deposit cycles into {}.", canister))?;
}
}
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/dfx/src/commands/cycles/top_up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub async fn exec(env: &dyn Environment, opts: TopUpOpts) -> DfxResult {
);

let to = get_canister_id(env, &opts.to)?;
let result = cycles_ledger::send(
let result = cycles_ledger::withdraw(
agent,
env.get_logger(),
to,
Expand Down
2 changes: 1 addition & 1 deletion src/dfx/src/lib/cycles_ledger_types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// TODO(FI-1022): Import types from cycles ledger crate once available
pub mod create_canister;
pub mod deposit;
pub mod send;
pub mod withdraw;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::Deserialize;
pub type NumCycles = Nat;

#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct SendArgs {
pub struct WithdrawArgs {
#[serde(default)]
pub from_subaccount: Option<Subaccount>,
pub to: Principal,
Expand All @@ -18,7 +18,7 @@ pub struct SendArgs {
}

#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum SendError {
pub enum WithdrawError {
BadFee {
expected_fee: NumCycles,
},
Expand All @@ -33,7 +33,7 @@ pub enum SendError {
Duplicate {
duplicate_of: BlockIndex,
},
FailedToSend {
FailedToWithdraw {
fee_block: Option<Nat>,
rejection_code: RejectionCode,
rejection_reason: String,
Expand Down
16 changes: 8 additions & 8 deletions src/dfx/src/lib/operations/cycles_ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::lib::cycles_ledger_types::create_canister::{
CmcCreateCanisterArgs, CreateCanisterArgs, CreateCanisterError, CreateCanisterSuccess,
};
use crate::lib::cycles_ledger_types::deposit::DepositArg;
use crate::lib::cycles_ledger_types::send::SendError;
use crate::lib::cycles_ledger_types::withdraw::WithdrawError;
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::ic_attributes::CanisterSettings as DfxCanisterSettings;
Expand Down Expand Up @@ -39,7 +39,7 @@ const ICRC1_BALANCE_OF_METHOD: &str = "icrc1_balance_of";
const ICRC1_TRANSFER_METHOD: &str = "icrc1_transfer";
const ICRC2_APPROVE_METHOD: &str = "icrc2_approve";
const ICRC2_TRANSFER_FROM_METHOD: &str = "icrc2_transfer_from";
const SEND_METHOD: &str = "send";
const WITHDRAW_METHOD: &str = "withdraw";
const CREATE_CANISTER_METHOD: &str = "create_canister";
const CYCLES_LEDGER_DEPOSIT_METHOD: &str = "deposit";
const CYCLES_LEDGER_CANISTER_ID: Principal =
Expand Down Expand Up @@ -256,7 +256,7 @@ pub async fn approve(
Ok(block_index)
}

pub async fn send(
pub async fn withdraw(
agent: &Agent,
logger: &Logger,
to: Principal,
Expand All @@ -271,30 +271,30 @@ pub async fn send(

let retry_policy = ExponentialBackoff::default();
let block_index: BlockIndex = retry(retry_policy, || async {
let arg = cycles_ledger_types::send::SendArgs {
let arg = cycles_ledger_types::withdraw::WithdrawArgs {
from_subaccount,
to,
created_at_time: Some(created_at_time),
amount: Nat::from(amount),
};
match canister
.update(SEND_METHOD)
.update(WITHDRAW_METHOD)
.with_arg(arg)
.build()
.map(|result: (Result<BlockIndex, SendError>,)| (result.0,))
.map(|result: (Result<BlockIndex, WithdrawError>,)| (result.0,))
.call_and_wait()
.await
.map(|(result,)| result)
{
Ok(Ok(block_index)) => Ok(block_index),
Ok(Err(SendError::Duplicate { duplicate_of })) => {
Ok(Err(WithdrawError::Duplicate { duplicate_of })) => {
info!(
logger,
"transaction is a duplicate of another transaction in block {}", duplicate_of
);
Ok(duplicate_of)
}
Ok(Err(SendError::InvalidReceiver { receiver })) => {
Ok(Err(WithdrawError::InvalidReceiver { receiver })) => {
Err(backoff::Error::permanent(anyhow!(
"Invalid receiver: {}. Make sure the receiver is a canister.",
receiver
Expand Down

0 comments on commit dd20e6f

Please sign in to comment.