diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6ad8c79..6d94971 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,18 +23,11 @@ jobs: strategy: matrix: node-version: [12.x] - rust: ['1.70.0'] steps: - name: Checkout uses: actions/checkout@v2 - - name: Install Rust - run: | - rustup update ${{ matrix.rust }} --no-self-update - rustup default ${{ matrix.rust }} - rustup target add wasm32-unknown-unknown - - name: Install dfx uses: dfinity/setup-dfx@main with: diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index d106355..eb83e04 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -24,7 +24,6 @@ jobs: strategy: matrix: os: [ macos-latest, ubuntu-latest ] - rust: [ '1.70.0' ] # only dfx >= 0.8.3 lets us query multiple controllers dfx: [ '0.9.2' ] env: @@ -43,12 +42,6 @@ jobs: ~/.cargo/git ./target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.rust }}-1 - - name: Install Rust - run: | - rustup update ${{ matrix.rust }} --no-self-update - rustup default ${{ matrix.rust }} - rustup target add wasm32-unknown-unknown - rustup component add rustfmt - name: Provision Darwin if: matrix.os == 'macos-latest' run: bash .github/workflows/provision-darwin.sh diff --git a/.github/workflows/fmt.yaml b/.github/workflows/fmt.yaml index d535b98..8b48abd 100644 --- a/.github/workflows/fmt.yaml +++ b/.github/workflows/fmt.yaml @@ -20,7 +20,6 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - rust: [ '1.70.0' ] os: [ ubuntu-latest ] steps: @@ -34,12 +33,6 @@ jobs: ./target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install Rust - run: | - rustup update ${{ matrix.rust }} - rustup default ${{ matrix.rust }} - rustup component add rustfmt - - name: Run Cargo fmt run: cargo fmt --all -- --check env: diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 0522efc..a5f5faa 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -21,7 +21,6 @@ jobs: strategy: fail-fast: false matrix: - rust: ['1.70.0'] os: [ubuntu-latest] node-version: ['12.x'] @@ -35,12 +34,6 @@ jobs: ~/.cargo/git ./target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Install Rust - run: | - rustup update ${{ matrix.rust }} - rustup default ${{ matrix.rust }} - rustup component add clippy - name: Install dfx uses: dfinity/setup-dfx@main with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5aa4d2..14de511 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,6 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - rust: ['1.70.0'] node-version: ['12.x'] steps: - uses: actions/checkout@v1 @@ -35,10 +34,6 @@ jobs: ~/.cargo/git ./target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install Rust - run: | - rustup update ${{ matrix.rust }} - rustup default ${{ matrix.rust }} - name: Install dfx uses: dfinity/setup-dfx@main with: diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e0d6a..989fbb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [UNRELEASED] +### Added + +- Added `wallet_call_with_max_cycles` + ## [20230530] ### Fixed diff --git a/e2e/bash/send.bash b/e2e/bash/send.bash new file mode 100644 index 0000000..d80f42a --- /dev/null +++ b/e2e/bash/send.bash @@ -0,0 +1,47 @@ +#!/usr/bin/env bats + +# shellcheck source=/dev/null +source "$BATS_SUPPORT/load.bash" + +load util/assertions + +setup() { + # We want to work from a temporary directory, different for every test. + x=$(mktemp -d -t dfx-usage-env-home-XXXXXXXX) + cd "$x" || exit + export DFX_CONFIG_ROOT=$x + + dfx new --no-frontend e2e_project + cd e2e_project || exit 1 + dfx start --background --clean +} + +teardown() { + dfx stop + rm -rf "$DFX_CONFIG_ROOT" +} + +@test "wallet_call_with_max_cycles" { + dfx identity new alice + dfx identity new bob + WALLET_ALICE=$(dfx --identity alice identity get-wallet) + WALLET_BOB=$(dfx --identity bob identity get-wallet) + + ALICE_CYCLES_BEFORE_SEND=$(dfx --identity alice wallet balance | sed 's/[^0-9]//g') + if (( ALICE_CYCLES_BEFORE_SEND < 2000000000000 )); then + echo "alice has unexpectedly few cycles before sending: ${ALICE_CYCLES_BEFORE_SEND}" + exit 1 + fi + + # non-controller can't make the call + assert_command_fail dfx --identity bob canister call "${WALLET_ALICE}" wallet_call_with_max_cycles "(record { canister = principal \"${WALLET_BOB}\"; method_name = \"wallet_receive\"; args = blob \"\44\49\44\4c\00\00\"; })" + + assert_command dfx --identity alice canister call "${WALLET_ALICE}" wallet_call_with_max_cycles "(record { canister = principal \"${WALLET_BOB}\"; method_name = \"wallet_receive\"; args = blob \"\44\49\44\4c\00\00\"; })" + + # has less than 0.2T cycles afterwards + ALICE_CYCLES_AFTER_SEND=$(dfx --identity alice wallet balance | sed 's/[^0-9]//g') + if (( ALICE_CYCLES_AFTER_SEND > 200000000000 )); then + echo "expected alice to have <1TC after wallet_call_with_max_cycles, actually has ${ALICE_CYCLES_AFTER_SEND}, before was ${ALICE_CYCLES_BEFORE_SEND}" + exit 1 + fi +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b2fca04..b959a67 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.70.0" +channel = "1.74.1" components = ["clippy", "rustfmt"] targets = ["wasm32-unknown-unknown"] diff --git a/wallet/src/lib.did b/wallet/src/lib.did index 7b0ec6b..729c10e 100644 --- a/wallet/src/lib.did +++ b/wallet/src/lib.did @@ -161,6 +161,14 @@ type WalletResultCall = variant { Err : text; }; +type WalletResultCallWithMaxCycles = variant { + Ok : record { + return: blob; + attached_cycles: nat; + }; + Err : text; +}; + type CanisterSettings = record { controller: opt principal; controllers: opt vec principal; @@ -258,6 +266,11 @@ service : { args: blob; cycles: nat; }) -> (WalletResultCall); + wallet_call_with_max_cycles: (record{ + canister: principal; + method_name: text; + args: blob; + }) -> (WalletResultCallWithMaxCycles); // Address book add_address: (address: AddressEntry) -> (); diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index a67ac87..24b4b3e 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -892,6 +892,13 @@ mod wallet { r#return: Vec, } + #[derive(CandidType, Deserialize)] + struct CallResultWithMaxCycles { + #[serde(with = "serde_bytes")] + r#return: Vec, + attached_cycles: u128, + } + /// Forward a call to another canister. #[update(guard = "is_custodian_or_controller", name = "wallet_call")] async fn call( @@ -935,6 +942,39 @@ mod wallet { )), } } + + #[derive(CandidType, Deserialize)] + struct CallWithMaxCyclesArgs { + canister: Principal, + method_name: String, + args: Vec, + } + + #[update( + guard = "is_custodian_or_controller", + name = "wallet_call_with_max_cycles" + )] + async fn call_with_max_cycles( + args: CallWithMaxCyclesArgs, + ) -> Result { + let available_cycles = ic_cdk::api::canister_balance128(); + // If no margin is used then the call either fails locally with `Couldn't send message` or processing the response traps with `Canister out of cycles`. + // On the local network the margin needs to be ~1.7B cycles. (Experimentally determined in April 2024) + // Extrapolating, a margin of 100B should work up to a subnet of ~60 nodes. + const MARGIN: u128 = 100_000_000_000; + let cycles_to_attach = available_cycles.saturating_sub(MARGIN); + let result = call128(CallCanisterArgs { + canister: args.canister, + method_name: args.method_name, + args: args.args, + cycles: cycles_to_attach, + }) + .await?; + Ok(CallResultWithMaxCycles { + r#return: result.r#return, + attached_cycles: cycles_to_attach, + }) + } } /***************************************************************************************************