diff --git a/cycles-ledger/src/storage.rs b/cycles-ledger/src/storage.rs index 824b462..6bc7322 100644 --- a/cycles-ledger/src/storage.rs +++ b/cycles-ledger/src/storage.rs @@ -121,6 +121,12 @@ pub enum Operation { Mint { to: Account, amount: u128, + // Custom non-standard fee to record the amount + // of cycles "burn" when cycles are deposited, i.e. + // the diffence between the cycles deposited and + // the cycles minted. Note that this field has + // no effect on the balance of the `to` account. + fee: u128, }, Transfer { from: Account, @@ -177,10 +183,11 @@ impl Display for Operation { write!(f, ", amount: {amount}")?; write!(f, " }}") } - Self::Mint { to, amount } => { + Self::Mint { to, amount, fee } => { write!(f, "Mint {{")?; write!(f, " to: {to}")?; write!(f, ", amount: {amount}")?; + write!(f, ", fee: {fee}")?; write!(f, " }}") } Self::Transfer { @@ -260,6 +267,9 @@ impl TryFrom for Transaction { "mint" => Operation::Mint { to: value.to.ok_or("`to` field required for `mint` operation")?, amount: value.amount, + fee: value + .fee + .ok_or("`fee` field required for `mint` operation")?, }, "xfer" => Operation::Transfer { from: value @@ -328,6 +338,7 @@ impl From for FlattenedTransaction { }, fee: match &t.operation { Transfer { fee, .. } | Approve { fee, .. } => fee.to_owned(), + Mint { fee, .. } => Some(fee.to_owned()), _ => None, }, expected_allowance: match &t.operation { @@ -720,32 +731,6 @@ pub fn balance_of(account: &Account) -> u128 { read_state(|s| s.balances.get(&to_account_key(account)).unwrap_or_default()) } -pub fn record_deposit( - account: &Account, - amount: u128, - memo: Option, - now: u64, -) -> anyhow::Result<(u64, u128, Hash)> { - mutate_state(|s| { - let new_balance = s.credit(account, amount)?; - let phash = s.last_block_hash(); - let block_hash = s.emit_block(Block { - transaction: Transaction { - operation: Operation::Mint { - to: *account, - amount, - }, - memo, - created_at_time: None, - }, - timestamp: now, - phash, - effective_fee: Some(0), - }); - Ok((s.blocks.len() - 1, new_balance, block_hash)) - }) -} - pub fn deposit( to: Account, amount: u128, @@ -786,7 +771,11 @@ pub fn mint(to: Account, amount: u128, memo: Option, now: u64) -> anyhow:: // we are not checking for duplicates, since mint is executed with created_at_time: None let block_index = process_transaction( Transaction { - operation: Operation::Mint { to, amount }, + operation: Operation::Mint { + to, + amount, + fee: crate::config::FEE, + }, created_at_time: None, memo, }, @@ -1409,7 +1398,8 @@ fn validate_suggested_fee(op: &Operation) -> Result, u128> { use Operation as Op; match op { - Op::Burn { .. } | Op::Mint { .. } => Ok(Some(config::FEE)), + Op::Mint { .. } => Ok(Some(0)), + Op::Burn { .. } => Ok(Some(config::FEE)), Op::Transfer { fee, .. } | Op::Approve { fee, .. } => { if fee.is_some() && fee != &Some(config::FEE) { return Err(config::FEE); @@ -2003,7 +1993,11 @@ fn reimburse( memo: [u8; MAX_MEMO_LENGTH as usize], ) -> Result { let transaction = Transaction { - operation: Operation::Mint { to: acc, amount }, + operation: Operation::Mint { + to: acc, + amount, + fee: 0, + }, created_at_time: None, memo: Some(Memo::from(ByteBuf::from(memo))), }; @@ -2304,8 +2298,9 @@ mod tests { icrc3, }; use proptest::{ - prelude::any, prop_assert, prop_assert_eq, prop_compose, prop_oneof, proptest, - strategy::Strategy, + prelude::any, + prop_assert, prop_assert_eq, prop_compose, prop_oneof, proptest, + strategy::{Just, Strategy}, }; use crate::{ @@ -2361,9 +2356,10 @@ mod tests { prop_compose! { fn mint_strategy() (to in account_strategy(), - amount in any::()) + amount in any::(), + fee in prop_oneof![Just(0), Just(config::FEE)]) -> Operation { - Operation::Mint { to, amount } + Operation::Mint { to, amount, fee } } } diff --git a/cycles-ledger/tests/tests.rs b/cycles-ledger/tests/tests.rs index 1913a95..2799a1e 100644 --- a/cycles-ledger/tests/tests.rs +++ b/cycles-ledger/tests/tests.rs @@ -623,8 +623,8 @@ fn test_deposit_flow() { &Block { // 1.2.0 first block has no parent hash. phash: None, - // 1.2.1 effective fee of mint blocks is the configured fee. - effective_fee: Some(fee), + // 1.2.1 effective fee of mint blocks is 0. + effective_fee: Some(0), // 1.2.2 timestamp is set by the ledger. timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { @@ -639,6 +639,8 @@ fn test_deposit_flow() { to: account0, // 1.2.7 transaction.operation.amount is the deposited amount. amount: 1_000_000_000, + // 1.2.8 transaction.operation.fee is the ledger fee. + fee, }, }, }, @@ -663,8 +665,8 @@ fn test_deposit_flow() { &Block { // 2.2.0 second block has the first block hash as parent hash. phash: Some(block0.hash().unwrap()), - // 2.2.1 effective fee of mint blocks is the configured fee. - effective_fee: Some(fee), + // 2.2.1 effective fee of mint blocks is 0. + effective_fee: Some(0), // 2.2.2 timestamp is set by the ledger. timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { @@ -678,6 +680,8 @@ fn test_deposit_flow() { to: account0, // 2.2.7 transaction.operation.amount is the deposited amount. amount: 500_000_000, + // 2.2.8 transaction.operation.fee is the ledger fee. + fee, }, }, }, @@ -1089,7 +1093,7 @@ fn test_withdraw_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1099,6 +1103,7 @@ fn test_withdraw_fails() { // refund the amount minus the fee to make // the caller pay for the refund block too amount: 500_000_000_u128 - FEE, + fee: 0, }, }, } @@ -1740,7 +1745,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1748,6 +1753,7 @@ fn test_withdraw_from_fails() { operation: Operation::Mint { to: account1_2, amount: 400_000_000_u128, + fee: 0, }, }, } @@ -1860,7 +1866,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1868,6 +1874,7 @@ fn test_withdraw_from_fails() { operation: Operation::Mint { to: account1_3, amount: 10_000_u128, + fee: 0, }, }, } @@ -1958,7 +1965,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1966,6 +1973,7 @@ fn test_withdraw_from_fails() { operation: Operation::Mint { to: account1_4, amount: 10_000_u128, + fee: 0, }, }, } @@ -2091,7 +2099,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -2099,6 +2107,7 @@ fn test_withdraw_from_fails() { operation: Operation::Mint { to: account1_6, amount: 400_000_000_u128, + fee: 0, }, }, } @@ -4482,6 +4491,7 @@ fn test_icrc3_get_blocks() { Mint { to: account1, amount: 5_000_000_000, + fee, }, None, None, @@ -4506,6 +4516,7 @@ fn test_icrc3_get_blocks() { Mint { to: account2, amount: 3_000_000_000, + fee, }, None, None, @@ -4709,7 +4720,8 @@ fn block( phash: Option<[u8; 32]>, ) -> Block { let effective_fee = match operation { - Burn { .. } | Mint { .. } => Some(FEE), + Mint { .. } => Some(0), + Burn { .. } => Some(FEE), Transfer { fee, .. } | Approve { fee, .. } => { if fee.is_none() { Some(FEE) @@ -5506,7 +5518,7 @@ fn test_create_canister_fail() { id: Nat::from(blocks.len() + 1), block: Block { phash: Some(burn_block.block.clone().hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -5514,6 +5526,7 @@ fn test_create_canister_fail() { operation: Operation::Mint { to: account1, amount: amount - fee, + fee: 0, }, }, } @@ -6131,7 +6144,7 @@ fn test_create_canister_from_fail() { id: Nat::from(blocks.len() + 1), block: Block { phash: Some(burn_block.block.clone().hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -6139,6 +6152,7 @@ fn test_create_canister_from_fail() { operation: Operation::Mint { to: account1_3, amount: FEE / 2, + fee: 0, }, }, } @@ -6232,7 +6246,7 @@ fn test_create_canister_from_fail() { id: Nat::from(blocks.len() + 1), block: Block { phash: Some(burn_block.block.clone().hash()), - effective_fee: Some(config::FEE), + effective_fee: Some(0), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -6240,6 +6254,7 @@ fn test_create_canister_from_fail() { operation: Operation::Mint { to: account1_4, amount: FEE + FEE / 2, + fee: 0, }, }, }