-
Notifications
You must be signed in to change notification settings - Fork 391
feat(ic-icrc1): add AuthorizedMint/AuthorizedBurn Operation variants #9642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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}; | ||
|
|
||
|
|
@@ -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>, | ||
| }, | ||
| } | ||
|
|
||
| // A [Transaction] but flattened meaning that [Operation] | ||
|
|
@@ -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> { | ||
|
|
@@ -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))?, | ||
| }; | ||
|
|
@@ -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, | ||
| }, | ||
| } | ||
|
|
@@ -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") | ||
| } | ||
|
|
@@ -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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point — done. Added explicit match arms for |
||
| }; | ||
| 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), | ||
|
|
@@ -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, | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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>toMintandBurn(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)?There was a problem hiding this comment.
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.