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: dfx deploy --using-icp-amount <icp> #3259

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions e2e/tests-dfx/ledger.bash
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@ current_time_nanoseconds() {
echo "$(date +%s)"000000000
}

@test "deploy icp retry options" {
dfx identity use alice
dfx ledger balance
dfx ledger account-id
dfx_new
assert_command_fail dfx deploy --using-icp-amount 1 --retry-create-canister-name abc
assert_contains "error: the following required arguments were not provided"
assert_contains "<--retry-create-canister-transfer-created-at-time <RETRY_CREATE_CANISTER_TRANSFER_CREATED_AT_TIME>|--retry-create-canister-notify-block-height <RETRY_CREATE_CANISTER_NOTIFY_BLOCK_HEIGHT>>"

assert_command_fail dfx deploy --using-icp-amount 1 --retry-create-canister-transfer-created-at-time 777
assert_contains "error: the following required arguments were not provided"
assert_contains "<--retry-create-canister-transfer-created-at-time <RETRY_CREATE_CANISTER_TRANSFER_CREATED_AT_TIME>|--retry-create-canister-notify-block-height <RETRY_CREATE_CANISTER_NOTIFY_BLOCK_HEIGHT>>"

assert_command_fail dfx deploy --using-icp-amount 1 --retry-create-canister-notify-block-height 87
assert_contains "ABC"
assert_contains "error: the following required arguments were not provided"
assert_contains "--retry-create-canister-name <RETRY_CREATE_CANISTER_NAME>"
}

@test "deploy --using-icp-amount" {
dfx identity use alice
dfx ledger balance
dfx ledger account-id
dfx_new
assert_command dfx deploy --using-icp-amount 1
assert_contains "Using transfer at block height" # todo
assert_command dfx ledger balance
assert_eq "999999997.99980000 ICP"
}

@test "ledger account-id" {
dfx identity use alice
assert_command dfx ledger account-id
Expand Down
2 changes: 1 addition & 1 deletion scripts/workflows/e2e-matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_scripts(prefix):
test = sorted(test_scripts("dfx") + test_scripts("replica") + test_scripts("icx-asset"))

matrix = {
"test": test,
"test": ["dfx/ledger"],
"backend": ["replica"],
"os": ["macos-12", "ubuntu-20.04"],
"exclude": [
Expand Down
8 changes: 4 additions & 4 deletions src/dfx/src/commands/canister/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::lib::ic_attributes::{
get_compute_allocation, get_freezing_threshold, get_memory_allocation, CanisterSettings,
};
use crate::lib::identity::wallet::get_or_create_wallet_canister;
use crate::lib::operations::canister::create_canister;
use crate::lib::operations::canister::{create_canister, Funding};
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::util::clap::parsers::cycle_amount_parser;
use crate::util::clap::parsers::{
Expand Down Expand Up @@ -77,7 +77,7 @@ pub async fn exec(

fetch_root_key_if_needed(env).await?;

let with_cycles = opts.with_cycles;
let funding = Funding::MaybeCycles(opts.with_cycles);

let config_interface = config.get_config();
let network = env.get_network_descriptor();
Expand Down Expand Up @@ -163,7 +163,7 @@ pub async fn exec(
create_canister(
env,
canister_name,
with_cycles,
&funding,
opts.specified_id,
call_sender,
CanisterSettings {
Expand Down Expand Up @@ -222,7 +222,7 @@ pub async fn exec(
create_canister(
env,
canister_name,
with_cycles,
&funding,
None,
call_sender,
CanisterSettings {
Expand Down
79 changes: 72 additions & 7 deletions src/dfx/src/commands/deploy.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use crate::lib::agent::create_agent_environment;
use crate::lib::canister_info::CanisterInfo;
use crate::lib::error::DfxResult;
use crate::lib::ledger_types::BlockHeight;
use crate::lib::network::network_opt::NetworkOpt;
use crate::lib::operations::canister::deploy_canisters::deploy_canisters;
use crate::lib::operations::canister::deploy_canisters::DeployMode::{
use crate::lib::nns_types::account_identifier::Subaccount;
use crate::lib::nns_types::icpts::ICPTs;
use crate::lib::operations::canister::DeployMode::{
ComputeEvidence, ForceReinstallSingleCanister, NormalDeploy, PrepareForProposal,
};
use crate::lib::operations::canister::{
deploy_canisters, Funding, ICPFunding, ICPFundingRetry, ICPFundingRetryPhase,
};
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 anyhow::{anyhow, bail, Context};
use candid::Principal;
use clap::Parser;
use clap::{ArgGroup, Parser};
use console::Style;
use dfx_core::config::model::network_descriptor::NetworkDescriptor;
use dfx_core::identity::CallSender;
Expand All @@ -29,6 +34,10 @@ const MAINNET_CANDID_INTERFACE_PRINCIPAL: &str = "a4gq6-oaaaa-aaaab-qaa4q-cai";

/// Deploys all or a specific canister from the code in your project. By default, all canisters are deployed.
#[derive(Parser)]
#[clap(
group(ArgGroup::new("using_icp_group").multiple(false).required(false)),
group(ArgGroup::new("icp_retry").multiple(false).required(false)),
)]
pub struct DeployOpts {
/// Specifies the name of the canister you want to deploy.
/// If you don’t specify a canister name, all canisters defined in the dfx.json file are deployed.
Expand Down Expand Up @@ -59,7 +68,7 @@ pub struct DeployOpts {
/// Specifies the initial cycle balance to deposit into the newly created canister.
/// The specified amount needs to take the canister create fee into account.
/// This amount is deducted from the wallet's cycle balance.
#[arg(long, value_parser = cycle_amount_parser)]
#[arg(long, value_parser = cycle_amount_parser, conflicts_with("using_icp_group"))]
with_cycles: Option<u128>,

/// Attempts to create the canister with this Canister ID.
Expand Down Expand Up @@ -99,6 +108,41 @@ pub struct DeployOpts {
/// Compute evidence and compare it against expected evidence
#[arg(long, conflicts_with("by_proposal"))]
compute_evidence: bool,

// /// Group to enforce at most one of --using-icp-amount or --using-icp
// #[clap(group = ArgGroup::with_name("using_icp_group").required(false).multiple(false))]
// using_icp_group: bool,
/// ICP to mint into cycles and deposit into destination canister
/// Can be specified as a Decimal with the fractional portion up to 8 decimal places
/// i.e. 100.012
/// This option implies the --no-wallet flag.
#[arg(long, group = "using_icp_group", conflicts_with("with_cycles"))]
using_icp_amount: Option<ICPTs>,

/// Mint ICP into cycles and deposit into destination canister
/// This option implies the --no-wallet flag.
#[arg(long, group = "using_icp_group", conflicts_with("with_cycles"))]
using_icp: bool,

/// Subaccount to withdraw from
#[arg(long, requires("using_icp_group"))]
icp_from_subaccount: Option<Subaccount>,

/// Transaction fee, default is 10000 e8s.
#[arg(long, requires("using_icp_group"))]
icp_fee: Option<ICPTs>,

/// Resume creation of this canister
#[arg(long, requires("using_icp_group"), requires("icp_retry"))]
retry_create_canister_name: Option<String>,

/// 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-
#[arg(long, group = "icp_retry", requires("retry_create_canister_name"))]
retry_create_canister_transfer_created_at_time: Option<u64>,

/// Block height at which transaction took place
#[arg(long, group = "icp_retry", requires("retry_create_canister_name"))]
retry_create_canister_notify_block_height: Option<BlockHeight>,
}

pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
Expand All @@ -119,7 +163,10 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
.output_env_file
.or_else(|| config.get_config().output_env_file.clone());

let with_cycles = opts.with_cycles;
let no_wallet = opts.no_wallet
|| opts.specified_id.is_some()
|| opts.using_icp_amount.is_some()
|| opts.using_icp;

let deploy_mode = match (mode, canister_name) {
(Some(InstallMode::Reinstall), Some(canister_name)) => {
Expand Down Expand Up @@ -156,6 +203,24 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {

let runtime = Runtime::new().expect("Unable to create a runtime");

let funding = if opts.using_icp || opts.using_icp_amount.is_some() {
// as_cycles_with_current_exchange_rate ?
let amount = opts
.using_icp_amount
.unwrap_or_else(|| ICPTs::from_icpts(1).unwrap());

Funding::Icp(ICPFunding {
amount,
from_subaccount: opts.icp_from_subaccount,
fee: opts.icp_fee,
retry_canister_name: opts.retry_create_canister_name,
retry_transfer_created_at_time: opts.retry_create_canister_transfer_created_at_time,
retry_notify_block_height: opts.retry_create_canister_notify_block_height,
})
} else {
Funding::MaybeCycles(opts.with_cycles)
};

let call_sender = CallSender::from(&opts.wallet)
.map_err(|e| anyhow!("Failed to determine call sender: {}", e))?;

Expand All @@ -168,10 +233,10 @@ pub fn exec(env: &dyn Environment, opts: DeployOpts) -> DfxResult {
argument_type,
&deploy_mode,
opts.upgrade_unchanged,
with_cycles,
&funding,
opts.specified_id,
&call_sender,
opts.no_wallet,
no_wallet,
opts.yes,
env_file,
opts.no_asset_upgrade,
Expand Down
4 changes: 1 addition & 3 deletions src/dfx/src/commands/ledger/create_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ use crate::lib::ledger_types::Memo;
use crate::lib::ledger_types::NotifyError::Refunded;
use crate::lib::nns_types::account_identifier::Subaccount;
use crate::lib::nns_types::icpts::{ICPTs, TRANSACTION_FEE};
use crate::lib::operations::cmc::{notify_create, transfer_cmc};
use crate::lib::operations::cmc::{notify_create, transfer_cmc, MEMO_CREATE_CANISTER};
use crate::lib::root_key::fetch_root_key_if_needed;
use crate::util::clap::parsers::e8s_parser;
use anyhow::{anyhow, bail, Context};
use candid::Principal;
use clap::Parser;

pub const MEMO_CREATE_CANISTER: u64 = 1095062083_u64;

/// Create a canister from ICP
#[derive(Parser)]
pub struct CreateCanisterOpts {
Expand Down
2 changes: 1 addition & 1 deletion src/dfx/src/commands/quickstart.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::lib::error::NotifyCreateCanisterError::Notify;
use crate::lib::ledger_types::NotifyError::Refunded;
use crate::lib::operations::cmc::MEMO_CREATE_CANISTER;
use crate::{
commands::ledger::create_canister::MEMO_CREATE_CANISTER,
lib::{
agent::create_agent_environment,
environment::Environment,
Expand Down
82 changes: 76 additions & 6 deletions src/dfx/src/lib/operations/canister/create_canister.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
use crate::lib::diagnosis::DiagnosedError;
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::error::NotifyCreateCanisterError::Notify;
use crate::lib::ic_attributes::CanisterSettings;
use crate::lib::ledger_types::{Memo, NotifyError};
use crate::lib::nns_types::icpts::{ICPTs, TRANSACTION_FEE};
use crate::lib::operations::canister::deploy_canisters::{
ICPFunding, ICPFundingRetry, ICPFundingRetryPhase,
};
use crate::lib::operations::canister::motoko_playground::reserve_canister_with_playground;
use crate::lib::operations::canister::Funding;
use crate::lib::operations::cmc::{notify_create, transfer_cmc, MEMO_CREATE_CANISTER};
use anyhow::{anyhow, bail, Context};
use candid::Principal;
use dfx_core::canister::build_wallet_canister;
Expand All @@ -25,7 +34,7 @@ const CANISTER_INITIAL_CYCLE_BALANCE: u128 = 3_000_000_000_000_u128;
pub async fn create_canister(
env: &dyn Environment,
canister_name: &str,
with_cycles: Option<u128>,
funding: &Funding,
specified_id: Option<Principal>,
call_sender: &CallSender,
settings: CanisterSettings,
Expand Down Expand Up @@ -76,12 +85,18 @@ pub async fn create_canister(
let agent = env
.get_agent()
.ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?;
let cid = match call_sender {
CallSender::SelectedId => {
create_with_management_canister(env, agent, with_cycles, specified_id, settings).await
let cid = match (call_sender, funding) {
(CallSender::SelectedId, Funding::Icp(icp_funding)) => {
create_with_ledger(agent, canister_name, icp_funding, settings).await
}
(CallSender::SelectedId, Funding::MaybeCycles(cycles)) => {
create_with_management_canister(env, agent, *cycles, specified_id, settings).await
}
(CallSender::Wallet(wallet_id), Funding::MaybeCycles(cycles)) => {
create_with_wallet(agent, wallet_id, *cycles, settings).await
}
CallSender::Wallet(wallet_id) => {
create_with_wallet(agent, wallet_id, with_cycles, settings).await
(CallSender::Wallet(_), Funding::Icp(_)) => {
unreachable!("Cannot create canister with wallet and ICP at the same time.")
}
}?;
let canister_id = cid.to_text();
Expand All @@ -97,6 +112,61 @@ pub async fn create_canister(
Ok(())
}

async fn create_with_ledger(
agent: &Agent,
canister_name: &str,
funding: &ICPFunding,
_settings: CanisterSettings,
) -> DfxResult<Principal> {
let to_principal = agent.get_principal().unwrap();

let canister_name_matches = matches!(&funding.retry_canister_name, Some(canister_name));

let retry_block_height = funding
.retry_notify_block_height
.as_ref()
.filter(|_| canister_name_matches);

let height = if let Some(height) = retry_block_height {
*height
} else {
let retry_transfer_created_at_time = funding
.retry_transfer_created_at_time
.as_ref()
.filter(|_| canister_name_matches)
.copied();
let fee = TRANSACTION_FEE;
let memo = Memo(MEMO_CREATE_CANISTER);
let amount = funding.amount;
let from_subaccount = funding.from_subaccount;
let height = transfer_cmc(
agent,
memo,
amount,
fee,
from_subaccount,
to_principal,
retry_transfer_created_at_time,
)
.await?;
println!("Using transfer at block height {height}");
height
};

let controller = to_principal;
let subnet_type = None;

let principal = notify_create(agent, controller, height, subnet_type)
.await
.map_err(|e| {
DiagnosedError::new(
format!("Failed to notify cmc: {}", e),
format!("Re-run the command with --icp-block-height {}", height),
)
})?;
Ok(principal)
}

async fn create_with_management_canister(
env: &dyn Environment,
agent: &Agent,
Expand Down
Loading