Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/icrc-ledger-types/src/icrc122/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ pub const MTHD_152_BURN: &str = "152burn";
/// * `account_field` – `"to"` for mint, `"from"` for burn
/// * `strict` – when `true`, `caller` and `mthd` are required (ICRC-152);
/// when `false` they are optional (ICRC-122).
fn block_validator(btype: &'static str, account_field: &'static str, strict: bool) -> ValuePredicate {
fn block_validator(
btype: &'static str,
account_field: &'static str,
strict: bool,
) -> ValuePredicate {
use ItemRequirement::*;
let caller_mthd_req = if strict { Required } else { Optional };

Expand Down
6 changes: 6 additions & 0 deletions rs/ledger_suite/icrc1/index-ng/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,9 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block<Tokens>) {
} => {
// Does not affect the balance
}
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet supported in index-ng")
}
},
);
}
Expand Down Expand Up @@ -1156,6 +1159,9 @@ fn get_accounts(block: &Block<Tokens>) -> Vec<Account> {
}
Operation::Approve { from, .. } => vec![from],
Operation::FeeCollector { .. } => vec![],
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet supported in index-ng")
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions rs/ledger_suite/icrc1/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ impl<Tokens: TokensType> From<Block<Tokens>> for Transaction {
mthd,
});
}
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn endpoint conversion not yet implemented")
}
}

tx
Expand Down
82 changes: 74 additions & 8 deletions rs/ledger_suite/icrc1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use ic_ledger_core::{
};
use ic_ledger_hash_of::HashOf;
use icrc_ledger_types::icrc1::account::Account;
use icrc_ledger_types::icrc122::schema::{BTYPE_122_BURN, BTYPE_122_MINT};
use icrc_ledger_types::{icrc1::transfer::Memo, icrc3::transactions::TRANSACTION_FEE_COLLECTOR};
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -54,6 +55,20 @@ pub enum Operation<Tokens: TokensType> {
caller: Option<Principal>,
mthd: Option<String>,
},
AuthorizedMint {
to: Account,
amount: Tokens,
caller: Option<Principal>,
mthd: Option<String>,
reason: Option<String>,
},
AuthorizedBurn {
from: Account,
amount: Tokens,
caller: Option<Principal>,
mthd: Option<String>,
reason: Option<String>,
},
Comment on lines +58 to +71
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We recently added fee: Option<Tokens> to Mint and Burn (due to the cycles ledger). Would it make sense to add them here also for consistency (the standard itself would then also need to change, not only the implementation here)?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good question! I'd lean toward not adding it — the primary use case for ICRC-122/152 is RWA tokens, where fees may not even be a thing for regular transactions, let alone for privileged mints/burns. More broadly, charging a fee on authorized mint/burn doesn't make sense when the tokens represent real-world assets. Adding an optional fee field to the standard just for consistency with the cycles ledger feels speculative, so I'd rather keep the schema clean and revisit if a concrete need arises.

}

// A [Transaction] but flattened meaning that [Operation]
Expand Down Expand Up @@ -122,6 +137,10 @@ struct FlattenedTransaction<Tokens: TokensType> {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub mthd: Option<String>,

#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}

impl<Tokens: TokensType> TryFrom<FlattenedTransaction<Tokens>> for Transaction<Tokens> {
Expand Down Expand Up @@ -162,6 +181,26 @@ impl<Tokens: TokensType> TryFrom<(Option<String>, FlattenedTransaction<Tokens>)>
caller: value.caller,
mthd: value.mthd,
},
Some(BTYPE_122_MINT) => Operation::AuthorizedMint {
to: value.to.ok_or("`to` field required for `122mint` block")?,
amount: value
.amount
.ok_or("`amount` required for `122mint` block")?,
caller: value.caller,
mthd: value.mthd,
reason: value.reason,
},
Some(BTYPE_122_BURN) => Operation::AuthorizedBurn {
from: value
.from
.ok_or("`from` field required for `122burn` block")?,
amount: value
.amount
.ok_or("`amount` required for `122burn` block")?,
caller: value.caller,
mthd: value.mthd,
reason: value.reason,
},
_ => Operation::try_from(value)
.map_err(|e| format!("{} and/or unknown btype {:?}", e, btype_str))?,
};
Expand Down Expand Up @@ -241,55 +280,74 @@ impl<Tokens: TokensType> From<Transaction<Tokens>> for FlattenedTransaction<Toke
Mint { .. } => Some("mint".to_string()),
Transfer { .. } => Some("xfer".to_string()),
Approve { .. } => Some("approve".to_string()),
FeeCollector { .. } => None,
FeeCollector { .. } | AuthorizedMint { .. } | AuthorizedBurn { .. } => None,
},
from: match &t.operation {
Transfer { from, .. } | Burn { from, .. } | Approve { from, .. } => Some(*from),
Transfer { from, .. }
| Burn { from, .. }
| Approve { from, .. }
| AuthorizedBurn { from, .. } => Some(*from),
AuthorizedMint { .. } => None,
_ => None,
},
to: match &t.operation {
Mint { to, .. } | Transfer { to, .. } => Some(*to),
Mint { to, .. } | Transfer { to, .. } | AuthorizedMint { to, .. } => Some(*to),
AuthorizedBurn { .. } => None,
_ => None,
},
spender: match &t.operation {
Transfer { spender, .. } | Burn { spender, .. } => spender.to_owned(),
Approve { spender, .. } => Some(*spender),
AuthorizedMint { .. } | AuthorizedBurn { .. } => None,
_ => None,
},
amount: match &t.operation {
Burn { amount, .. }
| Mint { amount, .. }
| Transfer { amount, .. }
| Approve { amount, .. } => Some(amount.clone()),
| Approve { amount, .. }
| AuthorizedMint { amount, .. }
| AuthorizedBurn { amount, .. } => Some(amount.clone()),
FeeCollector { .. } => None,
},
fee: match &t.operation {
Transfer { fee, .. }
| Approve { fee, .. }
| Mint { fee, .. }
| Burn { fee, .. } => fee.to_owned(),
FeeCollector { .. } => None,
FeeCollector { .. } | AuthorizedMint { .. } | AuthorizedBurn { .. } => None,
},
expected_allowance: match &t.operation {
Approve {
expected_allowance, ..
} => expected_allowance.to_owned(),
AuthorizedMint { .. } | AuthorizedBurn { .. } => None,
_ => None,
},
expires_at: match &t.operation {
Approve { expires_at, .. } => expires_at.to_owned(),
AuthorizedMint { .. } | AuthorizedBurn { .. } => None,
_ => None,
},
fee_collector: match &t.operation {
FeeCollector { fee_collector, .. } => fee_collector.to_owned(),
AuthorizedMint { .. } | AuthorizedBurn { .. } => None,
_ => None,
},
caller: match &t.operation {
FeeCollector { caller, .. } => caller.to_owned(),
FeeCollector { caller, .. }
| AuthorizedMint { caller, .. }
| AuthorizedBurn { caller, .. } => caller.to_owned(),
_ => None,
},
mthd: match &t.operation {
FeeCollector { mthd, .. } => mthd.to_owned(),
FeeCollector { mthd, .. }
| AuthorizedMint { mthd, .. }
| AuthorizedBurn { mthd, .. } => mthd.to_owned(),
_ => None,
},
reason: match &t.operation {
AuthorizedMint { reason, .. } | AuthorizedBurn { reason, .. } => reason.to_owned(),
_ => None,
},
}
Expand Down Expand Up @@ -482,6 +540,9 @@ impl<Tokens: TokensType> LedgerTransaction for Transaction<Tokens> {
return Err(e);
}
}
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet implemented")
}
Operation::FeeCollector { .. } => {
panic!("FeeCollector107 not implemented")
}
Expand Down Expand Up @@ -669,6 +730,11 @@ impl<Tokens: TokensType> BlockType for Block<Tokens> {
effective_fee: Tokens,
fee_collector: Option<FeeCollector<Self::AccountId>>,
) -> Self {
let btype = match &transaction.operation {
Operation::AuthorizedMint { .. } => Some(BTYPE_122_MINT.to_string()),
Operation::AuthorizedBurn { .. } => Some(BTYPE_122_BURN.to_string()),
_ => None,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not only regarding this PR: We should get rid of these catch-all _ => None match arms, since it's not always clear if the intention is to end up there, or if we simply forgot to add a new arm for a new variant. I created DEFI-2747 to take care of this for the existing code, and I think it would make sense to, in this PR, make sure we don't end up in the catch-all arm for any of the newly-added variants.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Good point — done. Added explicit match arms for AuthorizedMint/AuthorizedBurn in all the FlattenedTransaction::from fields where they were falling into catch-all _ => None arms (from, to, spender, expected_allowance, expires_at, fee_collector).

};
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),
Expand All @@ -692,7 +758,7 @@ impl<Tokens: TokensType> BlockType for Block<Tokens> {
timestamp: timestamp.as_nanos_since_unix_epoch(),
fee_collector,
fee_collector_block_index,
btype: None,
btype,
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion rs/ledger_suite/icrc1/test_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ pub fn blocks_strategy<Tokens: TokensType>(
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,
Operation::FeeCollector { .. }
| Operation::AuthorizedMint { .. }
| Operation::AuthorizedBurn { .. } => None,
};
let btype = match transaction.operation {
Operation::FeeCollector { .. } => Some(BTYPE_107.to_string()),
Expand Down Expand Up @@ -625,6 +627,9 @@ impl TransactionsAndBalances {
Operation::FeeCollector { .. } => {
panic!("FeeCollector107 not implemented")
}
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet implemented in test_utils")
}
};
self.transactions.push(tx);

Expand Down Expand Up @@ -655,6 +660,9 @@ impl TransactionsAndBalances {
Operation::FeeCollector { .. } => {
panic!("FeeCollector107 not implemented")
}
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet implemented in test_utils")
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions rs/ledger_suite/test_utils/in_memory_ledger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ where
Operation::FeeCollector { .. } => {
panic!("FeeCollector107 not implemented")
}
Operation::AuthorizedMint { .. } | Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet implemented in in_memory_ledger")
}
}
}
self.post_process_ledger_blocks(blocks);
Expand Down
3 changes: 3 additions & 0 deletions rs/rosetta-api/icrc1/src/common/storage/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,9 @@ where
caller,
mthd,
},
Op::AuthorizedMint { .. } | Op::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet supported in Rosetta")
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions rs/rosetta-api/icrc1/src/construction_api/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,10 @@ mod tests {
ic_icrc1::Operation::FeeCollector { .. } => {
panic!("FeeCollector107 not implemented")
}
ic_icrc1::Operation::AuthorizedMint { .. }
| ic_icrc1::Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet supported in Rosetta")
}
};
let args = match arg_with_caller.arg {
LedgerEndpointArg::TransferArg(arg) => Encode!(&arg),
Expand Down
4 changes: 4 additions & 0 deletions rs/rosetta-api/icrc1/tests/multitoken_system_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1842,6 +1842,10 @@ fn test_construction_submit() {
ic_icrc1::Operation::Mint { .. } => None,
ic_icrc1::Operation::Burn { .. } => None,
ic_icrc1::Operation::FeeCollector { .. } => None,
ic_icrc1::Operation::AuthorizedMint { .. }
| ic_icrc1::Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet supported in Rosetta")
}
};

if matches!(
Expand Down
4 changes: 4 additions & 0 deletions rs/rosetta-api/icrc1/tests/system_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,10 @@ fn test_construction_submit() {
ic_icrc1::Operation::Mint { .. } => None,
ic_icrc1::Operation::Burn { .. } => None,
ic_icrc1::Operation::FeeCollector { .. } => None,
ic_icrc1::Operation::AuthorizedMint { .. }
| ic_icrc1::Operation::AuthorizedBurn { .. } => {
panic!("AuthorizedMint/AuthorizedBurn not yet supported in Rosetta")
}
};

// Rosetta does not support mint and burn operations
Expand Down
Loading