diff --git a/MODULE.bazel b/MODULE.bazel index 6838db9e3cd5..a4caa86aa489 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1102,11 +1102,13 @@ canisters( "ck_btc_ledger": "ic-icrc1-ledger.wasm.gz", "ck_btc_ledger_v1": "ic-icrc1-ledger.wasm.gz", "ck_btc_ledger_v3": "ic-icrc1-ledger.wasm.gz", + "ck_btc_ledger_v5": "ic-icrc1-ledger.wasm.gz", "ck_btc_index": "ic-icrc1-index-ng.wasm.gz", "ck_eth_archive": "ic-icrc1-archive-u256.wasm.gz", "ck_eth_ledger": "ic-icrc1-ledger-u256.wasm.gz", "ck_eth_ledger_v1": "ic-icrc1-ledger-u256.wasm.gz", "ck_eth_ledger_v3": "ic-icrc1-ledger-u256.wasm.gz", + "ck_eth_ledger_v5": "ic-icrc1-ledger-u256.wasm.gz", "ck_eth_index": "ic-icrc1-index-ng-u256.wasm.gz", "sns_root": "sns-root-canister.wasm.gz", "sns_governance": "sns-governance-canister.wasm.gz", diff --git a/mainnet-canister-revisions.json b/mainnet-canister-revisions.json index b2f1ac7b65c9..79d22dd63f2e 100644 --- a/mainnet-canister-revisions.json +++ b/mainnet-canister-revisions.json @@ -27,6 +27,10 @@ "rev": "2190613d3b5bcd9b74c382b22d151580b8ac271a", "sha256": "25071c2c55ad4571293e00d8e277f442aec7aed88109743ac52df3125209ff45" }, + "ck_btc_ledger_v5": { + "rev": "e446c64d99a97e38166be23ff2bfade997d15ff7", + "sha256": "71c27c5dc10034a1175296892b37827df0265d0ae072f5c59e99b8a1f6c45c76" + }, "ck_eth_archive": { "rev": "512cf412f33d430b79f42330518166d14fc6884e", "sha256": "3fafdd895c44886e38199882afcf06efb8e6e0b73af51eca327dcba4da7a0106" @@ -47,6 +51,10 @@ "rev": "2190613d3b5bcd9b74c382b22d151580b8ac271a", "sha256": "9637743e1215a4db376a62ee807a0986faf20833be2b332df09b3d5dbdd7339e" }, + "ck_eth_ledger_v5": { + "rev": "e446c64d99a97e38166be23ff2bfade997d15ff7", + "sha256": "15ec452faf00c40135b96a3ba0951ea13050e6e95e38cff249305462f81db62d" + }, "cycles-minting": { "rev": "6f1ce3bb4c253f1bc4c5f432c7c47b06dccdba7e", "sha256": "40a204bf4abc14bd61553b2b56413709a2fc7175227253d2809ffdb977c934de" diff --git a/packages/icrc-ledger-types/src/icrc107/mod.rs b/packages/icrc-ledger-types/src/icrc107/mod.rs index 1ce7e1766648..7625c8eec378 100644 --- a/packages/icrc-ledger-types/src/icrc107/mod.rs +++ b/packages/icrc-ledger-types/src/icrc107/mod.rs @@ -1 +1,2 @@ pub mod schema; +pub mod set_fee_collector; diff --git a/packages/icrc-ledger-types/src/icrc107/set_fee_collector.rs b/packages/icrc-ledger-types/src/icrc107/set_fee_collector.rs new file mode 100644 index 000000000000..63f3e940437e --- /dev/null +++ b/packages/icrc-ledger-types/src/icrc107/set_fee_collector.rs @@ -0,0 +1,25 @@ +use candid::{CandidType, Deserialize, Nat}; +use serde::Serialize; + +use super::super::icrc1::account::Account; + +/// The arguments for the +/// [ICRC-107 `icrc107_set_fee_collector`](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107/ICRC-107.md#icrc107_set_fee_collector) +/// endpoint. +#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct SetFeeCollectorArgs { + #[serde(default)] + pub fee_collector: Option, + pub created_at_time: u64, +} + +/// The error return type for the +/// [ICRC-107 `icrc107_set_fee_collector`](https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-107/ICRC-107.md#icrc107_set_fee_collector) +/// endpoint. +#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub enum SetFeeCollectorError { + AccessDenied(String), + InvalidAccount(String), + Duplicate { duplicate_of: Nat }, + GenericError { error_code: Nat, message: String }, +} diff --git a/rs/ethereum/cketh/minter/tests/ckerc20.rs b/rs/ethereum/cketh/minter/tests/ckerc20.rs index ad5a39a3eb39..f5f04f9bae00 100644 --- a/rs/ethereum/cketh/minter/tests/ckerc20.rs +++ b/rs/ethereum/cketh/minter/tests/ckerc20.rs @@ -848,7 +848,7 @@ mod withdraw_erc20 { .assert_has_unique_events_in_order(&[EventPayload::ReimbursedErc20Withdrawal { withdrawal_id: cketh_block_index.clone(), burn_in_block: ckerc20_block_index.clone(), - reimbursed_in_block: Nat::from(3_u8), + reimbursed_in_block: Nat::from(4_u8), ledger_id: deposit_params.token().ledger_canister_id, reimbursed_amount: ckerc20_withdrawal_amount.into(), transaction_hash: Some( @@ -857,7 +857,7 @@ mod withdraw_erc20 { }]) .call_ckerc20_ledger_get_transaction( deposit_params.token().ledger_canister_id, - 3_u8, + 4_u8, ) .expect_mint(Mint { amount: ckerc20_withdrawal_amount.into(), @@ -1346,7 +1346,7 @@ fn should_deposit_ckerc20() { ckerc20 .deposit(params.clone()) .expect_mint() - .call_ckerc20_ledger_get_transaction(params.token().ledger_canister_id, 0_u8) + .call_ckerc20_ledger_get_transaction(params.token().ledger_canister_id, 1_u8) .expect_mint(Mint { amount: Nat::from(params.ckerc20_amount()), to: params.recipient(), @@ -1452,7 +1452,7 @@ fn should_deposit_cketh_and_ckerc20() { created_at_time: None, fee: None, }) - .call_ckerc20_ledger_get_transaction(params.token().ledger_canister_id, 0_u8) + .call_ckerc20_ledger_get_transaction(params.token().ledger_canister_id, 1_u8) .expect_mint(Mint { amount: Nat::from(params.ckerc20_amount()), to: params.recipient(), @@ -1548,7 +1548,7 @@ fn should_deposit_cketh_and_ckerc20_when_ledger_temporary_offline() { }, ckerc20_token_symbol: ckusdc.ckerc20_token_symbol, erc20_contract_address: ckusdc.erc20_contract_address, - mint_block_index: Nat::from(0_u8), + mint_block_index: Nat::from(1_u8), }, ]); diff --git a/rs/ethereum/cketh/test_utils/src/ckerc20.rs b/rs/ethereum/cketh/test_utils/src/ckerc20.rs index 023674311327..25ed6760408b 100644 --- a/rs/ethereum/cketh/test_utils/src/ckerc20.rs +++ b/rs/ethereum/cketh/test_utils/src/ckerc20.rs @@ -278,7 +278,7 @@ impl CkErc20Setup { amount, from_subaccount, ) - .expect_ok(1); + .expect_ok(2); self } @@ -754,7 +754,7 @@ impl CkErc20DepositFlow { }, ckerc20_token_symbol: self.params.token().ckerc20_token_symbol.clone(), erc20_contract_address: self.params.token().erc20_contract_address.clone(), - mint_block_index: Nat::from(0_u8), + mint_block_index: Nat::from(1_u8), }, ]); self.setup diff --git a/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs b/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs index 3cbfbc459adf..2e6c1e348134 100644 --- a/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs +++ b/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs @@ -16,7 +16,7 @@ use std::time::Duration; use crate::archive::{ArchivingGuardError, FailedToArchiveBlocks, LedgerArchivingGuard}; use ic_ledger_core::balances::{BalanceError, Balances, BalancesStore}; -use ic_ledger_core::block::{BlockIndex, BlockType, EncodedBlock, FeeCollector}; +use ic_ledger_core::block::{BlockIndex, BlockType, EncodedBlock}; use ic_ledger_core::timestamp::TimeStamp; use ic_ledger_core::tokens::{TokensType, Zero}; use ic_ledger_hash_of::HashOf; @@ -76,7 +76,8 @@ pub trait LedgerContext { fn approvals(&self) -> &AllowanceTable; fn approvals_mut(&mut self) -> &mut AllowanceTable; - fn fee_collector(&self) -> Option<&FeeCollector>; + fn fee_collector(&self) -> Option; + fn set_fee_collector(&mut self, fee_collector: Option); } pub trait LedgerTransaction: Sized { @@ -186,8 +187,6 @@ pub trait LedgerData: LedgerContext { /// The callback that the ledger framework calls when it purges a transaction. fn on_purged_transaction(&mut self, height: BlockIndex); - fn fee_collector_mut(&mut self) -> Option<&mut FeeCollector>; - fn increment_archiving_failure_metric(&mut self); fn get_archiving_failure_metric(&self) -> u64; @@ -274,13 +273,11 @@ where }, })?; - let fee_collector = ledger.fee_collector().cloned(); let block = L::Block::from_transaction( ledger.blockchain().last_hash, transaction, now, effective_fee, - fee_collector, ); let block_timestamp = block.timestamp(); @@ -288,11 +285,6 @@ where .blockchain_mut() .add_block(block) .expect("failed to add block"); - if let Some(fee_collector) = ledger.fee_collector_mut().as_mut() - && fee_collector.block_index.is_none() - { - fee_collector.block_index = Some(height); - } if let Some((_, tx_hash)) = maybe_time_and_hash { // The caller requested deduplication, so we have to remember this diff --git a/rs/ledger_suite/common/ledger_core/src/block.rs b/rs/ledger_suite/common/ledger_core/src/block.rs index b4065f08eeb1..aebc79232688 100644 --- a/rs/ledger_suite/common/ledger_core/src/block.rs +++ b/rs/ledger_suite/common/ledger_core/src/block.rs @@ -74,7 +74,6 @@ pub trait BlockType: Sized + Clone { tx: Self::Transaction, block_timestamp: TimeStamp, effective_fee: Self::Tokens, - fee_collector: Option>, ) -> Self; /// Encodes this block into a binary representation. diff --git a/rs/ledger_suite/icp/ledger/src/lib.rs b/rs/ledger_suite/icp/ledger/src/lib.rs index 4ffdcbb57d33..0fab9f0d1e2e 100644 --- a/rs/ledger_suite/icp/ledger/src/lib.rs +++ b/rs/ledger_suite/icp/ledger/src/lib.rs @@ -255,9 +255,13 @@ impl LedgerContext for Ledger { &mut self.stable_approvals } - fn fee_collector(&self) -> Option<&ic_ledger_core::block::FeeCollector> { + fn fee_collector(&self) -> Option { None } + + fn set_fee_collector(&mut self, _fee_collector: Option) { + trap("ICP ledger does not allow setting the fee collector"); + } } impl LedgerData for Ledger { @@ -319,12 +323,6 @@ impl LedgerData for Ledger { self.blocks_notified.remove(height); } - fn fee_collector_mut( - &mut self, - ) -> Option<&mut ic_ledger_core::block::FeeCollector> { - None - } - fn increment_archiving_failure_metric(&mut self) { ARCHIVING_FAILURES.with(|cell| cell.set(cell.get() + 1)); } diff --git a/rs/ledger_suite/icp/src/lib.rs b/rs/ledger_suite/icp/src/lib.rs index 6f0adc11a8ec..737cd75b891c 100644 --- a/rs/ledger_suite/icp/src/lib.rs +++ b/rs/ledger_suite/icp/src/lib.rs @@ -8,7 +8,7 @@ use ic_ledger_canister_core::ledger::{LedgerContext, LedgerTransaction, TxApplyE use ic_ledger_core::{ approvals::{AllowanceTable, HeapAllowancesData}, balances::Balances, - block::{BlockType, EncodedBlock, FeeCollector}, + block::{BlockType, EncodedBlock}, tokens::CheckedAdd, }; use ic_ledger_hash_of::HASH_LENGTH; @@ -381,7 +381,6 @@ impl Block { transaction, timestamp, effective_fee, - None, )) } @@ -392,7 +391,7 @@ impl Block { timestamp: TimeStamp, effective_fee: Tokens, ) -> Self { - Self::from_transaction(parent_hash, transaction, timestamp, effective_fee, None) + Self::from_transaction(parent_hash, transaction, timestamp, effective_fee) } pub fn transaction(&self) -> Cow<'_, Transaction> { @@ -436,7 +435,6 @@ impl BlockType for Block { transaction: Self::Transaction, timestamp: TimeStamp, _effective_fee: Tokens, - _fee_collector: Option>, ) -> Self { Self { parent_hash, diff --git a/rs/ledger_suite/icp/tests/tests.rs b/rs/ledger_suite/icp/tests/tests.rs index 2ea45b5e52bc..2d2f9de69fa1 100644 --- a/rs/ledger_suite/icp/tests/tests.rs +++ b/rs/ledger_suite/icp/tests/tests.rs @@ -64,7 +64,6 @@ fn example_block() -> Block { transaction, TimeStamp::new(1, 1), DEFAULT_TRANSFER_FEE, - None, ) } diff --git a/rs/ledger_suite/icrc1/index-ng/BUILD.bazel b/rs/ledger_suite/icrc1/index-ng/BUILD.bazel index 55b4d3305853..f1d661919778 100644 --- a/rs/ledger_suite/icrc1/index-ng/BUILD.bazel +++ b/rs/ledger_suite/icrc1/index-ng/BUILD.bazel @@ -102,6 +102,7 @@ rust_test( conf["index_wasm"], conf["ledger_wasm"], "//rs/ledger_suite/icrc1/test_utils/icrc3_test_ledger:icrc3_test_ledger_canister.wasm.gz", + conf["ledger_wasm_v5"], ], env = { "RUST_TEST_THREADS": "4", @@ -109,6 +110,7 @@ rust_test( "IC_ICRC1_INDEX_NG_WASM_PATH": "$(rootpath " + conf["index_wasm"] + ")", "IC_ICRC1_LEDGER_WASM_PATH": "$(rootpath " + conf["ledger_wasm"] + ")", "IC_ICRC3_TEST_LEDGER_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/test_utils/icrc3_test_ledger:icrc3_test_ledger_canister.wasm.gz)", + "IC_ICRC1_LEDGER_V5_VERSION_WASM_PATH": "$(rootpath " + conf["ledger_wasm_v5"] + ")", }, extra_srcs = ["tests/common/mod.rs"], tags = ["cpu:4"], @@ -151,12 +153,14 @@ rust_test( "crate_features": [], "index_wasm": ":index_ng_canister.wasm.gz", "ledger_wasm": "//rs/ledger_suite/icrc1/ledger:ledger_canister.wasm.gz", + "ledger_wasm_v5": "@mainnet_canisters//:ck_btc_ledger_v5.wasm.gz", "test_suffix": "", }, { "crate_features": ["u256-tokens"], "index_wasm": ":index_ng_canister_u256.wasm.gz", "ledger_wasm": "//rs/ledger_suite/icrc1/ledger:ledger_canister_u256.wasm.gz", + "ledger_wasm_v5": "@mainnet_canisters//:ck_eth_ledger_v5.wasm.gz", "test_suffix": "_u256", }, # - Ledger with ICRC-3 and without get-blocks @@ -164,6 +168,7 @@ rust_test( "crate_features": ["get_blocks_disabled"], "index_wasm": ":index_ng_canister.wasm.gz", "ledger_wasm": "//rs/ledger_suite/icrc1/ledger:ledger_canister_getblocksdisabled.wasm.gz", + "ledger_wasm_v5": "@mainnet_canisters//:ck_btc_ledger_v5.wasm.gz", "test_suffix": "_wo_getblocks", }, { @@ -173,6 +178,7 @@ rust_test( ], "index_wasm": ":index_ng_canister_u256.wasm.gz", "ledger_wasm": "//rs/ledger_suite/icrc1/ledger:ledger_canister_u256_getblocksdisabled.wasm.gz", + "ledger_wasm_v5": "@mainnet_canisters//:ck_eth_ledger_v5.wasm.gz", "test_suffix": "_u256_wo_getblocks", }, # - Ledger without ICRC-3 and with get-blocks @@ -180,6 +186,7 @@ rust_test( "crate_features": ["icrc3_disabled"], "index_wasm": ":index_ng_canister.wasm.gz", "ledger_wasm": "@ic-icrc1-ledger-wo-icrc-3.wasm.gz//file", + "ledger_wasm_v5": "@mainnet_canisters//:ck_btc_ledger_v5.wasm.gz", "test_suffix": "_wo_icrc3", }, { @@ -189,6 +196,7 @@ rust_test( ], "index_wasm": ":index_ng_canister_u256.wasm.gz", "ledger_wasm": "@ic-icrc1-ledger-wo-icrc-3-u256.wasm.gz//file", + "ledger_wasm_v5": "@mainnet_canisters//:ck_eth_ledger_v5.wasm.gz", "test_suffix": "_wo_icrc3_u256", }, ] diff --git a/rs/ledger_suite/icrc1/index-ng/tests/common/mod.rs b/rs/ledger_suite/icrc1/index-ng/tests/common/mod.rs index 55272c7fbc8d..65710c607477 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/common/mod.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/common/mod.rs @@ -76,6 +76,24 @@ pub fn install_ledger( archive_options: ArchiveOptions, fee_collector_account: Option, minter_principal: Principal, +) -> CanisterId { + install_ledger_with_wasm( + env, + initial_balances, + archive_options, + fee_collector_account, + minter_principal, + ledger_wasm(), + ) +} + +pub fn install_ledger_with_wasm( + env: &StateMachine, + initial_balances: Vec<(Account, u64)>, + archive_options: ArchiveOptions, + fee_collector_account: Option, + minter_principal: Principal, + ledger_wasm: Vec, ) -> CanisterId { let mut builder = LedgerInitArgsBuilder::with_symbol_and_name(TOKEN_SYMBOL, TOKEN_NAME) .with_minting_account(minter_principal) @@ -93,7 +111,7 @@ pub fn install_ledger( builder = builder.with_initial_balance(account, amount); } env.install_canister_with_cycles( - ledger_wasm(), + ledger_wasm, Encode!(&LedgerArgument::Init(builder.build())).unwrap(), None, ic_types::Cycles::new(STARTING_CYCLES_PER_CANISTER), @@ -148,6 +166,11 @@ pub fn ledger_wasm() -> Vec { }) } +#[allow(dead_code)] +pub fn ledger_mainnet_v5_wasm() -> Vec { + std::fs::read(std::env::var("IC_ICRC1_LEDGER_V5_VERSION_WASM_PATH").unwrap()).unwrap() +} + #[cfg(feature = "icrc3_disabled")] pub fn ledger_get_blocks( env: &StateMachine, diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index 75ec1f0e8e38..7fd1b0e504ec 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -1,8 +1,8 @@ use crate::common::{ ARCHIVE_TRIGGER_THRESHOLD, FEE, MAX_BLOCKS_FROM_ARCHIVE, account, default_archive_options, index_ng_wasm, install_icrc3_test_ledger, install_index_ng, install_ledger, - ledger_get_all_blocks, ledger_wasm, parse_index_logs, wait_until_sync_is_completed, - wait_until_sync_is_completed_or_error, + install_ledger_with_wasm, ledger_get_all_blocks, ledger_mainnet_v5_wasm, ledger_wasm, + parse_index_logs, wait_until_sync_is_completed, wait_until_sync_is_completed_or_error, }; use candid::{Decode, Encode, Nat, Principal}; use ic_agent::identity::Identity; @@ -14,7 +14,9 @@ use ic_icrc1_index_ng::{ GetAccountTransactionsResponse, GetAccountTransactionsResult, GetBlocksResponse, IndexArg, InitArg as IndexInitArg, ListSubaccountsArgs, TransactionWithId, }; -use ic_icrc1_ledger::{ChangeFeeCollector, LedgerArgument, UpgradeArgs as LedgerUpgradeArgs}; +use ic_icrc1_ledger::{ + BTYPE_107_LEDGER_SET, ChangeFeeCollector, LedgerArgument, UpgradeArgs as LedgerUpgradeArgs, +}; use ic_icrc1_test_utils::icrc3::account_to_icrc3_value; use ic_icrc1_test_utils::{ ArgWithCaller, LedgerEndpointArg, icrc3::BlockBuilder, minter_identity, @@ -24,7 +26,9 @@ use ic_ledger_core::block::BlockType; use ic_ledger_suite_state_machine_helpers::{ add_block, archive_blocks, get_logs, set_icrc3_enabled, }; -use ic_ledger_suite_state_machine_tests::test_http_request_decoding_quota; +use ic_ledger_suite_state_machine_tests::{ + set_fc_107_by_controller, test_http_request_decoding_quota, +}; use ic_state_machine_tests::StateMachine; use icrc_ledger_types::icrc::generic_value::ICRC3Value; use icrc_ledger_types::icrc1::account::{Account, Subaccount}; @@ -37,6 +41,8 @@ use icrc_ledger_types::icrc3::transactions::{Mint, Transaction, Transfer}; use icrc_ledger_types::icrc107::schema::{BTYPE_107, SET_FEE_COL_107}; use num_traits::cast::ToPrimitive; use proptest::test_runner::{Config as TestRunnerConfig, TestRunner}; +#[cfg(not(feature = "icrc3_disabled"))] +use std::collections::BTreeMap; use std::collections::HashSet; use std::fmt::Debug; use std::hash::Hash; @@ -63,6 +69,7 @@ fn upgrade_ledger( env: &StateMachine, ledger_id: CanisterId, fee_collector_account: Option, + legacy_fc_wasm: bool, ) { let change_fee_collector = Some(fee_collector_account.map_or(ChangeFeeCollector::Unset, ChangeFeeCollector::SetTo)); @@ -77,7 +84,12 @@ fn upgrade_ledger( change_archive_options: None, index_principal: None, })); - env.upgrade_canister(ledger_id, ledger_wasm(), Encode!(&args).unwrap()) + let wasm = if legacy_fc_wasm { + ledger_mainnet_v5_wasm() + } else { + ledger_wasm() + }; + env.upgrade_canister(ledger_id, wasm, Encode!(&args).unwrap()) .unwrap() } @@ -240,6 +252,27 @@ fn transfer( icrc1_transfer(env, ledger_id, owner.into(), req) } +fn transfer_from( + env: &StateMachine, + ledger_id: CanisterId, + from: Account, + to: Account, + spender: Account, + amount: u64, +) -> BlockIndex { + let Account { owner, subaccount } = spender; + let req = TransferFromArgs { + spender_subaccount: subaccount, + from, + to, + amount: amount.into(), + created_at_time: None, + fee: None, + memo: None, + }; + icrc2_transfer_from(env, ledger_id, owner.into(), req) +} + fn icrc2_approve( env: &StateMachine, ledger_id: CanisterId, @@ -1232,28 +1265,52 @@ fn assert_contain_same_elements(vl: Vec, vr: Vec) { ) } -#[test] -fn test_fee_collector() { +fn test_fee_collector_ranges(legacy: bool) { let env = &StateMachine::new(); let fee_collector = account(42, 0); let minter = minter_identity().sender().unwrap(); - let ledger_id = install_ledger( - env, - vec![(account(1, 0), 10_000_000)], // txid: 0 - default_archive_options(), - Some(fee_collector), - minter, - ); + let ledger_id = if legacy { + install_ledger_with_wasm( + env, + vec![(account(1, 0), 10_000_000)], + default_archive_options(), + Some(fee_collector), + minter, + ledger_mainnet_v5_wasm(), + ) + } else { + install_ledger( + env, + vec![(account(1, 0), 10_000_000)], + default_archive_options(), + Some(fee_collector), + minter, + ) + }; let index_id = install_index_ng(env, index_init_arg_without_interval(ledger_id)); + let mut range_start = 0u64; + let mut curr_txid = 0u64; + if !legacy { + curr_txid += 1; // init fee collector block + } + assert_eq!( icrc1_balance_of(env, ledger_id, fee_collector), icrc1_balance_of(env, index_id, fee_collector) ); - transfer(env, ledger_id, account(1, 0), account(2, 0), 100_000); // txid: 1 - transfer(env, ledger_id, account(1, 0), account(3, 0), 200_000); // txid: 2 - transfer(env, ledger_id, account(1, 0), account(2, 0), 300_000); // txid: 3 + transfer(env, ledger_id, account(1, 0), account(2, 0), 100_000); + approve(env, ledger_id, account(1, 0), account(3, 0), 200_000); + transfer_from( + env, + ledger_id, + account(1, 0), + account(2, 0), + account(3, 0), + 150_000, + ); + curr_txid += 3; wait_until_sync_is_completed(env, index_id, ledger_id); @@ -1262,16 +1319,25 @@ fn test_fee_collector() { icrc1_balance_of(env, index_id, fee_collector) ); + let mut expected = vec![( + fee_collector, + vec![(range_start.into(), (curr_txid + 1).into())], + )]; + assert_contain_same_elements( get_fee_collectors_ranges(env, index_id).ranges, - vec![(fee_collector, vec![(0u8.into(), 4u8.into())])], + expected.clone(), ); // Remove the fee collector to burn some transactions fees. - upgrade_ledger(env, ledger_id, None); + upgrade_ledger(env, ledger_id, None, legacy); + if !legacy { + curr_txid += 1; // upgrade fee collector block + } - transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); // txid: 4 - transfer(env, ledger_id, account(1, 0), account(2, 0), 500_000); // txid: 5 + transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); + transfer(env, ledger_id, account(1, 0), account(2, 0), 500_000); + curr_txid += 2; wait_until_sync_is_completed(env, index_id, ledger_id); @@ -1282,14 +1348,22 @@ fn test_fee_collector() { assert_contain_same_elements( get_fee_collectors_ranges(env, index_id).ranges, - vec![(fee_collector, vec![(0u8.into(), 4u8.into())])], + expected.clone(), ); // Add a new fee collector different from the first one. let new_fee_collector = account(42, 42); - upgrade_ledger(env, ledger_id, Some(new_fee_collector)); + upgrade_ledger(env, ledger_id, Some(new_fee_collector), legacy); + if !legacy { + curr_txid += 1; // upgrade fee collector block + range_start = curr_txid; // new fee collector starts at the upgrade block + } - transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); // txid: 6 + transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); + curr_txid += 1; + if legacy { + range_start = curr_txid; // legacy fee collector starts at the first tx after it is set + } wait_until_sync_is_completed(env, index_id, ledger_id); @@ -1300,19 +1374,32 @@ fn test_fee_collector() { ); } + expected.push(( + new_fee_collector, + vec![(range_start.into(), (curr_txid + 1).into())], + )); + + println!("expected: {:?}", expected); + assert_contain_same_elements( get_fee_collectors_ranges(env, index_id).ranges, - vec![ - (new_fee_collector, vec![(6u8.into(), 7u8.into())]), - (fee_collector, vec![(0u8.into(), 4u8.into())]), - ], + expected.clone(), ); // Add back the original fee_collector and make a couple of transactions again. - upgrade_ledger(env, ledger_id, Some(fee_collector)); + upgrade_ledger(env, ledger_id, Some(fee_collector), legacy); + if !legacy { + curr_txid += 1; // upgrade fee collector block + range_start = curr_txid; // new fee collector starts at the upgrade block + } - transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); // txid: 7 - transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); // txid: 8 + transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); + curr_txid += 1; + if legacy { + range_start = curr_txid; // legacy fee collector starts at the first tx after it is set + } + approve(env, ledger_id, account(1, 0), account(2, 0), 400_000); + curr_txid += 1; wait_until_sync_is_completed(env, index_id, ledger_id); @@ -1323,20 +1410,56 @@ fn test_fee_collector() { ); } + expected[0] + .1 + .push((range_start.into(), (curr_txid + 1).into())); + assert_contain_same_elements( get_fee_collectors_ranges(env, index_id).ranges, - vec![ - (new_fee_collector, vec![(6u8.into(), 7u8.into())]), - ( - fee_collector, - vec![(0u8.into(), 4u8.into()), (7u8.into(), 9u8.into())], - ), - ], + expected.clone(), ); + + if !legacy { + set_fc_107_by_controller(env, ledger_id, Some(new_fee_collector)); + curr_txid += 1; + range_start = curr_txid; + transfer(env, ledger_id, account(1, 0), account(2, 0), 400_000); + approve(env, ledger_id, account(1, 0), account(2, 0), 400_000); + curr_txid += 2; + + wait_until_sync_is_completed(env, index_id, ledger_id); + + for fee_collector in &[fee_collector, new_fee_collector] { + assert_eq!( + icrc1_balance_of(env, ledger_id, *fee_collector), + icrc1_balance_of(env, index_id, *fee_collector) + ); + } + + expected[1] + .1 + .push((range_start.into(), (curr_txid + 1).into())); + + assert_contain_same_elements(get_fee_collectors_ranges(env, index_id).ranges, expected); + } +} + +#[test] +fn test_fee_collector_ranges_legacy() { + test_fee_collector_ranges(true); +} + +#[cfg(not(feature = "icrc3_disabled"))] +#[test] +fn test_fee_collector_ranges_107() { + test_fee_collector_ranges(false); } +// This test uses the test ledger to test edge cases such as +// specifying the legacy fee collector after the 107 fee collector block +// was generated, which could not be tested with the prod ledger. #[test] -fn test_fee_collector_107() { +fn test_fee_collector_107_edge_cases() { let env = &StateMachine::new(); let ledger_id = install_icrc3_test_ledger(env); let index_id = install_index_ng(env, index_init_arg_without_interval(ledger_id)); @@ -1445,7 +1568,7 @@ fn test_fee_collector_107() { assert_eq!(4, icrc1_balance_of(env, index_id, feecol_107)); // Set 107 fee collector to burn - block_id = add_fee_collector_107_block(block_id, None, Some("107ledger_set".to_string())); + block_id = add_fee_collector_107_block(block_id, None, Some(BTYPE_107_LEDGER_SET.to_string())); // No fees collected add_mint_block(block_id, None, None); @@ -1476,6 +1599,83 @@ fn add_custom_block( ); } +#[cfg(not(feature = "icrc3_disabled"))] +#[test] +fn test_fee_collector_107_with_ledger() { + let env = &StateMachine::new(); + let feecol_legacy = account(101, 0); + let feecol_107_1 = account(102, 0); + let feecol_107_2 = account(103, 0); + let sending_account = account(1, 0); + let receiving_account = account(2, 0); + let mut expected_balances = BTreeMap::new(); + let ledger_id = install_ledger_with_wasm( + env, + vec![(sending_account, 10_000_000)], + default_archive_options(), + Some(feecol_legacy), + account(1000, 0).owner, + ledger_mainnet_v5_wasm(), + ); + let index_id = install_index_ng(env, index_init_arg_without_interval(ledger_id)); + + let verify_fc_balances = |expected_balances: &BTreeMap| { + wait_until_sync_is_completed(env, index_id, ledger_id); + for (fc_account, balance) in expected_balances { + assert_eq!(*balance, icrc1_balance_of(env, index_id, *fc_account)); + assert_eq!(*balance, icrc1_balance_of(env, ledger_id, *fc_account)); + } + }; + + // Legacy fee collector collects the fees + transfer(env, ledger_id, sending_account, receiving_account, 1); + expected_balances.insert(feecol_legacy, FEE); + expected_balances.insert(feecol_107_1, 0); + expected_balances.insert(feecol_107_2, 0); + verify_fc_balances(&expected_balances); + + // Legacy fee collector does not collect approve fees + approve(env, ledger_id, sending_account, receiving_account, 1); + verify_fc_balances(&expected_balances); + + // Set 107 fee collector to burn + upgrade_ledger(env, ledger_id, None, false); + + // No fees are collected + transfer(env, ledger_id, sending_account, receiving_account, 1); + verify_fc_balances(&expected_balances); + approve(env, ledger_id, sending_account, receiving_account, 1); + verify_fc_balances(&expected_balances); + + set_fc_107_by_controller(env, ledger_id, Some(feecol_107_1)); + + // The new fee collector collects all fees + transfer(env, ledger_id, sending_account, receiving_account, 1); + expected_balances.insert(feecol_107_1, expected_balances[&feecol_107_1] + FEE); + verify_fc_balances(&expected_balances); + approve(env, ledger_id, sending_account, receiving_account, 1); + expected_balances.insert(feecol_107_1, expected_balances[&feecol_107_1] + FEE); + verify_fc_balances(&expected_balances); + + set_fc_107_by_controller(env, ledger_id, Some(feecol_107_2)); + + // The second new fee collector collects all fees + transfer(env, ledger_id, sending_account, receiving_account, 1); + expected_balances.insert(feecol_107_2, expected_balances[&feecol_107_2] + FEE); + verify_fc_balances(&expected_balances); + approve(env, ledger_id, sending_account, receiving_account, 1); + expected_balances.insert(feecol_107_2, expected_balances[&feecol_107_2] + FEE); + verify_fc_balances(&expected_balances); + + set_fc_107_by_controller(env, ledger_id, None); + + // No fees are collected + transfer(env, ledger_id, sending_account, receiving_account, 1); + verify_fc_balances(&expected_balances); + approve(env, ledger_id, sending_account, receiving_account, 1); + verify_fc_balances(&expected_balances); +} + #[test] fn test_fee_collector_107_irregular_mthd() { const UNRECOGNIZED_MTHD_NAME: &str = "non_standard_fee_col_setter_endpoint_method_name"; diff --git a/rs/ledger_suite/icrc1/ledger/BUILD.bazel b/rs/ledger_suite/icrc1/ledger/BUILD.bazel index 166a9f7c8a90..1208d3b53a35 100644 --- a/rs/ledger_suite/icrc1/ledger/BUILD.bazel +++ b/rs/ledger_suite/icrc1/ledger/BUILD.bazel @@ -262,9 +262,11 @@ package(default_visibility = ["//visibility:public"]) "@mainnet_canisters//:ck_btc_ledger.wasm.gz", "@mainnet_canisters//:ck_btc_ledger_v1.wasm.gz", "@mainnet_canisters//:ck_btc_ledger_v3.wasm.gz", + "@mainnet_canisters//:ck_btc_ledger_v5.wasm.gz", "@mainnet_canisters//:ck_eth_ledger.wasm.gz", "@mainnet_canisters//:ck_eth_ledger_v1.wasm.gz", "@mainnet_canisters//:ck_eth_ledger_v3.wasm.gz", + "@mainnet_canisters//:ck_eth_ledger_v5.wasm.gz", "@mainnet_canisters//:sns_ledger.wasm.gz", ], env = { @@ -272,9 +274,11 @@ package(default_visibility = ["//visibility:public"]) "CKBTC_IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_btc_ledger.wasm.gz)", "CKBTC_IC_ICRC1_LEDGER_V1_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_btc_ledger_v1.wasm.gz)", "CKBTC_IC_ICRC1_LEDGER_V3_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_btc_ledger_v3.wasm.gz)", + "CKBTC_IC_ICRC1_LEDGER_V5_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_btc_ledger_v5.wasm.gz)", "CKETH_IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_eth_ledger.wasm.gz)", "CKETH_IC_ICRC1_LEDGER_V1_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_eth_ledger_v1.wasm.gz)", "CKETH_IC_ICRC1_LEDGER_V3_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_eth_ledger_v3.wasm.gz)", + "CKETH_IC_ICRC1_LEDGER_V5_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:ck_eth_ledger_v5.wasm.gz)", "IC_ICRC1_ARCHIVE_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/archive:archive_canister" + name_suffix + ".wasm.gz)", "IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_canisters//:sns_ledger.wasm.gz)", "IC_ICRC1_LEDGER_WASM_PATH": "$(rootpath :ledger_canister" + name_suffix + ".wasm.gz)", diff --git a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml index b41d106916f7..658d90cad020 100644 --- a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml +++ b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml @@ -2,70 +2,70 @@ benches: bench_icrc1_transfers: total: calls: 1 - instructions: 54416296904 - heap_increase: 264 + instructions: 54937060262 + heap_increase: 263 stable_memory_increase: 256 scopes: icrc103_get_allowances: calls: 1 - instructions: 6447843 + instructions: 6575675 heap_increase: 0 stable_memory_increase: 0 icrc1_transfer: calls: 1 - instructions: 12925814586 - heap_increase: 32 + instructions: 12642008281 + heap_increase: 34 stable_memory_increase: 0 icrc2_approve: calls: 1 - instructions: 19344327170 - heap_increase: 29 + instructions: 20550386820 + heap_increase: 25 stable_memory_increase: 128 icrc2_transfer_from: calls: 1 - instructions: 21428424367 - heap_increase: 3 + instructions: 21030573148 + heap_increase: 4 stable_memory_increase: 0 icrc3_get_blocks: calls: 1 - instructions: 8924170 + instructions: 8219706 heap_increase: 0 stable_memory_increase: 0 post_upgrade: calls: 1 - instructions: 357456809 + instructions: 358175074 heap_increase: 71 stable_memory_increase: 0 pre_upgrade: calls: 1 - instructions: 151084681 + instructions: 151082853 heap_increase: 129 stable_memory_increase: 128 upgrade: calls: 1 - instructions: 508544206 + instructions: 509260768 heap_increase: 200 stable_memory_increase: 128 bench_upgrade_baseline: total: calls: 1 - instructions: 8694844 + instructions: 8694615 heap_increase: 258 stable_memory_increase: 128 scopes: post_upgrade: calls: 1 - instructions: 8613900 + instructions: 8613927 heap_increase: 129 stable_memory_increase: 0 pre_upgrade: calls: 1 - instructions: 78039 + instructions: 77783 heap_increase: 129 stable_memory_increase: 128 upgrade: calls: 1 - instructions: 8693947 + instructions: 8693718 heap_increase: 258 stable_memory_increase: 128 version: 0.4.0 diff --git a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml index 3b8bd3eb8434..326157a0bf46 100644 --- a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml +++ b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml @@ -2,70 +2,70 @@ benches: bench_icrc1_transfers: total: calls: 1 - instructions: 51998427061 + instructions: 52744554410 heap_increase: 263 stable_memory_increase: 256 scopes: icrc103_get_allowances: calls: 1 - instructions: 5725519 + instructions: 5819830 heap_increase: 0 stable_memory_increase: 0 icrc1_transfer: calls: 1 - instructions: 12258350811 + instructions: 12036791007 heap_increase: 34 stable_memory_increase: 0 icrc2_approve: calls: 1 - instructions: 18441742835 - heap_increase: 25 + instructions: 19653795168 + heap_increase: 24 stable_memory_increase: 128 icrc2_transfer_from: calls: 1 - instructions: 20588416094 + instructions: 20344140192 heap_increase: 3 stable_memory_increase: 0 icrc3_get_blocks: calls: 1 - instructions: 8433296 + instructions: 7842330 heap_increase: 0 stable_memory_increase: 0 post_upgrade: calls: 1 - instructions: 357942981 - heap_increase: 72 + instructions: 357931065 + heap_increase: 73 stable_memory_increase: 0 pre_upgrade: calls: 1 - instructions: 150874163 + instructions: 150873614 heap_increase: 129 stable_memory_increase: 128 upgrade: calls: 1 - instructions: 508819859 - heap_increase: 201 + instructions: 508807525 + heap_increase: 202 stable_memory_increase: 128 bench_upgrade_baseline: total: calls: 1 - instructions: 8696438 + instructions: 8695725 heap_increase: 258 stable_memory_increase: 128 scopes: post_upgrade: calls: 1 - instructions: 8614539 + instructions: 8614024 heap_increase: 129 stable_memory_increase: 0 pre_upgrade: calls: 1 - instructions: 78994 + instructions: 78796 heap_increase: 129 stable_memory_increase: 128 upgrade: calls: 1 - instructions: 8695541 + instructions: 8694828 heap_increase: 258 stable_memory_increase: 128 version: 0.4.0 diff --git a/rs/ledger_suite/icrc1/ledger/ledger.did b/rs/ledger_suite/icrc1/ledger/ledger.did index 7cb5f7119cbe..5cc4d2b08b05 100644 --- a/rs/ledger_suite/icrc1/ledger/ledger.did +++ b/rs/ledger_suite/icrc1/ledger/ledger.did @@ -548,6 +548,20 @@ type GetIndexPrincipalError = variant { } }; +type SetFeeCollectorArgs = record { + fee_collector : opt Account; + created_at_time : Timestamp +}; + +type SetFeeCollectorError = variant { + AccessDenied : text; + InvalidAccount : text; + Duplicate : record { duplicate_of : BlockIndex }; + GenericError : record { error_code : nat; message : text } +}; + +type SetFeeCollectorResult = variant { Ok : BlockIndex; Err : SetFeeCollectorError }; + service : (ledger_arg : LedgerArg) -> { archives : () -> (vec ArchiveInfo) query; get_transactions : (GetTransactionsRequest) -> (GetTransactionsResponse) query; @@ -581,5 +595,8 @@ service : (ledger_arg : LedgerArg) -> { icrc106_get_index_principal : () -> (GetIndexPrincipalResult) query; + icrc107_set_fee_collector : (SetFeeCollectorArgs) -> (SetFeeCollectorResult); + icrc107_get_fee_collector: () -> (variant { Ok: opt Account; Err: record { error_code : nat; message : text } }) query; + is_ledger_ready : () -> (bool) query } diff --git a/rs/ledger_suite/icrc1/ledger/src/lib.rs b/rs/ledger_suite/icrc1/ledger/src/lib.rs index 5d6cb3327bee..7fb1823b3e33 100644 --- a/rs/ledger_suite/icrc1/ledger/src/lib.rs +++ b/rs/ledger_suite/icrc1/ledger/src/lib.rs @@ -11,8 +11,8 @@ use ic_certification::{ HashTree, hash_tree::{Label, empty, fork, label, leaf}, }; -use ic_icrc1::blocks::encoded_block_to_generic_block; use ic_icrc1::{Block, LedgerAllowances, LedgerBalances, Transaction}; +use ic_icrc1::{Operation, blocks::encoded_block_to_generic_block}; pub use ic_ledger_canister_core::archive::ArchiveOptions; use ic_ledger_canister_core::runtime::{CdkRuntime, Runtime}; use ic_ledger_canister_core::{archive::Archive, blockchain::BlockDataContainer}; @@ -72,6 +72,7 @@ const MAX_TRANSACTIONS_TO_PURGE: usize = 100_000; const MAX_U64_ENCODING_BYTES: usize = 10; const DEFAULT_MAX_MEMO_LENGTH: u16 = 32; const MAX_TAKE_ALLOWANCES: u64 = 500; +pub const BTYPE_107_LEDGER_SET: &str = "107ledger_set"; #[cfg(not(feature = "u256-tokens"))] pub type Tokens = ic_icrc1_tokens_u64::U64; @@ -87,11 +88,12 @@ pub type Tokens = ic_icrc1_tokens_u256::U256; /// * 1 - the allowances are stored in stable structures. /// * 2 - the balances are stored in stable structures. /// * 3 - the blocks are stored in stable structures. +/// * 4 - the ledger uses the ICRC-107 fee collector. #[cfg(not(feature = "next-ledger-version"))] -pub const LEDGER_VERSION: u64 = 3; +pub const LEDGER_VERSION: u64 = 4; #[cfg(feature = "next-ledger-version")] -pub const LEDGER_VERSION: u64 = 4; +pub const LEDGER_VERSION: u64 = 5; #[derive(Clone, Debug)] pub struct Icrc1ArchiveWasm; @@ -283,15 +285,6 @@ pub enum ChangeFeeCollector { SetTo(Account), } -impl From for Option> { - fn from(value: ChangeFeeCollector) -> Self { - match value { - ChangeFeeCollector::Unset => None, - ChangeFeeCollector::SetTo(account) => Some(FeeCollector::from(account)), - } - } -} - #[derive(Clone, Eq, PartialEq, Debug, Default, CandidType, Deserialize)] pub struct ChangeArchiveOptions { pub trigger_threshold: Option, @@ -547,6 +540,7 @@ type StableLedgerBalances = Balances; #[derive(Debug, Deserialize, Serialize)] #[serde(bound = "")] pub struct Ledger { + #[serde(default)] balances: LedgerBalances, #[serde(default)] stable_balances: StableLedgerBalances, @@ -590,6 +584,9 @@ pub struct Ledger { #[serde(default = "wasm_token_type")] pub token_type: String, + + #[serde(default)] + fee_collector_107: Option, } #[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)] @@ -701,7 +698,7 @@ impl Ledger { transactions_by_hash: BTreeMap::new(), transactions_by_height: VecDeque::new(), minting_account, - fee_collector: fee_collector_account.map(FeeCollector::from), + fee_collector: None, transfer_fee: Tokens::try_from(transfer_fee.clone()).unwrap_or_else(|e| { panic!("failed to convert transfer fee {transfer_fee} to tokens: {e}") }), @@ -716,12 +713,15 @@ impl Ledger { ledger_version: LEDGER_VERSION, index_principal, token_type: wasm_token_type(), + fee_collector_107: fee_collector_account, }; - if ledger.fee_collector.as_ref().map(|fc| fc.fee_collector) == Some(ledger.minting_account) - { + if ledger.fee_collector_107 == Some(ledger.minting_account) { ic_cdk::trap("The fee collector account cannot be the same as the minting account"); } + if ledger.fee_collector_107.is_some() { + ledger.ledger_set_107_fee_collector(ledger.fee_collector_107); + } for (account, balance) in initial_balances.into_iter() { let amount = Tokens::try_from(balance.clone()).unwrap_or_else(|e| { @@ -735,6 +735,28 @@ impl Ledger { ledger } + + pub fn ledger_set_107_fee_collector(&mut self, fee_collector: Option) { + let tx = Transaction { + operation: Operation::FeeCollector { + fee_collector, + caller: None, + mthd: Some(BTYPE_107_LEDGER_SET.to_string()), + }, + created_at_time: None, + memo: None, + }; + let now = TimeStamp::from_nanos_since_unix_epoch(ic_cdk::api::time()); + if let Err(e) = apply_transaction(self, tx, now, Tokens::ZERO) { + ic_cdk::trap(format!( + "failed to add fee collector block to the ledger: {e:?}" + )); + } + } + + pub fn legacy_fee_collector(&self) -> Option { + self.fee_collector.as_ref().map(|fc| fc.fee_collector) + } } impl LedgerContext for Ledger { @@ -759,8 +781,12 @@ impl LedgerContext for Ledger { &mut self.stable_approvals } - fn fee_collector(&self) -> Option<&FeeCollector> { - self.fee_collector.as_ref() + fn fee_collector(&self) -> Option { + self.fee_collector_107 + } + + fn set_fee_collector(&mut self, fee_collector: Option) { + self.fee_collector_107 = fee_collector; } } @@ -821,10 +847,6 @@ impl LedgerData for Ledger { fn on_purged_transaction(&mut self, _height: BlockIndex) {} - fn fee_collector_mut(&mut self) -> Option<&mut FeeCollector> { - self.fee_collector.as_mut() - } - fn increment_archiving_failure_metric(&mut self) { ARCHIVING_FAILURES.with(|cell| cell.set(cell.get() + 1)); } @@ -936,9 +958,13 @@ impl Ledger { self.max_memo_length = max_memo_length; } if let Some(change_fee_collector) = args.change_fee_collector { - self.fee_collector = change_fee_collector.into(); - if self.fee_collector.as_ref().map(|fc| fc.fee_collector) == Some(self.minting_account) - { + let fee_collector = match change_fee_collector { + ChangeFeeCollector::Unset => None, + ChangeFeeCollector::SetTo(account) => Some(account), + }; + + self.ledger_set_107_fee_collector(fee_collector); + if self.fee_collector_107 == Some(self.minting_account) { ic_cdk::trap( "The fee collector account cannot be the same account as the minting account", ); @@ -1363,3 +1389,9 @@ impl BlockDataContainer for StableBlockDataContainer { BLOCKS_MEMORY.with(|cell| f(&mut cell.borrow_mut())) } } + +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)] +pub struct GetFeeCollectorError { + pub error_code: Nat, + pub message: String, +} diff --git a/rs/ledger_suite/icrc1/ledger/src/main.rs b/rs/ledger_suite/icrc1/ledger/src/main.rs index e1665313f633..300cc77aef4b 100644 --- a/rs/ledger_suite/icrc1/ledger/src/main.rs +++ b/rs/ledger_suite/icrc1/ledger/src/main.rs @@ -16,8 +16,8 @@ use ic_icrc1::{ endpoints::{StandardRecord, convert_transfer_error}, }; use ic_icrc1_ledger::{ - InitArgs, LEDGER_VERSION, Ledger, LedgerArgument, UPGRADES_MEMORY, balances_len, - get_allowances, read_first_balance, wasm_token_type, + GetFeeCollectorError, InitArgs, LEDGER_VERSION, Ledger, LedgerArgument, UPGRADES_MEMORY, + balances_len, get_allowances, read_first_balance, wasm_token_type, }; use ic_ledger_canister_core::ledger::{ LedgerAccess, LedgerContext, LedgerData, TransferError as CoreTransferError, apply_transaction, @@ -29,8 +29,6 @@ use ic_ledger_core::timestamp::TimeStamp; use ic_ledger_core::tokens::Zero; use ic_stable_structures::reader::{BufferedReader, Reader}; use ic_stable_structures::writer::{BufferedWriter, Writer}; -use icrc_ledger_types::icrc2::approve::{ApproveArgs, ApproveError}; -use icrc_ledger_types::icrc3::blocks::DataCertificate; #[cfg(not(feature = "get-blocks-disabled"))] use icrc_ledger_types::icrc3::blocks::GetBlocksResponse; use icrc_ledger_types::icrc3::blocks::ICRC3DataCertificate; @@ -66,6 +64,11 @@ use icrc_ledger_types::{ icrc1::transfer::{TransferArg, TransferError}, icrc2::transfer_from::{TransferFromArgs, TransferFromError}, }; +use icrc_ledger_types::{ + icrc2::approve::{ApproveArgs, ApproveError}, + icrc107::set_fee_collector::{SetFeeCollectorArgs, SetFeeCollectorError}, +}; +use icrc_ledger_types::{icrc3::blocks::DataCertificate, icrc107::schema::SET_FEE_COL_107}; use num_traits::{ToPrimitive, bounds::Bounded}; use serde_bytes::ByteBuf; use std::{ @@ -233,23 +236,36 @@ fn post_upgrade_internal(args: Option) { upgrade_from_version }); - if let Some(args) = args { + let fee_collector_changed_with_args = if let Some(args) = args { match args { LedgerArgument::Init(_) => panic!( "Cannot upgrade the canister with an Init argument. Please provide an Upgrade argument." ), LedgerArgument::Upgrade(upgrade_args) => { if let Some(upgrade_args) = upgrade_args { - Access::with_ledger_mut(|ledger| ledger.upgrade(&LOG, upgrade_args)); + Access::with_ledger_mut(|ledger| ledger.upgrade(&LOG, upgrade_args.clone())); + upgrade_args.change_fee_collector.is_some() + } else { + false } } } - } + } else { + false + }; PRE_UPGRADE_INSTRUCTIONS_CONSUMED.with(|n| *n.borrow_mut() = pre_upgrade_instructions_consumed); initialize_total_volume(); + // Migrate the legacy fee collector, only do it if wasn't already set by upgrade args + if upgrade_from_version < 4 && !fee_collector_changed_with_args { + Access::with_ledger_mut(|ledger| { + if ledger.legacy_fee_collector().is_some() { + ledger.ledger_set_107_fee_collector(ledger.legacy_fee_collector()); + } + }); + } if upgrade_from_version < 3 { let msg = "Migration to stable structures not supported, please upgrade first to git revision e446c64d99a97e38166be23ff2bfade997d15ff7 https://github.com/dfinity/ic/releases/tag/ledger-suite-icrc-2025-10-27"; log_message(msg); @@ -1020,6 +1036,53 @@ fn icrc103_get_allowances(arg: GetAllowancesArgs) -> Result Result { + let caller = ic_cdk::api::caller(); + + if !ic_cdk::api::is_controller(&caller) { + return Err(SetFeeCollectorError::AccessDenied( + "The `icrc107_set_fee_collector` endpoint can only be called by the canister controller".to_string(), + )); + } + + let tx = Transaction { + operation: Operation::FeeCollector { + fee_collector: arg.fee_collector, + caller: Some(caller), + mthd: Some(SET_FEE_COL_107.to_string()), + }, + created_at_time: Some(arg.created_at_time), + memo: None, + }; + + let (block_idx, _) = Access::with_ledger_mut(|ledger| { + let now = TimeStamp::from_nanos_since_unix_epoch(ic_cdk::api::time()); + apply_transaction(ledger, tx, now, Tokens::ZERO) + .map_err(convert_transfer_error) + .map_err(|err| match err.0 { + CoreTransferError::TxDuplicate { duplicate_of } => { + SetFeeCollectorError::Duplicate { + duplicate_of: Nat::from(duplicate_of), + } + } + _ => ic_cdk::trap("unable to convert error"), + }) + })?; + + // NB. we need to set the certified data before the first async call to make sure that the + // blockchain state agrees with the certificate while archiving is in progress. + ic_cdk::api::set_certified_data(&Access::with_ledger(Ledger::root_hash)); + + archive_blocks::(&LOG, MAX_MESSAGE_SIZE).await; + Ok(Nat::from(block_idx)) +} + +#[query] +fn icrc107_get_fee_collector() -> Result, GetFeeCollectorError> { + Ok(Access::with_ledger(|ledger| ledger.fee_collector())) +} + candid::export_service!(); #[query] diff --git a/rs/ledger_suite/icrc1/ledger/tests/tests.rs b/rs/ledger_suite/icrc1/ledger/tests/tests.rs index 7dc39fb1f927..9b5c582494a5 100644 --- a/rs/ledger_suite/icrc1/ledger/tests/tests.rs +++ b/rs/ledger_suite/icrc1/ledger/tests/tests.rs @@ -14,7 +14,6 @@ use ic_ledger_suite_in_memory_ledger::{AllowancesRecentlyPurged, verify_ledger_s use ic_ledger_suite_state_machine_helpers::{AllowanceProvider, send_approval, send_transfer_from}; use ic_ledger_suite_state_machine_tests::MINTER; use ic_ledger_suite_state_machine_tests::archiving::icrc_archives; -use ic_ledger_suite_state_machine_tests::fee_collector::BlockRetrieval; use ic_ledger_suite_state_machine_tests_constants::{ ARCHIVE_TRIGGER_THRESHOLD, BLOB_META_KEY, BLOB_META_VALUE, DECIMAL_PLACES, FEE, INT_META_KEY, INT_META_VALUE, NAT_META_KEY, NAT_META_VALUE, NUM_BLOCKS_TO_ARCHIVE, TEXT_META_KEY, @@ -79,6 +78,14 @@ fn ledger_mainnet_wasm() -> Vec { mainnet_wasm } +fn ledger_mainnet_v5_wasm() -> Vec { + #[cfg(not(feature = "u256-tokens"))] + let mainnet_wasm = ledger_mainnet_v5_u64_wasm(); + #[cfg(feature = "u256-tokens")] + let mainnet_wasm = ledger_mainnet_v5_u256_wasm(); + mainnet_wasm +} + fn ledger_mainnet_v3_wasm() -> Vec { #[cfg(not(feature = "u256-tokens"))] let mainnet_wasm = ledger_mainnet_v3_u64_wasm(); @@ -101,6 +108,11 @@ fn ledger_mainnet_u64_wasm() -> Vec { .unwrap() } +#[cfg(not(feature = "u256-tokens"))] +fn ledger_mainnet_v5_u64_wasm() -> Vec { + std::fs::read(std::env::var("CKBTC_IC_ICRC1_LEDGER_V5_VERSION_WASM_PATH").unwrap()).unwrap() +} + #[cfg(not(feature = "u256-tokens"))] fn ledger_mainnet_v3_u64_wasm() -> Vec { std::fs::read(std::env::var("CKBTC_IC_ICRC1_LEDGER_V3_VERSION_WASM_PATH").unwrap()).unwrap() @@ -117,6 +129,11 @@ fn ledger_mainnet_u256_wasm() -> Vec { .unwrap() } +#[cfg(feature = "u256-tokens")] +fn ledger_mainnet_v5_u256_wasm() -> Vec { + std::fs::read(std::env::var("CKETH_IC_ICRC1_LEDGER_V5_VERSION_WASM_PATH").unwrap()).unwrap() +} + #[cfg(feature = "u256-tokens")] fn ledger_mainnet_v3_u256_wasm() -> Vec { std::fs::read(std::env::var("CKETH_IC_ICRC1_LEDGER_V3_VERSION_WASM_PATH").unwrap()).unwrap() @@ -351,24 +368,6 @@ fn check_fee_collector() { ); } -#[test] -fn check_fee_collector_blocks() { - ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_blocks( - ledger_wasm(), - encode_init_args, - BlockRetrieval::Legacy, - ); -} - -#[test] -fn check_fee_collector_icrc3_blocks() { - ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_blocks( - ledger_wasm(), - encode_init_args, - BlockRetrieval::Icrc3, - ); -} - #[test] fn check_memo_max_len() { ic_ledger_suite_state_machine_tests::test_memo_max_len(ledger_wasm(), encode_init_args); @@ -668,7 +667,7 @@ fn test_block_transformation() { #[test] fn icrc1_test_upgrade_serialization_from_mainnet() { - icrc1_test_upgrade_serialization(ledger_mainnet_wasm(), false); + icrc1_test_upgrade_serialization(ledger_mainnet_wasm(), true); } fn icrc1_test_upgrade_serialization(ledger_mainnet_wasm: Vec, mainnet_on_prev_version: bool) { @@ -696,7 +695,7 @@ fn icrc1_test_downgrade_from_incompatible_version() { ledger_wasm_nextledgerversion(), ledger_wasm(), encode_init_args, - true, + false, ); } @@ -760,6 +759,58 @@ fn test_cycles_for_archive_creation_default_spawns_archive() { ); } +#[test] +fn test_fee_collector_107_access_denied() { + ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_107_access_denied( + ledger_wasm(), + encode_init_args, + ); +} + +#[test] +fn test_fee_collector_107_smoke() { + ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_107_smoke( + ledger_wasm(), + encode_init_args, + ); +} + +#[test] +fn test_fee_collector_107_with_proptest() { + let minter = Arc::new(minter_identity()); + let builder = LedgerInitArgsBuilder::with_symbol_and_name(TOKEN_SYMBOL, TOKEN_NAME) + .with_minting_account(minter.sender().unwrap()) + .with_transfer_fee(FEE); + let init_args = Encode!(&LedgerArgument::Init(builder.build())).unwrap(); + ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_107_with_proptest::< + Tokens, + >(ledger_wasm(), init_args, minter); +} + +#[test] +fn test_fee_collector_107_upgrade() { + ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_107_upgrade( + ledger_wasm(), + encode_init_args, + ); +} + +#[test] +fn test_fee_collector_107_init() { + ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_107_init( + ledger_wasm(), + encode_init_args, + ); +} + +#[test] +fn test_fee_collector_107_upgrade_legacy() { + ic_ledger_suite_state_machine_tests::fee_collector::test_fee_collector_107_upgrade_legacy::< + LedgerArgument, + Tokens, + >(ledger_mainnet_v5_wasm(), ledger_wasm(), encode_init_args); +} + mod metrics { use crate::{encode_init_args, encode_upgrade_args, ledger_wasm}; use ic_ledger_suite_state_machine_tests::metrics::LedgerSuiteType; diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index baa23f2f4d09..95893f66c606 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -10,12 +10,12 @@ use ic_ledger_canister_core::ledger::{LedgerContext, LedgerTransaction, TxApplyE use ic_ledger_core::{ approvals::{AllowanceTable, HeapAllowancesData}, balances::Balances, - block::{BlockType, EncodedBlock, FeeCollector}, + block::{BlockType, EncodedBlock}, timestamp::TimeStamp, tokens::TokensType, }; use ic_ledger_hash_of::HashOf; -use icrc_ledger_types::icrc1::account::Account; +use icrc_ledger_types::{icrc1::account::Account, icrc107::schema::BTYPE_107}; use icrc_ledger_types::{icrc1::transfer::Memo, icrc3::transactions::TRANSACTION_FEE_COLLECTOR}; use serde::{Deserialize, Serialize}; @@ -379,7 +379,7 @@ impl LedgerTransaction for Transaction { where C: LedgerContext, { - let fee_collector = context.fee_collector().map(|fc| fc.fee_collector); + let fee_collector = context.fee_collector(); let fee_collector = fee_collector.as_ref(); match &self.operation { Operation::Transfer { @@ -460,9 +460,11 @@ impl LedgerTransaction for Transaction { expires_at, fee, } => { - context - .balances_mut() - .burn(from, fee.clone().unwrap_or(effective_fee.clone()))?; + let fee_amount = fee.clone().unwrap_or(effective_fee.clone()); + context.balances_mut().burn(from, fee_amount.clone())?; + if let Some(fee_collector) = fee_collector { + context.balances_mut().mint(fee_collector, fee_amount)?; + } let result = context .approvals_mut() .approve( @@ -482,8 +484,12 @@ impl LedgerTransaction for Transaction { return Err(e); } } - Operation::FeeCollector { .. } => { - panic!("FeeCollector107 not implemented") + Operation::FeeCollector { + fee_collector, + caller: _, + mthd: _, + } => { + context.set_fee_collector(*fee_collector); } } Ok(()) @@ -667,32 +673,28 @@ impl BlockType for Block { transaction: Self::Transaction, timestamp: TimeStamp, effective_fee: Tokens, - fee_collector: Option>, ) -> Self { let effective_fee = match &transaction.operation { Operation::Transfer { fee, .. } => fee.is_none().then_some(effective_fee), Operation::Approve { fee, .. } => fee.is_none().then_some(effective_fee), - Operation::FeeCollector { .. } => { - panic!("FeeCollector107 not implemented") - } _ => None, }; - let (fee_collector, fee_collector_block_index) = match fee_collector { - Some(FeeCollector { - fee_collector, - block_index: None, - }) => (Some(fee_collector), None), - Some(FeeCollector { block_index, .. }) => (None, block_index), - None => (None, None), + let btype = match &transaction.operation { + Operation::FeeCollector { + fee_collector: _, + caller: _, + mthd: _, + } => Some(BTYPE_107.to_string()), + _ => None, }; Self { parent_hash, transaction, effective_fee, timestamp: timestamp.as_nanos_since_unix_epoch(), - fee_collector, - fee_collector_block_index, - btype: None, + fee_collector: None, + fee_collector_block_index: None, + btype, } } } diff --git a/rs/ledger_suite/icrc1/test_utils/src/lib.rs b/rs/ledger_suite/icrc1/test_utils/src/lib.rs index 0b7466e357ad..6715f3e27fc1 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/lib.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/lib.rs @@ -605,7 +605,7 @@ impl TransactionsAndBalances { self.debit(from, fee); } Operation::FeeCollector { .. } => { - panic!("FeeCollector107 not implemented") + // We don't support transfers from the fee collector, we don't need to keep track of the account balance } }; self.transactions.push(tx); @@ -635,7 +635,7 @@ impl TransactionsAndBalances { self.check_and_update_account_validity(*from, default_fee); } Operation::FeeCollector { .. } => { - panic!("FeeCollector107 not implemented") + // We don't support transfers from the fee collector, we don't need to keep track of the account balance } } } diff --git a/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs b/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs index bba69e21ba5f..d7013043027e 100644 --- a/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs +++ b/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs @@ -17,6 +17,7 @@ use ic_nns_test_utils_golden_nns_state::new_state_machine_with_golden_fiduciary_ use ic_state_machine_tests::{StateMachine, UserError}; use icrc_ledger_types::icrc1::account::Account; use icrc_ledger_types::icrc106::errors::Icrc106Error; +use icrc_ledger_types::icrc107::schema::BTYPE_107; use lazy_static::lazy_static; use std::str::FromStr; use std::time::Duration; @@ -186,7 +187,11 @@ impl LedgerSuiteConfig { } } - fn perform_upgrade_downgrade_testing(&self, state_machine: &StateMachine) { + fn perform_upgrade_downgrade_testing( + &self, + state_machine: &StateMachine, + expect_107_block: bool, + ) { println!( "Processing {}, ledger id: {}, index id: {}", self.canister_name, self.ledger_id, self.index_id @@ -219,6 +224,18 @@ impl LedgerSuiteConfig { // Upgrade to the new canister versions self.upgrade_to_master(state_machine); if self.extended_testing { + if expect_107_block { + let blocks = previous_ledger_state + .as_mut() + .expect("extended testing should have a state") + .fetch_and_ingest_next_ledger_and_archive_blocks( + state_machine, + ledger_canister_id, + None, + ); + assert_eq!(blocks.blocks.len(), 1); + assert_eq!(blocks.blocks[0].btype, Some(BTYPE_107.to_string())); + } previous_ledger_state = Some(LedgerState::verify_state_and_generate_transactions( state_machine, ledger_canister_id, @@ -616,7 +633,7 @@ fn should_upgrade_icrc_ck_btc_canister_with_golden_state() { Some(burns_without_spender), true, ) - .perform_upgrade_downgrade_testing(&state_machine); + .perform_upgrade_downgrade_testing(&state_machine, true); } #[cfg(feature = "u256-tokens")] @@ -747,7 +764,7 @@ fn should_upgrade_icrc_ck_u256_canisters_with_golden_state() { let state_machine = new_state_machine_with_golden_fiduciary_state_or_panic(); for canister_config in canister_configs { - canister_config.perform_upgrade_downgrade_testing(&state_machine); + canister_config.perform_upgrade_downgrade_testing(&state_machine, true); } } @@ -991,7 +1008,7 @@ fn should_upgrade_icrc_sns_canisters_with_golden_state() { ic_nns_test_utils_golden_nns_state::new_state_machine_with_golden_sns_state_or_panic(); for canister_config in canister_configs { - canister_config.perform_upgrade_downgrade_testing(&state_machine); + canister_config.perform_upgrade_downgrade_testing(&state_machine, false); } } diff --git a/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs b/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs index ddada9b67bac..a1d38757ced6 100644 --- a/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs +++ b/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs @@ -77,11 +77,12 @@ fn should_upgrade_and_downgrade_ledger_canister_suite() { ) .unwrap(); - env.upgrade_canister( - ledger_id, - ledger_mainnet_wasm(), - Encode!(&ledger_upgrade_arg).unwrap(), - ).expect("Downgrading ledger to the mainnet version should succeed, since there are no breaking changes"); + // TODO: This version is not backwards compatible, uncomment once this code is on mainnet. + // env.upgrade_canister( + // ledger_id, + // ledger_mainnet_wasm(), + // Encode!(&ledger_upgrade_arg).unwrap(), + // ).expect("Downgrading ledger to the mainnet version should succeed, since there are no breaking changes"); } fn default_archive_options() -> ArchiveOptions { diff --git a/rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs b/rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs index 39ce7e849c78..c1ad556ebd09 100644 --- a/rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs +++ b/rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs @@ -89,6 +89,7 @@ pub trait InMemoryLedgerState { amount: &Self::Tokens, fee: &Option, ); + fn process_fee_collector_107(&mut self, fee_collector: &Option); fn validate_invariants(&self); } @@ -103,6 +104,7 @@ where burns_without_spender: Option>, transactions: u64, latest_block_timestamp: Option, + fee_collector_107: Option>, } impl PartialEq for InMemoryLedger @@ -141,6 +143,13 @@ where ); return false; } + if self.fee_collector_107 != other.fee_collector_107 { + println!( + "Mismatch in fee collector 107: {:?} vs {:?}", + self.fee_collector_107, other.fee_collector_107 + ); + return false; + } if self.burns_without_spender != other.burns_without_spender { println!( "Mismatch in burns without spender: {:?} vs {:?}", @@ -223,7 +232,8 @@ where fee: &Option, now: TimeStamp, ) { - self.burn_fee(from, fee); + let fee_collector = &self.fee_collector_107.clone().unwrap_or(None); + self.collect_fee(from, fee, fee_collector); self.set_allowance(from, spender, amount, expected_allowance, expires_at, now); self.transactions += 1; } @@ -271,7 +281,11 @@ where fee: &Option, ) { self.decrease_balance(from, amount); - self.collect_fee(from, fee); + let fee_collector = &self + .fee_collector_107 + .clone() + .unwrap_or(self.fee_collector.clone()); + self.collect_fee(from, fee, fee_collector); if let Some(fee) = fee && let Some(spender) = spender && from != spender @@ -293,6 +307,11 @@ where assert_ne!(&allowance.amount, &Tokens::zero()); } } + + fn process_fee_collector_107(&mut self, fee_collector: &Option) { + self.fee_collector_107 = Some(fee_collector.clone()); + self.transactions += 1; + } } impl Default for InMemoryLedger @@ -309,6 +328,7 @@ where burns_without_spender: None, transactions: 0, latest_block_timestamp: None, + fee_collector_107: None, } } } @@ -449,24 +469,22 @@ where .unwrap_or_else(|| panic!("Total supply overflow")); } - fn collect_fee(&mut self, from: &AccountId, amount: &Option) { + fn collect_fee( + &mut self, + from: &AccountId, + amount: &Option, + fee_collector: &Option, + ) { if let Some(amount) = amount { self.decrease_balance(from, amount); - if let Some(fee_collector) = &self.fee_collector { - self.increase_balance(&fee_collector.clone(), amount); + if let Some(fee_collector) = fee_collector { + self.increase_balance(fee_collector, amount); } else { self.decrease_total_supply(amount); } } } - fn burn_fee(&mut self, from: &AccountId, amount: &Option) { - if let Some(amount) = amount { - self.decrease_balance(from, amount); - self.decrease_total_supply(amount); - } - } - fn prune_expired_allowances(&mut self, now: TimeStamp) { let expired_allowances: Vec> = self .allowances @@ -538,8 +556,8 @@ where &fee.clone().or(block.effective_fee.clone()), TimeStamp::from_nanos_since_unix_epoch(block.timestamp), ), - Operation::FeeCollector { .. } => { - panic!("FeeCollector107 not implemented") + Operation::FeeCollector { fee_collector, .. } => { + self.process_fee_collector_107(fee_collector) } } } @@ -699,6 +717,16 @@ where self.validate_invariants(); } + pub fn set_fee_collector_107( + &mut self, + timestamp: TimeStamp, + fee_collector: &Option, + ) { + self.process_fee_collector_107(fee_collector); + self.latest_block_timestamp = Some(timestamp.as_nanos_since_unix_epoch()); + self.validate_invariants(); + } + pub fn verify_balances_and_allowances( &self, env: &StateMachine, diff --git a/rs/ledger_suite/tests/sm-tests/src/fee_collector.rs b/rs/ledger_suite/tests/sm-tests/src/fee_collector.rs index f4b5812afe9c..5970f28033bd 100644 --- a/rs/ledger_suite/tests/sm-tests/src/fee_collector.rs +++ b/rs/ledger_suite/tests/sm-tests/src/fee_collector.rs @@ -4,8 +4,8 @@ use crate::{ install_ledger, total_supply, transfer, }; use candid::{CandidType, Encode}; +use ic_icrc1_ledger::GetFeeCollectorError; use ic_state_machine_tests::StateMachine; -use icrc_ledger_types::icrc3::blocks::GenericBlock; use proptest::prelude::Strategy; use proptest::test_runner::TestRunner; use std::collections::HashSet; @@ -126,209 +126,359 @@ where .unwrap(); } -pub enum BlockRetrieval { - Legacy, - Icrc3, -} - -pub fn test_fee_collector_blocks( +pub fn test_fee_collector_107_access_denied( ledger_wasm: Vec, encode_init_args: fn(InitArgs) -> T, - block_retrieval: BlockRetrieval, ) where T: CandidType, { - fn retrieve_blocks_from_ledger( - env: &StateMachine, - ledger_id: CanisterId, - start: u64, - length: usize, - block_retrieval: &BlockRetrieval, - ) -> Vec { - match block_retrieval { - BlockRetrieval::Legacy => get_blocks(env, ledger_id.get().0, start, length).blocks, - BlockRetrieval::Icrc3 => icrc3_get_blocks(env, ledger_id, start, length) - .blocks - .into_iter() - .map(|b| GenericBlock::from(b.block)) - .collect(), - } + let (env, canister_id) = setup(ledger_wasm, encode_init_args, vec![]); + + let fee_collector = Account::from(PrincipalId::new_user_test_id(1).0); + let non_controller = Account::from(PrincipalId::new_user_test_id(2).0); + let result = set_fc_107( + &env, + canister_id, + non_controller.owner.into(), + Some(fee_collector), + ); + + let err = result.unwrap_err(); + assert_eq!(err, SetFeeCollectorError::AccessDenied("The `icrc107_set_fee_collector` endpoint can only be called by the canister controller".to_string())); +} + +fn get_fc_107_from_ledger(env: &StateMachine, canister_id: CanisterId) -> Option { + Decode!( + &env.query(canister_id, "icrc107_get_fee_collector", Encode!().unwrap()) + .expect("failed to query 107 fee collector") + .bytes(), + Result, GetFeeCollectorError> + ) + .expect("failed to decode icrc107_get_fee_collector response") + .expect("icrc107_get_fee_collector should not fail") +} + +fn send_tx_and_verify_fee_collection( + env: &StateMachine, + canister_id: CanisterId, + active_fc: Option, + inactive_fcs: Vec, + legacy_fc: bool, +) { + if !legacy_fc { + assert_eq!(get_fc_107_from_ledger(env, canister_id), active_fc); } - fn value_as_u64(value: &icrc_ledger_types::icrc::generic_value::Value) -> u64 { - use icrc_ledger_types::icrc::generic_value::Value; - match value { - Value::Nat64(n) => *n, - Value::Nat(n) => n.0.to_u64().expect("block index should fit into u64"), - Value::Int(int) => int.0.to_u64().expect("block index should fit into u64"), - value => panic!("Expected a numeric value but found {value:?}"), - } + let from = Account::from(PrincipalId::new_user_test_id(1001).0); + let spender = Account::from(PrincipalId::new_user_test_id(1002).0); + let to = Account::from(PrincipalId::new_user_test_id(1003).0); + let from_balance = balance_of(env, canister_id, from); + + let active_fc_balance = if let Some(fc) = active_fc { + assert_ne!(from, fc); + assert_ne!(spender, fc); + assert_ne!(to, fc); + Some(balance_of(env, canister_id, fc)) + } else { + None + }; + let mut inactive_fcs_balances = vec![]; + for fc in &inactive_fcs { + inactive_fcs_balances.push(balance_of(env, canister_id, *fc)); + assert_ne!(from, *fc); + assert_ne!(spender, *fc); + assert_ne!(to, *fc); } + let tot_supply = total_supply(env, canister_id); - fn value_as_account(value: &icrc_ledger_types::icrc::generic_value::Value) -> Account { - use icrc_ledger_types::icrc::generic_value::Value; - - match value { - Value::Array(array) => match &array[..] { - [Value::Blob(principal_bytes)] => Account { - owner: Principal::try_from(principal_bytes.as_ref()) - .expect("failed to parse account owner"), - subaccount: None, - }, - [Value::Blob(principal_bytes), Value::Blob(subaccount_bytes)] => Account { - owner: Principal::try_from(principal_bytes.as_ref()) - .expect("failed to parse account owner"), - subaccount: Some( - Subaccount::try_from(subaccount_bytes.as_ref()) - .expect("failed to parse subaccount"), - ), - }, - _ => panic!("Unexpected account representation: {array:?}"), - }, - value => panic!("Expected Value::Array but found {value:?}"), - } + const NUM_FEE_DEDUCTED: u64 = 3; + let num_fee_collected = if legacy_fc { 2u64 } else { 3u64 }; + const MINT_AMOUNT: u64 = 1_000_000; + const BURN_AMOUNT: u64 = 12_000; + + transfer(env, canister_id, MINTER, from, MINT_AMOUNT).expect("failed to mint funds"); + transfer(env, canister_id, from, to, 1).expect("failed to transfer funds"); + let approve_args = default_approve_args(spender.owner, u64::MAX); + send_approval(env, canister_id, from.owner, &approve_args).expect("approval failed"); + let transfer_from_args = default_transfer_from_args(from.owner, to.owner, 1); + send_transfer_from(env, canister_id, spender.owner, &transfer_from_args) + .expect("transfer from failed"); + transfer(env, canister_id, from, MINTER, BURN_AMOUNT).expect("failed to burn funds"); + + assert_eq!( + balance_of(env, canister_id, from), + from_balance + MINT_AMOUNT - BURN_AMOUNT - 2 - NUM_FEE_DEDUCTED * FEE + ); + if let Some(active_fc) = active_fc { + assert_eq!( + balance_of(env, canister_id, active_fc), + active_fc_balance.unwrap() + num_fee_collected * FEE + ); + assert_eq!( + total_supply(env, canister_id), + tot_supply + MINT_AMOUNT - BURN_AMOUNT - NUM_FEE_DEDUCTED * FEE + + num_fee_collected * FEE + ); + } else { + assert_eq!( + total_supply(env, canister_id), + tot_supply + MINT_AMOUNT - BURN_AMOUNT - NUM_FEE_DEDUCTED * FEE + ); } - fn fee_collector_from_block( - block: &icrc_ledger_types::icrc::generic_value::Value, - ) -> (Option, Option) { - match block { - icrc_ledger_types::icrc::generic_value::Value::Map(block_map) => { - let fee_collector = block_map.get("fee_col").map(value_as_account); - let fee_collector_block_index = block_map.get("fee_col_block").map(value_as_u64); - (fee_collector, fee_collector_block_index) - } - _ => panic!("A block should be a map!"), - } + for (fc, balance) in inactive_fcs.iter().zip(inactive_fcs_balances.iter()) { + assert_eq!(balance_of(env, canister_id, *fc), *balance); } +} - let env = StateMachine::new(); - // Only 1 test case because we modify the ledger within the test. +pub fn test_fee_collector_107_smoke(ledger_wasm: Vec, encode_init_args: fn(InitArgs) -> T) +where + T: CandidType, +{ + let (env, canister_id) = setup(ledger_wasm, encode_init_args, vec![]); + + let fee_collector_1 = Account::from(PrincipalId::new_user_test_id(1).0); + let fee_collector_2 = Account::from(PrincipalId::new_user_test_id(2).0); + + send_tx_and_verify_fee_collection( + &env, + canister_id, + None, + vec![fee_collector_1, fee_collector_2], + false, + ); + + set_fc_107_by_controller(&env, canister_id, Some(fee_collector_1)); + + send_tx_and_verify_fee_collection( + &env, + canister_id, + Some(fee_collector_1), + vec![fee_collector_2], + false, + ); + + set_fc_107_by_controller(&env, canister_id, Some(fee_collector_2)); + + send_tx_and_verify_fee_collection( + &env, + canister_id, + Some(fee_collector_2), + vec![fee_collector_1], + false, + ); + + set_fc_107_by_controller(&env, canister_id, None); + + send_tx_and_verify_fee_collection( + &env, + canister_id, + None, + vec![fee_collector_1, fee_collector_2], + false, + ); +} + +pub fn test_fee_collector_107_with_proptest( + ledger_wasm_current: Vec, + init_args: Vec, + minter: Arc, +) where + Tokens: TokensType + Default + std::fmt::Display + From, +{ let mut runner = TestRunner::new(TestRunnerConfig::with_cases(1)); + let now = SystemTime::now(); + let minter_principal: Principal = minter.sender().unwrap(); + const TX_COUNT: usize = 150; runner .run( &( - arb_account(), - arb_account(), - arb_account(), - arb_account(), - 1..10_000_000u64, - ) - .prop_filter( - "The four accounts must be different", - |(a1, a2, a3, a4, _)| HashSet::from([a1, a2, a3, a4]).len() == 4, + valid_transactions_strategy(minter, FEE, TX_COUNT, now).no_shrink(), + proptest::collection::vec( + proptest::option::of(proptest::option::of(arb_account())), + TX_COUNT..=TX_COUNT, ) .no_shrink(), - |(account_from, account_to, account_spender, fee_collector_account, amount)| { - let args = encode_init_args(InitArgs { - fee_collector_account: Some(fee_collector_account), - initial_balances: vec![(account_from, Nat::from((amount + FEE) * 7))], - ..init_args(vec![]) - }); - let args = Encode!(&args).unwrap(); + ), + |(transactions, fee_collectors)| { + let env = StateMachine::new(); + env.set_time(now); let ledger_id = env - .install_canister(ledger_wasm.clone(), args, None) + .install_canister(ledger_wasm_current.clone(), init_args.clone(), None) .unwrap(); - // The block at index 0 is the minting operation for account_from and - // has the fee collector set. - // Make 2 more transfers that should point to the first block index. - transfer(&env, ledger_id, account_from, account_to, amount) - .expect("Unable to perform the transfer"); - transfer(&env, ledger_id, account_from, account_to, amount) - .expect("Unable to perform the transfer"); - - let blocks = retrieve_blocks_from_ledger(&env, ledger_id, 0, 4, &block_retrieval); - - // The first block must have the fee collector explicitly defined. - assert_eq!( - fee_collector_from_block(blocks.first().unwrap()), - (Some(fee_collector_account), None) - ); - // The other two blocks must have a pointer to the first block. - assert_eq!( - fee_collector_from_block(blocks.get(1).unwrap()), - (None, Some(0)) - ); - assert_eq!( - fee_collector_from_block(blocks.get(2).unwrap()), - (None, Some(0)) - ); - - // Change the fee collector to a new one. The next block must have - // the fee collector set while the ones that follow will point - // to that one. - let ledger_upgrade_arg = LedgerArgument::Upgrade(Some(UpgradeArgs { - change_fee_collector: Some(ChangeFeeCollector::SetTo(account_from)), - ..UpgradeArgs::default() - })); - env.upgrade_canister( - ledger_id, - ledger_wasm.clone(), - Encode!(&ledger_upgrade_arg).unwrap(), - ) - .unwrap(); + let mut in_memory_ledger = InMemoryLedger::::default(); - let block_id = transfer(&env, ledger_id, account_from, account_to, amount) - .expect("Unable to perform the transfer"); - transfer(&env, ledger_id, account_from, account_to, amount) - .expect("Unable to perform the transfer"); - send_approval( - &env, - ledger_id, - account_from.owner, - &ApproveArgs { - from_subaccount: account_from.subaccount, - spender: account_spender, - amount: Nat::from(amount + FEE), - expected_allowance: None, - expires_at: None, - fee: None, - memo: None, - created_at_time: None, - }, - ) - .expect("Unable to perform the approval"); - send_transfer_from( + let mut total_blocks = 0u64; + for tx_index in 0..TX_COUNT { + total_blocks += 1; + if let Some(fee_collector) = fee_collectors[tx_index] { + total_blocks += 1; + in_memory_ledger.set_fee_collector_107( + TimeStamp::from_nanos_since_unix_epoch(system_time_to_nanos( + env.time(), + )), + &fee_collector, + ); + set_fc_107_by_controller(&env, ledger_id, fee_collector); + } + in_memory_ledger.apply_arg_with_caller( + &transactions[tx_index], + TimeStamp::from_nanos_since_unix_epoch(system_time_to_nanos(env.time())), + minter_principal, + Some(FEE.into()), + ); + apply_arg_with_caller(&env, ledger_id, &transactions[tx_index]); + } + in_memory_ledger.verify_balances_and_allowances( &env, ledger_id, - account_spender.owner, - &TransferFromArgs { - spender_subaccount: account_spender.subaccount, - from: account_from, - to: account_to, - amount: Nat::from(amount), - fee: None, - memo: None, - created_at_time: None, - }, - ) - .expect("Unable to perform the transfer_from"); - - let blocks = - retrieve_blocks_from_ledger(&env, ledger_id, block_id, 4, &block_retrieval); - - assert_eq!( - fee_collector_from_block(blocks.first().unwrap()), - (Some(account_from), None) - ); - assert_eq!( - fee_collector_from_block(blocks.get(1).unwrap()), - (None, Some(block_id)) - ); - // Expect the fee collector to be set in an approve block. - assert_eq!( - fee_collector_from_block(blocks.get(2).unwrap()), - (None, Some(block_id)) - ); - // Expect the fee collector to be set in a transfer_from block. - assert_eq!( - fee_collector_from_block(blocks.get(3).unwrap()), - (None, Some(block_id)) + total_blocks, + AllowancesRecentlyPurged::Yes, ); + verify_ledger_state::(&env, ledger_id, None, AllowancesRecentlyPurged::Yes); + Ok(()) }, ) - .unwrap() + .unwrap(); +} + +pub fn test_fee_collector_107_upgrade(ledger_wasm: Vec, encode_init_args: fn(InitArgs) -> T) +where + T: CandidType, +{ + let (env, canister_id) = setup(ledger_wasm.clone(), encode_init_args, vec![]); + + let fee_collector_1 = Account::from(PrincipalId::new_user_test_id(1).0); + + send_tx_and_verify_fee_collection(&env, canister_id, None, vec![fee_collector_1], false); + + let upgrade_args = LedgerArgument::Upgrade(Some(UpgradeArgs::default())); + env.upgrade_canister( + canister_id, + ledger_wasm.clone(), + Encode!(&upgrade_args).unwrap(), + ) + .expect("failed to upgrade the ledger"); + + send_tx_and_verify_fee_collection(&env, canister_id, None, vec![fee_collector_1], false); + + let upgrade_args = LedgerArgument::Upgrade(Some(UpgradeArgs { + change_fee_collector: Some(ChangeFeeCollector::SetTo(fee_collector_1)), + ..UpgradeArgs::default() + })); + env.upgrade_canister( + canister_id, + ledger_wasm.clone(), + Encode!(&upgrade_args).unwrap(), + ) + .expect("failed to upgrade the ledger"); + + send_tx_and_verify_fee_collection(&env, canister_id, Some(fee_collector_1), vec![], false); + + let upgrade_args = LedgerArgument::Upgrade(Some(UpgradeArgs::default())); + env.upgrade_canister( + canister_id, + ledger_wasm.clone(), + Encode!(&upgrade_args).unwrap(), + ) + .expect("failed to upgrade the ledger"); + + send_tx_and_verify_fee_collection(&env, canister_id, Some(fee_collector_1), vec![], false); + + let upgrade_args = LedgerArgument::Upgrade(Some(UpgradeArgs { + change_fee_collector: Some(ChangeFeeCollector::Unset), + ..UpgradeArgs::default() + })); + env.upgrade_canister(canister_id, ledger_wasm, Encode!(&upgrade_args).unwrap()) + .expect("failed to upgrade the ledger"); + + send_tx_and_verify_fee_collection(&env, canister_id, None, vec![fee_collector_1], false); +} + +pub fn test_fee_collector_107_init(ledger_wasm: Vec, encode_init_args: fn(InitArgs) -> T) +where + T: CandidType, +{ + let fee_collector = Account::from(PrincipalId::new_user_test_id(1).0); + + for fee_collector_account in [None, Some(fee_collector)] { + let env = StateMachine::new(); + + let args = encode_init_args(InitArgs { + fee_collector_account, + ..init_args(vec![]) + }); + let args = Encode!(&args).unwrap(); + let canister_id = env + .install_canister(ledger_wasm.clone(), args, None) + .unwrap(); + + send_tx_and_verify_fee_collection(&env, canister_id, fee_collector_account, vec![], false); + } +} + +pub fn test_fee_collector_107_upgrade_legacy( + ledger_wasm_legacy_fc: Vec, + ledger_wasm: Vec, + encode_init_args: fn(InitArgs) -> T, +) where + T: CandidType, + Tokens: TokensType + Default + std::fmt::Display, +{ + let fee_col_legacy = Account::from(PrincipalId::new_user_test_id(1).0); + let fee_col_107 = Account::from(PrincipalId::new_user_test_id(2).0); + + let init_params = [None, Some(fee_col_legacy)]; + let upgrade_params = [ + None, + Some(ChangeFeeCollector::Unset), + Some(ChangeFeeCollector::SetTo(fee_col_107)), + ]; + + for init_fee_collector in init_params { + for upgrade_fee_collector in &upgrade_params { + let env = StateMachine::new(); + let args = encode_init_args(InitArgs { + fee_collector_account: init_fee_collector, + ..init_args(vec![]) + }); + let args = Encode!(&args).unwrap(); + let canister_id = env + .install_canister(ledger_wasm_legacy_fc.clone(), args, None) + .unwrap(); + + send_tx_and_verify_fee_collection(&env, canister_id, init_fee_collector, vec![], true); + + let upgrade_args = LedgerArgument::Upgrade(Some(UpgradeArgs { + change_fee_collector: upgrade_fee_collector.clone(), + ..UpgradeArgs::default() + })); + env.upgrade_canister( + canister_id, + ledger_wasm.clone(), + Encode!(&upgrade_args).unwrap(), + ) + .expect("failed to upgrade the ledger"); + + let active_fc = match upgrade_fee_collector { + Some(ChangeFeeCollector::SetTo(fee_collector)) => Some(*fee_collector), + Some(ChangeFeeCollector::Unset) => None, + None => init_fee_collector, + }; + let mut inactive_fcs = vec![]; + if active_fc != Some(fee_col_legacy) { + inactive_fcs.push(fee_col_legacy); + } + if active_fc != Some(fee_col_107) { + inactive_fcs.push(fee_col_107); + } + + send_tx_and_verify_fee_collection(&env, canister_id, active_fc, inactive_fcs, false); + + verify_ledger_state::(&env, canister_id, None, AllowancesRecentlyPurged::Yes); + } + } } diff --git a/rs/ledger_suite/tests/sm-tests/src/icrc_106.rs b/rs/ledger_suite/tests/sm-tests/src/icrc_106.rs index ee502c32b1d0..6ca900d51008 100644 --- a/rs/ledger_suite/tests/sm-tests/src/icrc_106.rs +++ b/rs/ledger_suite/tests/sm-tests/src/icrc_106.rs @@ -149,16 +149,19 @@ pub fn test_upgrade_downgrade_with_mainnet_ledger( .expect("should successfully self-upgrade ledger canister"); assert_index_set(&env, canister_id, index_principal); - // Downgrade the ledger to the mainnet version that does not support ICRC-106 - env.upgrade_canister(canister_id, mainnet_ledger_wasm, encoded_empty_upgrade_args) - .expect("should successfully downgrade ledger canister"); - assert_index_not_set(&env, canister_id, false); - - // Upgrade to a ledger version that supports ICRC-106, but do not set the index principal - let encoded_empty_upgrade_args = Encode!(&encode_empty_upgrade_args()).unwrap(); - env.upgrade_canister(canister_id, ledger_wasm, encoded_empty_upgrade_args) - .expect("should successfully upgrade ledger canister"); - assert_index_not_set(&env, canister_id, true); + // Downgrade to mainnet is currently not possible. The rest of the test can + // be uncommented again, once the PR that introduced this line is on mainnet. + + // // Downgrade the ledger to the mainnet version that does not support ICRC-106 + // env.upgrade_canister(canister_id, mainnet_ledger_wasm, encoded_empty_upgrade_args) + // .expect("should successfully downgrade ledger canister"); + // assert_index_not_set(&env, canister_id, false); + + // // Upgrade to a ledger version that supports ICRC-106, but do not set the index principal + // let encoded_empty_upgrade_args = Encode!(&encode_empty_upgrade_args()).unwrap(); + // env.upgrade_canister(canister_id, ledger_wasm, encoded_empty_upgrade_args) + // .expect("should successfully upgrade ledger canister"); + // assert_index_not_set(&env, canister_id, true); } fn assert_index_not_set( diff --git a/rs/ledger_suite/tests/sm-tests/src/lib.rs b/rs/ledger_suite/tests/sm-tests/src/lib.rs index 5ae0bf06c655..7c8fc067f7db 100644 --- a/rs/ledger_suite/tests/sm-tests/src/lib.rs +++ b/rs/ledger_suite/tests/sm-tests/src/lib.rs @@ -41,7 +41,7 @@ use icrc_ledger_types::icrc::generic_metadata_value::MetadataValue as Value; use icrc_ledger_types::icrc::generic_value::ICRC3Value; use icrc_ledger_types::icrc::generic_value::Value as GenericValue; use icrc_ledger_types::icrc::metadata_key::MetadataKey; -use icrc_ledger_types::icrc1::account::{Account, DEFAULT_SUBACCOUNT, Subaccount}; +use icrc_ledger_types::icrc1::account::{Account, DEFAULT_SUBACCOUNT}; use icrc_ledger_types::icrc1::transfer::{Memo, TransferArg, TransferError}; use icrc_ledger_types::icrc2::allowance::AllowanceArgs; use icrc_ledger_types::icrc2::approve::{ApproveArgs, ApproveError}; @@ -61,6 +61,7 @@ use icrc_ledger_types::icrc103::get_allowances::{Allowances, GetAllowancesArgs}; use icrc_ledger_types::icrc106::errors::Icrc106Error; use icrc_ledger_types::icrc107; use icrc_ledger_types::icrc107::schema::{BTYPE_107, SET_FEE_COL_107}; +use icrc_ledger_types::icrc107::set_fee_collector::{SetFeeCollectorArgs, SetFeeCollectorError}; use num_bigint::BigUint; use num_traits::ToPrimitive; use proptest::prelude::*; @@ -4431,6 +4432,42 @@ pub fn test_cycles_for_archive_creation_default_spawns_archive( assert_eq!(archives.len(), 1); } +fn set_fc_107( + env: &StateMachine, + canister_id: CanisterId, + caller: PrincipalId, + fee_collector: Option, +) -> Result { + let now = system_time_to_nanos(env.time()); + let fc107_args = SetFeeCollectorArgs { + fee_collector, + created_at_time: now, + }; + Decode!( + &env.execute_ingress_as( + caller, + canister_id, + "icrc107_set_fee_collector", + Encode!(&fc107_args).unwrap(), + ).unwrap().bytes(), Result + ) + .expect("failed to decode set fee collector result") +} + +pub fn set_fc_107_by_controller( + env: &StateMachine, + canister_id: CanisterId, + fee_collector: Option, +) { + let controllers = env + .get_controllers(canister_id) + .expect("ledger should have a controller"); + assert_eq!(controllers.len(), 1); + let controller = controllers[0]; + let result = set_fc_107(env, canister_id, controller, fee_collector); + assert!(result.is_ok()); +} + pub mod metadata { use super::*; diff --git a/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/test_utils/src/sample_data.rs b/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/test_utils/src/sample_data.rs index 538f855653f0..a5b0e291ba1d 100644 --- a/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/test_utils/src/sample_data.rs +++ b/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/test_utils/src/sample_data.rs @@ -111,13 +111,8 @@ impl Scribe { let parent_hash = self.blockchain.back().map(|hb| hb.hash); let index = self.next_index(); - let block = Block::from_transaction( - parent_hash, - transaction, - self.time().into(), - effective_fee, - None, - ); + let block = + Block::from_transaction(parent_hash, transaction, self.time().into(), effective_fee); let timestamp = block.timestamp; diff --git a/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/tests/store_tests.rs b/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/tests/store_tests.rs index 00759596bbd0..8d355d10baa6 100644 --- a/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/tests/store_tests.rs +++ b/rs/rosetta-api/icp/ledger_canister_blocks_synchronizer/tests/store_tests.rs @@ -45,9 +45,13 @@ impl LedgerContext for TestContext { &mut self.approvals } - fn fee_collector(&self) -> Option<&ic_ledger_core::block::FeeCollector> { + fn fee_collector(&self) -> Option { None } + + fn set_fee_collector(&mut self, _fee_collector: Option) { + panic!("not implemented") + } } #[actix_rt::test] diff --git a/rs/tests/cross_chain/ic_xc_ledger_suite_orchestrator_test.rs b/rs/tests/cross_chain/ic_xc_ledger_suite_orchestrator_test.rs index 1668e4616377..240bf4c17ef5 100644 --- a/rs/tests/cross_chain/ic_xc_ledger_suite_orchestrator_test.rs +++ b/rs/tests/cross_chain/ic_xc_ledger_suite_orchestrator_test.rs @@ -162,7 +162,7 @@ fn ic_xc_ledger_suite_orchestrator_test(env: TestEnv) { assert_eq!( index_status, ic_icrc1_index_ng::Status { - num_blocks_synced: Nat::from(0_u8) + num_blocks_synced: Nat::from(1_u8) } ); });