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

feat: use cycles ledger to create canisters #3473

Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 0 additions & 5 deletions e2e/tests-dfx/create.bash
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,3 @@ teardown() {
assert_command dfx wallet upgrade --identity alice
assert_command dfx canister create --all --controller alice --controller bob --identity alice
}

@test "canister-create on mainnet without wallet does not propagate the 404" {
assert_command_fail dfx deploy --network ic --no-wallet
assert_match 'dfx ledger create-canister'
}
100 changes: 88 additions & 12 deletions e2e/tests-dfx/cycles-ledger.bash
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ current_time_nanoseconds() {
ALICE_SUBACCT2_CANDID="\9C\9B\9A\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f"
BOB=$(dfx identity get-principal --identity bob)

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --precise
Expand All @@ -58,16 +58,16 @@ current_time_nanoseconds() {


assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 1_700_400_200_150;})" --identity cycle-giver
assert_eq "(record { balance = 1_700_400_200_150 : nat; txid = 0 : nat })"
assert_eq "(record { balance = 1_700_400_200_150 : nat; block_index = 0 : nat })"

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 3_750_000_000_000;})" --identity cycle-giver
assert_eq "(record { balance = 3_750_000_000_000 : nat; txid = 1 : nat })"
assert_eq "(record { balance = 3_750_000_000_000 : nat; block_index = 1 : nat })"

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT2_CANDID\"};cycles = 760_500_000_000;})" --identity cycle-giver
assert_eq "(record { balance = 760_500_000_000 : nat; txid = 2 : nat })"
assert_eq "(record { balance = 760_500_000_000 : nat; block_index = 2 : nat })"

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$BOB\";};cycles = 2_900_000_000_000;})" --identity cycle-giver
assert_eq "(record { balance = 2_900_000_000_000 : nat; txid = 3 : nat })"
assert_eq "(record { balance = 2_900_000_000_000 : nat; block_index = 3 : nat })"


assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --precise --identity alice
Expand Down Expand Up @@ -116,7 +116,7 @@ current_time_nanoseconds() {
BOB=$(dfx identity get-principal --identity bob)
BOB_SUBACCT1="7C7B7A030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_000_000_000_000;})" --identity cycle-giver
Expand Down Expand Up @@ -190,7 +190,7 @@ current_time_nanoseconds() {
ALICE=$(dfx identity get-principal --identity alice)
BOB=$(dfx identity get-principal --identity bob)

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_000_000_000_000;})" --identity cycle-giver
Expand Down Expand Up @@ -249,7 +249,7 @@ current_time_nanoseconds() {
@test "top up canister principal check" {
BOB=$(dfx identity get-principal --identity bob)

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger

assert_command_fail dfx cycles top-up "$BOB" 600000 --identity alice --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)"
assert_contains "Invalid receiver: $BOB. Make sure the receiver is a canister."
Expand All @@ -266,7 +266,7 @@ current_time_nanoseconds() {
BOB_SUBACCT2="6C6B6A030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
BOB_SUBACCT2_CANDID="\6C\6B\6A\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f"

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000

assert_command dfx deploy
Expand Down Expand Up @@ -326,7 +326,7 @@ current_time_nanoseconds() {
BOB_SUBACCT2="6C6B6A030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
BOB_SUBACCT2_CANDID="\6C\6B\6A\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f"

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000

assert_command dfx deploy
Expand Down Expand Up @@ -371,7 +371,7 @@ current_time_nanoseconds() {
ALICE=$(dfx identity get-principal --identity alice)
BOB=$(dfx identity get-principal --identity bob)

assert_command dfx deploy cycles-ledger
assert_command deploy_cycles_ledger
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000

assert_command dfx ledger balance --identity cycle-giver
Expand All @@ -390,7 +390,7 @@ current_time_nanoseconds() {


assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 500_000_000;})" --identity cycle-giver
assert_eq "(record { balance = 500_000_000 : nat; txid = 0 : nat })"
assert_eq "(record { balance = 500_000_000 : nat; block_index = 0 : nat })"

assert_command dfx canister status cycles-depositor
assert_contains "Balance: 9_999_500_000_000 Cycles"
Expand All @@ -416,3 +416,79 @@ current_time_nanoseconds() {
assert_command dfx canister status cycles-depositor
assert_contains "Balance: 9_999_500_100_000 Cycles"
}

@test "canister creation" {
# skip "can't be properly tested with feature flag turned off (`CYCLES_LEDGER_ENABLED`). TODO(SDK-1331): re-enable this test"
dfx_new temporary
add_cycles_ledger_canisters_to_project
install_cycles_ledger_canisters

ALICE=$(dfx identity get-principal --identity alice)
ALICE_SUBACCT1="7C7B7A030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
ALICE_SUBACCT1_CANDID="\7C\7B\7A\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f"

assert_command deploy_cycles_ledger
CYCLES_LEDGER_ID=$(dfx canister id cycles-ledger)
echo "Cycles ledger deployed at id $CYCLES_LEDGER_ID"
assert_command dfx deploy cycles-depositor --argument "(record {ledger_id = principal \"$(dfx canister id cycles-ledger)\"})" --with-cycles 10000000000000
echo "Cycles depositor deployed at id $(dfx canister id cycles-depositor)"

assert_command dfx deploy

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 3_400_000_000_000;})" --identity cycle-giver
assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\"; subaccount = opt blob \"$ALICE_SUBACCT1_CANDID\"};cycles = 2_600_000_000_000;})" --identity cycle-giver

cd ..
dfx_new
# setup done

# using dfx canister create
dfx identity use alice
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" --cycles-ledger-canister-id "$CYCLES_LEDGER_ID"
assert_command dfx canister id e2e_project_backend
E2E_PROJECT_BACKEND_CANISTER_ID=$(dfx canister id e2e_project_backend)
assert_command dfx cycles balance --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" --precise
assert_eq "2399900000000 cycles."
# forget about canister. If --created-at-time is a valid idempotency key we should end up with the same canister id
rm .dfx/local/canister_ids.json
assert_command dfx canister create e2e_project_backend --with-cycles 1T --created-at-time "$t" --cycles-ledger-canister-id "$CYCLES_LEDGER_ID"
assert_command dfx canister id e2e_project_backend
assert_contains "$E2E_PROJECT_BACKEND_CANISTER_ID"
assert_command dfx cycles balance --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" --precise
assert_eq "2399900000000 cycles."
dfx canister stop e2e_project_backend
dfx canister delete e2e_project_backend

assert_command dfx canister create e2e_project_backend --with-cycles 0.5T --from-subaccount "$ALICE_SUBACCT1" --cycles-ledger-canister-id "$CYCLES_LEDGER_ID"
assert_command dfx canister id e2e_project_backend
assert_command dfx cycles balance --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" --subaccount "$ALICE_SUBACCT1" --precise
assert_eq "2099900000000 cycles."

# reset deployment status
rm -r .dfx

# using dfx deploy
t=$(current_time_nanoseconds)
assert_command dfx deploy e2e_project_backend --with-cycles 1T --created-at-time "$t" --cycles-ledger-canister-id "$CYCLES_LEDGER_ID"
assert_command dfx canister id e2e_project_backend
E2E_PROJECT_BACKEND_CANISTER_ID=$(dfx canister id e2e_project_backend)
assert_command dfx cycles balance --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" --precise
assert_eq "1399800000000 cycles."
# reset and forget about canister. If --created-at-time is a valid idempotency key we should end up with the same canister id
dfx canister uninstall-code e2e_project_backend
rm .dfx/local/canister_ids.json
assert_command dfx deploy e2e_project_backend --with-cycles 1T --created-at-time "$t" --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" -vv
assert_command dfx canister id e2e_project_backend
assert_contains "$E2E_PROJECT_BACKEND_CANISTER_ID"
assert_command dfx cycles balance --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" --precise
assert_eq "1399800000000 cycles."
dfx canister stop e2e_project_backend
dfx canister delete e2e_project_backend

assert_command dfx deploy e2e_project_backend --with-cycles 0.5T --from-subaccount "$ALICE_SUBACCT1" --cycles-ledger-canister-id "$CYCLES_LEDGER_ID"
assert_command dfx canister id e2e_project_backend
assert_command dfx cycles balance --cycles-ledger-canister-id "$CYCLES_LEDGER_ID" --subaccount "$ALICE_SUBACCT1" --precise
assert_eq "1599800000000 cycles."
}
1 change: 1 addition & 0 deletions e2e/tests-dfx/error_diagnosis.bash
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ teardown() {
}

@test "Instruct user to set a wallet" {
sesi200 marked this conversation as resolved.
Show resolved Hide resolved
skip "TODO(SDK-1331): remove this test"
dfx_new hello
install_asset greet
assert_command dfx identity new alice --storage-mode plaintext
Expand Down
6 changes: 5 additions & 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.1"
CYCLES_LEDGER_VERSION="0.2.6"

build_artifact_url() {
echo "https://github.com/dfinity/cycles-ledger/releases/download/cycles-ledger-v$CYCLES_LEDGER_VERSION/${1}"
Expand Down Expand Up @@ -34,3 +34,7 @@ install_cycles_ledger_canisters() {
download_cycles_ledger_canisters
cp "$(downloaded_cycles_ledger_canisters_dir)"/* .
}

deploy_cycles_ledger() {
dfx deploy cycles-ledger --argument '(variant { Init = record { max_transactions_per_request = 100; index_id = null; } })'
}
48 changes: 42 additions & 6 deletions src/dfx/src/commands/canister/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ use crate::lib::ic_attributes::{
};
use crate::lib::identity::wallet::get_or_create_wallet_canister;
use crate::lib::operations::canister::create_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 crate::util::clap::parsers::{
compute_allocation_parser, freezing_threshold_parser, memory_allocation_parser,
reserved_cycles_limit_parser,
};
use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser};
use anyhow::{bail, Context};
use byte_unit::Byte;
use candid::Principal as CanisterId;
use clap::{ArgAction, Parser};
use dfx_core::error::identity::instantiate_identity_from_name::InstantiateIdentityFromNameError::GetIdentityPrincipalFailed;
use dfx_core::identity::CallSender;
use ic_agent::Identity as _;
use slog::info;
use icrc_ledger_types::icrc1::account::Subaccount;
use slog::{debug, info};

/// Creates an empty canister and associates the assigned Canister ID to the canister name.
#[derive(Parser)]
Expand Down Expand Up @@ -80,6 +82,23 @@ pub struct CanisterCreateOpts {
/// Bypasses the Wallet canister.
#[arg(long)]
no_wallet: bool,

/// 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>,
sesi200 marked this conversation as resolved.
Show resolved Hide resolved

/// Subaccount of the selected identity to spend cycles from.
//TODO(SDK-1331): unhide
#[arg(long, value_parser = icrc_subaccount_parser, hide = true)]
from_subaccount: Option<Subaccount>,

/// Canister ID of the cycles ledger canister.
/// If not specified, the default cycles ledger canister ID will be used.
// todo: remove this. See https://dfinity.atlassian.net/browse/SDK-1262
#[arg(long, hide = true)]
cycles_ledger_canister_id: Option<CanisterId>,
}

pub async fn exec(
Expand All @@ -102,14 +121,25 @@ pub async fn exec(
&& !matches!(call_sender, CallSender::Wallet(_))
&& !network.is_playground()
{
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 {
debug!(env.get_logger(), "No wallet configured.");
sesi200 marked this conversation as resolved.
Show resolved Hide resolved
} else {
return Err(err.into());
}
}
};
}

let controllers: Option<Vec<_>> = opts
Expand Down Expand Up @@ -186,13 +216,16 @@ pub async fn exec(
with_cycles,
opts.specified_id,
call_sender,
opts.from_subaccount,
CanisterSettings {
controllers,
compute_allocation,
memory_allocation,
freezing_threshold,
reserved_cycles_limit,
},
opts.created_at_time,
opts.cycles_ledger_canister_id,
)
.await?;
Ok(())
Expand Down Expand Up @@ -254,13 +287,16 @@ pub async fn exec(
with_cycles,
None,
call_sender,
opts.from_subaccount,
CanisterSettings {
controllers: controllers.clone(),
compute_allocation,
memory_allocation,
freezing_threshold,
reserved_cycles_limit,
},
opts.created_at_time,
opts.cycles_ledger_canister_id,
)
.await?;
}
Expand Down
32 changes: 31 additions & 1 deletion src/dfx/src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::lib::operations::canister::deploy_canisters::DeployMode::{
};
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::lib::{environment::Environment, named_canister};
use crate::util::clap::parsers::cycle_amount_parser;
use crate::util::clap::parsers::{cycle_amount_parser, icrc_subaccount_parser};
use anyhow::{anyhow, bail, Context};
use candid::Principal;
use clap::Parser;
Expand All @@ -17,10 +17,12 @@ use dfx_core::config::model::network_descriptor::NetworkDescriptor;
use dfx_core::identity::CallSender;
use fn_error_context::context;
use ic_utils::interfaces::management_canister::builders::InstallMode;
use icrc_ledger_types::icrc1::account::Subaccount;
use slog::info;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::runtime::Runtime;
use url::Host::Domain;
use url::Url;
Expand Down Expand Up @@ -99,6 +101,23 @@ pub struct DeployOpts {
/// Compute evidence and compare it against expected evidence
#[arg(long, conflicts_with("by_proposal"))]
compute_evidence: bool,

/// 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>,
sesi200 marked this conversation as resolved.
Show resolved Hide resolved

/// Subaccount of the selected identity to spend cycles from.
//TODO(SDK-1331): unhide
#[arg(long, value_parser = icrc_subaccount_parser, hide = true)]
from_subaccount: Option<Subaccount>,

/// Canister ID of the cycles ledger canister.
/// If not specified, the default cycles ledger canister ID will be used.
// todo: remove this. See https://dfinity.atlassian.net/browse/SDK-1262
#[arg(long, hide = true)]
cycles_ledger_canister_id: Option<Principal>,
}

pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
Expand Down Expand Up @@ -156,6 +175,14 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {

let call_sender = CallSender::from(&opts.wallet)
.map_err(|e| anyhow!("Failed to determine call sender: {}", e))?;
let created_at_time = opts.created_at_time.or_else(|| {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
info!(env.get_logger(), "created-at-time is {now}.");
sesi200 marked this conversation as resolved.
Show resolved Hide resolved
Some(now)
});

runtime.block_on(fetch_root_key_if_needed(&env))?;

Expand All @@ -167,12 +194,15 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
&deploy_mode,
opts.upgrade_unchanged,
with_cycles,
created_at_time,
opts.specified_id,
&call_sender,
opts.from_subaccount,
opts.no_wallet,
opts.yes,
env_file,
opts.no_asset_upgrade,
opts.cycles_ledger_canister_id,
))?;

if matches!(deploy_mode, NormalDeploy | ForceReinstallSingleCanister(_)) {
Expand Down
Loading
Loading