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: add cycles balance command #3416

Merged
merged 1 commit into from
Oct 11, 2023
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 @@ -85,6 +85,10 @@ The parameter was erroneously passed twice. Now it is passed only once.

Removed this warning: "Project-specific networks are deprecated and will be removed after February 2023." While we may remove project-specific networks in the future, it is not imminent. One key requirement is the ability to run more than one subnet type at one time.

### feat: added `cycles balance` command

This won't work on mainnet yet, but it can work locally after installing the cycles ledger.

## Dependencies

### icx-proxy
Expand Down
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ aes-gcm = "0.9.4"
anyhow = "1.0.56"
anstyle = "1.0.0"
argon2 = "0.4.0"
backoff = "0.4.0"
backoff = { version = "0.4.0", features = [ "futures", "tokio" ] }
base64 = "0.13.0"
byte-unit = "4.0.14"
bytes = "1.2.1"
Expand Down
67 changes: 67 additions & 0 deletions docs/cli-reference/dfx-cycles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# dfx cycles

> **NOTE**: The cycles ledger is in development and the dfx cycles command is not expected to work on mainnet at this time.

Use the `dfx cycles` command to manage cycles associated with an identity's principal.

The basic syntax for running `dfx cycles` commands is:

``` bash
dfx cycles [subcommand] [options]
```

The following subcommands are available:

| Command | Description |
|---------------------------------------|--------------------------------------------------------------------------------------|
| [`balance`](#dfx-ledger-balance) | Prints the account balance of the user. |
| `help` | Displays usage information message for a specified subcommand. |

To view usage information for a specific subcommand, specify the subcommand and the `--help` flag. For example, to see usage information for `dfx cycles balance`, you can run the following command:

`dfx cycles balance --help`

## dfx cycles balance

Use the `dfx cycles balance` command to print your account balance or that of another user.

### Basic usage

``` bash
dfx cycles balance [flag] --network ic
```

### Options

You can specify the following arguments for the `dfx cycles balance` command.

| Option | Description |
|---------------------------------------------|---------------------------------------------------------------------|
| `--owner <principal>` | Display the balance of this principal |
| `--subaccount <subaccount>` | Display the balance of this subaccount |
| `--precise` | Displays the exact balance, without scaling to trillions of cycles. |
| `--cycles-ledger-canister-id <canister id>` | Specify the ID of the cycles ledger canister. |

### Examples

> **NOTE**: None of the examples below specify the `--cycles-ledger-canister-id` option, but it is required until the cycles ledger canister ID is known.

Check the cycles balance of the selected identity.

```
$ dfx cycles balance --network ic
89.000 TC (trillion cycles).
```

To see the exact amount of cycles, you can use the `--precise` option:
```
$ dfx cycles balance --network ic --precise
89000000000000 cycles.
```

You can use the `dfx cycles balance` command to check the balance of another principal:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality is actually not part of the design but it's a nice addition.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dfx cycles balance: The function prints out the balance associated with the user’s principal ID by calling the icrc1_balance_of endpoint of the cycles ledger.

You are right, and I hadn't noticed that. I was looking at the arguments of icrc1_balance_of, which has an owner field, but the design doc doesn't require that dfx cycles balance passes anything other than the user's principal.


``` bash
dfx cycles balance --owner raxcz-bidhr-evrzj-qyivt-nht5a-eltcc-24qfc-o6cvi-hfw7j-dcecz-kae --network ic
```

2 changes: 2 additions & 0 deletions docs/cli-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ When you have the SDK installed, you can use the following commands to specify t

- [dfx canister](./dfx-canister.md)

- [dfx cycles](./dfx-cycles.md)

- [dfx deploy](./dfx-deploy.md)

- [dfx deps](./dfx-deps.md)
Expand Down
99 changes: 87 additions & 12 deletions e2e/tests-dfx/cycles-ledger.bash
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,80 @@ teardown() {
standard_teardown
}

@test "cycles ledger balance" {
ALICE=$(dfx identity get-principal --identity alice)
ALICE_SUBACCT1="000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
ALICE_SUBACCT1_CANDID="\00\01\02\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"
ALICE_SUBACCT2="9C9B9A030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
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 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
assert_eq "0 cycles."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice
assert_eq "0.000 TC (trillion cycles)."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob --precise
assert_eq "0 cycles."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob
assert_eq "0.000 TC (trillion cycles)."


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_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_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_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_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --precise --identity alice
assert_eq "1700400200150 cycles."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --precise --identity alice --subaccount "$ALICE_SUBACCT1"
assert_eq "3750000000000 cycles."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --precise --identity alice --subaccount "$ALICE_SUBACCT2"
assert_eq "760500000000 cycles."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --precise --identity bob
assert_eq "2900000000000 cycles."


assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice
assert_eq "1.700 TC (trillion cycles)."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --subaccount "$ALICE_SUBACCT1"
assert_eq "3.750 TC (trillion cycles)."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --subaccount "$ALICE_SUBACCT2"
assert_eq "0.760 TC (trillion cycles)."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob
assert_eq "2.900 TC (trillion cycles)."


# can see cycles balance of other accounts
assert_command dfx cycles balance --owner "$ALICE" --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob
assert_eq "1.700 TC (trillion cycles)."

assert_command dfx cycles balance --owner "$ALICE" --subaccount "$ALICE_SUBACCT1" --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob
assert_eq "3.750 TC (trillion cycles)."

assert_command dfx cycles balance --owner "$BOB" --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity anonymous
assert_eq "2.900 TC (trillion cycles)."
}

@test "cycles ledger howto" {
# This is the equivalent of https://www.notion.so/dfinityorg/How-to-install-and-test-the-cycles-ledger-521c9f3c410f4a438514a03e35464299
ALICE=$(dfx identity get-principal --identity alice)
Expand All @@ -41,35 +115,36 @@ teardown() {

dfx canister status cycles-depositor

assert_command dfx canister call cycles-ledger icrc1_balance_of "(record {owner = principal \"$ALICE\"})"
assert_eq "(0 : nat)"
assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --precise
assert_eq "0 cycles."

assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob --precise
assert_eq "0 cycles."

assert_command dfx canister call cycles-ledger icrc1_balance_of "(record {owner = principal \"$BOB\"})"
assert_eq "(0 : nat)"

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_command dfx canister status cycles-depositor
assert_contains "Balance: 9_999_500_000_000 Cycles"

assert_command dfx canister call cycles-ledger icrc1_balance_of "(record {owner = principal \"$ALICE\"})"
assert_eq "(500_000_000 : nat)"
assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --precise
assert_eq "500000000 cycles."

assert_command dfx canister call cycles-ledger icrc1_transfer "(record {to = record{owner = principal \"$BOB\"}; amount = 100_000;})" --identity alice
assert_eq "(variant { Ok = 1 : nat })"

assert_command dfx canister call cycles-ledger icrc1_balance_of "(record {owner = principal \"$ALICE\"})"
assert_eq "(399_900_000 : nat)"
assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --precise
assert_eq "399900000 cycles."

assert_command dfx canister call cycles-ledger icrc1_balance_of "(record {owner = principal \"$BOB\"})"
assert_eq "(100_000 : nat)"
assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity bob --precise
assert_eq "100000 cycles."

assert_command dfx canister call cycles-ledger send "(record {amount = 100_000;to = principal \"$(dfx canister id cycles-depositor)\"})" --identity alice
assert_eq "(variant { Ok = 2 : nat })"

assert_command dfx canister call cycles-ledger icrc1_balance_of "(record {owner = principal \"$ALICE\"})"
assert_eq "(299_800_000 : nat)"
assert_command dfx cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --precise
assert_eq "299800000 cycles."

assert_command dfx canister status cycles-depositor
assert_contains "Balance: 9_999_500_100_000 Cycles"
Expand Down
1 change: 1 addition & 0 deletions src/dfx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ ic-asset.workspace = true
ic-identity-hsm = { workspace = true }
ic-utils = { workspace = true }
ic-wasm = "0.4.0"
icrc-ledger-types = "0.1.1"
indicatif = "0.16.0"
itertools.workspace = true
json-patch = "1.0.0"
Expand Down
54 changes: 54 additions & 0 deletions src/dfx/src/commands/cycles/balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::nns_types::account_identifier::Subaccount;
use crate::lib::operations::cycles_ledger;
use crate::util::{format_as_trillions, pretty_thousand_separators};
use candid::Principal;
use clap::Parser;

/// Get the cycle balance of the selected Identity's cycles wallet.
#[derive(Parser)]
pub struct CyclesBalanceOpts {
/// Specifies a Principal to get the balance of
#[arg(long)]
owner: Option<Principal>,

/// Subaccount of the selected identity to get the balance of
#[arg(long)]
subaccount: Option<Subaccount>,

/// Get balance raw value (without upscaling to trillions of cycles).
#[arg(long)]
precise: bool,

/// 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)]
cycles_ledger_canister_id: Principal,
}

pub async fn exec(env: &dyn Environment, opts: CyclesBalanceOpts) -> DfxResult {
let agent = env.get_agent();

let owner = opts.owner.unwrap_or_else(|| {
env.get_selected_identity_principal()
.expect("Selected identity not instantiated.")
});

let subaccount = opts.subaccount.map(|x| x.0);

let balance =
cycles_ledger::balance(agent, owner, subaccount, opts.cycles_ledger_canister_id).await?;

if opts.precise {
println!("{} cycles.", balance);
} else {
println!(
"{} TC (trillion cycles).",
pretty_thousand_separators(format_as_trillions(balance))
);
}

Ok(())
}
34 changes: 34 additions & 0 deletions src/dfx/src/commands/cycles/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use crate::lib::agent::create_agent_environment;
use crate::lib::environment::Environment;
use crate::lib::error::DfxResult;
use crate::lib::network::network_opt::NetworkOpt;
use clap::Parser;
use tokio::runtime::Runtime;

mod balance;

/// Helper commands to manage the user's cycles.
#[derive(Parser)]
#[command(name = "wallet")]
pub struct CyclesOpts {
#[command(flatten)]
network: NetworkOpt,

#[command(subcommand)]
subcmd: SubCommand,
}

#[derive(Parser)]
enum SubCommand {
Balance(balance::CyclesBalanceOpts),
}

pub fn exec(env: &dyn Environment, opts: CyclesOpts) -> DfxResult {
let agent_env = create_agent_environment(env, opts.network.to_network_name())?;
let runtime = Runtime::new().expect("Unable to create a runtime");
runtime.block_on(async {
match opts.subcmd {
SubCommand::Balance(v) => balance::exec(&agent_env, v).await,
}
})
}
4 changes: 4 additions & 0 deletions src/dfx/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod beta;
mod build;
mod cache;
mod canister;
mod cycles;
mod deploy;
mod deps;
mod diagnose;
Expand Down Expand Up @@ -35,6 +36,8 @@ pub enum DfxCommand {
Build(build::CanisterBuildOpts),
Cache(cache::CacheOpts),
Canister(canister::CanisterOpts),
#[command(hide = true)]
Cycles(cycles::CyclesOpts),
Deploy(deploy::DeployOpts),
Deps(deps::DepsOpts),
Diagnose(diagnose::DiagnoseOpts),
Expand Down Expand Up @@ -64,6 +67,7 @@ pub fn exec(env: &dyn Environment, cmd: DfxCommand) -> DfxResult {
DfxCommand::Build(v) => build::exec(env, v),
DfxCommand::Cache(v) => cache::exec(env, v),
DfxCommand::Canister(v) => canister::exec(env, v),
DfxCommand::Cycles(v) => cycles::exec(env, v),
DfxCommand::Deploy(v) => deploy::exec(env, v),
DfxCommand::Deps(v) => deps::exec(env, v),
DfxCommand::Diagnose(v) => diagnose::exec(env, v),
Expand Down
1 change: 1 addition & 0 deletions src/dfx/src/lib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub mod progress_bar;
pub mod project;
pub mod replica;
pub mod replica_config;
pub mod retryable;
pub mod root_key;
pub mod sign;
pub mod state_tree;
Expand Down
Loading
Loading