Skip to content

Commit

Permalink
feat: wallet_call_with_max_cycles (#170)
Browse files Browse the repository at this point in the history
  • Loading branch information
sesi200 authored Apr 10, 2024
1 parent 7dc0ce2 commit 98a8db8
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 34 deletions.
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

0 comments on commit 98a8db8

Please sign in to comment.