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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
strategy:
matrix:
node-version: [12.x]
rust: ['1.70.0']
rust: ['1.74.1']
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove the rust: ['1.74.1'] and the following "Install Rust" step from workflow files.

GitHub Action runners default to have Rust. And rust-toolchain.toml will automatically take effect.

Therefore, we will only need the Rust version in rust-toolchain.toml which is more maintainable.


steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
matrix:
os: [ macos-latest, ubuntu-latest ]
rust: [ '1.70.0' ]
rust: [ '1.74.1' ]
# only dfx >= 0.8.3 lets us query multiple controllers
dfx: [ '0.9.2' ]
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/fmt.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
rust: [ '1.70.0' ]
rust: [ '1.74.1' ]
os: [ ubuntu-latest ]

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: ['1.70.0']
rust: ['1.74.1']
os: [ubuntu-latest]
node-version: ['12.x']

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
rust: ['1.70.0']
rust: ['1.74.1']
node-version: ['12.x']
steps:
- uses: actions/checkout@v1
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 August 2024)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

August 2024

Time travel ¯_(ツ)_/¯

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