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: wallet_call_with_max_cycles #170

Merged
merged 14 commits into from
Apr 10, 2024
7 changes: 0 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/fmt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
rust: [ '1.70.0' ]
os: [ ubuntu-latest ]

steps:
Expand All @@ -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:
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: ['1.70.0']
os: [ubuntu-latest]
node-version: ['12.x']

Expand All @@ -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:
Expand Down
5 changes: 0 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions e2e/bash/send.bash
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.70.0"
channel = "1.74.1"
components = ["clippy", "rustfmt"]
targets = ["wasm32-unknown-unknown"]
13 changes: 13 additions & 0 deletions wallet/src/lib.did
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) -> ();
Expand Down
40 changes: 40 additions & 0 deletions wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,13 @@ mod wallet {
r#return: Vec<u8>,
}

#[derive(CandidType, Deserialize)]
struct CallResultWithMaxCycles {
#[serde(with = "serde_bytes")]
r#return: Vec<u8>,
attached_cycles: u128,
}

/// Forward a call to another canister.
#[update(guard = "is_custodian_or_controller", name = "wallet_call")]
async fn call(
Expand Down Expand Up @@ -935,6 +942,39 @@ mod wallet {
)),
}
}

#[derive(CandidType, Deserialize)]
struct CallWithMaxCyclesArgs {
canister: Principal,
method_name: String,
args: Vec<u8>,
}

#[update(
guard = "is_custodian_or_controller",
name = "wallet_call_with_max_cycles"
)]
async fn call_with_max_cycles(
args: CallWithMaxCyclesArgs,
) -> Result<CallResultWithMaxCycles, String> {
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,
})
}
}

/***************************************************************************************************
Expand Down
Loading