Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,10 @@ Changed the text in this case to read:

### chore: add retry logic to dfx download script

### feat: Add subnet type argument when creating canisters

`dfx ledger create-canister` gets a new option `--subnet-type` that allows users to choose a type of subnet that their canister can be created on. Additionally, a `dfx ledger show-subnet-types` is introduced which allows to list the available subnet types.

## Dependencies

### Replica
Expand Down
53 changes: 48 additions & 5 deletions docs/cli-reference/dfx-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,13 @@ You can specify the following argument for the `dfx ledger create-canister` comm

| Option | Description |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--amount <amount>` | Specify the number of ICP tokens to mint into cycles and deposit into destination canister. You can specify an amount as a number with up to eight (8) decimal places. |
| `--e8s <e8s>` | Specify ICP token fractional units—called e8s—as a whole number, where one e8 is smallest partition of an ICP token. For example, 1.05000000 is 1 ICP and 5000000 e8s. You can use this option on its own or in conjunction with the `--icp` option. |
| `--fee <fee>` | Specify a transaction fee. The default is 10000 e8s. |
| `--icp <icp>` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. |
| `--max-fee <max-fee>` | Specify a maximum transaction fee. The default is 10000 e8s. |
| `--amount <amount>` | Specify the number of ICP tokens to mint into cycles and deposit into destination canister. You can specify an amount as a number with up to eight (8) decimal places. |
| `--e8s <e8s>` | Specify ICP token fractional units—called e8s—as a whole number, where one e8 is smallest partition of an ICP token. For example, 1.05000000 is 1 ICP and 5000000 e8s. You can use this option on its own or in conjunction with the `--icp` option. |
| `--fee <fee>` | Specify a transaction fee. The default is 10000 e8s. |
| `--icp <icp>` | Specify ICP tokens as a whole number. You can use this option on its own or in conjunction with `--e8s`. |
| `--max-fee <max-fee>` | Specify a maximum transaction fee. The default is 10000 e8s. |
| `--subnet-type <subnet-type>` | Specify the optional subnet type to create the canister on. If no subnet type is provided, the canister will be created on a random default application subnet. |


### Examples

Expand Down Expand Up @@ -264,6 +266,47 @@ The following example illustrates sending a `notify` message to the ledger in re
dfx ledger notify 75948 tsqwz-udeik-5migd-ehrev-pvoqv-szx2g-akh5s-fkyqc-zy6q7-snav6-uqe --network ic
```

## dfx ledger show-subnet-types

Use the `dfx ledger show-subnet-types` command to list the available subnet types that can be chosen to create a canister on.

### Basic usage

``` bash
dfx ledger show-subnet-types [options] [flag]
```

### Flags

You can use the following optional flags with the `dfx ledger show-subnet-types` command.

| Flag | Description |
|-------------------|-------------------------------|
| `-h`, `--help` | Displays usage information. |
| `-V`, `--version` | Displays version information. |

### Options

You can specify the following options for the `dfx ledger show-subnet-types` command.

| Option | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--cycles-minting-canister-id <cycles-minting-canister-id>` | Canister id of the cycles minting canister. Useful if you want to test locally with a different id for the cycles minting canister. |

### Examples

You can use the `dfx ledger show-subnet-types` command to list the available subnet types that can be chosen to create a canister on. If a specific cycles minting canister id is not provided, then the mainnet cycles minting canister id will be used.

For example, you can run the following command to get the subnet types available on mainnet:

``` bash
dfx ledger show-subnet-types
```

This command displays output similar to the following:

["Type1", "Type2", ..., "TypeN"]

## dfx ledger top-up

Use the `dfx ledger top-up` command to top up a canister with cycles minted from ICP tokens.
Expand Down
22 changes: 22 additions & 0 deletions e2e/assets/cmc/main.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Array "mo:base/Array";
import P "mo:base/Principal";
import Text "mo:base/Text";
import Prim "mo:⛔";

actor Call {
type SubnetTypesToSubnetsResponse = {
data: [(Text, [Principal])];
};

public query func get_subnet_types_to_subnets() : async SubnetTypesToSubnetsResponse {
let type1 = "type1";
let type2 = "type2";
{
data = [
(type1, [Prim.principalOfBlob("\00")]),
(type2, [Prim.principalOfBlob("\01"), Prim.principalOfBlob("\02")]),
];
}
};

}
1 change: 1 addition & 0 deletions e2e/assets/cmc/patch.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jq '.canisters.cmc.main="main.mo"' dfx.json | sponge dfx.json
18 changes: 18 additions & 0 deletions e2e/tests-dfx/ledger.bash
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,21 @@ tc_to_num() {

(( balance_now - balance > 4000000000000 ))
}

@test "ledger create-canister" {
dfx identity use alice
assert_command dfx ledger create-canister --amount=100 --subnet-type "type1" "$(dfx identity get-principal)"
assert_match "Transfer sent at block height 6"
assert_match "Refunded at block height 7 with message: Provided subnet type type1 does not exist"
}

@test "ledger show-subnet-types" {
install_asset cmc

dfx deploy cmc

CANISTER_ID=$(dfx canister id cmc)

assert_command dfx ledger show-subnet-types --cycles-minting-canister-id "$CANISTER_ID"
assert_eq '["type1", "type2"]'
}
9 changes: 8 additions & 1 deletion src/dfx/src/commands/ledger/create_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ pub struct CreateCanisterOpts {
/// Max fee, default is 10000 e8s.
#[clap(long, validator(icpts_amount_validator))]
max_fee: Option<String>,

/// Specify the optional subnet type to create the canister on. If no
/// subnet type is provided, the canister will be created on a random
/// default application subnet.
#[clap(long)]
subnet_type: Option<String>,
}

pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult {
Expand Down Expand Up @@ -73,9 +79,10 @@ pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult
.ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?;

fetch_root_key_if_needed(env).await?;

let height = transfer_cmc(agent, memo, amount, fee, opts.from_subaccount, controller).await?;
println!("Transfer sent at block height {height}");
let result = notify_create(agent, controller, height).await?;
let result = notify_create(agent, controller, height, opts.subnet_type).await?;

match result {
Ok(principal) => {
Expand Down
5 changes: 5 additions & 0 deletions src/dfx/src/commands/ledger/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mod balance;
pub mod create_canister;
mod fabricate_cycles;
mod notify;
pub mod show_subnet_types;
mod top_up;
mod transfer;

Expand All @@ -54,6 +55,7 @@ enum SubCommand {
CreateCanister(create_canister::CreateCanisterOpts),
FabricateCycles(fabricate_cycles::FabricateCyclesOpts),
Notify(notify::NotifyOpts),
ShowSubnetTypes(show_subnet_types::ShowSubnetTypesOpts),
TopUp(top_up::TopUpOpts),
Transfer(transfer::TransferOpts),
}
Expand All @@ -68,6 +70,7 @@ pub fn exec(env: &dyn Environment, opts: LedgerOpts) -> DfxResult {
SubCommand::CreateCanister(v) => create_canister::exec(&agent_env, v).await,
SubCommand::FabricateCycles(v) => fabricate_cycles::exec(&agent_env, v).await,
SubCommand::Notify(v) => notify::exec(&agent_env, v).await,
SubCommand::ShowSubnetTypes(v) => show_subnet_types::exec(&agent_env, v).await,
SubCommand::TopUp(v) => top_up::exec(&agent_env, v).await,
SubCommand::Transfer(v) => transfer::exec(&agent_env, v).await,
}
Expand Down Expand Up @@ -199,13 +202,15 @@ pub async fn notify_create(
agent: &Agent,
controller: Principal,
block_height: BlockHeight,
subnet_type: Option<String>,
) -> DfxResult<NotifyCreateCanisterResult> {
let result = agent
.update(&MAINNET_CYCLE_MINTER_CANISTER_ID, NOTIFY_CREATE_METHOD)
.with_arg(
Encode!(&NotifyCreateCanisterArg {
block_index: block_height,
controller,
subnet_type,
})
.context("Failed to encode notify arguments.")?,
)
Expand Down
8 changes: 7 additions & 1 deletion src/dfx/src/commands/ledger/notify/create_canister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ pub struct NotifyCreateOpts {

/// The controller of the created canister.
controller: String,

/// Specify the optional subnet type to create the canister on. If no
/// subnet type is provided, the canister will be created on a random
/// default application subnet.
#[clap(long)]
subnet_type: Option<String>,
}

pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult {
Expand All @@ -34,7 +40,7 @@ pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult {

fetch_root_key_if_needed(env).await?;

let result = notify_create(agent, controller, block_height).await?;
let result = notify_create(agent, controller, block_height, opts.subnet_type).await?;

match result {
Ok(principal) => {
Expand Down
51 changes: 51 additions & 0 deletions src/dfx/src/commands/ledger/show_subnet_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::ledger_types::{GetSubnetTypesToSubnetsResult, MAINNET_CYCLE_MINTER_CANISTER_ID};
use crate::lib::waiter::waiter_with_timeout;
use crate::util::expiry_duration;

use crate::lib::root_key::fetch_root_key_if_needed;

use anyhow::{anyhow, Context};
use candid::{Decode, Encode, Principal};
use clap::Parser;

const GET_SUBNET_TYPES_TO_SUBNETS_METHOD: &str = "get_subnet_types_to_subnets";

/// Show available subnet types in the cycles minting canister.
#[derive(Parser)]
pub struct ShowSubnetTypesOpts {
#[clap(long)]
/// Canister ID of the cycles minting canister.
cycles_minting_canister_id: Option<Principal>,
}

pub async fn exec(env: &dyn Environment, opts: ShowSubnetTypesOpts) -> DfxResult {
let agent = env
.get_agent()
.ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?;

fetch_root_key_if_needed(env).await?;

let cycles_minting_canister_id = opts
.cycles_minting_canister_id
.unwrap_or(MAINNET_CYCLE_MINTER_CANISTER_ID);

let result = agent
.update(
&cycles_minting_canister_id,
GET_SUBNET_TYPES_TO_SUBNETS_METHOD,
)
.with_arg(Encode!(&()).context("Failed to encode get_subnet_types_to_subnets arguments.")?)
.call_and_wait(waiter_with_timeout(expiry_duration()))
.await
.context("get_subnet_types_to_subnets call failed.")?;
let result = Decode!(&result, GetSubnetTypesToSubnetsResult)
.context("Failed to decode get_subnet_types_to_subnets response")?;

let available_subnet_types: Vec<String> = result.data.into_iter().map(|(x, _)| x).collect();

println!("{:?}", available_subnet_types);

Ok(())
}
2 changes: 1 addition & 1 deletion src/dfx/src/commands/quickstart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ async fn step_interact_ledger(
let notify_spinner = ProgressBar::new_spinner();
notify_spinner.set_message("Notifying the the cycles minting canister...");
notify_spinner.enable_steady_tick(100);
let res = notify_create(agent, ident_principal, height).await
let res = notify_create(agent, ident_principal, height, None).await
.with_context(|| format!("Failed to notify the CMC of the transfer. Write down that height ({height}), and once the error is fixed, use `dfx ledger notify create-canister`."))?;
let wallet = match res {
Ok(principal) => principal,
Expand Down
6 changes: 6 additions & 0 deletions src/dfx/src/lib/ledger_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub struct TimeStamp {
pub struct NotifyCreateCanisterArg {
pub block_index: BlockIndex,
pub controller: Principal,
pub subnet_type: Option<String>,
}

#[derive(CandidType)]
Expand Down Expand Up @@ -157,6 +158,11 @@ pub type NotifyCreateCanisterResult = Result<Principal, NotifyError>;

pub type NotifyTopUpResult = Result<u128, NotifyError>;

#[derive(CandidType, Deserialize, Debug)]
pub struct GetSubnetTypesToSubnetsResult {
pub data: Vec<(String, Vec<Principal>)>,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down