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 20 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'
}
98 changes: 92 additions & 6 deletions e2e/tests-dfx/cycles-ledger.bash
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ add_cycles_ledger_canisters_to_project() {
}

deploy_cycles_ledger() {
assert_command dfx deploy cycles-ledger --specified-id "um5iw-rqaaa-aaaaq-qaaba-cai"
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-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 @@ -62,16 +62,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 --precise --identity alice
Expand Down Expand Up @@ -389,7 +389,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 @@ -415,3 +415,89 @@ 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)\"})"
echo "Cycles depositor deployed at id $(dfx canister id cycles-depositor)"
assert_command dfx ledger fabricate-cycles --canister cycles-depositor --t 9999

assert_command dfx deploy

assert_command dfx canister call cycles-depositor deposit "(record {to = record{owner = principal \"$ALICE\";};cycles = 13_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"
assert_command dfx canister id e2e_project_backend
E2E_PROJECT_BACKEND_CANISTER_ID=$(dfx canister id e2e_project_backend)
assert_command dfx cycles balance --precise
assert_eq "12399900000000 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"
assert_command dfx canister id e2e_project_backend
assert_contains "$E2E_PROJECT_BACKEND_CANISTER_ID"
assert_command dfx cycles balance --precise
assert_eq "12399900000000 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"
assert_command dfx canister id e2e_project_backend
assert_command dfx cycles balance --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"
assert_command dfx canister id e2e_project_backend
E2E_PROJECT_BACKEND_CANISTER_ID=$(dfx canister id e2e_project_backend)
assert_command dfx cycles balance --precise
assert_eq "11399800000000 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" -vv
assert_command dfx canister id e2e_project_backend
assert_contains "$E2E_PROJECT_BACKEND_CANISTER_ID"
assert_command dfx cycles balance --precise
assert_eq "11399800000000 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"
assert_command dfx canister id e2e_project_backend
assert_command dfx cycles balance --subaccount "$ALICE_SUBACCT1" --precise
assert_eq "1599800000000 cycles."
dfx canister stop e2e_project_backend
dfx canister delete e2e_project_backend

assert_command dfx deploy --with-cycles 1T
assert_command dfx canister id e2e_project_backend
assert_command dfx canister id e2e_project_frontend
assert_not_contains "$(dfx canister id e2e_project_backend)"
assert_command dfx cycles balance --precise
assert_eq "9399600000000 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
2 changes: 1 addition & 1 deletion e2e/tests-dfx/wallet.bash
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ teardown() {
# assert: no wallet configured
export DFX_DISABLE_AUTO_WALLET=1
assert_command_fail dfx wallet balance
assert_match "command requires a configured wallet"
assert_match "No wallet configured"

assert_command dfx wallet redeem-faucet-coupon --faucet "$(dfx canister id faucet)" 'valid-coupon'
assert_match "Redeemed coupon valid-coupon for a new wallet"
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.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
47 changes: 40 additions & 7 deletions src/dfx/src/commands/canister/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@ use crate::lib::ic_attributes::{
get_compute_allocation, get_freezing_threshold, get_memory_allocation,
get_reserved_cycles_limit, CanisterSettings,
};
use crate::lib::identity::wallet::get_or_create_wallet_canister;
use crate::lib::identity::wallet::{get_or_create_wallet_canister, GetOrCreateWalletCanisterError};
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,17 @@ 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, conflicts_with = "all")]
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>,
}

pub async fn exec(
Expand All @@ -102,14 +115,30 @@ 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
&& matches!(
err,
GetOrCreateWalletCanisterError::NoWalletConfigured { .. }
)
{
debug!(env.get_logger(), "No wallet configured.");
sesi200 marked this conversation as resolved.
Show resolved Hide resolved
} else {
bail!(err)
}
}
};
}

let controllers: Option<Vec<_>> = opts
Expand Down Expand Up @@ -186,13 +215,15 @@ 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,
)
.await?;
Ok(())
Expand Down Expand Up @@ -254,13 +285,15 @@ 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,
)
.await?;
}
Expand Down
16 changes: 15 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,6 +17,7 @@ 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;
Expand Down Expand Up @@ -99,6 +100,17 @@ 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, requires = "canister_name")]
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>,
}

pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
Expand Down Expand Up @@ -167,8 +179,10 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
&deploy_mode,
opts.upgrade_unchanged,
with_cycles,
opts.created_at_time,
opts.specified_id,
&call_sender,
opts.from_subaccount,
opts.no_wallet,
opts.yes,
env_file,
Expand Down
1 change: 1 addition & 0 deletions src/dfx/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum DfxCommand {
Build(build::CanisterBuildOpts),
Cache(cache::CacheOpts),
Canister(canister::CanisterOpts),
//TODO(SDK-1331): unhide
#[command(hide = true)]
Cycles(cycles::CyclesOpts),
Deploy(deploy::DeployOpts),
Expand Down
28 changes: 27 additions & 1 deletion src/dfx/src/lib/ic_attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use ic_utils::interfaces::management_canister::attributes::{
};
use std::convert::TryFrom;

#[derive(Default)]
#[derive(Default, Debug, Clone)]
pub struct CanisterSettings {
pub controllers: Option<Vec<Principal>>,
pub compute_allocation: Option<ComputeAllocation>,
Expand All @@ -18,6 +18,32 @@ pub struct CanisterSettings {
pub reserved_cycles_limit: Option<ReservedCyclesLimit>,
}

impl From<CanisterSettings>
for ic_utils::interfaces::management_canister::builders::CanisterSettings
{
fn from(value: CanisterSettings) -> Self {
Self {
controllers: value.controllers,
compute_allocation: value
.compute_allocation
.map(u8::from)
.map(candid::Nat::from),
memory_allocation: value
.memory_allocation
.map(u64::from)
.map(candid::Nat::from),
freezing_threshold: value
.freezing_threshold
.map(u64::from)
.map(candid::Nat::from),
reserved_cycles_limit: value
.reserved_cycles_limit
.map(u128::from)
.map(candid::Nat::from),
}
}
}

#[context("Failed to get compute allocation.")]
pub fn get_compute_allocation(
compute_allocation: Option<u64>,
Expand Down
Loading
Loading