Skip to content

Commit

Permalink
feat: add cycles balance command (#3416)
Browse files Browse the repository at this point in the history
Adds the `dfx cycles balance` subcommand, hidden for the time being.

Fixes https://dfinity.atlassian.net/browse/SDK-1160
  • Loading branch information
ericswanson-dfinity authored Oct 11, 2023
1 parent 490d4e2 commit 5ba48e3
Show file tree
Hide file tree
Showing 14 changed files with 323 additions and 13 deletions.
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:

``` 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

0 comments on commit 5ba48e3

Please sign in to comment.