From fc1e131bf3a6e4676855b9dc12f33b8058ee1e4d Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 24 Oct 2025 09:52:22 +0000 Subject: [PATCH 01/40] add fee collector operation --- .../src/icrc3/transactions.rs | 24 +++++++++ rs/ledger_suite/icrc1/src/endpoints.rs | 16 +++++- rs/ledger_suite/icrc1/src/lib.rs | 53 +++++++++++++++++++ rs/ledger_suite/icrc1/test_utils/src/lib.rs | 37 ++++++++----- .../test_utils/in_memory_ledger/src/lib.rs | 6 +++ 5 files changed, 123 insertions(+), 13 deletions(-) diff --git a/packages/icrc-ledger-types/src/icrc3/transactions.rs b/packages/icrc-ledger-types/src/icrc3/transactions.rs index f71945bce7aa..f01f0a718a7a 100644 --- a/packages/icrc-ledger-types/src/icrc3/transactions.rs +++ b/packages/icrc-ledger-types/src/icrc3/transactions.rs @@ -58,6 +58,13 @@ pub struct Approve { pub created_at_time: Option, } +#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct FeeCollector { + pub fee_collector: Option, + pub caller: Option, + pub created_at_time: Option, +} + // Representation of a Transaction which supports the Icrc1 Standard functionalities #[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct Transaction { @@ -66,6 +73,7 @@ pub struct Transaction { pub burn: Option, pub transfer: Option, pub approve: Option, + pub fee_collector: Option, pub timestamp: u64, } @@ -78,6 +86,7 @@ impl Transaction { burn: Some(burn), transfer: None, approve: None, + fee_collector: None, } } @@ -89,6 +98,7 @@ impl Transaction { burn: None, transfer: None, approve: None, + fee_collector: None, } } @@ -100,6 +110,7 @@ impl Transaction { burn: None, transfer: Some(transfer), approve: None, + fee_collector: None, } } @@ -111,6 +122,19 @@ impl Transaction { burn: None, transfer: None, approve: Some(approve), + fee_collector: None, + } + } + + pub fn fee_collector(fee_collector: FeeCollector, timestamp: u64) -> Self { + Self { + kind: "107feecol".into(), + timestamp, + mint: None, + burn: None, + transfer: None, + approve: None, + fee_collector: Some(fee_collector), } } } diff --git a/rs/ledger_suite/icrc1/src/endpoints.rs b/rs/ledger_suite/icrc1/src/endpoints.rs index 4930df6f1902..581898d3fdad 100644 --- a/rs/ledger_suite/icrc1/src/endpoints.rs +++ b/rs/ledger_suite/icrc1/src/endpoints.rs @@ -6,7 +6,9 @@ use ic_ledger_core::tokens::TokensType; use icrc_ledger_types::icrc1::transfer::TransferError; use icrc_ledger_types::icrc2::approve::ApproveError; use icrc_ledger_types::icrc2::transfer_from::TransferFromError; -use icrc_ledger_types::icrc3::transactions::{Approve, Burn, Mint, Transaction, Transfer}; +use icrc_ledger_types::icrc3::transactions::{ + Approve, Burn, FeeCollector, Mint, Transaction, Transfer, +}; use serde::Deserialize; pub fn convert_transfer_error( @@ -159,6 +161,7 @@ impl From> for Transaction { burn: None, transfer: None, approve: None, + fee_collector: None, timestamp: b.timestamp, }; let created_at_time = b.transaction.created_at_time; @@ -231,6 +234,17 @@ impl From> for Transaction { memo, }); } + Operation::FeeCollector { + fee_collector, + caller, + } => { + tx.kind = "107feecol".to_string(); + tx.fee_collector = Some(FeeCollector { + fee_collector, + caller, + created_at_time, + }); + } } tx diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index 7389b917586e..9afaeecff31b 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -80,6 +80,21 @@ pub enum Operation { #[serde(skip_serializing_if = "Option::is_none")] fee: Option, }, + #[serde(rename = "107feecol")] + FeeCollector { + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "compact_account::opt" + )] + fee_collector: Option, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "compact_account::opt" + )] + caller: Option, + }, } // A [Transaction] but flattened meaning that [Operation] @@ -131,6 +146,16 @@ struct FlattenedTransaction { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] expires_at: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "compact_account::opt")] + fee_collector: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "compact_account::opt")] + caller: Option, } impl TryFrom> for Transaction { @@ -194,6 +219,7 @@ impl From> for FlattenedTransaction "mint", Transfer { .. } => "xfer", Approve { .. } => "approve", + FeeCollector { .. } => "107feecol", } .into(), from: match &t.operation { @@ -214,12 +240,14 @@ impl From> for FlattenedTransaction amount.clone(), + FeeCollector { .. } => Tokens::zero(), }, fee: match &t.operation { Transfer { fee, .. } | Approve { fee, .. } | Mint { fee, .. } | Burn { fee, .. } => fee.to_owned(), + FeeCollector { .. } => None, }, expected_allowance: match &t.operation { Approve { @@ -231,6 +259,14 @@ impl From> for FlattenedTransaction expires_at.to_owned(), _ => None, }, + fee_collector: match &t.operation { + FeeCollector { fee_collector, .. } => fee_collector.to_owned(), + _ => None, + }, + caller: match &t.operation { + FeeCollector { caller, .. } => caller.to_owned(), + _ => None, + }, } } } @@ -421,6 +457,12 @@ impl LedgerTransaction for Transaction { return Err(e); } } + Operation::FeeCollector { + fee_collector, + caller, + } => { + panic!("not implemented") + } } Ok(()) } @@ -554,6 +596,17 @@ impl TryFrom( Operation::Approve { ref fee, .. } => fee.clone().is_none().then_some(arb_fee), Operation::Burn { ref fee, .. } => fee.clone().is_none().then_some(arb_fee), Operation::Mint { ref fee, .. } => fee.clone().is_none().then_some(arb_fee), + Operation::FeeCollector { .. } => None, }; Block { @@ -539,19 +540,19 @@ impl TransactionsAndBalances { if let Some(spender_account) = spender { let used_allowance = amount.get_e8s() + fee; self.allowances.entry((from, spender_account)).and_modify( - |current_allowance| { - let current_amount = current_allowance.get_e8s(); - *current_allowance = Tokens::from_e8s( - current_amount - .checked_sub(used_allowance) - .unwrap_or_else(|| { - panic!( - "Allowance {current_amount} not enough to cover amount and fee {used_allowance} - from: {from}, to: {to}, spender: {spender_account}" - ) - }), + |current_allowance| { + let current_amount = current_allowance.get_e8s(); + *current_allowance = Tokens::from_e8s( + current_amount + .checked_sub(used_allowance) + .unwrap_or_else(|| { + panic!( + "Allowance {current_amount} not enough to cover amount and fee {used_allowance} - from: {from}, to: {to}, spender: {spender_account}" + ) + }), + ); + }, ); - }, - ); // Remove allowance entry if it's now zero if let Some(allowance) = self.allowances.get(&(from, spender_account)) @@ -577,6 +578,12 @@ impl TransactionsAndBalances { .or_insert(amount); self.debit(from, fee); } + Operation::FeeCollector { + fee_collector, + caller, + } => { + panic!("not implemented") + } }; self.transactions.push(tx); @@ -604,6 +611,12 @@ impl TransactionsAndBalances { // (allowance was added/modified for this account) self.check_and_update_account_validity(*from, default_fee); } + Operation::FeeCollector { + fee_collector, + caller, + } => { + panic!("not implemented") + } } } 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 f7a18a09f146..bf6532f4ddfc 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 @@ -538,6 +538,12 @@ where &fee.clone().or(block.effective_fee.clone()), TimeStamp::from_nanos_since_unix_epoch(block.timestamp), ), + Operation::FeeCollector { + fee_collector, + caller, + } => { + panic!("not implemented") + } } } self.post_process_ledger_blocks(blocks); From 9e7c58d08a5c36524785f0f94f965fb9279d5bd0 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 24 Oct 2025 15:31:23 +0000 Subject: [PATCH 02/40] index build fix --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 6984dbcece0d..8b404f3ad6ed 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -989,6 +989,9 @@ fn get_accounts(block: &Block) -> Vec { Operation::Mint { to, .. } => vec![to], Operation::Transfer { from, to, .. } => vec![from, to], Operation::Approve { from, .. } => vec![from], + Operation::FeeCollector { fee_collector, .. } => { + panic!("not implemented") + } } } From 8914b52b0c97e146536314af0402258e9edcdc05 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Mon, 27 Oct 2025 14:39:44 +0000 Subject: [PATCH 03/40] remove unused conversion --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 6 ++ rs/ledger_suite/icrc1/src/lib.rs | 102 --------------------- 2 files changed, 6 insertions(+), 102 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 8b404f3ad6ed..cc389ac8c0fd 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -955,6 +955,12 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { debit(block_index, from, fee); } + Operation::FeeCollector { + fee_collector, + caller, + } => { + panic!("not implemented") + } }, ); } diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index 9afaeecff31b..023b89fe393d 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -509,108 +509,6 @@ impl Transaction { } } -impl TryFrom - for Transaction -{ - type Error = String; - fn try_from( - value: icrc_ledger_types::icrc3::transactions::Transaction, - ) -> Result { - if let Some(mint) = value.mint { - let amount = Tokens::try_from(mint.amount) - .map_err(|_| "Could not convert Nat to Tokens".to_string())?; - let fee = match mint.fee { - Some(fee) => Some( - Tokens::try_from(fee) - .map_err(|_| "Could not convert Nat to Tokens".to_string())?, - ), - None => None, - }; - let operation = Operation::Mint { - to: mint.to, - amount, - fee, - }; - return Ok(Self { - operation, - created_at_time: mint.created_at_time, - memo: mint.memo, - }); - } - if let Some(burn) = value.burn { - let amount = Tokens::try_from(burn.amount) - .map_err(|_| "Could not convert Nat to Tokens".to_string())?; - let fee = match burn.fee { - Some(fee) => Some( - Tokens::try_from(fee) - .map_err(|_| "Could not convert Nat to Tokens".to_string())?, - ), - None => None, - }; - let operation = Operation::Burn { - from: burn.from, - spender: burn.spender, - amount, - fee, - }; - return Ok(Self { - operation, - created_at_time: burn.created_at_time, - memo: burn.memo, - }); - } - if let Some(transfer) = value.transfer { - let amount = Tokens::try_from(transfer.amount) - .map_err(|_| "Could not convert Nat to Tokens".to_string())?; - match transfer.fee { - Some(fee) => { - let fee = Tokens::try_from(fee) - .map_err(|_| "Could not convert Nat to Tokens".to_string())?; - - let operation = Operation::Transfer { - to: transfer.to, - amount, - from: transfer.from, - spender: transfer.spender, - fee: Some(fee), - }; - return Ok(Self { - operation, - created_at_time: transfer.created_at_time, - memo: transfer.memo, - }); - } - None => { - let operation = Operation::Transfer { - to: transfer.to, - amount, - from: transfer.from, - spender: transfer.spender, - fee: None, - }; - return Ok(Self { - operation, - created_at_time: transfer.created_at_time, - memo: transfer.memo, - }); - } - } - } - if let Some(fee_collector) = value.fee_collector { - let operation = Operation::FeeCollector { - fee_collector: fee_collector.fee_collector, - caller: fee_collector.caller, - }; - return Ok(Self { - operation: operation, - created_at_time: fee_collector.created_at_time, - memo: None, - }); - } - Err("Transaction has neither mint, burn nor transfer operation".to_owned()) - } -} - #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)] #[serde(bound = "")] pub struct Block { From 440545a185058105258c266e5346a2c011f5e302 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Mon, 27 Oct 2025 14:44:59 +0000 Subject: [PATCH 04/40] convert back from flattened --- rs/ledger_suite/icrc1/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index 023b89fe393d..0f9556805296 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -197,6 +197,10 @@ impl TryFrom> for Transaction Operation::FeeCollector { + fee_collector: value.fee_collector, + caller: value.caller, + }, unknown_op => return Err(format!("Unknown operation name {unknown_op}")), }; Ok(Transaction { From 5ed8d30d46232010a5bdb2127a17db3957fea4fa Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Tue, 28 Oct 2025 19:21:59 +0000 Subject: [PATCH 05/40] clippy --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 7 ++----- rs/ledger_suite/icrc1/src/lib.rs | 5 +---- rs/ledger_suite/icrc1/test_utils/src/lib.rs | 10 ++-------- rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs | 5 +---- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index cc389ac8c0fd..484b5f04fb59 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -955,10 +955,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { debit(block_index, from, fee); } - Operation::FeeCollector { - fee_collector, - caller, - } => { + Operation::FeeCollector { .. } => { panic!("not implemented") } }, @@ -995,7 +992,7 @@ fn get_accounts(block: &Block) -> Vec { Operation::Mint { to, .. } => vec![to], Operation::Transfer { from, to, .. } => vec![from, to], Operation::Approve { from, .. } => vec![from], - Operation::FeeCollector { fee_collector, .. } => { + Operation::FeeCollector { .. } => { panic!("not implemented") } } diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index 0f9556805296..a84bb5876911 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -461,10 +461,7 @@ impl LedgerTransaction for Transaction { return Err(e); } } - Operation::FeeCollector { - fee_collector, - caller, - } => { + Operation::FeeCollector { .. } => { panic!("not implemented") } } diff --git a/rs/ledger_suite/icrc1/test_utils/src/lib.rs b/rs/ledger_suite/icrc1/test_utils/src/lib.rs index 616ddcec6c81..ffaa1cca4699 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/lib.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/lib.rs @@ -578,10 +578,7 @@ impl TransactionsAndBalances { .or_insert(amount); self.debit(from, fee); } - Operation::FeeCollector { - fee_collector, - caller, - } => { + Operation::FeeCollector { .. } => { panic!("not implemented") } }; @@ -611,10 +608,7 @@ impl TransactionsAndBalances { // (allowance was added/modified for this account) self.check_and_update_account_validity(*from, default_fee); } - Operation::FeeCollector { - fee_collector, - caller, - } => { + Operation::FeeCollector { .. } => { panic!("not implemented") } } 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 bf6532f4ddfc..94e8b9484454 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 @@ -538,10 +538,7 @@ where &fee.clone().or(block.effective_fee.clone()), TimeStamp::from_nanos_since_unix_epoch(block.timestamp), ), - Operation::FeeCollector { - fee_collector, - caller, - } => { + Operation::FeeCollector { .. } => { panic!("not implemented") } } From b1a7a4068fe4241624ce38fc4b5dc6d00d076291 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Tue, 28 Oct 2025 19:24:05 +0000 Subject: [PATCH 06/40] panic for fee col blocks --- rs/ledger_suite/icrc1/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index a84bb5876911..4634cf3289e8 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -588,6 +588,9 @@ impl BlockType for Block { 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!("not implemented") + } _ => None, }; let (fee_collector, fee_collector_block_index) = match fee_collector { From 372c508b09d5cf5acc5cc7cddb7402a42018a97d Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Wed, 29 Oct 2025 09:38:27 +0000 Subject: [PATCH 07/40] build fix --- rs/nervous_system/initial_supply/src/tests.rs | 1 + rs/sns/governance/token_valuation/src/tests.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/rs/nervous_system/initial_supply/src/tests.rs b/rs/nervous_system/initial_supply/src/tests.rs index baa9a761bc00..94274e15c173 100644 --- a/rs/nervous_system/initial_supply/src/tests.rs +++ b/rs/nervous_system/initial_supply/src/tests.rs @@ -76,6 +76,7 @@ async fn test_initial_supply() { burn: None, approve: None, transfer: None, + fee_collector: None, } }; diff --git a/rs/sns/governance/token_valuation/src/tests.rs b/rs/sns/governance/token_valuation/src/tests.rs index 5b4e5875cce4..09eb9eada405 100644 --- a/rs/sns/governance/token_valuation/src/tests.rs +++ b/rs/sns/governance/token_valuation/src/tests.rs @@ -163,6 +163,7 @@ async fn test_icps_per_sns_token_client() { burn: None, approve: None, transfer: None, + fee_collector: None, }, ], From 3dcf6a60c5f508c51b7ad48eb7a9833cda25316c Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Wed, 29 Oct 2025 14:40:09 +0000 Subject: [PATCH 08/40] build fix --- rs/rosetta-api/icrc1/src/common/storage/types.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rs/rosetta-api/icrc1/src/common/storage/types.rs b/rs/rosetta-api/icrc1/src/common/storage/types.rs index 7f473f9828fd..b3d9ed714a5e 100644 --- a/rs/rosetta-api/icrc1/src/common/storage/types.rs +++ b/rs/rosetta-api/icrc1/src/common/storage/types.rs @@ -608,6 +608,9 @@ where amount: amount.into(), fee: fee.map(Into::into), }, + Op::FeeCollector { .. } => { + panic!("not implemented") + } } } } From d010fbdb011f31e65a40af3b68d95d7ec32a95b4 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Wed, 29 Oct 2025 14:58:57 +0000 Subject: [PATCH 09/40] revert unintentional change --- rs/ledger_suite/icrc1/test_utils/src/lib.rs | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rs/ledger_suite/icrc1/test_utils/src/lib.rs b/rs/ledger_suite/icrc1/test_utils/src/lib.rs index ffaa1cca4699..4643d55ff108 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/lib.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/lib.rs @@ -540,19 +540,19 @@ impl TransactionsAndBalances { if let Some(spender_account) = spender { let used_allowance = amount.get_e8s() + fee; self.allowances.entry((from, spender_account)).and_modify( - |current_allowance| { - let current_amount = current_allowance.get_e8s(); - *current_allowance = Tokens::from_e8s( - current_amount - .checked_sub(used_allowance) - .unwrap_or_else(|| { - panic!( - "Allowance {current_amount} not enough to cover amount and fee {used_allowance} - from: {from}, to: {to}, spender: {spender_account}" - ) - }), - ); - }, + |current_allowance| { + let current_amount = current_allowance.get_e8s(); + *current_allowance = Tokens::from_e8s( + current_amount + .checked_sub(used_allowance) + .unwrap_or_else(|| { + panic!( + "Allowance {current_amount} not enough to cover amount and fee {used_allowance} - from: {from}, to: {to}, spender: {spender_account}" + ) + }), ); + }, + ); // Remove allowance entry if it's now zero if let Some(allowance) = self.allowances.get(&(from, spender_account)) From 42072ed7d75b4ab90f26a11f351cb0023c059eef Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 30 Oct 2025 10:30:30 +0000 Subject: [PATCH 10/40] build fix --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 4 +--- rs/ledger_suite/icrc1/index-ng/tests/tests.rs | 1 + rs/rosetta-api/icrc1/src/construction_api/services.rs | 3 +++ rs/rosetta-api/icrc1/tests/multitoken_system_tests.rs | 1 + rs/rosetta-api/icrc1/tests/system_tests.rs | 1 + 5 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 484b5f04fb59..d31d0b51e7d8 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -992,9 +992,7 @@ fn get_accounts(block: &Block) -> Vec { Operation::Mint { to, .. } => vec![to], Operation::Transfer { from, to, .. } => vec![from, to], Operation::Approve { from, .. } => vec![from], - Operation::FeeCollector { .. } => { - panic!("not implemented") - } + Operation::FeeCollector { .. } => vec![], } } diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index 2c197e22aa77..f603ced774da 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -889,6 +889,7 @@ fn test_get_account_transactions_pagination() { transfer: None, approve: None, timestamp: 0, + fee_collector: None, }, transaction, ); diff --git a/rs/rosetta-api/icrc1/src/construction_api/services.rs b/rs/rosetta-api/icrc1/src/construction_api/services.rs index 297742f6d2b0..969c60c88854 100644 --- a/rs/rosetta-api/icrc1/src/construction_api/services.rs +++ b/rs/rosetta-api/icrc1/src/construction_api/services.rs @@ -544,6 +544,9 @@ mod tests { ic_icrc1::Operation::Approve { .. } => CanisterMethodName::Icrc2Approve, ic_icrc1::Operation::Mint { .. } => CanisterMethodName::Icrc1Transfer, ic_icrc1::Operation::Burn { .. } => CanisterMethodName::Icrc1Transfer, + ic_icrc1::Operation::FeeCollector { .. } => { + panic!("not implemented") + } }; let args = match arg_with_caller.arg { LedgerEndpointArg::TransferArg(arg) => Encode!(&arg), diff --git a/rs/rosetta-api/icrc1/tests/multitoken_system_tests.rs b/rs/rosetta-api/icrc1/tests/multitoken_system_tests.rs index 36f6e2e3369b..22f2bf570f8c 100644 --- a/rs/rosetta-api/icrc1/tests/multitoken_system_tests.rs +++ b/rs/rosetta-api/icrc1/tests/multitoken_system_tests.rs @@ -1839,6 +1839,7 @@ fn test_construction_submit() { ic_icrc1::Operation::Approve { fee, .. } => fee, ic_icrc1::Operation::Mint { .. } => None, ic_icrc1::Operation::Burn { .. } => None, + ic_icrc1::Operation::FeeCollector { .. } => None, }; if matches!( diff --git a/rs/rosetta-api/icrc1/tests/system_tests.rs b/rs/rosetta-api/icrc1/tests/system_tests.rs index 65f43e55de03..51ea41998273 100644 --- a/rs/rosetta-api/icrc1/tests/system_tests.rs +++ b/rs/rosetta-api/icrc1/tests/system_tests.rs @@ -1321,6 +1321,7 @@ fn test_construction_submit() { ic_icrc1::Operation::Approve { fee, .. } => fee, ic_icrc1::Operation::Mint { .. } => None, ic_icrc1::Operation::Burn { .. } => None, + ic_icrc1::Operation::FeeCollector { .. } => None, }; // Rosetta does not support mint and burn operations From 03980119be9d0fd6e01df5f2013068a974bab0bb Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 30 Oct 2025 11:31:31 +0000 Subject: [PATCH 11/40] update did files --- rs/ledger_suite/icrc1/archive/archive.did | 7 +++++++ rs/ledger_suite/icrc1/index-ng/index-ng.did | 7 +++++++ rs/ledger_suite/icrc1/ledger/ledger.did | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/rs/ledger_suite/icrc1/archive/archive.did b/rs/ledger_suite/icrc1/archive/archive.did index 4eb4323600db..fffc257829cf 100644 --- a/rs/ledger_suite/icrc1/archive/archive.did +++ b/rs/ledger_suite/icrc1/archive/archive.did @@ -6,6 +6,7 @@ type Transaction = record { kind : text; mint : opt Mint; approve : opt Approve; + fee_collector : opt FeeCollector; timestamp : nat64; transfer : opt Transfer }; @@ -21,6 +22,12 @@ type Approve = record { spender : Account }; +type FeeCollector = record { + caller : opt Account; + fee_collector : opt Account; + created_at_time : opt nat64 +}; + type Burn = record { from : Account; memo : opt vec nat8; diff --git a/rs/ledger_suite/icrc1/index-ng/index-ng.did b/rs/ledger_suite/icrc1/index-ng/index-ng.did index 0f2351be1f92..91d629b99a15 100644 --- a/rs/ledger_suite/icrc1/index-ng/index-ng.did +++ b/rs/ledger_suite/icrc1/index-ng/index-ng.did @@ -56,10 +56,17 @@ type Transaction = record { kind : text; mint : opt Mint; approve : opt Approve; + fee_collector : opt FeeCollector; timestamp : nat64; transfer : opt Transfer }; +type FeeCollector = record { + caller : opt Account; + fee_collector : opt Account; + created_at_time : opt nat64 +}; + type Approve = record { fee : opt Tokens; from : Account; diff --git a/rs/ledger_suite/icrc1/ledger/ledger.did b/rs/ledger_suite/icrc1/ledger/ledger.did index b093ec9bfada..d9376f97bbb0 100644 --- a/rs/ledger_suite/icrc1/ledger/ledger.did +++ b/rs/ledger_suite/icrc1/ledger/ledger.did @@ -224,10 +224,17 @@ type Transaction = record { kind : text; mint : opt Mint; approve : opt Approve; + fee_collector : opt FeeCollector; timestamp : Timestamp; transfer : opt Transfer }; +type FeeCollector = record { + caller : opt Account; + fee_collector : opt Account; + created_at_time : opt nat64 +}; + type Burn = record { from : Account; memo : opt blob; From a5b886cdf4371e008150020585b850b10bdc45c6 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 31 Oct 2025 20:31:36 +0000 Subject: [PATCH 12/40] handle new fee collector in index --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 31 ++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index d31d0b51e7d8..a4dd93d9b7b7 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -140,6 +140,8 @@ struct State { /// index. Lower values will result in a more responsive UI, but higher costs due to increased /// cycle burn for the index, ledger and archive(s). retrieve_blocks_from_ledger_interval: Option, + + fee_collector_107: Option>, } impl State { @@ -161,6 +163,7 @@ impl Default for State { fee_collectors: Default::default(), last_fee: None, retrieve_blocks_from_ledger_interval: None, + fee_collector_107: None, } } } @@ -955,8 +958,11 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { debit(block_index, from, fee); } - Operation::FeeCollector { .. } => { - panic!("not implemented") + Operation::FeeCollector { + fee_collector, + caller: _, + } => { + mutate_state(|s| s.fee_collector_107 = Some(fee_collector)); } }, ); @@ -997,6 +1003,27 @@ fn get_accounts(block: &Block) -> Vec { } fn get_fee_collector(block_index: BlockIndex64, block: &Block) -> Option { + let fee_collector_107 = match block.transaction.operation { + Operation::FeeCollector { + fee_collector, + caller: _, + } => Some(fee_collector), + _ => None, + }; + match fee_collector_107 { + Some(fee_collector) => fee_collector, + None => match get_fee_collector_from_state() { + Some(fee_collector) => fee_collector, + None => get_legacy_fee_collector(block_index, block), + }, + } +} + +fn get_fee_collector_from_state() -> Option> { + with_state(|s| s.fee_collector_107) +} + +fn get_legacy_fee_collector(block_index: BlockIndex64, block: &Block) -> Option { if block.fee_collector.is_some() { block.fee_collector } else if let Some(fee_collector_block_index) = block.fee_collector_block_index { From 2fe87b4e0c1cf0729670dfb633228befa4c683ac Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 31 Oct 2025 20:34:36 +0000 Subject: [PATCH 13/40] add fee collector to approve --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index a4dd93d9b7b7..2c524b32fbe9 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -957,6 +957,12 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { change_balance(spender, |balance| balance); debit(block_index, from, fee); + + if let Some(fee_collector_107) = get_fee_collector_107() { + if let Some(fee_collector) = fee_collector_107 { + credit(block_index, fee_collector, fee); + } + } } Operation::FeeCollector { fee_collector, @@ -1012,14 +1018,14 @@ fn get_fee_collector(block_index: BlockIndex64, block: &Block) -> Option }; match fee_collector_107 { Some(fee_collector) => fee_collector, - None => match get_fee_collector_from_state() { + None => match get_fee_collector_107() { Some(fee_collector) => fee_collector, None => get_legacy_fee_collector(block_index, block), }, } } -fn get_fee_collector_from_state() -> Option> { +fn get_fee_collector_107() -> Option> { with_state(|s| s.fee_collector_107) } From 5d5e118de0f33b5c6198551859e689e7f3845045 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 31 Oct 2025 20:48:13 +0000 Subject: [PATCH 14/40] clippy --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 2c524b32fbe9..169e8c90f51c 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -958,10 +958,10 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { debit(block_index, from, fee); - if let Some(fee_collector_107) = get_fee_collector_107() { - if let Some(fee_collector) = fee_collector_107 { - credit(block_index, fee_collector, fee); - } + if let Some(fee_collector_107) = get_fee_collector_107() + && let Some(fee_collector) = fee_collector_107 + { + credit(block_index, fee_collector, fee); } } Operation::FeeCollector { From 8102274eaabc71baa3e409920b16ece6b9c8e289 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Tue, 11 Nov 2025 18:59:55 +0000 Subject: [PATCH 15/40] refactor get_fee_collector --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 59 +++++++++++++--------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 169e8c90f51c..5646997a4319 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -786,8 +786,11 @@ fn append_block(block_index: BlockIndex64, block: GenericBlock) -> Result<(), Sy } }); + // change the fee collector if block is a 107 block + process_fee_collector_block(&decoded_block); + // add the block to the fee_collector if one is set - index_fee_collector(block_index, &decoded_block); + index_fee_collector(block_index); // change the balance of the involved accounts process_balance_changes(block_index, &decoded_block); @@ -831,8 +834,20 @@ fn append_icrc3_blocks(new_blocks: Vec) -> Result<(), SyncError> { Ok(()) } -fn index_fee_collector(block_index: BlockIndex64, block: &Block) { - if let Some(fee_collector) = get_fee_collector(block_index, block) { +fn process_fee_collector_block(block: &Block) { + match block.transaction.operation { + Operation::FeeCollector { + fee_collector, + caller: _, + } => { + mutate_state(|s| s.fee_collector_107 = Some(fee_collector)); + } + _ => {} + } +} + +fn index_fee_collector(block_index: BlockIndex64) { + if let Some(fee_collector) = get_fee_collector() { mutate_state(|s| { s.fee_collectors .entry(fee_collector) @@ -878,7 +893,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { )) }); mutate_state(|s| s.last_fee = Some(fee)); - if let Some(fee_collector) = get_fee_collector(block_index, block) { + if let Some(fee_collector) = get_fee_collector() { credit(block_index, fee_collector, fee); } } @@ -894,7 +909,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { )) }); mutate_state(|s| s.last_fee = Some(fee)); - if let Some(fee_collector) = get_fee_collector(block_index, block) { + if let Some(fee_collector) = get_fee_collector() { credit(block_index, fee_collector, fee); } } @@ -923,7 +938,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { }), ); credit(block_index, to, amount); - if let Some(fee_collector) = get_fee_collector(block_index, block) { + if let Some(fee_collector) = get_fee_collector() { credit(block_index, fee_collector, fee); } } @@ -965,10 +980,10 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { } } Operation::FeeCollector { - fee_collector, + fee_collector: _, caller: _, } => { - mutate_state(|s| s.fee_collector_107 = Some(fee_collector)); + // Does not affect the balance } }, ); @@ -1008,20 +1023,10 @@ fn get_accounts(block: &Block) -> Vec { } } -fn get_fee_collector(block_index: BlockIndex64, block: &Block) -> Option { - let fee_collector_107 = match block.transaction.operation { - Operation::FeeCollector { - fee_collector, - caller: _, - } => Some(fee_collector), - _ => None, - }; - match fee_collector_107 { +fn get_fee_collector() -> Option { + match get_fee_collector_107() { Some(fee_collector) => fee_collector, - None => match get_fee_collector_107() { - Some(fee_collector) => fee_collector, - None => get_legacy_fee_collector(block_index, block), - }, + None => get_legacy_fee_collector(), } } @@ -1029,16 +1034,22 @@ fn get_fee_collector_107() -> Option> { with_state(|s| s.fee_collector_107) } -fn get_legacy_fee_collector(block_index: BlockIndex64, block: &Block) -> Option { +fn get_legacy_fee_collector() -> Option { + let chain_length = with_blocks(|blocks| blocks.len()); + let last_block_index = chain_length - 1; + let block = match get_decoded_block(last_block_index) { + Some(block) => block, + None => return None, + }; if block.fee_collector.is_some() { block.fee_collector } else if let Some(fee_collector_block_index) = block.fee_collector_block_index { let block = get_decoded_block(fee_collector_block_index) .unwrap_or_else(|| - ic_cdk::trap(format!("Block at index {block_index} has fee_collector_block_index {fee_collector_block_index} but there is no block at that index"))); + ic_cdk::trap(format!("Block at index {last_block_index} has fee_collector_block_index {fee_collector_block_index} but there is no block at that index"))); if block.fee_collector.is_none() { ic_cdk::trap(format!( - "Block at index {block_index} has fee_collector_block_index {fee_collector_block_index} but that block has no fee_collector set" + "Block at index {last_block_index} has fee_collector_block_index {fee_collector_block_index} but that block has no fee_collector set" )) } else { block.fee_collector From cbe515638b375add990365759ff7014a9f27950f Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Tue, 11 Nov 2025 22:15:13 +0000 Subject: [PATCH 16/40] clippy --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 5646997a4319..ca5083c900e7 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -835,14 +835,12 @@ fn append_icrc3_blocks(new_blocks: Vec) -> Result<(), SyncError> { } fn process_fee_collector_block(block: &Block) { - match block.transaction.operation { - Operation::FeeCollector { - fee_collector, - caller: _, - } => { - mutate_state(|s| s.fee_collector_107 = Some(fee_collector)); - } - _ => {} + if let Operation::FeeCollector { + fee_collector, + caller: _, + } = block.transaction.operation + { + mutate_state(|s| s.fee_collector_107 = Some(fee_collector)); } } @@ -1036,11 +1034,12 @@ fn get_fee_collector_107() -> Option> { fn get_legacy_fee_collector() -> Option { let chain_length = with_blocks(|blocks| blocks.len()); + if chain_length == 0 { + return None; + } let last_block_index = chain_length - 1; - let block = match get_decoded_block(last_block_index) { - Some(block) => block, - None => return None, - }; + let block = get_decoded_block(last_block_index) + .expect("chain_length is positive, should have at least one block"); if block.fee_collector.is_some() { block.fee_collector } else if let Some(fee_collector_block_index) = block.fee_collector_block_index { From b5530ec47b6665c84ce650699e04a3566c2e6a9f Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Wed, 12 Nov 2025 00:01:08 +0000 Subject: [PATCH 17/40] change op name to 107set_fee_collector --- packages/icrc-ledger-types/src/icrc3/transactions.rs | 2 +- rs/ledger_suite/icrc1/src/endpoints.rs | 2 +- rs/ledger_suite/icrc1/src/lib.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/icrc-ledger-types/src/icrc3/transactions.rs b/packages/icrc-ledger-types/src/icrc3/transactions.rs index f01f0a718a7a..cf3c41bd50fc 100644 --- a/packages/icrc-ledger-types/src/icrc3/transactions.rs +++ b/packages/icrc-ledger-types/src/icrc3/transactions.rs @@ -128,7 +128,7 @@ impl Transaction { pub fn fee_collector(fee_collector: FeeCollector, timestamp: u64) -> Self { Self { - kind: "107feecol".into(), + kind: "107set_fee_collector".into(), timestamp, mint: None, burn: None, diff --git a/rs/ledger_suite/icrc1/src/endpoints.rs b/rs/ledger_suite/icrc1/src/endpoints.rs index 581898d3fdad..d1169c7fa65b 100644 --- a/rs/ledger_suite/icrc1/src/endpoints.rs +++ b/rs/ledger_suite/icrc1/src/endpoints.rs @@ -238,7 +238,7 @@ impl From> for Transaction { fee_collector, caller, } => { - tx.kind = "107feecol".to_string(); + tx.kind = "107set_fee_collector".to_string(); tx.fee_collector = Some(FeeCollector { fee_collector, caller, diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index 4634cf3289e8..9c8549704d40 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -80,7 +80,7 @@ pub enum Operation { #[serde(skip_serializing_if = "Option::is_none")] fee: Option, }, - #[serde(rename = "107feecol")] + #[serde(rename = "107set_fee_collector")] FeeCollector { #[serde( default, @@ -197,7 +197,7 @@ impl TryFrom> for Transaction Operation::FeeCollector { + "107set_fee_collector" => Operation::FeeCollector { fee_collector: value.fee_collector, caller: value.caller, }, @@ -223,7 +223,7 @@ impl From> for FlattenedTransaction "mint", Transfer { .. } => "xfer", Approve { .. } => "approve", - FeeCollector { .. } => "107feecol", + FeeCollector { .. } => "107set_fee_collector", } .into(), from: match &t.operation { From fd4dab5f9bb497bd3cd120b9dd425aa697887814 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Wed, 12 Nov 2025 20:26:30 +0000 Subject: [PATCH 18/40] add fee collector block generation to the test block builder --- .../src/icrc/generic_value.rs | 15 +++++ rs/ledger_suite/icrc1/test_utils/src/icrc3.rs | 62 ++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/icrc-ledger-types/src/icrc/generic_value.rs b/packages/icrc-ledger-types/src/icrc/generic_value.rs index c578796875dd..752d81230039 100644 --- a/packages/icrc-ledger-types/src/icrc/generic_value.rs +++ b/packages/icrc-ledger-types/src/icrc/generic_value.rs @@ -334,6 +334,21 @@ impl From for Value { } } +impl TryFrom for Principal { + type Error = String; + + fn try_from(value: Value) -> Result { + Principal::try_from_slice(value.as_blob()?.as_slice()) + .map_err(|err| format!("Unable to decode the principal, error {err}")) + } +} + +impl From for Value { + fn from(principal: Principal) -> Self { + Self::blob(principal.as_slice()) + } +} + impl std::fmt::Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs index fe1e31a92d38..794ea106eb16 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs @@ -1,6 +1,6 @@ -use candid::Nat; +use candid::{Nat, Principal}; use ic_ledger_core::tokens::TokensType; -use icrc_ledger_types::icrc::generic_value::ICRC3Value; +use icrc_ledger_types::icrc::generic_value::{ICRC3Value, Value}; use icrc_ledger_types::icrc1::account::Account; use serde_bytes::ByteBuf; use std::collections::BTreeMap; @@ -21,6 +21,7 @@ pub struct BlockBuilder { fee_collector_block: Option, fee: Option, parent_hash: Option>, + btype: Option, } impl BlockBuilder { @@ -33,6 +34,7 @@ impl BlockBuilder { fee_collector_block: None, fee: None, parent_hash: None, + btype: None, } } @@ -60,6 +62,12 @@ impl BlockBuilder { self } + /// Set the block type + pub fn with_btype(mut self, btype: String) -> Self { + self.btype = Some(btype); + self + } + /// Create a transfer operation pub fn transfer(self, from: Account, to: Account, amount: Tokens) -> TransferBuilder { TransferBuilder { @@ -107,6 +115,21 @@ impl BlockBuilder { } } + /// Create an approve operation + pub fn fee_collector( + self, + fee_collector: Option, + caller: Option, + ts: Option, + ) -> FeeCollectorBuilder { + FeeCollectorBuilder { + builder: self, + fee_collector, + caller, + ts, + } + } + /// Build the final ICRC3Value block fn build_with_operation( self, @@ -156,6 +179,11 @@ impl BlockBuilder { ); } + // Add fee collector block if specified + if let Some(btype) = self.btype { + block_map.insert("btype".to_string(), ICRC3Value::Text(btype)); + } + ICRC3Value::Map(block_map) } } @@ -289,6 +317,36 @@ impl ApproveBuilder { } } +/// Builder for fee collector operations +pub struct FeeCollectorBuilder { + builder: BlockBuilder, + fee_collector: Option, + caller: Option, + ts: Option, +} + +impl FeeCollectorBuilder { + /// Build the mint block + pub fn build(self) -> ICRC3Value { + let mut tx_fields = BTreeMap::new(); + match &self.fee_collector { + Some(fee_collector) => tx_fields.insert( + "fee_collector".to_string(), + account_to_icrc3_value(fee_collector), + ), + None => tx_fields.insert("fee_collector".to_string(), ICRC3Value::Array(vec![])), + }; + if let Some(caller) = &self.caller { + tx_fields.insert("caller".to_string(), ICRC3Value::from(Value::from(*caller))); + } + if let Some(ts) = self.ts { + tx_fields.insert("ts".to_string(), ICRC3Value::Nat(Nat::from(ts))); + } + self.builder + .build_with_operation("107set_fee_collector", tx_fields) + } +} + #[cfg(test)] mod builder_tests { use super::*; From 08aba671ada7efbdf5f64eab434889034e7e2006 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 00:03:59 +0000 Subject: [PATCH 19/40] fix block conversion, caller doesn't work still --- rs/ledger_suite/icrc1/BUILD.bazel | 1 + rs/ledger_suite/icrc1/src/lib.rs | 29 ++++++++++++++----- rs/ledger_suite/icrc1/test_utils/src/lib.rs | 3 ++ rs/ledger_suite/icrc1/tests/tests.rs | 31 +++++++++++++++++++++ rs/ledger_suite/tests/sm-tests/src/lib.rs | 1 + 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/rs/ledger_suite/icrc1/BUILD.bazel b/rs/ledger_suite/icrc1/BUILD.bazel index 4e3a84b71017..70d5926e1c87 100644 --- a/rs/ledger_suite/icrc1/BUILD.bazel +++ b/rs/ledger_suite/icrc1/BUILD.bazel @@ -75,6 +75,7 @@ rust_test( "//packages/ic-ledger-hash-of:ic_ledger_hash_of", "//rs/ledger_suite/icrc1/tokens_u256", "//rs/ledger_suite/icrc1/tokens_u64", + "//rs/types/base_types", ] + DEV_DEPENDENCIES, ) diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index 9c8549704d40..cd261be8040c 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -132,8 +132,10 @@ struct FlattenedTransaction { #[serde(with = "compact_account::opt")] spender: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "amt")] - amount: Tokens, + amount: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -167,13 +169,17 @@ impl TryFrom> for Transaction Operation::Mint { to: value.to.ok_or("`to` field required for `mint` operation")?, - amount: value.amount.clone(), + amount: value + .amount + .ok_or("`amount` required for `mint` operations")?, fee: value.fee, }, "xfer" => Operation::Transfer { @@ -182,7 +188,9 @@ impl TryFrom> for Transaction Operation::Approve { @@ -192,7 +200,9 @@ impl TryFrom> for Transaction From> for FlattenedTransaction amount.clone(), - FeeCollector { .. } => Tokens::zero(), + | Approve { amount, .. } => Some(amount.clone()), + FeeCollector { .. } => None, }, fee: match &t.operation { Transfer { fee, .. } @@ -535,6 +545,10 @@ pub struct Block { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "fee_col_block")] pub fee_collector_block_index: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "btype")] + pub btype: Option, } type TaggedBlock = Required, 55799>; @@ -608,6 +622,7 @@ impl BlockType for Block { timestamp: timestamp.as_nanos_since_unix_epoch(), fee_collector, fee_collector_block_index, + btype: None, } } } diff --git a/rs/ledger_suite/icrc1/test_utils/src/lib.rs b/rs/ledger_suite/icrc1/test_utils/src/lib.rs index 4643d55ff108..54a0ee2a741b 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/lib.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/lib.rs @@ -235,6 +235,7 @@ pub fn blocks_strategy( timestamp, fee_collector, fee_collector_block_index: None, + btype: None, } .encode(), )), @@ -243,6 +244,7 @@ pub fn blocks_strategy( timestamp, fee_collector, fee_collector_block_index: None, + btype: None, } }) } @@ -1499,6 +1501,7 @@ where timestamp: ts, fee_collector: fee_col, fee_collector_block_index: fee_col_block, + btype: None, }, ) } diff --git a/rs/ledger_suite/icrc1/tests/tests.rs b/rs/ledger_suite/icrc1/tests/tests.rs index ec4c8e6572ad..252326f6c508 100644 --- a/rs/ledger_suite/icrc1/tests/tests.rs +++ b/rs/ledger_suite/icrc1/tests/tests.rs @@ -1,8 +1,10 @@ +use ic_base_types::PrincipalId; use ic_icrc1::blocks::{ encoded_block_to_generic_block, generic_block_to_encoded_block, generic_transaction_from_generic_block, }; use ic_icrc1::{Block, Transaction, hash}; +use ic_icrc1_test_utils::icrc3::BlockBuilder; use ic_icrc1_test_utils::{arb_amount, arb_block, arb_small_amount, blocks_strategy}; use ic_icrc1_tokens_u64::U64; use ic_icrc1_tokens_u256::U256; @@ -11,6 +13,7 @@ use ic_ledger_core::Tokens; use ic_ledger_core::block::BlockType; use ic_ledger_core::tokens::TokensType; use ic_ledger_hash_of::HashOf; +use icrc_ledger_types::icrc1::account::Account; use proptest::prelude::*; fn arb_u256() -> impl Strategy { @@ -170,3 +173,31 @@ fn test_encoding_decoding_block_u256( fn arb_token_u256() -> impl Strategy { (any::(), any::()).prop_map(|(hi, lo)| U256::from_words(hi, lo)) } + +#[test] +fn test_fee_collector_block() { + const TEST_USER: PrincipalId = PrincipalId::new_user_test_id(1); + const TEST_ACCOUNT: Account = Account { + owner: TEST_USER.0, + subaccount: Some([1u8; 32]), + }; + + let original_block = BlockBuilder::::new(1, 111) + .with_btype("107feecol".to_string()) + .with_parent_hash(vec![1u8; 32]) + .fee_collector(Some(TEST_ACCOUNT), None, Some(123)) + .build(); + + let block = generic_block_to_encoded_block(original_block.clone().into()) + .expect("failed to decode generic block"); + + let decoded_block = + Block::::decode(block.clone()).expect("failed to decode encoded block"); + + let decoded_value = encoded_block_to_generic_block(&decoded_block.clone().encode()); + + println!("original: {}", original_block); + println!("decoded: {}", decoded_value); + + assert_eq!(original_block.clone().hash(), decoded_value.hash()); +} diff --git a/rs/ledger_suite/tests/sm-tests/src/lib.rs b/rs/ledger_suite/tests/sm-tests/src/lib.rs index 8a9a33b9fb26..3a747f93a0a7 100644 --- a/rs/ledger_suite/tests/sm-tests/src/lib.rs +++ b/rs/ledger_suite/tests/sm-tests/src/lib.rs @@ -343,6 +343,7 @@ fn arb_block() -> impl Strategy> { timestamp: ts, fee_collector: fee_col, fee_collector_block_index: fee_col_block, + btype: None, }, ) } From eba90cd8329ef6aeaa84f315939f488bd55c058d Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 00:09:25 +0000 Subject: [PATCH 20/40] change caller to principal --- packages/icrc-ledger-types/src/icrc3/transactions.rs | 4 ++-- rs/ledger_suite/icrc1/src/lib.rs | 12 ++++-------- rs/ledger_suite/icrc1/tests/tests.rs | 2 +- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/icrc-ledger-types/src/icrc3/transactions.rs b/packages/icrc-ledger-types/src/icrc3/transactions.rs index cf3c41bd50fc..92a199b452bd 100644 --- a/packages/icrc-ledger-types/src/icrc3/transactions.rs +++ b/packages/icrc-ledger-types/src/icrc3/transactions.rs @@ -1,4 +1,4 @@ -use candid::{CandidType, Deserialize, Nat}; +use candid::{CandidType, Deserialize, Nat, Principal}; use serde::Serialize; use crate::{ @@ -61,7 +61,7 @@ pub struct Approve { #[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct FeeCollector { pub fee_collector: Option, - pub caller: Option, + pub caller: Option, pub created_at_time: Option, } diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index cd261be8040c..c6a60c84b5a8 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -4,6 +4,7 @@ pub mod endpoints; pub mod hash; pub(crate) mod known_tags; +use candid::Principal; use ciborium::tag::Required; use ic_ledger_canister_core::ledger::{LedgerContext, LedgerTransaction, TxApplyError}; use ic_ledger_core::{ @@ -88,12 +89,8 @@ pub enum Operation { with = "compact_account::opt" )] fee_collector: Option, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "compact_account::opt" - )] - caller: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + caller: Option, }, } @@ -156,8 +153,7 @@ struct FlattenedTransaction { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - #[serde(with = "compact_account::opt")] - caller: Option, + caller: Option, } impl TryFrom> for Transaction { diff --git a/rs/ledger_suite/icrc1/tests/tests.rs b/rs/ledger_suite/icrc1/tests/tests.rs index 252326f6c508..424f28dda4e1 100644 --- a/rs/ledger_suite/icrc1/tests/tests.rs +++ b/rs/ledger_suite/icrc1/tests/tests.rs @@ -185,7 +185,7 @@ fn test_fee_collector_block() { let original_block = BlockBuilder::::new(1, 111) .with_btype("107feecol".to_string()) .with_parent_hash(vec![1u8; 32]) - .fee_collector(Some(TEST_ACCOUNT), None, Some(123)) + .fee_collector(Some(TEST_ACCOUNT), Some(TEST_USER.0), Some(123)) .build(); let block = generic_block_to_encoded_block(original_block.clone().into()) From 8125a18a565585b89eccffe549c9d81f1910d277 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 00:43:53 +0000 Subject: [PATCH 21/40] build fix --- rs/ledger_suite/icrc1/archive/tests/tests.rs | 2 ++ rs/ledger_suite/icrc1/ledger/tests/tests.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/rs/ledger_suite/icrc1/archive/tests/tests.rs b/rs/ledger_suite/icrc1/archive/tests/tests.rs index df26c0d640fd..e928a2766f45 100644 --- a/rs/ledger_suite/icrc1/archive/tests/tests.rs +++ b/rs/ledger_suite/icrc1/archive/tests/tests.rs @@ -111,6 +111,7 @@ fn test_icrc3_get_blocks() { created_at_time: None, memo: None, }, + btype: None, } }; @@ -265,6 +266,7 @@ fn test_icrc3_get_blocks_number_of_blocks_limit() { created_at_time: None, memo: None, }, + btype: None, } .encode() } diff --git a/rs/ledger_suite/icrc1/ledger/tests/tests.rs b/rs/ledger_suite/icrc1/ledger/tests/tests.rs index ddf47da6e0a2..d23890c9602f 100644 --- a/rs/ledger_suite/icrc1/ledger/tests/tests.rs +++ b/rs/ledger_suite/icrc1/ledger/tests/tests.rs @@ -1216,6 +1216,7 @@ fn test_icrc3_get_archives() { timestamp: 0, fee_collector: None, fee_collector_block_index: None, + btype: None, } .encode() .size_bytes(); From a782c66f043f1b2747807cfc1be0bd439b43bc8b Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 04:23:43 +0000 Subject: [PATCH 22/40] update did files --- rs/ledger_suite/icrc1/archive/archive.did | 2 +- rs/ledger_suite/icrc1/index-ng/index-ng.did | 2 +- rs/ledger_suite/icrc1/ledger/ledger.did | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rs/ledger_suite/icrc1/archive/archive.did b/rs/ledger_suite/icrc1/archive/archive.did index fffc257829cf..5ceb4a125a84 100644 --- a/rs/ledger_suite/icrc1/archive/archive.did +++ b/rs/ledger_suite/icrc1/archive/archive.did @@ -23,7 +23,7 @@ type Approve = record { }; type FeeCollector = record { - caller : opt Account; + caller : opt principal; fee_collector : opt Account; created_at_time : opt nat64 }; diff --git a/rs/ledger_suite/icrc1/index-ng/index-ng.did b/rs/ledger_suite/icrc1/index-ng/index-ng.did index 91d629b99a15..f025893421c1 100644 --- a/rs/ledger_suite/icrc1/index-ng/index-ng.did +++ b/rs/ledger_suite/icrc1/index-ng/index-ng.did @@ -62,7 +62,7 @@ type Transaction = record { }; type FeeCollector = record { - caller : opt Account; + caller : opt principal; fee_collector : opt Account; created_at_time : opt nat64 }; diff --git a/rs/ledger_suite/icrc1/ledger/ledger.did b/rs/ledger_suite/icrc1/ledger/ledger.did index d9376f97bbb0..6a3e865a8628 100644 --- a/rs/ledger_suite/icrc1/ledger/ledger.did +++ b/rs/ledger_suite/icrc1/ledger/ledger.did @@ -230,7 +230,7 @@ type Transaction = record { }; type FeeCollector = record { - caller : opt Account; + caller : opt principal; fee_collector : opt Account; created_at_time : opt nat64 }; From a201faf1ad05f0fc51ec702e625d4af7815e80c5 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 17:54:20 +0000 Subject: [PATCH 23/40] create a proptest --- rs/ledger_suite/icrc1/test_utils/src/icrc3.rs | 9 ++- rs/ledger_suite/icrc1/tests/tests.rs | 60 +++++++++++-------- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs index 794ea106eb16..37bf1e28e9f2 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs @@ -329,13 +329,12 @@ impl FeeCollectorBuilder { /// Build the mint block pub fn build(self) -> ICRC3Value { let mut tx_fields = BTreeMap::new(); - match &self.fee_collector { - Some(fee_collector) => tx_fields.insert( + if let Some(fee_collector) = &self.fee_collector { + tx_fields.insert( "fee_collector".to_string(), account_to_icrc3_value(fee_collector), - ), - None => tx_fields.insert("fee_collector".to_string(), ICRC3Value::Array(vec![])), - }; + ); + } if let Some(caller) = &self.caller { tx_fields.insert("caller".to_string(), ICRC3Value::from(Value::from(*caller))); } diff --git a/rs/ledger_suite/icrc1/tests/tests.rs b/rs/ledger_suite/icrc1/tests/tests.rs index 424f28dda4e1..3cb3faa5ad42 100644 --- a/rs/ledger_suite/icrc1/tests/tests.rs +++ b/rs/ledger_suite/icrc1/tests/tests.rs @@ -1,11 +1,11 @@ -use ic_base_types::PrincipalId; +use candid::Principal; use ic_icrc1::blocks::{ encoded_block_to_generic_block, generic_block_to_encoded_block, generic_transaction_from_generic_block, }; use ic_icrc1::{Block, Transaction, hash}; use ic_icrc1_test_utils::icrc3::BlockBuilder; -use ic_icrc1_test_utils::{arb_amount, arb_block, arb_small_amount, blocks_strategy}; +use ic_icrc1_test_utils::{arb_account, arb_amount, arb_block, arb_small_amount, blocks_strategy}; use ic_icrc1_tokens_u64::U64; use ic_icrc1_tokens_u256::U256; use ic_ledger_canister_core::ledger::LedgerTransaction; @@ -13,7 +13,7 @@ use ic_ledger_core::Tokens; use ic_ledger_core::block::BlockType; use ic_ledger_core::tokens::TokensType; use ic_ledger_hash_of::HashOf; -use icrc_ledger_types::icrc1::account::Account; +use icrc_ledger_types::icrc::generic_value::ICRC3Value; use proptest::prelude::*; fn arb_u256() -> impl Strategy { @@ -174,30 +174,40 @@ fn arb_token_u256() -> impl Strategy { (any::(), any::()).prop_map(|(hi, lo)| U256::from_words(hi, lo)) } -#[test] -fn test_fee_collector_block() { - const TEST_USER: PrincipalId = PrincipalId::new_user_test_id(1); - const TEST_ACCOUNT: Account = Account { - owner: TEST_USER.0, - subaccount: Some([1u8; 32]), - }; - - let original_block = BlockBuilder::::new(1, 111) - .with_btype("107feecol".to_string()) - .with_parent_hash(vec![1u8; 32]) - .fee_collector(Some(TEST_ACCOUNT), Some(TEST_USER.0), Some(123)) - .build(); +pub fn arb_fee_collector_block() -> impl Strategy { + ( + any::(), + any::(), + any::>(), + proptest::option::of(arb_account()), + proptest::option::of(proptest::collection::vec(any::(), 28)), + any::>(), + ) + .prop_map( + |(block_id, block_ts, parent_hash, fee_collector, caller, tx_ts)| { + let caller = caller.map(|mut c| { + c.push(0x00); + Principal::try_from_slice(&c[..]).unwrap() + }); + let builder = BlockBuilder::::new(block_id, block_ts) + .with_btype("107feecol".to_string()); + let builder = match parent_hash { + Some(parent_hash) => builder.with_parent_hash(parent_hash.to_vec()), + None => builder, + }; + builder.fee_collector(fee_collector, caller, tx_ts).build() + }, + ) +} - let block = generic_block_to_encoded_block(original_block.clone().into()) +#[test_strategy::proptest] +fn test_encoding_decoding_fee_collector_block( + #[strategy(arb_fee_collector_block())] original_block: ICRC3Value, +) { + let encoded_block = generic_block_to_encoded_block(original_block.clone().into()) .expect("failed to decode generic block"); - let decoded_block = - Block::::decode(block.clone()).expect("failed to decode encoded block"); - + Block::::decode(encoded_block.clone()).expect("failed to decode encoded block"); let decoded_value = encoded_block_to_generic_block(&decoded_block.clone().encode()); - - println!("original: {}", original_block); - println!("decoded: {}", decoded_value); - - assert_eq!(original_block.clone().hash(), decoded_value.hash()); + prop_assert_eq!(original_block.clone().hash(), decoded_value.hash()); } From 5f2c5dfb55805778f76639f3f8f38d16f05c4046 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 18:05:06 +0000 Subject: [PATCH 24/40] test u64 and u256 blocks --- rs/ledger_suite/icrc1/tests/tests.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/rs/ledger_suite/icrc1/tests/tests.rs b/rs/ledger_suite/icrc1/tests/tests.rs index 3cb3faa5ad42..cfce334ea75f 100644 --- a/rs/ledger_suite/icrc1/tests/tests.rs +++ b/rs/ledger_suite/icrc1/tests/tests.rs @@ -174,7 +174,10 @@ fn arb_token_u256() -> impl Strategy { (any::(), any::()).prop_map(|(hi, lo)| U256::from_words(hi, lo)) } -pub fn arb_fee_collector_block() -> impl Strategy { +pub fn arb_fee_collector_block() -> impl Strategy +where + Tokens: TokensType, +{ ( any::(), any::(), @@ -189,7 +192,7 @@ pub fn arb_fee_collector_block() -> impl Strategy { c.push(0x00); Principal::try_from_slice(&c[..]).unwrap() }); - let builder = BlockBuilder::::new(block_id, block_ts) + let builder = BlockBuilder::::new(block_id, block_ts) .with_btype("107feecol".to_string()); let builder = match parent_hash { Some(parent_hash) => builder.with_parent_hash(parent_hash.to_vec()), @@ -201,8 +204,8 @@ pub fn arb_fee_collector_block() -> impl Strategy { } #[test_strategy::proptest] -fn test_encoding_decoding_fee_collector_block( - #[strategy(arb_fee_collector_block())] original_block: ICRC3Value, +fn test_encoding_decoding_fee_collector_block_u64( + #[strategy(arb_fee_collector_block::())] original_block: ICRC3Value, ) { let encoded_block = generic_block_to_encoded_block(original_block.clone().into()) .expect("failed to decode generic block"); @@ -211,3 +214,15 @@ fn test_encoding_decoding_fee_collector_block( let decoded_value = encoded_block_to_generic_block(&decoded_block.clone().encode()); prop_assert_eq!(original_block.clone().hash(), decoded_value.hash()); } + +#[test_strategy::proptest] +fn test_encoding_decoding_fee_collector_block_u256( + #[strategy(arb_fee_collector_block::())] original_block: ICRC3Value, +) { + let encoded_block = generic_block_to_encoded_block(original_block.clone().into()) + .expect("failed to decode generic block"); + let decoded_block = + Block::::decode(encoded_block.clone()).expect("failed to decode encoded block"); + let decoded_value = encoded_block_to_generic_block(&decoded_block.clone().encode()); + prop_assert_eq!(original_block.clone().hash(), decoded_value.hash()); +} From f28d46b34beb056b4ce887f90e774425e96ae19c Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 18:22:57 +0000 Subject: [PATCH 25/40] change created_at_time to ts --- packages/icrc-ledger-types/src/icrc3/transactions.rs | 2 +- rs/ledger_suite/icrc1/archive/archive.did | 2 +- rs/ledger_suite/icrc1/index-ng/index-ng.did | 2 +- rs/ledger_suite/icrc1/ledger/ledger.did | 2 +- rs/ledger_suite/icrc1/src/endpoints.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/icrc-ledger-types/src/icrc3/transactions.rs b/packages/icrc-ledger-types/src/icrc3/transactions.rs index 92a199b452bd..f1da04573c5b 100644 --- a/packages/icrc-ledger-types/src/icrc3/transactions.rs +++ b/packages/icrc-ledger-types/src/icrc3/transactions.rs @@ -62,7 +62,7 @@ pub struct Approve { pub struct FeeCollector { pub fee_collector: Option, pub caller: Option, - pub created_at_time: Option, + pub ts: Option, } // Representation of a Transaction which supports the Icrc1 Standard functionalities diff --git a/rs/ledger_suite/icrc1/archive/archive.did b/rs/ledger_suite/icrc1/archive/archive.did index 5ceb4a125a84..cf9bc873895a 100644 --- a/rs/ledger_suite/icrc1/archive/archive.did +++ b/rs/ledger_suite/icrc1/archive/archive.did @@ -25,7 +25,7 @@ type Approve = record { type FeeCollector = record { caller : opt principal; fee_collector : opt Account; - created_at_time : opt nat64 + ts : opt nat64 }; type Burn = record { diff --git a/rs/ledger_suite/icrc1/index-ng/index-ng.did b/rs/ledger_suite/icrc1/index-ng/index-ng.did index f025893421c1..5bba0a9060e4 100644 --- a/rs/ledger_suite/icrc1/index-ng/index-ng.did +++ b/rs/ledger_suite/icrc1/index-ng/index-ng.did @@ -64,7 +64,7 @@ type Transaction = record { type FeeCollector = record { caller : opt principal; fee_collector : opt Account; - created_at_time : opt nat64 + ts : opt nat64 }; type Approve = record { diff --git a/rs/ledger_suite/icrc1/ledger/ledger.did b/rs/ledger_suite/icrc1/ledger/ledger.did index 6a3e865a8628..79bd1c745855 100644 --- a/rs/ledger_suite/icrc1/ledger/ledger.did +++ b/rs/ledger_suite/icrc1/ledger/ledger.did @@ -232,7 +232,7 @@ type Transaction = record { type FeeCollector = record { caller : opt principal; fee_collector : opt Account; - created_at_time : opt nat64 + ts : opt nat64 }; type Burn = record { diff --git a/rs/ledger_suite/icrc1/src/endpoints.rs b/rs/ledger_suite/icrc1/src/endpoints.rs index d1169c7fa65b..2f3864c81087 100644 --- a/rs/ledger_suite/icrc1/src/endpoints.rs +++ b/rs/ledger_suite/icrc1/src/endpoints.rs @@ -242,7 +242,7 @@ impl From> for Transaction { tx.fee_collector = Some(FeeCollector { fee_collector, caller, - created_at_time, + ts: created_at_time, }); } } From eeca831f77946e1a0c507291b39604e657eefaec Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 20:58:49 +0000 Subject: [PATCH 26/40] test index handling of the 107 fee collector --- rs/ledger_suite/icrc1/index-ng/tests/tests.rs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index f603ced774da..efcd4314d94a 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -1259,6 +1259,96 @@ fn test_fee_collector() { ); } +#[test] +fn test_fee_collector_107() { + 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)); + let fee_collector_1 = account(101, 0); + let fee_collector_2 = account(102, 0); + let regular_account = account(1, 0); + + let mut block_id = 0; + + let add_mint_block = |block_id: u64, fc: Option, fc_id: Option| { + let mint = BlockBuilder::new(block_id, block_id).with_fee(Tokens::from(1)); + let mint = match fc { + Some(fc) => mint.with_fee_collector(fc), + None => mint, + }; + let mint = match fc_id { + Some(fc_id) => mint.with_fee_collector_block(fc_id), + None => mint, + }; + let mint = mint.mint(regular_account, Tokens::from(1)).build(); + + assert_eq!( + Nat::from(block_id), + add_block(&env, ledger_id, &mint) + .expect("error adding mint block to ICRC-3 test ledger") + ); + wait_until_sync_is_completed(env, index_id, ledger_id); + block_id + 1 + }; + + let add_fee_collector_107_block = |block_id: u64, fc: Option| { + let fee_collector = BlockBuilder::::new(block_id, block_id) + .with_btype("107feecol".to_string()) + .fee_collector(fc, None, None) + .build(); + + assert_eq!( + Nat::from(block_id), + add_block(&env, ledger_id, &fee_collector) + .expect("error adding mint block to ICRC-3 test ledger") + ); + wait_until_sync_is_completed(env, index_id, ledger_id); + block_id + 1 + }; + + // Legacy fee collector collects the fees + block_id = add_mint_block(block_id, Some(fee_collector_1), None); + assert_eq!(1, icrc1_balance_of(env, index_id, fee_collector_1)); + block_id = add_mint_block(block_id, None, Some(0)); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + + // Set 107 fee collector to burn + block_id = add_fee_collector_107_block(block_id, None); + + // No fees collected + block_id = add_mint_block(block_id, None, None); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(0, icrc1_balance_of(env, index_id, fee_collector_2)); + + // No fees collected with the legacy fee collector + block_id = add_mint_block(block_id, Some(fee_collector_1), None); + block_id = add_mint_block(block_id, None, Some(block_id - 1)); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(0, icrc1_balance_of(env, index_id, fee_collector_2)); + + // Set 107 fee collector to fee_collector_2 + block_id = add_fee_collector_107_block(block_id, Some(fee_collector_2)); + + // New fee collector receives the fees + block_id = add_mint_block(block_id, None, None); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(1, icrc1_balance_of(env, index_id, fee_collector_2)); + + // Legacy fee collector has no effect, new fee collector receives the fees + block_id = add_mint_block(block_id, Some(fee_collector_1), None); + block_id = add_mint_block(block_id, None, Some(block_id - 1)); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(3, icrc1_balance_of(env, index_id, fee_collector_2)); + + // Set 107 fee collector to burn + block_id = add_fee_collector_107_block(block_id, None); + + // No fees collected + add_mint_block(block_id, None, None); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(3, icrc1_balance_of(env, index_id, fee_collector_2)); +} + #[test] fn test_index_ledger_coherence() { let mut runner = TestRunner::new(TestRunnerConfig::with_cases(1)); From c9a3a327313681446b44544a2fd31dbb297167d1 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 21:07:23 +0000 Subject: [PATCH 27/40] change panic message --- rs/ledger_suite/icrc1/src/lib.rs | 4 ++-- rs/ledger_suite/icrc1/test_utils/src/lib.rs | 4 ++-- rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs | 2 +- rs/rosetta-api/icrc1/src/common/storage/types.rs | 2 +- rs/rosetta-api/icrc1/src/construction_api/services.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rs/ledger_suite/icrc1/src/lib.rs b/rs/ledger_suite/icrc1/src/lib.rs index c6a60c84b5a8..7af166b27b06 100644 --- a/rs/ledger_suite/icrc1/src/lib.rs +++ b/rs/ledger_suite/icrc1/src/lib.rs @@ -468,7 +468,7 @@ impl LedgerTransaction for Transaction { } } Operation::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } } Ok(()) @@ -599,7 +599,7 @@ impl BlockType for Block { Operation::Transfer { fee, .. } => fee.is_none().then_some(effective_fee), Operation::Approve { fee, .. } => fee.is_none().then_some(effective_fee), Operation::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } _ => None, }; diff --git a/rs/ledger_suite/icrc1/test_utils/src/lib.rs b/rs/ledger_suite/icrc1/test_utils/src/lib.rs index 54a0ee2a741b..100445935b35 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/lib.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/lib.rs @@ -581,7 +581,7 @@ impl TransactionsAndBalances { self.debit(from, fee); } Operation::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } }; self.transactions.push(tx); @@ -611,7 +611,7 @@ impl TransactionsAndBalances { self.check_and_update_account_validity(*from, default_fee); } Operation::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } } } 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 94e8b9484454..39ce7e849c78 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 @@ -539,7 +539,7 @@ where TimeStamp::from_nanos_since_unix_epoch(block.timestamp), ), Operation::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } } } diff --git a/rs/rosetta-api/icrc1/src/common/storage/types.rs b/rs/rosetta-api/icrc1/src/common/storage/types.rs index b3d9ed714a5e..3648a7bb985f 100644 --- a/rs/rosetta-api/icrc1/src/common/storage/types.rs +++ b/rs/rosetta-api/icrc1/src/common/storage/types.rs @@ -609,7 +609,7 @@ where fee: fee.map(Into::into), }, Op::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } } } diff --git a/rs/rosetta-api/icrc1/src/construction_api/services.rs b/rs/rosetta-api/icrc1/src/construction_api/services.rs index 969c60c88854..1db71511166c 100644 --- a/rs/rosetta-api/icrc1/src/construction_api/services.rs +++ b/rs/rosetta-api/icrc1/src/construction_api/services.rs @@ -545,7 +545,7 @@ mod tests { ic_icrc1::Operation::Mint { .. } => CanisterMethodName::Icrc1Transfer, ic_icrc1::Operation::Burn { .. } => CanisterMethodName::Icrc1Transfer, ic_icrc1::Operation::FeeCollector { .. } => { - panic!("not implemented") + panic!("FeeCollector107 not implemented") } }; let args = match arg_with_caller.arg { From 61d4e0ba06893e8183d794abfbfa8b6c6e7ccf6c Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 21:19:20 +0000 Subject: [PATCH 28/40] build fix --- rs/ledger_suite/icrc1/index-ng/tests/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index efcd4314d94a..caf82115ef78 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -1271,7 +1271,7 @@ fn test_fee_collector_107() { let mut block_id = 0; let add_mint_block = |block_id: u64, fc: Option, fc_id: Option| { - let mint = BlockBuilder::new(block_id, block_id).with_fee(Tokens::from(1)); + let mint = BlockBuilder::new(block_id, block_id).with_fee(Tokens::from(1u64)); let mint = match fc { Some(fc) => mint.with_fee_collector(fc), None => mint, @@ -1280,7 +1280,7 @@ fn test_fee_collector_107() { Some(fc_id) => mint.with_fee_collector_block(fc_id), None => mint, }; - let mint = mint.mint(regular_account, Tokens::from(1)).build(); + let mint = mint.mint(regular_account, Tokens::from(1u64)).build(); assert_eq!( Nat::from(block_id), From 975cdc975e8ab64afd2bdff4720bacc0fb30673d Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 21:30:18 +0000 Subject: [PATCH 29/40] clippy --- rs/ledger_suite/icrc1/index-ng/tests/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index caf82115ef78..dfc74ffdf59d 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -1284,7 +1284,7 @@ fn test_fee_collector_107() { assert_eq!( Nat::from(block_id), - add_block(&env, ledger_id, &mint) + add_block(env, ledger_id, &mint) .expect("error adding mint block to ICRC-3 test ledger") ); wait_until_sync_is_completed(env, index_id, ledger_id); @@ -1299,7 +1299,7 @@ fn test_fee_collector_107() { assert_eq!( Nat::from(block_id), - add_block(&env, ledger_id, &fee_collector) + add_block(env, ledger_id, &fee_collector) .expect("error adding mint block to ICRC-3 test ledger") ); wait_until_sync_is_completed(env, index_id, ledger_id); From 50e90aea3a074151707d189862d22355c30fec44 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Thu, 13 Nov 2025 23:10:17 +0000 Subject: [PATCH 30/40] test approve fees --- rs/ledger_suite/icrc1/index-ng/tests/tests.rs | 34 +++++++++++++++++-- rs/ledger_suite/icrc1/test_utils/src/icrc3.rs | 7 ++-- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index dfc74ffdf59d..7102016c1a47 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -1280,7 +1280,7 @@ fn test_fee_collector_107() { Some(fc_id) => mint.with_fee_collector_block(fc_id), None => mint, }; - let mint = mint.mint(regular_account, Tokens::from(1u64)).build(); + let mint = mint.mint(regular_account, Tokens::from(1000u64)).build(); assert_eq!( Nat::from(block_id), @@ -1291,6 +1291,25 @@ fn test_fee_collector_107() { block_id + 1 }; + let add_approve_block = |block_id: u64, fc: Option| { + let approve = BlockBuilder::new(block_id, block_id).with_fee(Tokens::from(1u64)); + let approve = match fc { + Some(fc) => approve.with_fee_collector(fc), + None => approve, + }; + let approve = approve + .approve(regular_account, fee_collector_2, Tokens::from(1u64)) + .build(); + + assert_eq!( + Nat::from(block_id), + add_block(env, ledger_id, &approve) + .expect("error adding approve block to ICRC-3 test ledger") + ); + wait_until_sync_is_completed(env, index_id, ledger_id); + block_id + 1 + }; + let add_fee_collector_107_block = |block_id: u64, fc: Option| { let fee_collector = BlockBuilder::::new(block_id, block_id) .with_btype("107feecol".to_string()) @@ -1300,7 +1319,7 @@ fn test_fee_collector_107() { assert_eq!( Nat::from(block_id), add_block(env, ledger_id, &fee_collector) - .expect("error adding mint block to ICRC-3 test ledger") + .expect("error adding fee collector block to ICRC-3 test ledger") ); wait_until_sync_is_completed(env, index_id, ledger_id); block_id + 1 @@ -1312,6 +1331,10 @@ fn test_fee_collector_107() { block_id = add_mint_block(block_id, None, Some(0)); assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + // Legacy fee collector does not collect approve fees + block_id = add_approve_block(block_id, Some(fee_collector_1)); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + // Set 107 fee collector to burn block_id = add_fee_collector_107_block(block_id, None); @@ -1340,13 +1363,18 @@ fn test_fee_collector_107() { assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); assert_eq!(3, icrc1_balance_of(env, index_id, fee_collector_2)); + // 107 fee collector is credited the approve fee + block_id = add_approve_block(block_id, None); + assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(4, icrc1_balance_of(env, index_id, fee_collector_2)); + // Set 107 fee collector to burn block_id = add_fee_collector_107_block(block_id, None); // No fees collected add_mint_block(block_id, None, None); assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(3, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(4, icrc1_balance_of(env, index_id, fee_collector_2)); } #[test] diff --git a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs index 37bf1e28e9f2..47354e0f0816 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs @@ -294,10 +294,7 @@ impl ApproveBuilder { let mut tx_fields = BTreeMap::new(); tx_fields.insert("from".to_string(), account_to_icrc3_value(&self.from)); tx_fields.insert("spender".to_string(), account_to_icrc3_value(&self.spender)); - tx_fields.insert( - "allowance".to_string(), - ICRC3Value::Nat(self.allowance.into()), - ); + tx_fields.insert("amt".to_string(), ICRC3Value::Nat(self.allowance.into())); if let Some(expected_allowance) = self.expected_allowance { tx_fields.insert( @@ -326,7 +323,7 @@ pub struct FeeCollectorBuilder { } impl FeeCollectorBuilder { - /// Build the mint block + /// Build the fee collector block pub fn build(self) -> ICRC3Value { let mut tx_fields = BTreeMap::new(); if let Some(fee_collector) = &self.fee_collector { From bfb852ed422a76573c3d03ad59ab8fa145c2e51e Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 14 Nov 2025 02:24:41 +0000 Subject: [PATCH 31/40] small refactor --- rs/ledger_suite/icrc1/index-ng/tests/tests.rs | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index 7102016c1a47..c22a4dea163b 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -1264,8 +1264,8 @@ fn test_fee_collector_107() { 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)); - let fee_collector_1 = account(101, 0); - let fee_collector_2 = account(102, 0); + let feecol_legacy = account(101, 0); + let feecol_107 = account(102, 0); let regular_account = account(1, 0); let mut block_id = 0; @@ -1298,7 +1298,7 @@ fn test_fee_collector_107() { None => approve, }; let approve = approve - .approve(regular_account, fee_collector_2, Tokens::from(1u64)) + .approve(regular_account, regular_account, Tokens::from(1u64)) .build(); assert_eq!( @@ -1326,55 +1326,55 @@ fn test_fee_collector_107() { }; // Legacy fee collector collects the fees - block_id = add_mint_block(block_id, Some(fee_collector_1), None); - assert_eq!(1, icrc1_balance_of(env, index_id, fee_collector_1)); + block_id = add_mint_block(block_id, Some(feecol_legacy), None); + assert_eq!(1, icrc1_balance_of(env, index_id, feecol_legacy)); block_id = add_mint_block(block_id, None, Some(0)); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); // Legacy fee collector does not collect approve fees - block_id = add_approve_block(block_id, Some(fee_collector_1)); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); + block_id = add_approve_block(block_id, Some(feecol_legacy)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); // Set 107 fee collector to burn block_id = add_fee_collector_107_block(block_id, None); // No fees collected block_id = add_mint_block(block_id, None, None); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(0, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); + assert_eq!(0, icrc1_balance_of(env, index_id, feecol_107)); // No fees collected with the legacy fee collector - block_id = add_mint_block(block_id, Some(fee_collector_1), None); + block_id = add_mint_block(block_id, Some(feecol_legacy), None); block_id = add_mint_block(block_id, None, Some(block_id - 1)); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(0, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); + assert_eq!(0, icrc1_balance_of(env, index_id, feecol_107)); // Set 107 fee collector to fee_collector_2 - block_id = add_fee_collector_107_block(block_id, Some(fee_collector_2)); + block_id = add_fee_collector_107_block(block_id, Some(feecol_107)); // New fee collector receives the fees block_id = add_mint_block(block_id, None, None); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(1, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); + assert_eq!(1, icrc1_balance_of(env, index_id, feecol_107)); // Legacy fee collector has no effect, new fee collector receives the fees - block_id = add_mint_block(block_id, Some(fee_collector_1), None); + block_id = add_mint_block(block_id, Some(feecol_legacy), None); block_id = add_mint_block(block_id, None, Some(block_id - 1)); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(3, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); + assert_eq!(3, icrc1_balance_of(env, index_id, feecol_107)); // 107 fee collector is credited the approve fee block_id = add_approve_block(block_id, None); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(4, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); + 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); // No fees collected add_mint_block(block_id, None, None); - assert_eq!(2, icrc1_balance_of(env, index_id, fee_collector_1)); - assert_eq!(4, icrc1_balance_of(env, index_id, fee_collector_2)); + assert_eq!(2, icrc1_balance_of(env, index_id, feecol_legacy)); + assert_eq!(4, icrc1_balance_of(env, index_id, feecol_107)); } #[test] From 17cb2b23be0f68cffd9550c3a6b708faaf4ae0f2 Mon Sep 17 00:00:00 2001 From: maciejdfinity <122265298+maciejdfinity@users.noreply.github.com> Date: Fri, 14 Nov 2025 08:58:04 -0800 Subject: [PATCH 32/40] Update rs/ledger_suite/icrc1/index-ng/src/main.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Björkqvist --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index ca5083c900e7..8e442a100524 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -1022,10 +1022,7 @@ fn get_accounts(block: &Block) -> Vec { } fn get_fee_collector() -> Option { - match get_fee_collector_107() { - Some(fee_collector) => fee_collector, - None => get_legacy_fee_collector(), - } + get_fee_collector_107().unwrap_or_else(|| get_legacy_fee_collector()) } fn get_fee_collector_107() -> Option> { From dc2eb009382fa4d9b03f0bce1977d0042e1e1f9d Mon Sep 17 00:00:00 2001 From: maciejdfinity <122265298+maciejdfinity@users.noreply.github.com> Date: Fri, 14 Nov 2025 08:58:18 -0800 Subject: [PATCH 33/40] Update rs/ledger_suite/icrc1/test_utils/src/icrc3.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Björkqvist --- rs/ledger_suite/icrc1/test_utils/src/icrc3.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs index 47354e0f0816..89e948ca89fb 100644 --- a/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs +++ b/rs/ledger_suite/icrc1/test_utils/src/icrc3.rs @@ -115,7 +115,7 @@ impl BlockBuilder { } } - /// Create an approve operation + /// Create a fee collector block pub fn fee_collector( self, fee_collector: Option, From 3a858f5d4f9cbf6a558ae99b7e0ade7a59392344 Mon Sep 17 00:00:00 2001 From: maciejdfinity <122265298+maciejdfinity@users.noreply.github.com> Date: Fri, 14 Nov 2025 09:18:08 -0800 Subject: [PATCH 34/40] Update rs/ledger_suite/icrc1/index-ng/src/main.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Björkqvist --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 8e442a100524..8e1fe2f9bc91 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -971,10 +971,8 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block) { debit(block_index, from, fee); - if let Some(fee_collector_107) = get_fee_collector_107() - && let Some(fee_collector) = fee_collector_107 - { - credit(block_index, fee_collector, fee); + if let Some(fee_collector_107) = get_fee_collector_107().flatten() { + credit(block_index, fee_collector_107, fee); } } Operation::FeeCollector { From 26b1efc3e9a7dafa84bb085b3b35e312e97a1c78 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 14 Nov 2025 17:23:13 +0000 Subject: [PATCH 35/40] clippy --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 8e1fe2f9bc91..8d2d71f14bcd 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -1020,7 +1020,7 @@ fn get_accounts(block: &Block) -> Vec { } fn get_fee_collector() -> Option { - get_fee_collector_107().unwrap_or_else(|| get_legacy_fee_collector()) + get_fee_collector_107().unwrap_or_else(get_legacy_fee_collector) } fn get_fee_collector_107() -> Option> { From de0607830793dc4f7f4c1e2aaf53b36b402f8f6b Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Fri, 14 Nov 2025 17:52:12 +0000 Subject: [PATCH 36/40] rustdoc for fee collector 107 --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 8d2d71f14bcd..1e6ed064f81f 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -141,6 +141,10 @@ struct State { /// cycle burn for the index, ledger and archive(s). retrieve_blocks_from_ledger_interval: Option, + /// The ICRC-107 fee collector. Example values: + /// - `None` - legacy fee collector is used. + /// - `Some(None)` - 107 fee collecor is enabled but fees are burned. + /// - `Some(Some(account1))` - 107 fee collecor is enabled, `account1` collects the fees. fee_collector_107: Option>, } From f3c2280225b00947a036ed89b18af37e136ed1c7 Mon Sep 17 00:00:00 2001 From: maciejdfinity <122265298+maciejdfinity@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:13:51 +0100 Subject: [PATCH 37/40] Update rs/ledger_suite/icrc1/index-ng/src/main.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Björkqvist --- rs/ledger_suite/icrc1/index-ng/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/ledger_suite/icrc1/index-ng/src/main.rs b/rs/ledger_suite/icrc1/index-ng/src/main.rs index 1e6ed064f81f..16e8800243b7 100644 --- a/rs/ledger_suite/icrc1/index-ng/src/main.rs +++ b/rs/ledger_suite/icrc1/index-ng/src/main.rs @@ -143,8 +143,8 @@ struct State { /// The ICRC-107 fee collector. Example values: /// - `None` - legacy fee collector is used. - /// - `Some(None)` - 107 fee collecor is enabled but fees are burned. - /// - `Some(Some(account1))` - 107 fee collecor is enabled, `account1` collects the fees. + /// - `Some(None)` - 107 fee collector is enabled but fees are burned. + /// - `Some(Some(account1))` - 107 fee collector is enabled, `account1` collects the fees. fee_collector_107: Option>, } From 3c8283f93d5031282fd21f67e1e4066745c90e2e Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Wed, 19 Nov 2025 12:53:44 +0000 Subject: [PATCH 38/40] update changelog --- packages/icrc-ledger-types/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/icrc-ledger-types/CHANGELOG.md b/packages/icrc-ledger-types/CHANGELOG.md index 40734b730384..7978e708ecf8 100644 --- a/packages/icrc-ledger-types/CHANGELOG.md +++ b/packages/icrc-ledger-types/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add ICRC-107 fee collector transaction type. + ## 0.1.12 ### Added From bf53fb87893aedc48c073eb96d0c477fa507f6cf Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Mon, 24 Nov 2025 09:52:49 +0000 Subject: [PATCH 39/40] rename fee_collector to set_fee_collector --- packages/icrc-ledger-types/src/icrc3/transactions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/icrc-ledger-types/src/icrc3/transactions.rs b/packages/icrc-ledger-types/src/icrc3/transactions.rs index f1da04573c5b..4e855910cf01 100644 --- a/packages/icrc-ledger-types/src/icrc3/transactions.rs +++ b/packages/icrc-ledger-types/src/icrc3/transactions.rs @@ -126,7 +126,7 @@ impl Transaction { } } - pub fn fee_collector(fee_collector: FeeCollector, timestamp: u64) -> Self { + pub fn set_fee_collector(fee_collector: FeeCollector, timestamp: u64) -> Self { Self { kind: "107set_fee_collector".into(), timestamp, From b41b5667d43e5ba256e4cbcbf9dc14e421c7a2b4 Mon Sep 17 00:00:00 2001 From: Maciej Dfinity Date: Mon, 24 Nov 2025 12:24:14 +0000 Subject: [PATCH 40/40] update canbench results --- .../ledger/canbench_results/canbench_u256.yml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) 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 c4e7626a8e88..39e1c1b1eec5 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: 53985058770 - heap_increase: 262 + instructions: 54207644282 + heap_increase: 263 stable_memory_increase: 256 scopes: icrc103_get_allowances: calls: 1 - instructions: 6502280 + instructions: 6493127 heap_increase: 0 stable_memory_increase: 0 icrc1_transfer: calls: 1 - instructions: 12837236114 + instructions: 12874221706 heap_increase: 31 stable_memory_increase: 0 icrc2_approve: calls: 1 - instructions: 19208458344 - heap_increase: 28 + instructions: 19247077490 + heap_increase: 29 stable_memory_increase: 128 icrc2_transfer_from: calls: 1 - instructions: 21223966970 + instructions: 21368223296 heap_increase: 3 stable_memory_increase: 0 icrc3_get_blocks: calls: 1 - instructions: 8831859 + instructions: 8827484 heap_increase: 0 stable_memory_increase: 0 post_upgrade: calls: 1 - instructions: 357852389 + instructions: 357939096 heap_increase: 71 stable_memory_increase: 0 pre_upgrade: calls: 1 - instructions: 149850505 + instructions: 150903114 heap_increase: 129 stable_memory_increase: 128 upgrade: calls: 1 - instructions: 507705704 + instructions: 508845011 heap_increase: 200 stable_memory_increase: 128 bench_upgrade_baseline: total: calls: 1 - instructions: 8695350 + instructions: 8695587 heap_increase: 258 stable_memory_increase: 128 scopes: post_upgrade: calls: 1 - instructions: 8614551 + instructions: 8614550 heap_increase: 129 stable_memory_increase: 0 pre_upgrade: calls: 1 - instructions: 77963 + instructions: 78132 heap_increase: 129 stable_memory_increase: 128 upgrade: calls: 1 - instructions: 8694453 + instructions: 8694690 heap_increase: 258 stable_memory_increase: 128 version: 0.2.1