Skip to content

Commit d70b9eb

Browse files
author
Felipe Sodré
authored
feat(ICP-Ledger): FI-1508 add test icp allowance getter endpoint (#1934)
- Introduced the `allowance` endpoint to the ICP ledger canister that fetches an allowance given `AccountIdentifier`s. It's only available in test wasm and not exposed through the candid interface. - Refactored Bazel configuration by moving shared canister setup to `ledger_canisters.bzl` - Added tests to make sure the new endpoint works if and only if it's running from a test wasm.
1 parent f2b7672 commit d70b9eb

File tree

6 files changed

+187
-68
lines changed

6 files changed

+187
-68
lines changed

rs/ledger_suite/icp/ledger/BUILD.bazel

Lines changed: 9 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
2-
load("//bazel:canisters.bzl", "rust_canister")
32
load("//bazel:defs.bzl", "rust_ic_test")
3+
load("ledger_canisters.bzl", "LEDGER_CANISTER_DATA", "LEDGER_CANISTER_RUSTC_ENV", "rust_ledger_canister")
44

55
package(default_visibility = ["//visibility:public"])
66

@@ -61,70 +61,21 @@ rust_test(
6161
},
6262
)
6363

64-
LEDGER_CANISTER_DEPS = [
65-
# Keep sorted.
66-
":ledger",
67-
"//packages/ic-ledger-hash-of:ic_ledger_hash_of",
68-
"//packages/icrc-ledger-types:icrc_ledger_types",
69-
"//rs/ledger_suite/common/ledger_canister_core",
70-
"//rs/ledger_suite/common/ledger_core",
71-
"//rs/ledger_suite/icp:icp_ledger",
72-
"//rs/ledger_suite/icrc1",
73-
"//rs/rust_canisters/canister_log",
74-
"//rs/rust_canisters/dfn_candid",
75-
"//rs/rust_canisters/dfn_core",
76-
"//rs/rust_canisters/dfn_http_metrics",
77-
"//rs/rust_canisters/dfn_protobuf",
78-
"//rs/rust_canisters/on_wire",
79-
"//rs/types/base_types",
80-
"@crate_index//:candid",
81-
"@crate_index//:ciborium",
82-
"@crate_index//:ic-cdk",
83-
"@crate_index//:ic-metrics-encoder",
84-
"@crate_index//:ic-stable-structures",
85-
"@crate_index//:num-traits",
86-
"@crate_index//:serde_bytes",
87-
]
64+
rust_ledger_canister(name = "ledger-canister-wasm")
8865

89-
LEDGER_CANISTER_DATA = [
90-
"//rs/ledger_suite/icp:ledger.did",
91-
"//rs/ledger_suite/icp/ledger:ledger_candid_backwards_compatible.did",
92-
]
93-
94-
LEDGER_CANISTER_RUSTC_ENV = {
95-
"LEDGER_DID_PATH": "$(execpath //rs/ledger_suite/icp:ledger.did)",
96-
}
97-
98-
rust_canister(
99-
name = "ledger-canister-wasm",
100-
srcs = ["src/main.rs"],
101-
compile_data = LEDGER_CANISTER_DATA,
102-
data = LEDGER_CANISTER_DATA,
103-
rustc_env = LEDGER_CANISTER_RUSTC_ENV,
104-
service_file = "//rs/ledger_suite/icp:ledger.did",
105-
deps = LEDGER_CANISTER_DEPS,
66+
rust_ledger_canister(
67+
name = "ledger-canister-wasm-allowance-getter",
68+
crate_features = ["icp-allowance-getter"],
10669
)
10770

108-
rust_canister(
71+
rust_ledger_canister(
10972
name = "ledger-canister-wasm-notify-method",
110-
srcs = ["src/main.rs"],
111-
compile_data = LEDGER_CANISTER_DATA,
11273
crate_features = ["notify-method"],
113-
data = LEDGER_CANISTER_DATA,
114-
rustc_env = LEDGER_CANISTER_RUSTC_ENV,
115-
service_file = "//rs/ledger_suite/icp:ledger.did",
116-
deps = LEDGER_CANISTER_DEPS,
11774
)
11875

119-
rust_canister(
76+
rust_ledger_canister(
12077
name = "ledger-canister-wasm-upgrade-to-memory-manager",
121-
srcs = ["src/main.rs"],
122-
compile_data = LEDGER_CANISTER_DATA,
12378
crate_features = ["upgrade-to-memory-manager"],
124-
data = LEDGER_CANISTER_DATA,
125-
rustc_env = LEDGER_CANISTER_RUSTC_ENV,
126-
service_file = "//rs/ledger_suite/icp:ledger.did",
127-
deps = LEDGER_CANISTER_DEPS,
12879
)
12980

13081
rust_test(
@@ -144,6 +95,7 @@ rust_ic_test(
14495
srcs = ["tests/tests.rs"],
14596
data = [
14697
":ledger-canister-wasm",
98+
":ledger-canister-wasm-allowance-getter",
14799
":ledger-canister-wasm-upgrade-to-memory-manager",
148100
"@mainnet_icp_ledger_canister//file",
149101
],
@@ -152,6 +104,7 @@ rust_ic_test(
152104
"ICP_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_icp_ledger_canister//file)",
153105
"LEDGER_CANISTER_WASM_PATH": "$(rootpath :ledger-canister-wasm)",
154106
"LEDGER_CANISTER_UPGRADE_TO_MEMORY_MANAGER_WASM_PATH": "$(rootpath :ledger-canister-wasm-upgrade-to-memory-manager)",
107+
"LEDGER_CANISTER_ALLOWANCE_GETTER_WASM_PATH": "$(rootpath :ledger-canister-wasm-allowance-getter)",
155108
},
156109
flaky = True,
157110
deps = [

rs/ledger_suite/icp/ledger/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ ic-test-utilities-load-wasm = { path = "../../../test_utilities/load_wasm" }
5151

5252
[features]
5353
notify-method = []
54+
icp-allowance-getter = []
5455
upgrade-to-memory-manager = []
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
This Bazel file defines the configuration for building the Ledger Canister in Rust.
3+
4+
The `rust_ledger_canister` function is a wrapper around the `rust_canister` rule that
5+
sets up the necessary dependencies, environment variables, and service files required
6+
to build the Ledger Canister.
7+
8+
Usage:
9+
- The `rust_ledger_canister` function is used to instantiate a Rust canister with the
10+
provided `name` and optional `crate_features`. It simplifies the configuration for
11+
building the Ledger Canister by predefining common settings such as source files,
12+
dependencies, and environment variables.
13+
"""
14+
15+
load("//bazel:canisters.bzl", "rust_canister")
16+
17+
LEDGER_CANISTER_DATA = [
18+
"//rs/ledger_suite/icp:ledger.did",
19+
"//rs/ledger_suite/icp/ledger:ledger_candid_backwards_compatible.did",
20+
]
21+
22+
LEDGER_CANISTER_RUSTC_ENV = {
23+
"LEDGER_DID_PATH": "$(execpath //rs/ledger_suite/icp:ledger.did)",
24+
}
25+
26+
LEDGER_CANISTER_DEPS = [
27+
# Keep sorted.
28+
":ledger",
29+
"//packages/ic-ledger-hash-of:ic_ledger_hash_of",
30+
"//packages/icrc-ledger-types:icrc_ledger_types",
31+
"//rs/ledger_suite/common/ledger_canister_core",
32+
"//rs/ledger_suite/common/ledger_core",
33+
"//rs/ledger_suite/icp:icp_ledger",
34+
"//rs/ledger_suite/icrc1",
35+
"//rs/rust_canisters/canister_log",
36+
"//rs/rust_canisters/dfn_candid",
37+
"//rs/rust_canisters/dfn_core",
38+
"//rs/rust_canisters/dfn_http_metrics",
39+
"//rs/rust_canisters/dfn_protobuf",
40+
"//rs/rust_canisters/on_wire",
41+
"//rs/types/base_types",
42+
"@crate_index//:candid",
43+
"@crate_index//:ciborium",
44+
"@crate_index//:ic-cdk",
45+
"@crate_index//:ic-metrics-encoder",
46+
"@crate_index//:ic-stable-structures",
47+
"@crate_index//:num-traits",
48+
"@crate_index//:serde_bytes",
49+
]
50+
51+
def rust_ledger_canister(name, crate_features = None):
52+
rust_canister(
53+
name = name,
54+
srcs = ["src/main.rs"],
55+
compile_data = LEDGER_CANISTER_DATA,
56+
data = LEDGER_CANISTER_DATA,
57+
rustc_env = LEDGER_CANISTER_RUSTC_ENV,
58+
service_file = "//rs/ledger_suite/icp:ledger.did",
59+
deps = LEDGER_CANISTER_DEPS,
60+
crate_features = crate_features if crate_features else [],
61+
)

rs/ledger_suite/icp/ledger/src/main.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ use ic_ledger_core::{
2828
use ic_stable_structures::reader::{BufferedReader, Reader};
2929
#[cfg(feature = "upgrade-to-memory-manager")]
3030
use ic_stable_structures::writer::{BufferedWriter, Writer};
31+
#[cfg(feature = "icp-allowance-getter")]
32+
use icp_ledger::IcpAllowanceArgs;
3133
use icp_ledger::{
3234
max_blocks_per_request, protobuf, tokens_into_proto, AccountBalanceArgs, AccountIdBlob,
3335
AccountIdentifier, ArchiveInfo, ArchivedBlocksRange, ArchivedEncodedBlocksRange, Archives,
@@ -1620,27 +1622,43 @@ fn icrc2_approve_candid() {
16201622
})
16211623
}
16221624

1623-
#[candid_method(query, rename = "icrc2_allowance")]
1624-
fn icrc2_allowance(arg: AllowanceArgs) -> Allowance {
1625-
if !LEDGER.read().unwrap().feature_flags.icrc2 {
1626-
trap_with("ICRC-2 features are not enabled on the ledger.");
1627-
}
1625+
fn get_allowance(from: AccountIdentifier, spender: AccountIdentifier) -> Allowance {
16281626
let now = TimeStamp::from_nanos_since_unix_epoch(time_nanos());
16291627
let ledger = LEDGER.read().unwrap();
1630-
let account = AccountIdentifier::from(arg.account);
1631-
let spender = AccountIdentifier::from(arg.spender);
1632-
let allowance = ledger.approvals.allowance(&account, &spender, now);
1628+
let allowance = ledger.approvals.allowance(&from, &spender, now);
16331629
Allowance {
16341630
allowance: Nat::from(allowance.amount.get_e8s()),
16351631
expires_at: allowance.expires_at.map(|t| t.as_nanos_since_unix_epoch()),
16361632
}
16371633
}
16381634

1635+
#[candid_method(query, rename = "icrc2_allowance")]
1636+
fn icrc2_allowance(arg: AllowanceArgs) -> Allowance {
1637+
if !LEDGER.read().unwrap().feature_flags.icrc2 {
1638+
trap_with("ICRC-2 features are not enabled on the ledger.");
1639+
}
1640+
let from = AccountIdentifier::from(arg.account);
1641+
let spender = AccountIdentifier::from(arg.spender);
1642+
get_allowance(from, spender)
1643+
}
1644+
16391645
#[export_name = "canister_query icrc2_allowance"]
16401646
fn icrc2_allowance_candid() {
16411647
over(candid_one, icrc2_allowance)
16421648
}
16431649

1650+
#[cfg(feature = "icp-allowance-getter")]
1651+
#[candid_method(query, rename = "allowance")]
1652+
fn icp_allowance(arg: IcpAllowanceArgs) -> Allowance {
1653+
get_allowance(arg.account, arg.spender)
1654+
}
1655+
1656+
#[cfg(feature = "icp-allowance-getter")]
1657+
#[export_name = "canister_query allowance"]
1658+
fn allowance_candid() {
1659+
over(candid_one, icp_allowance)
1660+
}
1661+
16441662
#[candid_method(update, rename = "icrc21_canister_call_consent_message")]
16451663
fn icrc21_canister_call_consent_message(
16461664
consent_msg_request: ConsentMessageRequest,

rs/ledger_suite/icp/ledger/tests/tests.rs

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ use ic_state_machine_tests::{ErrorCode, PrincipalId, StateMachine, UserError};
1616
use icp_ledger::{
1717
AccountIdBlob, AccountIdentifier, ArchiveOptions, ArchivedBlocksRange, Block, CandidBlock,
1818
CandidOperation, CandidTransaction, FeatureFlags, GetBlocksArgs, GetBlocksRes, GetBlocksResult,
19-
GetEncodedBlocksResult, InitArgs, IterBlocksArgs, IterBlocksRes, LedgerCanisterInitPayload,
20-
LedgerCanisterPayload, LedgerCanisterUpgradePayload, Operation, QueryBlocksResponse,
21-
QueryEncodedBlocksResponse, TimeStamp, UpgradeArgs, DEFAULT_TRANSFER_FEE,
19+
GetEncodedBlocksResult, IcpAllowanceArgs, InitArgs, IterBlocksArgs, IterBlocksRes,
20+
LedgerCanisterInitPayload, LedgerCanisterPayload, LedgerCanisterUpgradePayload, Operation,
21+
QueryBlocksResponse, QueryEncodedBlocksResponse, TimeStamp, UpgradeArgs, DEFAULT_TRANSFER_FEE,
2222
MAX_BLOCKS_PER_INGRESS_REPLICATED_QUERY_REQUEST, MAX_BLOCKS_PER_REQUEST,
2323
};
2424
use icrc_ledger_types::icrc1::{
2525
account::Account,
2626
transfer::{Memo, TransferArg, TransferError},
2727
};
28-
use icrc_ledger_types::icrc2::allowance::AllowanceArgs;
28+
use icrc_ledger_types::icrc2::allowance::{Allowance, AllowanceArgs};
2929
use icrc_ledger_types::icrc2::approve::ApproveArgs;
3030
use num_traits::cast::ToPrimitive;
3131
use on_wire::{FromWire, IntoWire};
@@ -54,6 +54,14 @@ fn ledger_wasm_upgrade_to_memory_manager() -> Vec<u8> {
5454
)
5555
}
5656

57+
fn ledger_wasm_allowance_getter() -> Vec<u8> {
58+
ic_test_utilities_load_wasm::load_wasm(
59+
std::env::var("CARGO_MANIFEST_DIR").unwrap(),
60+
"ledger-canister-allowance-getter",
61+
&[],
62+
)
63+
}
64+
5765
fn encode_init_args(args: ic_icrc1_ledger_sm_tests::InitArgs) -> LedgerCanisterInitPayload {
5866
let initial_values = args
5967
.initial_balances
@@ -345,6 +353,78 @@ fn test_anonymous_approval() {
345353
);
346354
}
347355

356+
#[test]
357+
fn test_icp_allowance_getter_unavailable_in_prod() {
358+
let p1 = PrincipalId::new_user_test_id(1);
359+
let p2 = PrincipalId::new_user_test_id(2);
360+
let (env, canister_id) = setup(ledger_wasm(), encode_init_args, vec![]);
361+
let allowance_args = IcpAllowanceArgs {
362+
account: AccountIdentifier::from(p1.0),
363+
spender: AccountIdentifier::from(p2.0),
364+
};
365+
366+
let response = env.execute_ingress_as(
367+
p1,
368+
canister_id,
369+
"allowance",
370+
Encode!(&allowance_args).unwrap(),
371+
);
372+
let error = response.unwrap_err();
373+
374+
assert_eq!(error.code(), ErrorCode::CanisterMethodNotFound);
375+
}
376+
377+
#[test]
378+
fn test_get_icp_approval() {
379+
const INITIAL_BALANCE: u64 = 10_000_000;
380+
const APPROVE_AMOUNT: u64 = 1_000_000;
381+
let p1 = PrincipalId::new_user_test_id(1);
382+
let p2 = PrincipalId::new_user_test_id(2);
383+
let (env, canister_id) = setup(
384+
ledger_wasm_allowance_getter(),
385+
encode_init_args,
386+
vec![(Account::from(p1.0), INITIAL_BALANCE)],
387+
);
388+
assert_eq!(INITIAL_BALANCE, total_supply(&env, canister_id));
389+
assert_eq!(INITIAL_BALANCE, balance_of(&env, canister_id, p1.0));
390+
assert_eq!(0, balance_of(&env, canister_id, p2.0));
391+
let approve_args = ApproveArgs {
392+
from_subaccount: None,
393+
spender: p2.0.into(),
394+
amount: Nat::from(APPROVE_AMOUNT),
395+
fee: None,
396+
memo: None,
397+
expires_at: None,
398+
expected_allowance: None,
399+
created_at_time: None,
400+
};
401+
let response = env.execute_ingress_as(
402+
p1,
403+
canister_id,
404+
"icrc2_approve",
405+
Encode!(&approve_args).unwrap(),
406+
);
407+
assert!(response.is_ok());
408+
let allowance_args = IcpAllowanceArgs {
409+
account: AccountIdentifier::from(p1.0),
410+
spender: AccountIdentifier::from(p2.0),
411+
};
412+
413+
let response = env.execute_ingress_as(
414+
p1,
415+
canister_id,
416+
"allowance",
417+
Encode!(&allowance_args).unwrap(),
418+
);
419+
420+
let result = Decode!(
421+
&response.expect("failed to get allowance").bytes(),
422+
Allowance
423+
)
424+
.expect("failed to decode allowance response");
425+
assert_eq!(result.allowance.0.to_u64(), Some(APPROVE_AMOUNT));
426+
}
427+
348428
#[test]
349429
fn test_single_transfer() {
350430
ic_icrc1_ledger_sm_tests::test_single_transfer(ledger_wasm(), encode_init_args);

rs/ledger_suite/icp/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,12 @@ impl TryFrom<CandidBlock> for Block {
10381038
}
10391039
}
10401040

1041+
#[derive(Clone, Eq, PartialEq, Hash, Debug, CandidType, Deserialize, Serialize)]
1042+
pub struct IcpAllowanceArgs {
1043+
pub account: AccountIdentifier,
1044+
pub spender: AccountIdentifier,
1045+
}
1046+
10411047
/// Argument taken by the transfer fee endpoint
10421048
///
10431049
/// The reason it is a struct is so that it can be extended -- e.g., to be able

0 commit comments

Comments
 (0)