diff --git a/CHANGELOG.md b/CHANGELOG.md index acf2653443..513619849e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,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 diff --git a/Cargo.lock b/Cargo.lock index 722041bafe..2244eed2a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1364,6 +1364,7 @@ dependencies = [ "ic-identity-hsm", "ic-utils 0.29.0", "ic-wasm", + "icrc-ledger-types", "indicatif", "itertools 0.10.5", "json-patch", @@ -2988,6 +2989,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "icrc-ledger-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ee120bab077a71cf85fcc6b0e88674edfeca7fe18240bee9afa947d8e08865" +dependencies = [ + "candid 0.9.6", + "hex", + "num-traits", + "serde", + "serde_bytes", + "sha2 0.10.7", +] + [[package]] name = "icx-asset" version = "0.20.0" diff --git a/Cargo.toml b/Cargo.toml index 4cae352f04..18a85414e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 87e136081f..592765880b 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,14 @@ This command will install a binary compatible with your operating system, and ad Find a release for your architecture [here](https://github.com/dfinity/sdk/releases). +#### in GitHub Action, using [`dfinity/setup-dfx`](https://github.com/dfinity/setup-dfx) + +```yml + steps: + - name: Install dfx + uses: dfinity/setup-dfx@main +``` + ### Getting Help Once the `IC SDK` is installed, get acquainted with its capabilities by entering. diff --git a/docs/cli-reference/dfx-cycles.md b/docs/cli-reference/dfx-cycles.md new file mode 100644 index 0000000000..c027ecb7dc --- /dev/null +++ b/docs/cli-reference/dfx-cycles.md @@ -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 ` | Display the balance of this principal | +| `--subaccount ` | Display the balance of this subaccount | +| `--precise` | Displays the exact balance, without scaling to trillions of cycles. | +| `--cycles-ledger-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 +``` + diff --git a/docs/cli-reference/index.md b/docs/cli-reference/index.md index c4290ce2cd..d05b68d23a 100644 --- a/docs/cli-reference/index.md +++ b/docs/cli-reference/index.md @@ -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) diff --git a/e2e/assets/cycles-ledger/dfx.json b/e2e/assets/cycles-ledger/dfx.json new file mode 100644 index 0000000000..8b092b0d7b --- /dev/null +++ b/e2e/assets/cycles-ledger/dfx.json @@ -0,0 +1,14 @@ +{ + "canisters": { + "cycles-ledger": { + "type": "custom", + "wasm": "cycles-ledger.wasm.gz", + "candid": "cycles-ledger.did" + }, + "cycles-depositor": { + "type": "custom", + "wasm": "cycles-depositor.wasm.gz", + "candid": "cycles-depositor.did" + } + } +} diff --git a/e2e/tests-dfx/cycles-ledger.bash b/e2e/tests-dfx/cycles-ledger.bash new file mode 100644 index 0000000000..a0ee937b4f --- /dev/null +++ b/e2e/tests-dfx/cycles-ledger.bash @@ -0,0 +1,151 @@ +#!/usr/bin/env bats + +load ../utils/_ +load ../utils/cycles-ledger + +setup() { + standard_setup + install_asset cycles-ledger + install_shared_asset subnet_type/shared_network_settings/system + install_cycles_ledger_canisters + + dfx identity new --storage-mode plaintext cycle-giver + dfx identity new --storage-mode plaintext alice + dfx identity new --storage-mode plaintext bob + + dfx_start_for_nns_install + + dfx extension install nns --version 0.2.1 || true + dfx nns install --ledger-accounts "$(dfx ledger account-id --identity cycle-giver)" +} + +teardown() { + dfx_stop + + 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) + 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 ledger balance --identity cycle-giver + assert_eq "1000000000.00000000 ICP" + + assert_command dfx canister status cycles-depositor + assert_contains "Balance: 10_000_000_000_000 Cycles" + + dfx canister status cycles-depositor + + 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-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 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 cycles balance --cycles-ledger-canister-id "$(dfx canister id cycles-ledger)" --identity alice --precise + assert_eq "399900000 cycles." + + 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 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" +} diff --git a/e2e/utils/cycles-ledger.bash b/e2e/utils/cycles-ledger.bash new file mode 100644 index 0000000000..c58fa1cde2 --- /dev/null +++ b/e2e/utils/cycles-ledger.bash @@ -0,0 +1,36 @@ +CYCLES_LEDGER_VERSION="0.2.1" + +build_artifact_url() { + echo "https://raw.githubusercontent.com/dfinity/sdk/cycles-ledger-prerelease/cycles-ledger-v$CYCLES_LEDGER_VERSION/${1}" +} + +downloaded_cycles_ledger_canisters_dir() { + echo "$DFX_CACHE_ROOT/canisters/cycles-ledger/$CYCLES_LEDGER_VERSION" +} + +download_cycles_ledger_canisters() { + DOWNLOAD_DIR="$DFX_CACHE_ROOT/.download" + DEST_DIR="$(downloaded_cycles_ledger_canisters_dir)" + + if test -d "$DEST_DIR"; then + return + fi + + rm -rf "$DOWNLOAD_DIR" + mkdir -p "$DOWNLOAD_DIR" "$(dirname "$DEST_DIR")" + + for name in cycles-ledger cycles-depositor; do + for ext in wasm.gz wasm.gz.sha256 did; do + URL=$(build_artifact_url "${name}.${ext}") + curl -v --fail -o "$DOWNLOAD_DIR/${name}.${ext}" "$URL" + done + done + + ( cd "$DOWNLOAD_DIR" && shasum -c cycles-ledger.wasm.gz.sha256 && shasum -c cycles-depositor.wasm.gz.sha256 ) + mv "$DOWNLOAD_DIR" "$DEST_DIR" +} + +install_cycles_ledger_canisters() { + download_cycles_ledger_canisters + cp "$(downloaded_cycles_ledger_canisters_dir)"/* . +} diff --git a/nix/sources.json b/nix/sources.json index 14c77ab188..547ad97708 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -6,10 +6,10 @@ "homepage": "https://rustsec.org", "owner": "RustSec", "repo": "advisory-db", - "rev": "9b6403d856f71291aabb91511b53fa79c2758745", - "sha256": "1b839a6bja6x1660737yfv7b7qfldcjd36g2s3a1v74q3f6zv5nw", + "rev": "da470caa84d3dd3be02657a9cb35bd5269636127", + "sha256": "1hm7dkr0zpjrqjrdjw2sg0zyvnpi5nia8nkwi0cn9nzy7nniq002", "type": "tarball", - "url": "https://github.com/RustSec/advisory-db/archive/9b6403d856f71291aabb91511b53fa79c2758745.tar.gz", + "url": "https://github.com/RustSec/advisory-db/archive/da470caa84d3dd3be02657a9cb35bd5269636127.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "canister_sandbox-x86_64-darwin": { @@ -160,27 +160,27 @@ "builtin": false, "description": "The Motoko base library", "owner": "dfinity", - "sha256": "18qp0d8mr6hcnbym81a4hgvj036vm0qdbjb833w1rqqhibv1083s", + "sha256": "1ks5fflz4yjfqaf3lq6izdz7fqkczi40m3hc3rifjm8p618pxc2c", "type": "tarball", - "url": "https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-base-library.tar.gz", + "url": "https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-base-library.tar.gz", "url_template": "https://github.com/dfinity/motoko/releases/download//motoko-base-library.tar.gz", - "version": "0.9.8" + "version": "0.10.0" }, "motoko-x86_64-darwin": { "builtin": false, - "sha256": "153lnpkdna9dv76zvmy44farsy8r4ixv4yi8bf5qnkr9dmkmrdr7", + "sha256": "0kxxmx39sjy99qddhmd1m7v5jpcmlnhr1a7ffcv45xs0iva032x3", "type": "file", - "url": "https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-Darwin-x86_64-0.9.8.tar.gz", + "url": "https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-Darwin-x86_64-0.10.0.tar.gz", "url_template": "https://github.com/dfinity/motoko/releases/download//motoko-Darwin-x86_64-.tar.gz", - "version": "0.9.8" + "version": "0.10.0" }, "motoko-x86_64-linux": { "builtin": false, - "sha256": "11yaciq3fq6fckqrw9gh5wb23cz5wxk9fij0z107nsiys9z46g2i", + "sha256": "1639zr4hx1rhi0mkip75z600l6hkhysych9jy5l4lmy4gyrkn9h6", "type": "file", - "url": "https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-Linux-x86_64-0.9.8.tar.gz", + "url": "https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-Linux-x86_64-0.10.0.tar.gz", "url_template": "https://github.com/dfinity/motoko/releases/download//motoko-Linux-x86_64-.tar.gz", - "version": "0.9.8" + "version": "0.10.0" }, "replica-x86_64-darwin": { "builtin": false, diff --git a/src/canisters/frontend/ic-asset/src/canister_api/methods/api_version.rs b/src/canisters/frontend/ic-asset/src/canister_api/methods/api_version.rs index 235a221787..b882c1e255 100644 --- a/src/canisters/frontend/ic-asset/src/canister_api/methods/api_version.rs +++ b/src/canisters/frontend/ic-asset/src/canister_api/methods/api_version.rs @@ -4,7 +4,7 @@ use ic_utils::Canister; pub(crate) async fn api_version(canister: &Canister<'_>) -> u16 { canister - .query_(API_VERSION) + .query(API_VERSION) .build() .call() .await diff --git a/src/canisters/frontend/ic-asset/src/canister_api/methods/asset_properties.rs b/src/canisters/frontend/ic-asset/src/canister_api/methods/asset_properties.rs index a0fda24559..4efdd1c8da 100644 --- a/src/canisters/frontend/ic-asset/src/canister_api/methods/asset_properties.rs +++ b/src/canisters/frontend/ic-asset/src/canister_api/methods/asset_properties.rs @@ -49,7 +49,7 @@ pub(crate) async fn get_asset_properties( asset_id: &str, ) -> Result { let (asset_properties,): (AssetProperties,) = canister - .query_(GET_ASSET_PROPERTIES) + .query(GET_ASSET_PROPERTIES) .with_arg(GetAssetPropertiesArgument(asset_id.to_string())) .build() .call() diff --git a/src/canisters/frontend/ic-asset/src/canister_api/methods/batch.rs b/src/canisters/frontend/ic-asset/src/canister_api/methods/batch.rs index db4ad8d6f5..db0296f75a 100644 --- a/src/canisters/frontend/ic-asset/src/canister_api/methods/batch.rs +++ b/src/canisters/frontend/ic-asset/src/canister_api/methods/batch.rs @@ -24,7 +24,7 @@ pub(crate) async fn create_batch(canister: &Canister<'_>) -> Result( loop { match canister - .update_(method_name) + .update(method_name) .with_arg(&arg) .build() .call_and_wait() @@ -103,7 +103,7 @@ pub(crate) async fn compute_evidence( loop { match canister - .update_(COMPUTE_EVIDENCE) + .update(COMPUTE_EVIDENCE) .with_arg(arg) .build() .map(|result: (Option,)| (result.0,)) diff --git a/src/canisters/frontend/ic-asset/src/canister_api/methods/chunk.rs b/src/canisters/frontend/ic-asset/src/canister_api/methods/chunk.rs index 8fab2c9045..ad55aab8eb 100644 --- a/src/canisters/frontend/ic-asset/src/canister_api/methods/chunk.rs +++ b/src/canisters/frontend/ic-asset/src/canister_api/methods/chunk.rs @@ -26,7 +26,7 @@ pub(crate) async fn create_chunk( .build(); loop { - let builder = canister.update_(CREATE_CHUNK); + let builder = canister.update(CREATE_CHUNK); let builder = builder.with_arg(&args); let request_id_result = { let _releaser = semaphores.create_chunk_call.acquire(1).await; diff --git a/src/canisters/frontend/ic-asset/src/canister_api/methods/list.rs b/src/canisters/frontend/ic-asset/src/canister_api/methods/list.rs index 1d7734e35b..321e30b34c 100644 --- a/src/canisters/frontend/ic-asset/src/canister_api/methods/list.rs +++ b/src/canisters/frontend/ic-asset/src/canister_api/methods/list.rs @@ -9,7 +9,7 @@ pub(crate) async fn list_assets( canister: &Canister<'_>, ) -> Result, AgentError> { let (entries,): (Vec,) = canister - .query_(LIST) + .query(LIST) .with_arg(ListAssetsRequest {}) .build() .call() diff --git a/src/canisters/frontend/icx-asset/src/commands/list.rs b/src/canisters/frontend/icx-asset/src/commands/list.rs index 50ee387c86..5bbddd014b 100644 --- a/src/canisters/frontend/icx-asset/src/commands/list.rs +++ b/src/canisters/frontend/icx-asset/src/commands/list.rs @@ -26,7 +26,7 @@ pub async fn list(canister: &Canister<'_>, logger: &Logger) -> anyhow::Result<() struct EmptyRecord {} let (entries,): (Vec,) = canister - .query_("list") + .query("list") .with_arg(EmptyRecord {}) .build() .call() diff --git a/src/dfx-core/src/network/provider.rs b/src/dfx-core/src/network/provider.rs index dcc67b2cd6..4d39284ab0 100644 --- a/src/dfx-core/src/network/provider.rs +++ b/src/dfx-core/src/network/provider.rs @@ -474,7 +474,7 @@ mod tests { ReplicaLogLevel, }; use std::fs; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::net::SocketAddr; use std::str::FromStr; #[test] diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index cf60e8de61..fbf212e727 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -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" diff --git a/src/dfx/assets/dfx-asset-sources.toml b/src/dfx/assets/dfx-asset-sources.toml index 2ab11fd4f1..d98bfee288 100644 --- a/src/dfx/assets/dfx-asset-sources.toml +++ b/src/dfx/assets/dfx-asset-sources.toml @@ -30,8 +30,8 @@ url = 'https://download.dfinity.systems/ic/91bf38ff3cb927cb94027d9da513cd15f91a5 sha256 = '36fd4a9db0f3291bc7616f9a5900926a808fa3cd563175654744ad697f4bc26d' [x86_64-darwin.motoko] -url = 'https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-Darwin-x86_64-0.9.8.tar.gz' -sha256 = '27b75c676d294f8b8b5b287ab27b2419799d9523c4d7fdcdd92d29dbe6b57494' +url = 'https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-Darwin-x86_64-0.10.0.tar.gz' +sha256 = 'a38b01d48e40f7423673eea890a1a5955d59f6a9a155d81a4ec94b9d46afbd4f' # The replica and canister_sandbox binaries must have the same revision. [x86_64-darwin.replica] @@ -52,8 +52,8 @@ url = 'https://download.dfinity.systems/ic/91bf38ff3cb927cb94027d9da513cd15f91a5 sha256 = '243a3d9f15f16f4efe485d1dff458f1671b169a7298965b54cd643e22c8ca85e' [x86_64-darwin.motoko-base] -url = 'https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-base-library.tar.gz' -sha256 = 'cd8213878fc1663e5c343eeee9f72d10e31e0ea40ae3d1ac24936deb9ca617f4' +url = 'https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-base-library.tar.gz' +sha256 = '3e9a4b2310e669d104b94c6032dec7902cfd2d5f3b8dda6e3db75dcb4fa2d450' [x86_64-darwin.ic-btc-canister] url = 'https://github.com/dfinity/bitcoin-canister/releases/download/release%2F2023-03-31/ic-btc-canister.wasm.gz' @@ -88,8 +88,8 @@ url = 'https://download.dfinity.systems/ic/91bf38ff3cb927cb94027d9da513cd15f91a5 sha256 = '669daa35328b6539298e577c81ed8515d3e5c5bfe83c3008a9c932fbe058d036' [x86_64-linux.motoko] -url = 'https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-Linux-x86_64-0.9.8.tar.gz' -sha256 = '513c437ed23e6a7b40f840469766e7e5b321162ff0259ef164ce60377064ca87' +url = 'https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-Linux-x86_64-0.10.0.tar.gz' +sha256 = '06263bb37fc4574a68f13241e6b587131a0a80f9e5dc382b8830870e49fe6998' # The replica and canister_sandbox binaries must have the same revision. [x86_64-linux.replica] @@ -110,8 +110,8 @@ url = 'https://download.dfinity.systems/ic/91bf38ff3cb927cb94027d9da513cd15f91a5 sha256 = '7e062a6867e675fac1340e5bf8d22b5dcb1c1b34303102c34e16920abf202394' [x86_64-linux.motoko-base] -url = 'https://github.com/dfinity/motoko/releases/download/0.9.8/motoko-base-library.tar.gz' -sha256 = 'cd8213878fc1663e5c343eeee9f72d10e31e0ea40ae3d1ac24936deb9ca617f4' +url = 'https://github.com/dfinity/motoko/releases/download/0.10.0/motoko-base-library.tar.gz' +sha256 = '3e9a4b2310e669d104b94c6032dec7902cfd2d5f3b8dda6e3db75dcb4fa2d450' [x86_64-linux.ic-btc-canister] url = 'https://github.com/dfinity/bitcoin-canister/releases/download/release%2F2023-03-31/ic-btc-canister.wasm.gz' diff --git a/src/dfx/src/commands/canister/call.rs b/src/dfx/src/commands/canister/call.rs index 9f8eb53418..eb034120f6 100644 --- a/src/dfx/src/commands/canister/call.rs +++ b/src/dfx/src/commands/canister/call.rs @@ -94,7 +94,7 @@ struct CallIn { async fn do_wallet_call(wallet: &WalletCanister<'_>, args: &CallIn) -> DfxResult> { // todo change to wallet.call when IDLValue implements ArgumentDecoder let builder = if wallet.version_supports_u128_cycles() { - wallet.update_("wallet_call128").with_arg(args) + wallet.update("wallet_call128").with_arg(args) } else { let CallIn { canister, @@ -108,7 +108,7 @@ async fn do_wallet_call(wallet: &WalletCanister<'_>, args: &CallIn) -> DfxResult args, cycles: cycles as u64, }; - wallet.update_("wallet_call").with_arg(args64) + wallet.update("wallet_call").with_arg(args64) }; let (result,): (Result,) = builder .build() @@ -263,9 +263,7 @@ pub async fn exec( // Get the argument, get the type, convert the argument to the type and return // an error if any of it doesn't work. let arg_value = blob_from_arguments(arguments, opts.random.as_deref(), arg_type, &method_type)?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/canister/delete.rs b/src/dfx/src/commands/canister/delete.rs index 88c2b0de58..09f906c96b 100644 --- a/src/dfx/src/commands/canister/delete.rs +++ b/src/dfx/src/commands/canister/delete.rs @@ -10,7 +10,7 @@ use crate::lib::operations::canister::{ use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::assets::wallet_wasm; use crate::util::blob_from_arguments; -use anyhow::{anyhow, Context}; +use anyhow::Context; use candid::Principal; use clap::Parser; use dfx_core::canister::build_wallet_canister; @@ -149,9 +149,7 @@ async fn delete_canister( "Canister {canister} has not been stopped. Delete anyway?" ))?; } - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let mgr = ManagementCanister::create(agent); let canister_id = Principal::from_text(canister).or_else(|_| canister_id_store.get(canister))?; diff --git a/src/dfx/src/commands/canister/info.rs b/src/dfx/src/commands/canister/info.rs index 8139698cec..3e352fe71f 100644 --- a/src/dfx/src/commands/canister/info.rs +++ b/src/dfx/src/commands/canister/info.rs @@ -17,9 +17,7 @@ pub struct InfoOpts { } pub async fn exec(env: &dyn Environment, opts: InfoOpts) -> DfxResult { - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let callee_canister = opts.canister.as_str(); let canister_id_store = env.get_canister_id_store()?; diff --git a/src/dfx/src/commands/canister/metadata.rs b/src/dfx/src/commands/canister/metadata.rs index 86f9fbcb4e..2c4ea1337a 100644 --- a/src/dfx/src/commands/canister/metadata.rs +++ b/src/dfx/src/commands/canister/metadata.rs @@ -1,7 +1,7 @@ use crate::lib::error::DfxResult; use crate::lib::root_key::fetch_root_key_if_needed; use crate::Environment; -use anyhow::{anyhow, Context}; +use anyhow::Context; use candid::Principal; use clap::Parser; use std::io::{stdout, Write}; @@ -17,9 +17,7 @@ pub struct CanisterMetadataOpts { } pub async fn exec(env: &dyn Environment, opts: CanisterMetadataOpts) -> DfxResult { - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let callee_canister = opts.canister_name.as_str(); let canister_id_store = env.get_canister_id_store()?; diff --git a/src/dfx/src/commands/canister/request_status.rs b/src/dfx/src/commands/canister/request_status.rs index ba20dcccc6..12dcd352a8 100644 --- a/src/dfx/src/commands/canister/request_status.rs +++ b/src/dfx/src/commands/canister/request_status.rs @@ -3,7 +3,7 @@ use crate::lib::error::{DfxError, DfxResult}; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::parsers; use crate::util::print_idl_blob; -use anyhow::{anyhow, Context}; +use anyhow::Context; use backoff::backoff::Backoff; use backoff::ExponentialBackoff; use candid::Principal; @@ -36,9 +36,7 @@ pub struct RequestStatusOpts { pub async fn exec(env: &dyn Environment, opts: RequestStatusOpts) -> DfxResult { let request_id = RequestId::from_str(&opts.request_id[2..]).context("Invalid argument: request_id")?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/canister/sign.rs b/src/dfx/src/commands/canister/sign.rs index d8c317ca84..69515733de 100644 --- a/src/dfx/src/commands/canister/sign.rs +++ b/src/dfx/src/commands/canister/sign.rs @@ -125,9 +125,7 @@ pub async fn exec( // Get the argument, get the type, convert the argument to the type and return // an error if any of it doesn't work. let arg_value = blob_from_arguments(arguments, opts.random.as_deref(), arg_type, &method_type)?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let network = env .get_network_descriptor() diff --git a/src/dfx/src/commands/cycles/balance.rs b/src/dfx/src/commands/cycles/balance.rs new file mode 100644 index 0000000000..1cdbe78e1b --- /dev/null +++ b/src/dfx/src/commands/cycles/balance.rs @@ -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, + + /// Subaccount of the selected identity to get the balance of + #[arg(long)] + subaccount: Option, + + /// 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(()) +} diff --git a/src/dfx/src/commands/cycles/mod.rs b/src/dfx/src/commands/cycles/mod.rs new file mode 100644 index 0000000000..36a269a713 --- /dev/null +++ b/src/dfx/src/commands/cycles/mod.rs @@ -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, + } + }) +} diff --git a/src/dfx/src/commands/deps/deploy.rs b/src/dfx/src/commands/deps/deploy.rs index 9aef0e7328..d4685f0f3a 100644 --- a/src/dfx/src/commands/deps/deploy.rs +++ b/src/dfx/src/commands/deps/deploy.rs @@ -7,7 +7,7 @@ use crate::lib::deps::{ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::root_key::fetch_root_key_if_needed; -use anyhow::anyhow; + use candid::Principal; use clap::Parser; use fn_error_context::context; @@ -38,9 +38,7 @@ pub async fn exec(env: &dyn Environment, opts: DepsDeployOpts) -> DfxResult { let init_json = load_init_json(&project_root)?; fetch_root_key_if_needed(env).await?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let canister_ids = match &opts.canister { Some(canister) => { diff --git a/src/dfx/src/commands/deps/pull.rs b/src/dfx/src/commands/deps/pull.rs index 8edeaa7e71..1f53471207 100644 --- a/src/dfx/src/commands/deps/pull.rs +++ b/src/dfx/src/commands/deps/pull.rs @@ -53,9 +53,7 @@ pub async fn exec(env: &dyn Environment, opts: DepsPullOpts) -> DfxResult { fetch_root_key_if_needed(&env).await?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let all_dependencies = resolve_all_dependencies(agent, logger, &pull_canisters_in_config).await?; diff --git a/src/dfx/src/commands/identity/set_wallet.rs b/src/dfx/src/commands/identity/set_wallet.rs index 1b3fe3cfbe..74cd6b58a7 100644 --- a/src/dfx/src/commands/identity/set_wallet.rs +++ b/src/dfx/src/commands/identity/set_wallet.rs @@ -57,9 +57,7 @@ pub fn exec(env: &dyn Environment, opts: SetWalletOpts, network: NetworkOpt) -> "Skipping verification of availability of the canister on the network due to --force..." ); } else { - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); runtime .block_on(async { diff --git a/src/dfx/src/commands/ledger/balance.rs b/src/dfx/src/commands/ledger/balance.rs index 5ee77a5922..f02fd0657c 100644 --- a/src/dfx/src/commands/ledger/balance.rs +++ b/src/dfx/src/commands/ledger/balance.rs @@ -34,9 +34,7 @@ pub async fn exec(env: &dyn Environment, opts: BalanceOpts) -> DfxResult { |v| AccountIdentifier::from_str(&v), ) .map_err(|err| anyhow!(err))?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let balance = ledger::balance(agent, &acc_id, opts.ledger_canister_id).await?; diff --git a/src/dfx/src/commands/ledger/create_canister.rs b/src/dfx/src/commands/ledger/create_canister.rs index 8d0fc6606d..df10e517d1 100644 --- a/src/dfx/src/commands/ledger/create_canister.rs +++ b/src/dfx/src/commands/ledger/create_canister.rs @@ -9,7 +9,7 @@ use crate::lib::nns_types::icpts::{ICPTs, TRANSACTION_FEE}; use crate::lib::operations::cmc::{notify_create, transfer_cmc}; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::parsers::e8s_parser; -use anyhow::{anyhow, bail, Context}; +use anyhow::{bail, Context}; use candid::Principal; use clap::Parser; @@ -71,9 +71,7 @@ pub async fn exec(env: &dyn Environment, opts: CreateCanisterOpts) -> DfxResult ) })?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/ledger/notify/create_canister.rs b/src/dfx/src/commands/ledger/notify/create_canister.rs index a588cafa92..a9e2f53e65 100644 --- a/src/dfx/src/commands/ledger/notify/create_canister.rs +++ b/src/dfx/src/commands/ledger/notify/create_canister.rs @@ -3,7 +3,7 @@ use crate::lib::ledger_types::NotifyError::Refunded; use crate::lib::operations::cmc::notify_create; use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::{environment::Environment, error::DfxResult}; -use anyhow::{anyhow, bail}; +use anyhow::bail; use candid::Principal; use clap::Parser; @@ -27,9 +27,7 @@ pub async fn exec(env: &dyn Environment, opts: NotifyCreateOpts) -> DfxResult { let block_height = opts.block_height; let controller = opts.controller; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/ledger/notify/top_up.rs b/src/dfx/src/commands/ledger/notify/top_up.rs index a64578c29a..ae7885a018 100644 --- a/src/dfx/src/commands/ledger/notify/top_up.rs +++ b/src/dfx/src/commands/ledger/notify/top_up.rs @@ -3,7 +3,7 @@ use crate::lib::ledger_types::NotifyError::Refunded; use crate::lib::operations::cmc::notify_top_up; use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::{environment::Environment, error::DfxResult}; -use anyhow::{anyhow, bail}; +use anyhow::bail; use candid::Principal; use clap::Parser; @@ -21,9 +21,7 @@ pub async fn exec(env: &dyn Environment, opts: NotifyTopUpOpts) -> DfxResult { let block_height = opts.block_height; let canister = opts.canister; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/ledger/show_subnet_types.rs b/src/dfx/src/commands/ledger/show_subnet_types.rs index 30ea9e0850..305f164c85 100644 --- a/src/dfx/src/commands/ledger/show_subnet_types.rs +++ b/src/dfx/src/commands/ledger/show_subnet_types.rs @@ -2,7 +2,7 @@ use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::ledger_types::{GetSubnetTypesToSubnetsResult, MAINNET_CYCLE_MINTER_CANISTER_ID}; use crate::lib::root_key::fetch_root_key_if_needed; -use anyhow::{anyhow, Context}; +use anyhow::Context; use candid::{Decode, Encode, Principal}; use clap::Parser; @@ -17,9 +17,7 @@ pub struct ShowSubnetTypesOpts { } 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."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/ledger/top_up.rs b/src/dfx/src/commands/ledger/top_up.rs index 414efe946b..f68da4ca2d 100644 --- a/src/dfx/src/commands/ledger/top_up.rs +++ b/src/dfx/src/commands/ledger/top_up.rs @@ -8,7 +8,7 @@ use crate::lib::nns_types::icpts::{ICPTs, TRANSACTION_FEE}; use crate::lib::operations::cmc::{notify_top_up, transfer_cmc}; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::clap::parsers::e8s_parser; -use anyhow::{anyhow, bail, Context}; +use anyhow::{bail, Context}; use candid::Principal; use clap::Parser; @@ -65,9 +65,7 @@ pub async fn exec(env: &dyn Environment, opts: TopUpOpts) -> DfxResult { ) })?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/ledger/transfer.rs b/src/dfx/src/commands/ledger/transfer.rs index cd1c58925d..27d623c64c 100644 --- a/src/dfx/src/commands/ledger/transfer.rs +++ b/src/dfx/src/commands/ledger/transfer.rs @@ -70,9 +70,7 @@ pub async fn exec(env: &dyn Environment, opts: TransferOpts) -> DfxResult { })? .to_address(); - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; diff --git a/src/dfx/src/commands/mod.rs b/src/dfx/src/commands/mod.rs index 2a402902d5..139ac8ae6c 100644 --- a/src/dfx/src/commands/mod.rs +++ b/src/dfx/src/commands/mod.rs @@ -7,6 +7,7 @@ mod beta; mod build; mod cache; mod canister; +mod cycles; mod deploy; mod deps; mod diagnose; @@ -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), @@ -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), diff --git a/src/dfx/src/commands/quickstart.rs b/src/dfx/src/commands/quickstart.rs index 2059cb0713..19eb300825 100644 --- a/src/dfx/src/commands/quickstart.rs +++ b/src/dfx/src/commands/quickstart.rs @@ -45,7 +45,7 @@ pub struct QuickstartOpts; pub fn exec(env: &dyn Environment, _: QuickstartOpts) -> DfxResult { let env = create_agent_environment(env, Some("ic".to_string()))?; - let agent = env.get_agent().expect("Unable to create agent"); + let agent = env.get_agent(); let ident = env.get_selected_identity().unwrap(); let principal = env.get_selected_identity_principal().unwrap(); eprintln!("Your DFX user principal: {principal}"); diff --git a/src/dfx/src/commands/wallet/mod.rs b/src/dfx/src/commands/wallet/mod.rs index 0eb1a95564..bbc2538f4f 100644 --- a/src/dfx/src/commands/wallet/mod.rs +++ b/src/dfx/src/commands/wallet/mod.rs @@ -92,7 +92,7 @@ where let wallet = get_or_create_wallet_canister(env, network, &identity_name).await?; let out: O = wallet - .query_(method) + .query(method) .with_arg(arg) .build() .call() @@ -109,7 +109,7 @@ where { let wallet = get_wallet(env).await?; let out: O = wallet - .update_(method) + .update(method) .with_arg(arg) .build() .call_and_wait() diff --git a/src/dfx/src/commands/wallet/redeem_faucet_coupon.rs b/src/dfx/src/commands/wallet/redeem_faucet_coupon.rs index 45c790ae6a..93568c4f0c 100644 --- a/src/dfx/src/commands/wallet/redeem_faucet_coupon.rs +++ b/src/dfx/src/commands/wallet/redeem_faucet_coupon.rs @@ -33,9 +33,7 @@ pub async fn exec(env: &dyn Environment, opts: RedeemFaucetCouponOpts) -> DfxRes } else { Principal::from_text(DEFAULT_FAUCET_PRINCIPAL).unwrap() }; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); if fetch_root_key_if_needed(env).await.is_err() { bail!("Failed to connect to the local replica. Did you forget to use '--network ic'?"); } else if !env.get_network_descriptor().is_ic { diff --git a/src/dfx/src/commands/wallet/upgrade.rs b/src/dfx/src/commands/wallet/upgrade.rs index 4828f0bc10..a0d36b3076 100644 --- a/src/dfx/src/commands/wallet/upgrade.rs +++ b/src/dfx/src/commands/wallet/upgrade.rs @@ -4,7 +4,7 @@ use crate::lib::identity::wallet::wallet_canister_id; use crate::lib::operations::canister::install_canister::install_wallet; use crate::lib::root_key::fetch_root_key_if_needed; use crate::lib::state_tree::canister_info::read_state_tree_canister_module_hash; -use anyhow::{anyhow, bail}; +use anyhow::bail; use clap::Parser; use ic_utils::interfaces::management_canister::builders::InstallMode; @@ -31,9 +31,7 @@ pub async fn exec(env: &dyn Environment, _opts: UpgradeOpts) -> DfxResult { ); }; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); fetch_root_key_if_needed(env).await?; if read_state_tree_canister_module_hash(agent, canister_id) @@ -43,9 +41,7 @@ pub async fn exec(env: &dyn Environment, _opts: UpgradeOpts) -> DfxResult { bail!("The cycles wallet canister is empty. Try running `dfx identity deploy-wallet` to install code for the cycles wallet in this canister.") } - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); install_wallet(env, agent, canister_id, InstallMode::Upgrade).await?; diff --git a/src/dfx/src/lib/environment.rs b/src/dfx/src/lib/environment.rs index 917cdd30fd..f1056a72ee 100644 --- a/src/dfx/src/lib/environment.rs +++ b/src/dfx/src/lib/environment.rs @@ -43,7 +43,7 @@ pub trait Environment { // Explicit lifetimes are actually needed for mockall to work properly. #[allow(clippy::needless_lifetimes)] - fn get_agent<'a>(&'a self) -> Option<&'a Agent>; + fn get_agent<'a>(&'a self) -> &'a Agent; #[allow(clippy::needless_lifetimes)] fn get_network_descriptor<'a>(&'a self) -> &'a NetworkDescriptor; @@ -210,10 +210,8 @@ impl Environment for EnvironmentImpl { &self.identity_override } - fn get_agent(&self) -> Option<&Agent> { - // create an AgentEnvironment explicitly, in order to specify network and agent. - // See install, build for examples. - None + fn get_agent(&self) -> &Agent { + unreachable!("Agent only available from an AgentEnvironment"); } fn get_network_descriptor(&self) -> &NetworkDescriptor { @@ -337,8 +335,8 @@ impl<'a> Environment for AgentEnvironment<'a> { self.backend.get_identity_override() } - fn get_agent(&self) -> Option<&Agent> { - Some(&self.agent) + fn get_agent(&self) -> &Agent { + &self.agent } fn get_network_descriptor(&self) -> &NetworkDescriptor { diff --git a/src/dfx/src/lib/identity/wallet.rs b/src/dfx/src/lib/identity/wallet.rs index 93b0c0b907..d0b3abb454 100644 --- a/src/dfx/src/lib/identity/wallet.rs +++ b/src/dfx/src/lib/identity/wallet.rs @@ -3,7 +3,7 @@ use crate::lib::error::DfxResult; use crate::lib::root_key::fetch_root_key_if_needed; use crate::util::assets::wallet_wasm; use crate::Environment; -use anyhow::{anyhow, bail, Context}; +use anyhow::{bail, Context}; use candid::Principal; use dfx_core::canister::build_wallet_canister; use dfx_core::config::directories::get_user_dfx_config_dir; @@ -83,9 +83,7 @@ pub async fn create_wallet( some_canister_id: Option, ) -> DfxResult { fetch_root_key_if_needed(env).await?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let mgr = ManagementCanister::create(agent); info!( env.get_logger(), @@ -161,9 +159,7 @@ pub async fn get_or_create_wallet_canister<'env>( // without this async block, #[context] gives a spurious error async { let wallet_canister_id = get_or_create_wallet(env, network, name).await?; - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); build_wallet_canister(wallet_canister_id, agent) .await .map_err(Into::into) diff --git a/src/dfx/src/lib/migrate.rs b/src/dfx/src/lib/migrate.rs index 18d7710868..16642be28a 100644 --- a/src/dfx/src/lib/migrate.rs +++ b/src/dfx/src/lib/migrate.rs @@ -19,9 +19,7 @@ pub async fn migrate(env: &dyn Environment, network: &NetworkDescriptor, fix: bo fetch_root_key_if_needed(env).await?; let config = env.get_config_or_anyhow()?; let config = config.get_config(); - let agent = env - .get_agent() - .expect("Could not get agent from environment"); + let agent = env.get_agent(); let mut mgr = env.new_identity_manager()?; let ident = mgr.instantiate_selected_identity(env.get_logger())?; let mut did_migrate = false; diff --git a/src/dfx/src/lib/mod.rs b/src/dfx/src/lib/mod.rs index 221cd6999d..e53ae0828d 100644 --- a/src/dfx/src/lib/mod.rs +++ b/src/dfx/src/lib/mod.rs @@ -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; diff --git a/src/dfx/src/lib/named_canister.rs b/src/dfx/src/lib/named_canister.rs index 1484b74fc7..c3afd7bdb3 100644 --- a/src/dfx/src/lib/named_canister.rs +++ b/src/dfx/src/lib/named_canister.rs @@ -30,10 +30,7 @@ pub async fn install_ui_canister( )); } fetch_root_key_if_needed(env).await?; - let mgr = ManagementCanister::create( - env.get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?, - ); + let mgr = ManagementCanister::create(env.get_agent()); info!( env.get_logger(), "Creating UI canister on the {} network.", network.name diff --git a/src/dfx/src/lib/operations/canister/create_canister.rs b/src/dfx/src/lib/operations/canister/create_canister.rs index c093613da6..1880100739 100644 --- a/src/dfx/src/lib/operations/canister/create_canister.rs +++ b/src/dfx/src/lib/operations/canister/create_canister.rs @@ -73,9 +73,7 @@ pub async fn create_canister( return reserve_canister_with_playground(env, canister_name).await; } - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let cid = match call_sender { CallSender::SelectedId => { create_with_management_canister(env, agent, with_cycles, specified_id, settings).await diff --git a/src/dfx/src/lib/operations/canister/deploy_canisters.rs b/src/dfx/src/lib/operations/canister/deploy_canisters.rs index fb1fccb2d1..b8d4cf65c8 100644 --- a/src/dfx/src/lib/operations/canister/deploy_canisters.rs +++ b/src/dfx/src/lib/operations/canister/deploy_canisters.rs @@ -365,9 +365,7 @@ async fn prepare_assets_for_commit( ); } - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); prepare_assets_for_proposal(&canister_info, agent, env.get_logger()).await?; @@ -391,9 +389,7 @@ async fn compute_evidence( ); } - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let assets_canister_info = canister_info.as_info::()?; let source_paths = assets_canister_info.get_source_paths(); diff --git a/src/dfx/src/lib/operations/canister/install_canister.rs b/src/dfx/src/lib/operations/canister/install_canister.rs index a53375493f..c8be2ea47a 100644 --- a/src/dfx/src/lib/operations/canister/install_canister.rs +++ b/src/dfx/src/lib/operations/canister/install_canister.rs @@ -50,9 +50,7 @@ pub async fn install_canister( no_asset_upgrade: bool, ) -> DfxResult { let log = env.get_logger(); - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let network = env.get_network_descriptor(); if !network.is_ic && named_canister::get_ui_canister_id(canister_id_store).is_none() { named_canister::install_ui_canister(env, canister_id_store, None).await?; diff --git a/src/dfx/src/lib/operations/canister/mod.rs b/src/dfx/src/lib/operations/canister/mod.rs index 767182a021..0d7595690e 100644 --- a/src/dfx/src/lib/operations/canister/mod.rs +++ b/src/dfx/src/lib/operations/canister/mod.rs @@ -7,7 +7,7 @@ use crate::lib::canister_info::CanisterInfo; use crate::lib::environment::Environment; use crate::lib::error::DfxResult; use crate::lib::ic_attributes::CanisterSettings as DfxCanisterSettings; -use anyhow::{anyhow, bail, Context}; +use anyhow::{bail, Context}; use candid::utils::ArgumentDecoder; use candid::CandidType; use candid::Principal as CanisterId; @@ -44,14 +44,12 @@ where A: CandidType + Sync + Send, O: for<'de> ArgumentDecoder<'de> + Sync + Send, { - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let out = match call_sender { CallSender::SelectedId => { let mgr = ManagementCanister::create(agent); - mgr.update_(method) + mgr.update(method) .with_arg(arg) .with_effective_canister_id(destination_canister) .build() diff --git a/src/dfx/src/lib/operations/canister/motoko_playground.rs b/src/dfx/src/lib/operations/canister/motoko_playground.rs index b53293582c..e9f4d33dd4 100644 --- a/src/dfx/src/lib/operations/canister/motoko_playground.rs +++ b/src/dfx/src/lib/operations/canister/motoko_playground.rs @@ -76,7 +76,7 @@ pub async fn reserve_canister_with_playground( env: &dyn Environment, canister_name: &str, ) -> DfxResult { - let agent = env.get_agent().context("Failed to get HTTP agent")?; + let agent = env.get_agent(); let log = env.get_logger(); let playground_canister = if let NetworkTypeDescriptor::Playground { playground_canister, @@ -125,7 +125,7 @@ pub async fn authorize_asset_uploader( canister_timestamp: &SystemTime, principal_to_authorize: &Principal, ) -> DfxResult { - let agent = env.get_agent().context("Failed to get HTTP agent")?; + let agent = env.get_agent(); let playground_canister = if let NetworkTypeDescriptor::Playground { playground_canister, .. @@ -159,7 +159,7 @@ pub async fn playground_install_code( is_asset_canister: bool, ) -> DfxResult { let canister_info = CanisterInfo::from(canister_id, canister_timestamp)?; - let agent = env.get_agent().context("Failed to get HTTP agent")?; + let agent = env.get_agent(); let playground_canister = match env.get_network_descriptor().r#type { NetworkTypeDescriptor::Playground { playground_canister, diff --git a/src/dfx/src/lib/operations/cycles_ledger.rs b/src/dfx/src/lib/operations/cycles_ledger.rs new file mode 100644 index 0000000000..ba75220d4e --- /dev/null +++ b/src/dfx/src/lib/operations/cycles_ledger.rs @@ -0,0 +1,44 @@ +use crate::lib::error::DfxResult; +use crate::lib::retryable::retryable; +use anyhow::anyhow; +use backoff::future::retry; +use backoff::ExponentialBackoff; +use candid::Principal; +use ic_agent::Agent; +use ic_utils::call::SyncCall; +use ic_utils::Canister; +use icrc_ledger_types::icrc1; + +const ICRC1_BALANCE_OF_METHOD: &str = "icrc1_balance_of"; + +pub async fn balance( + agent: &Agent, + owner: Principal, + subaccount: Option, + cycles_ledger_canister_id: Principal, +) -> DfxResult { + let canister = Canister::builder() + .with_agent(agent) + .with_canister_id(cycles_ledger_canister_id) + .build()?; + let arg = icrc1::account::Account { owner, subaccount }; + + let retry_policy = ExponentialBackoff::default(); + + retry(retry_policy, || async { + let result = canister + .query(ICRC1_BALANCE_OF_METHOD) + .with_arg(arg) + .build() + .call() + .await; + match result { + Ok((balance,)) => Ok(balance), + Err(agent_err) if retryable(&agent_err) => { + Err(backoff::Error::transient(anyhow!(agent_err))) + } + Err(agent_err) => Err(backoff::Error::permanent(anyhow!(agent_err))), + } + }) + .await +} diff --git a/src/dfx/src/lib/operations/ledger.rs b/src/dfx/src/lib/operations/ledger.rs index f3f360d740..c5206bf173 100644 --- a/src/dfx/src/lib/operations/ledger.rs +++ b/src/dfx/src/lib/operations/ledger.rs @@ -36,7 +36,7 @@ pub async fn balance( .with_canister_id(canister_id) .build()?; let (result,) = canister - .query_(ACCOUNT_BALANCE_METHOD) + .query(ACCOUNT_BALANCE_METHOD) .with_arg(AccountBalanceArgs { account: acct.to_string(), }) @@ -53,7 +53,7 @@ pub async fn xdr_permyriad_per_icp(agent: &Agent) -> DfxResult { .with_canister_id(MAINNET_CYCLE_MINTER_CANISTER_ID) .build()?; let (certified_rate,): (IcpXdrConversionRateCertifiedResponse,) = canister - .query_("get_icp_xdr_conversion_rate") + .query("get_icp_xdr_conversion_rate") .build() .call() .await?; diff --git a/src/dfx/src/lib/operations/mod.rs b/src/dfx/src/lib/operations/mod.rs index bbe6390dd4..d7d7b006f5 100644 --- a/src/dfx/src/lib/operations/mod.rs +++ b/src/dfx/src/lib/operations/mod.rs @@ -1,3 +1,4 @@ pub mod canister; pub mod cmc; +pub mod cycles_ledger; pub mod ledger; diff --git a/src/dfx/src/lib/retryable.rs b/src/dfx/src/lib/retryable.rs new file mode 100644 index 0000000000..edc76f241f --- /dev/null +++ b/src/dfx/src/lib/retryable.rs @@ -0,0 +1,8 @@ +use ic_agent::AgentError; + +pub fn retryable(agent_error: &AgentError) -> bool { + matches!( + agent_error, + AgentError::TimeoutWaitingForResponse() | AgentError::TransportError(_) + ) +} diff --git a/src/dfx/src/lib/root_key.rs b/src/dfx/src/lib/root_key.rs index d39f0f029b..5b7cd486cd 100644 --- a/src/dfx/src/lib/root_key.rs +++ b/src/dfx/src/lib/root_key.rs @@ -1,11 +1,9 @@ use crate::{lib::error::DfxResult, Environment}; -use anyhow::anyhow; + use dfx_core::network::root_key; pub async fn fetch_root_key_if_needed(env: &dyn Environment) -> DfxResult { - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let network = env.get_network_descriptor(); root_key::fetch_root_key_when_local(agent, network).await?; Ok(()) @@ -14,9 +12,7 @@ pub async fn fetch_root_key_if_needed(env: &dyn Environment) -> DfxResult { /// Fetches the root key of the local network. /// Returns an error if attempted to run on the real IC. pub async fn fetch_root_key_or_anyhow(env: &dyn Environment) -> DfxResult { - let agent = env - .get_agent() - .ok_or_else(|| anyhow!("Cannot get HTTP client from environment."))?; + let agent = env.get_agent(); let network = env.get_network_descriptor(); root_key::fetch_root_key_when_local_or_error(agent, network).await?; Ok(())