From 804a450b62d9ceb8a463b702e4dfaed835add416 Mon Sep 17 00:00:00 2001 From: Mario Pastorelli Date: Thu, 21 Mar 2024 14:16:07 +0000 Subject: [PATCH 1/4] feat: charge fee for deposit --- cycles-ledger/src/storage.rs | 17 +-- cycles-ledger/tests/gen.rs | 2 +- cycles-ledger/tests/tests.rs | 231 +++++++++++++++++++---------------- 3 files changed, 134 insertions(+), 116 deletions(-) diff --git a/cycles-ledger/src/storage.rs b/cycles-ledger/src/storage.rs index 5062593..824b462 100644 --- a/cycles-ledger/src/storage.rs +++ b/cycles-ledger/src/storage.rs @@ -762,7 +762,7 @@ pub fn deposit( ) } - let block_index = mint(to, amount, memo, now)?; + let block_index = mint(to, amount - crate::config::FEE, memo, now)?; prune(now); @@ -1409,7 +1409,7 @@ fn validate_suggested_fee(op: &Operation) -> Result, u128> { use Operation as Op; match op { - Op::Burn { .. } | Op::Mint { .. } => Ok(Some(0)), + Op::Burn { .. } | Op::Mint { .. } => Ok(Some(config::FEE)), Op::Transfer { fee, .. } | Op::Approve { fee, .. } => { if fee.is_some() && fee != &Some(config::FEE) { return Err(config::FEE); @@ -1445,14 +1445,6 @@ fn check_duplicate(transaction: &Transaction) -> Result<(), ProcessTransactionEr } fn process_transaction(transaction: Transaction, now: u64) -> Result { - process_block(transaction, now, None) -} - -fn process_block( - transaction: Transaction, - now: u64, - effective_fee: Option, -) -> Result { use ProcessTransactionError as PTErr; // The ICRC-1 and ICP Ledgers trap when the memo validation fails @@ -1464,7 +1456,6 @@ fn process_block( validate_created_at_time(&transaction.created_at_time, now)?; let effective_fee = validate_suggested_fee(&transaction.operation) - .map(|fee| effective_fee.or(fee)) .map_err(|expected_fee| PTErr::BadFee { expected_fee })?; let block = Block { @@ -1704,7 +1695,7 @@ pub async fn withdraw( // 1. burn cycles + fee - let block_index = process_block(transaction.clone(), now, Some(config::FEE))?; + let block_index = process_transaction(transaction.clone(), now)?; if let Some(spender) = spender { if spender != from { @@ -1849,7 +1840,7 @@ pub async fn create_canister( // 1. burn cycles + fee - let block_index = process_block(transaction.clone(), now, Some(config::FEE))?; + let block_index = process_transaction(transaction.clone(), now)?; if let Some(spender) = spender { if spender != from { diff --git a/cycles-ledger/tests/gen.rs b/cycles-ledger/tests/gen.rs index 0160319..1924616 100644 --- a/cycles-ledger/tests/gen.rs +++ b/cycles-ledger/tests/gen.rs @@ -211,7 +211,7 @@ impl IsCyclesLedger for CyclesLedgerInMemory { amount, arg: DepositArg { to, .. }, } => { - let amount = amount.0.to_u128().ok_or("amount is not a u128")?; + let amount = amount.0.to_u128().ok_or("amount is not a u128")? - FEE; // The precise cost of calling the deposit endpoint is unknown. // depositor_cycles is decreased by an arbitrary number plus // the amount. diff --git a/cycles-ledger/tests/tests.rs b/cycles-ledger/tests/tests.rs index cd6a789..1913a95 100644 --- a/cycles-ledger/tests/tests.rs +++ b/cycles-ledger/tests/tests.rs @@ -597,6 +597,7 @@ impl IsCyclesLedger for TestEnv { fn test_deposit_flow() { let env = TestEnv::setup(); let account0 = account(0, None); + let fee = env.icrc1_fee(); // 0.0 Check that the total supply is 0. assert_eq!(env.icrc1_total_supply(), 0u128); @@ -605,7 +606,7 @@ fn test_deposit_flow() { assert_eq!(env.icrc1_balance_of(account0), 0u128); // 1 Make the first deposit to the user and check the result. - let deposit_res = env.deposit(account0, 1_000_000_000, None); + let deposit_res = env.deposit(account0, 1_000_000_000 + fee, None); assert_eq!(deposit_res.block_index, Nat::from(0_u128)); assert_eq!(deposit_res.balance, Nat::from(1_000_000_000_u128)); @@ -622,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 0. - effective_fee: Some(0), + // 1.2.1 effective fee of mint blocks is the configured fee. + effective_fee: Some(fee), // 1.2.2 timestamp is set by the ledger. timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { @@ -645,7 +646,7 @@ fn test_deposit_flow() { // 2 Make another deposit to the user and check the result. let memo = Memo::from(vec![0xa, 0xb, 0xc, 0xd, 0xe, 0xf]); - let deposit_res = env.deposit(account0, 500_000_000, Some(memo.clone())); + let deposit_res = env.deposit(account0, 500_000_000 + fee, Some(memo.clone())); assert_eq!(deposit_res.block_index, Nat::from(1_u128)); assert_eq!(deposit_res.balance, Nat::from(1_500_000_000_u128)); @@ -662,8 +663,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 0. - effective_fee: Some(0), + // 2.2.1 effective fee of mint blocks is the configured fee. + effective_fee: Some(fee), // 2.2.2 timestamp is set by the ledger. timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { @@ -699,6 +700,7 @@ fn test_deposit_amount_below_fee() { #[test] fn test_withdraw_flow() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let account1_1 = account(1, Some(1)); let account1_2 = account(1, Some(2)); @@ -707,13 +709,13 @@ fn test_withdraw_flow() { let withdraw_receiver = env.state_machine.create_canister(None); // make deposits to the user and check the result - let deposit_res = env.deposit(account1, 1_000_000_000, None); + let deposit_res = env.deposit(account1, 1_000_000_000 + fee, None); assert_eq!(deposit_res.block_index, 0_u128); assert_eq!(deposit_res.balance, 1_000_000_000_u128); - let _deposit_res = env.deposit(account1_1, 1_000_000_000, None); - let _deposit_res = env.deposit(account1_2, 1_000_000_000, None); - let _deposit_res = env.deposit(account1_3, 1_000_000_000, None); - let _deposit_res = env.deposit(account1_4, 1_000_000_000, None); + let _deposit_res = env.deposit(account1_1, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_2, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_3, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_4, 1_000_000_000 + fee, None); let mut expected_total_supply = 5_000_000_000; assert_eq!(env.icrc1_total_supply(), expected_total_supply); @@ -885,11 +887,12 @@ fn test_withdraw_flow() { #[test] fn test_withdraw_duplicate() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let withdraw_receiver = env.state_machine.create_canister(None); // make deposits to the user and check the result - let deposit_res = env.deposit(account1, 1_000_000_000, None); + let deposit_res = env.deposit(account1, 1_000_000_000 + fee, None); assert_eq!(deposit_res.block_index, 0_u128); assert_eq!(deposit_res.balance, 1_000_000_000_u128); @@ -938,10 +941,11 @@ fn test_withdraw_duplicate() { #[test] fn test_withdraw_fails() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); // make the first deposit to the user and check the result - let deposit_res = env.deposit(account1, 1_000_000_000_000, None); + let deposit_res = env.deposit(account1, 1_000_000_000_000 + fee, None); assert_eq!(deposit_res.block_index, Nat::from(0_u128)); assert_eq!(deposit_res.balance, 1_000_000_000_000_u128); let blocks = env.icrc3_get_blocks(vec![(u64::MIN, u64::MAX)]).blocks; @@ -1085,7 +1089,7 @@ fn test_withdraw_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1109,7 +1113,7 @@ fn test_withdraw_fails() { // user keeps the cycles if they don't have enough balance to pay the fee let account2 = account(2, None); - let _deposit_res = env.deposit(account2, FEE + 1, None); + let _deposit_res = env.deposit(account2, 2 * FEE + 1, None); let blocks = env.get_all_blocks_with_ids(); let _withdraw_res = env .withdraw( @@ -1139,7 +1143,7 @@ fn test_withdraw_fails() { assert_vec_display_eq(blocks, env.get_all_blocks_with_ids()); // test withdraw deduplication - let _deposit_res = env.deposit(account2, FEE * 3, None); + let _deposit_res = env.deposit(account2, FEE * 4, None); let created_at_time = env.nanos_since_epoch_u64(); let args = WithdrawArgs { from_subaccount: None, @@ -1161,6 +1165,7 @@ fn test_withdraw_fails() { #[test] fn test_withdraw_from_flow() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let account1_1 = account(1, Some(1)); let account1_2 = account(1, Some(2)); @@ -1171,13 +1176,13 @@ fn test_withdraw_from_flow() { let withdraw_receiver = env.state_machine.create_canister(None); // make deposits to the user and check the result - let deposit_res = env.deposit(account1, 1_000_000_000, None); + let deposit_res = env.deposit(account1, 1_000_000_000 + fee, None); assert_eq!(deposit_res.block_index, 0_u128); assert_eq!(deposit_res.balance, 1_000_000_000_u128); - let _deposit_res = env.deposit(account1_1, 1_000_000_000, None); - let _deposit_res = env.deposit(account1_2, 1_000_000_000, None); - let _deposit_res = env.deposit(account1_3, 1_000_000_000, None); - let _deposit_res = env.deposit(account1_4, 1_000_000_000, None); + let _deposit_res = env.deposit(account1_1, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_2, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_3, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_4, 1_000_000_000 + fee, None); let mut expected_total_supply = 5_000_000_000; assert_eq!(env.icrc1_total_supply(), expected_total_supply); @@ -1478,6 +1483,7 @@ fn test_withdraw_from_flow() { #[test] fn test_withdraw_from_fails() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let withdrawer1 = account(101, None); let account1 = account(1, None); let account1_1 = account(1, Some(1)); @@ -1489,14 +1495,14 @@ fn test_withdraw_from_fails() { let account1_7 = account(1, Some(7)); // make the first deposit to the user and check the result - let _deposit_res = env.deposit(account1, 1_000_000_000_000, None); - let _deposit_res = env.deposit(account1_1, 1_000_000_000_000, None); - let _deposit_res = env.deposit(account1_2, 1_000_000_000_000, None); - let _deposit_res = env.deposit(account1_3, 1_000_000_000_000, None); - let _deposit_res = env.deposit(account1_4, 3 * FEE + 10_000, None); - let _deposit_res = env.deposit(account1_5, 1_000_000_000_000, None); - let _deposit_res = env.deposit(account1_6, 1_000_000_000_000, None); - let _deposit_res = env.deposit(account1_7, 2 * FEE + 10_000, None); + let _deposit_res = env.deposit(account1, 1_000_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_1, 1_000_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_2, 1_000_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_3, 1_000_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_4, 4 * FEE + 10_000, None); + let _deposit_res = env.deposit(account1_5, 1_000_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_6, 1_000_000_000_000 + fee, None); + let _deposit_res = env.deposit(account1_7, 3 * FEE + 10_000, None); let mut expected_total_supply = 6_000_500_020_000_u128; assert_eq!(env.icrc1_total_supply(), expected_total_supply); @@ -1734,7 +1740,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1854,7 +1860,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -1952,7 +1958,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -2085,7 +2091,7 @@ fn test_withdraw_from_fails() { id: Nat::from(blocks.len()) + 1u8, block: Block { phash: Some(burn_block.block.hash()), - effective_fee: Some(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -2204,12 +2210,13 @@ fn test_withdraw_from_fails() { #[test] fn test_approve_max_allowance_size() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let from = account(0, None); let spender = account(1, None); // Deposit funds assert_eq!( - env.deposit(from, 1_000_000_000, None).balance, + env.deposit(from, 1_000_000_000 + fee, None).balance, 1_000_000_000_u128 ); @@ -2239,11 +2246,12 @@ fn test_approve_max_allowance_size() { #[test] fn test_icrc2_approve_self() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let from = account(0, None); // Deposit funds assert_eq!( - env.deposit(from, 1_000_000_000, None).balance, + env.deposit(from, 1_000_000_000 + fee, None).balance, 1_000_000_000_u128 ); @@ -2275,12 +2283,13 @@ fn test_icrc2_approve_self() { #[test] fn test_icrc2_approve_cap() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let from = account(0, None); let spender = account(1, None); // Deposit funds assert_eq!( - env.deposit(from, 1_000_000_000, None).balance, + env.deposit(from, 1_000_000_000 + fee, None).balance, 1_000_000_000_u128 ); @@ -2311,12 +2320,13 @@ fn test_icrc2_approve_cap() { #[test] fn test_approve_duplicate() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let from = account(0, None); let spender = account(1, None); // Deposit funds assert_eq!( - env.deposit(from, 1_000_000_000, None).balance, + env.deposit(from, 1_000_000_000 + fee, None).balance, 1_000_000_000u128 ); @@ -2351,6 +2361,7 @@ fn test_approve_duplicate() { #[test] fn test_approval_expiring() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let from = account(0, None); let spender1 = account(1, None); let spender2 = account(2, None); @@ -2358,7 +2369,7 @@ fn test_approval_expiring() { // Deposit funds assert_eq!( - env.deposit(from, 1_000_000_000, None).balance, + env.deposit(from, 1_000_000_000 + fee, None).balance, 1_000_000_000_u128 ); @@ -2567,7 +2578,7 @@ fn test_icrc1_transfer_ok_with_params( let args_fee = set_fee.then_some(fee); let args_memo = set_memo.then_some(Memo::from(vec![1u8; 32])); - let _deposit_res = env.deposit(account_from, amount + fee, None); + let _deposit_res = env.deposit(account_from, amount + 2 * fee, None); // state that should change after the transfer is executed let account_from_balance_before = env.icrc1_balance_of(account_from); @@ -2736,7 +2747,7 @@ fn test_icrc1_transfer_invalid_fee(env: &TestEnv) { // deposit enough funds to account_to such that the transaction // should happen if correct - let _deposit_index = env.deposit(account_from, fee, None); + let _deposit_index = env.deposit(account_from, 2 * fee, None); let account_to_balance = env.icrc1_balance_of(account_to); let account_from_balance = env.icrc1_balance_of(account_from); @@ -2779,7 +2790,7 @@ fn test_icrc1_transfer_too_old(env: &TestEnv) { // deposit enough funds to account_to such that the transaction // would be accepted if created_at_time was correct - let _deposit_index = env.deposit(account_from, env.icrc1_fee(), None); + let _deposit_index = env.deposit(account_from, 2 * env.icrc1_fee(), None); let account_to_balance = env.icrc1_balance_of(account_to); let account_from_balance = env.icrc1_balance_of(account_from); @@ -2816,7 +2827,7 @@ fn test_icrc1_transfer_in_the_future(env: &TestEnv) { // deposit enough funds to account_to such that the transaction // would be accepted if created_at_time was correct - let _deposit_index = env.deposit(account_from, env.icrc1_fee(), None); + let _deposit_index = env.deposit(account_from, 2 * env.icrc1_fee(), None); let account_to_balance = env.icrc1_balance_of(account_to); let account_from_balance = env.icrc1_balance_of(account_from); @@ -2861,7 +2872,7 @@ fn test_icrc1_transfer_insufficient_funds_with_params( let fee = env.icrc1_fee(); // Deposit so that account_from has at least the fee in its account - let _deposit_index = env.deposit(account_from, fee, None); + let _deposit_index = env.deposit(account_from, 2 * fee, None); let account_from_balance = env.icrc1_balance_of(account_from); let account_to_balance = env.icrc1_balance_of(account_to); let total_supply = env.icrc1_total_supply(); @@ -2970,7 +2981,7 @@ fn test_icrc1_transfer_duplicate_with_params( } else { fee }; - let _deposit_index = env.deposit(account_from, deposit_amount, None); + let _deposit_index = env.deposit(account_from, deposit_amount + fee, None); let args = TransferArgs { from_subaccount: account_from.subaccount, @@ -3050,7 +3061,7 @@ fn test_icrc2_approve_ok_with_params( env.nanos_since_epoch_u64() + Duration::from_secs(24 * 60 * 60).as_nanos() as u64, ); - let _deposit_res = env.deposit(account_from, amount + fee, None); + let _deposit_res = env.deposit(account_from, amount + 2 * fee, None); // state that should change after the transfer is executed let account_from_balance_before = env.icrc1_balance_of(account_from); @@ -3368,7 +3379,7 @@ where // deposit enough funds to account_to such that the transaction // would be accepted if created_at_time was correct - let _deposit_index = env.deposit(account_from, env.icrc1_fee(), None); + let _deposit_index = env.deposit(account_from, 2 * env.icrc1_fee(), None); let account_spender_balance = env.icrc1_balance_of(account_spender); let account_from_balance = env.icrc1_balance_of(account_from); @@ -3498,7 +3509,7 @@ fn test_icrc2_approve_duplicate_with_params( // deposit enough funds to account_from so that two approves // could go through - let _deposit_index = env.deposit(account_from, 2 * fee, None); + let _deposit_index = env.deposit(account_from, 3 * fee, None); let current_allowance = env.icrc2_allowance(account_from, account_spender).allowance; let args = ApproveArgs { @@ -3601,7 +3612,7 @@ fn test_icrc2_transfer_from_ok_with_params( let args_memo = set_memo.then_some(Memo::from(vec![1u8; 32])); // deposit the fee for approve plus the fee + amount for the transfer - let _deposit_res = env.deposit(account_from, amount + 2 * fee, None); + let _deposit_res = env.deposit(account_from, amount + 3 * fee, None); // approve so that transfer_from can succeed let args = ApproveArgs { @@ -3703,7 +3714,7 @@ fn test_icrc2_transfer_from_invalid_fee(env: &TestEnv) { // deposit enough funds to account_to such that the transaction // should happen if correct - let _deposit_index = env.deposit(account_from, 2 * fee, None); + let _deposit_index = env.deposit(account_from, 3 * fee, None); let args = ApproveArgs { from_subaccount: account_from.subaccount, spender: account_spender, @@ -3765,7 +3776,7 @@ fn test_icrc2_transfer_from_too_old(env: &TestEnv) { // deposit enough funds to account_to such that the transaction // would be accepted if created_at_time was correct - let _deposit_index = env.deposit(account_from, 2 * fee, None); + let _deposit_index = env.deposit(account_from, 3 * fee, None); let args = ApproveArgs { from_subaccount: account_from.subaccount, spender: account_spender, @@ -3821,7 +3832,7 @@ fn test_icrc2_transfer_from_in_the_future(env: &TestEnv) { // deposit enough funds to account_to such that the transaction // would be accepted if created_at_time was correct - let _deposit_index = env.deposit(account_from, 2 * fee, None); + let _deposit_index = env.deposit(account_from, 3 * fee, None); let args = ApproveArgs { from_subaccount: account_from.subaccount, spender: account_spender, @@ -3880,7 +3891,7 @@ fn test_icrc2_transfer_from_insufficient_funds_with_params( let account_spender = account(4, None); let fee = env.icrc1_fee(); - let _deposit_index = env.deposit(account_from, 3 * fee, None); + let _deposit_index = env.deposit(account_from, 4 * fee, None); // remove the cycles from account_from minus the 2 fees needed // for the test let amount_to_remove = env.icrc1_balance_of(account_from).saturating_sub(3 * fee); @@ -4018,7 +4029,7 @@ fn test_icrc2_transfer_from_duplicate_with_params( } else { 3 * fee }; - let _deposit_index = env.deposit(account_from, amount_to_deposit, None); + let _deposit_index = env.deposit(account_from, amount_to_deposit + fee, None); // remove the cycles from account_from minus the fees needed // for the test @@ -4132,7 +4143,7 @@ fn test_icrc2_transfer_fails_if_approve_smaller_than_amount_plus_fee() { let account2 = account(2, None); let fee = env.icrc1_fee(); - let deposit_res = env.deposit(account1, 2 * fee, None); + let deposit_res = env.deposit(account1, 3 * fee, None); let approve_block_index = env.icrc2_approve_or_trap( account1.owner, ApproveArgs { @@ -4197,7 +4208,7 @@ fn test_deduplication() { let account2 = account(2, None); let fee = env.icrc1_fee(); - let deposit_res = env.deposit(account1, 2 * fee, None); + let deposit_res = env.deposit(account1, 3 * fee, None); let approve_block_index = env.icrc2_approve_or_trap( account1.owner, ApproveArgs { @@ -4258,6 +4269,7 @@ fn test_deduplication() { #[test] fn test_pruning_transactions() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let account2 = account(2, None); let transfer_amount = Nat::from(100_000_u128); @@ -4289,7 +4301,7 @@ fn test_pruning_transactions() { assert!(tx_hashes.is_empty()); let deposit_amount = 100_000_000_000; - env.deposit(account1, deposit_amount, None); + env.deposit(account1, deposit_amount + fee, None); // A deposit does not have a `created_at_time` argument and is therefore not recorded let tx_hashes = env.transaction_hashes(); @@ -4390,11 +4402,12 @@ fn test_pruning_transactions() { #[test] fn test_total_supply_after_upgrade() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let account2 = account(2, None); - env.deposit(account1, 2_000_000_000, None); - env.deposit(account2, 3_000_000_000, None); + env.deposit(account1, 2_000_000_000 + fee, None); + env.deposit(account2, 3_000_000_000 + fee, None); let fee = env.icrc1_fee(); let _block_index = env .icrc1_transfer( @@ -4448,6 +4461,7 @@ fn test_icrc3_get_blocks() { } let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let get_blocks_res = env.icrc3_get_blocks(vec![(0u64, 10u64)]); assert_eq!(get_blocks_res.log_length, 0_u128); @@ -4459,7 +4473,7 @@ fn test_icrc3_get_blocks() { let account3 = account(3, None); // add the first mint block - env.deposit(account1, 5_000_000_000, None); + env.deposit(account1, 5_000_000_000 + fee, None); let get_blocks_res = env.icrc3_get_blocks(vec![(0u64, 10u64)]); assert_eq!(get_blocks_res.log_length, 1_u128); @@ -4483,7 +4497,7 @@ fn test_icrc3_get_blocks() { env.validate_certificate(0, block0.hash().unwrap()); // add a second mint block - env.deposit(account2, 3_000_000_000, None); + env.deposit(account2, 3_000_000_000 + fee, None); let get_blocks_res = env.icrc3_get_blocks(vec![(0u64, 10u64)]); assert_eq!(get_blocks_res.log_length, 2_u128); @@ -4695,16 +4709,8 @@ fn block( phash: Option<[u8; 32]>, ) -> Block { let effective_fee = match operation { - Burn { .. } => Some(FEE), - Mint { .. } => Some(0), - Transfer { fee, .. } => { - if fee.is_none() { - Some(FEE) - } else { - None - } - } - Approve { fee, .. } => { + Burn { .. } | Mint { .. } => Some(FEE), + Transfer { fee, .. } | Approve { fee, .. } => { if fee.is_none() { Some(FEE) } else { @@ -4736,13 +4742,14 @@ fn test_get_blocks_max_length() { max_blocks_per_request: MAX_BLOCKS_PER_REQUEST, index_id: None, }); + let fee = env.icrc1_fee(); let account10 = account(10, None); - let _deposit_res = env.deposit(account10, 1_000_000_000, None); - let _deposit_res = env.deposit(account10, 2_000_000_000, None); - let _deposit_res = env.deposit(account10, 3_000_000_000, None); - let _deposit_res = env.deposit(account10, 4_000_000_000, None); - let _deposit_res = env.deposit(account10, 5_000_000_000, None); + let _deposit_res = env.deposit(account10, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 2_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 3_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 4_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 5_000_000_000 + fee, None); let res = env.icrc3_get_blocks(vec![(0, u64::MAX)]); assert_eq!(MAX_BLOCKS_PER_REQUEST, res.blocks.len() as u64); @@ -4757,13 +4764,14 @@ fn test_get_blocks_max_length() { #[test] fn test_set_max_blocks_per_request_in_upgrade() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account10 = account(10, None); - let _deposit_res = env.deposit(account10, 1_000_000_000, None); - let _deposit_res = env.deposit(account10, 2_000_000_000, None); - let _deposit_res = env.deposit(account10, 3_000_000_000, None); - let _deposit_res = env.deposit(account10, 4_000_000_000, None); - let _deposit_res = env.deposit(account10, 5_000_000_000, None); + let _deposit_res = env.deposit(account10, 1_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 2_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 3_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 4_000_000_000 + fee, None); + let _deposit_res = env.deposit(account10, 5_000_000_000 + fee, None); let res = env.icrc3_get_blocks(vec![(0, u64::MAX)]); assert_eq!(5, res.blocks.len() as u64); @@ -4839,10 +4847,11 @@ fn test_change_index_id() { #[tokio::test] async fn test_icrc1_test_suite() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account10 = account(10, None); // make the first deposit to the user and check the result - let deposit_res = env.deposit(account10, 1_000_000_000_000_000, None); + let deposit_res = env.deposit(account10, 1_000_000_000_000_000 + fee, None); assert_eq!(deposit_res.block_index, Nat::from(0_u128)); assert_eq!(deposit_res.balance, 1_000_000_000_000_000_u128); assert_eq!(1_000_000_000_000_000, env.icrc1_balance_of(account10)); @@ -4945,7 +4954,13 @@ fn test_create_canister() { let mut expected_balance = 1_000_000_000_000_000_u128; // make the first deposit to the user and check the result - let deposit_res = deposit(&env, depositor_id, account10_0, expected_balance, None); + let deposit_res = deposit( + &env, + depositor_id, + account10_0, + expected_balance + config::FEE, + None, + ); assert_eq!(deposit_res.block_index, Nat::from(0_u128)); assert_eq!(deposit_res.balance, expected_balance); assert_eq!( @@ -5293,7 +5308,13 @@ fn test_create_canister_duplicate() { let mut expected_balance = 1_500_000_000_000_u128; // make the first deposit to the user and check the result - let deposit_res = deposit(&env, depositor_id, account10_0, expected_balance, None); + let deposit_res = deposit( + &env, + depositor_id, + account10_0, + expected_balance + config::FEE, + None, + ); assert_eq!(deposit_res.block_index, Nat::from(0u128)); assert_eq!(deposit_res.balance, expected_balance); assert_eq!( @@ -5353,9 +5374,10 @@ fn test_create_canister_duplicate() { #[test] fn test_create_canister_fail() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); - let _ = env.deposit(account1, 1_000_000_000_000_000_000, None); + let _ = env.deposit(account1, 1_000_000_000_000_000_000 + fee, None); let mut expected_total_supply = env.icrc1_total_supply(); let blocks = env.get_all_blocks_with_ids(); @@ -5484,7 +5506,7 @@ fn test_create_canister_fail() { id: Nat::from(blocks.len() + 1), block: Block { phash: Some(burn_block.block.clone().hash()), - effective_fee: Some(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -5509,6 +5531,7 @@ fn test_create_canister_fail() { fn test_create_canister_from() { const CREATE_CANISTER_CYCLES: u128 = 1_000_000_000_000; let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let account1_1 = account(1, Some(1)); let account1_2 = account(1, Some(2)); @@ -5517,10 +5540,10 @@ fn test_create_canister_from() { let withdrawer1_1 = account(102, Some(1)); // make deposits to the user and check the result - let _deposit_res = env.deposit(account1, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_1, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_2, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_3, 100 * CREATE_CANISTER_CYCLES, None); + let _deposit_res = env.deposit(account1, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_1, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_2, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_3, 100 * CREATE_CANISTER_CYCLES + fee, None); let mut expected_total_supply = 400 * CREATE_CANISTER_CYCLES; assert_eq!(env.icrc1_total_supply(), expected_total_supply); @@ -5882,6 +5905,7 @@ fn test_create_canister_from() { fn test_create_canister_from_fail() { const CREATE_CANISTER_CYCLES: u128 = 1_000_000_000_000; let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let withdrawer1 = account(101, None); let account1 = account(1, None); let account1_1 = account(1, Some(1)); @@ -5893,14 +5917,14 @@ fn test_create_canister_from_fail() { let account1_7 = account(1, Some(7)); // make the first deposit to the user and check the result - let _deposit_res = env.deposit(account1, CREATE_CANISTER_CYCLES / 2, None); - let _deposit_res = env.deposit(account1_1, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_2, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_3, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_4, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_5, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_6, 100 * CREATE_CANISTER_CYCLES, None); - let _deposit_res = env.deposit(account1_7, 100 * CREATE_CANISTER_CYCLES, None); + let _deposit_res = env.deposit(account1, CREATE_CANISTER_CYCLES / 2 + fee, None); + let _deposit_res = env.deposit(account1_1, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_2, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_3, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_4, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_5, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_6, 100 * CREATE_CANISTER_CYCLES + fee, None); + let _deposit_res = env.deposit(account1_7, 100 * CREATE_CANISTER_CYCLES + fee, None); let mut expected_total_supply = env.icrc1_total_supply(); // create with more than available in account @@ -6107,7 +6131,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(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -6208,7 +6232,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(0), + effective_fee: Some(config::FEE), timestamp: env.nanos_since_epoch_u64(), transaction: Transaction { created_at_time: None, @@ -6502,8 +6526,9 @@ fn test_deposit_invalid_memo() { #[should_panic(expected = "memo length exceeds the maximum")] fn test_icrc1_transfer_invalid_memo() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); - let _deposit_res = env.deposit(account1, 1_000_000_000, None); + let _deposit_res = env.deposit(account1, 1_000_000_000 + fee, None); // Attempt icrc1_transfer with memo exceeding `MAX_MEMO_LENGTH`. This call should panic. let _res = env.icrc1_transfer( @@ -6523,8 +6548,9 @@ fn test_icrc1_transfer_invalid_memo() { #[should_panic(expected = "memo length exceeds the maximum")] fn test_approve_invalid_memo() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); - let _deposit_res = env.deposit(account1, 1_000_000_000, None); + let _deposit_res = env.deposit(account1, 1_000_000_000 + fee, None); // Attempt approve with memo exceeding `MAX_MEMO_LENGTH`. This call should panic. let _approve_res = env.icrc2_approve( @@ -6546,10 +6572,11 @@ fn test_approve_invalid_memo() { #[should_panic(expected = "memo length exceeds the maximum")] fn test_icrc2_transfer_from_invalid_memo() { let env = TestEnv::setup(); + let fee = env.icrc1_fee(); let account1 = account(1, None); let account2 = account(2, None); let deposit_amount = 10_000_000_000; - let _deposit_res = env.deposit(account1, deposit_amount, None); + let _deposit_res = env.deposit(account1, deposit_amount + fee, None); let _block_index = env .icrc2_approve( From bc41828a11cc166a1fbafa1418628b3f793f4343 Mon Sep 17 00:00:00 2001 From: Mario Pastorelli Date: Thu, 21 Mar 2024 15:55:44 +0000 Subject: [PATCH 2/4] add fee to Mint operation --- cycles-ledger/src/storage.rs | 64 +++++++++++++++++------------------- cycles-ledger/tests/tests.rs | 41 +++++++++++++++-------- 2 files changed, 58 insertions(+), 47 deletions(-) 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, }, }, } From 8a65f3e4307ae3590daf3833b3845ee29cbe01b6 Mon Sep 17 00:00:00 2001 From: MarioDfinity <93518022+MarioDfinity@users.noreply.github.com> Date: Thu, 21 Mar 2024 18:19:07 +0100 Subject: [PATCH 3/4] Update cycles-ledger/src/storage.rs Co-authored-by: Thomas Locher --- cycles-ledger/src/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cycles-ledger/src/storage.rs b/cycles-ledger/src/storage.rs index 6bc7322..35ee215 100644 --- a/cycles-ledger/src/storage.rs +++ b/cycles-ledger/src/storage.rs @@ -122,7 +122,7 @@ pub enum Operation { to: Account, amount: u128, // Custom non-standard fee to record the amount - // of cycles "burn" when cycles are deposited, i.e. + // of cycles "burned" 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. From a52f95122a0d08573ef9c4b9c0c68df3ec8ccb5c Mon Sep 17 00:00:00 2001 From: Mario Pastorelli Date: Mon, 25 Mar 2024 09:00:51 +0000 Subject: [PATCH 4/4] prevent minting 0 tokens --- cycles-ledger/src/storage.rs | 30 ++++++++++++++++++++---------- cycles-ledger/tests/tests.rs | 14 ++++++++++++-- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/cycles-ledger/src/storage.rs b/cycles-ledger/src/storage.rs index 35ee215..60bc604 100644 --- a/cycles-ledger/src/storage.rs +++ b/cycles-ledger/src/storage.rs @@ -737,17 +737,27 @@ pub fn deposit( memo: Option, now: u64, ) -> anyhow::Result { - // check that the amount is at least the fee - if amount < crate::config::FEE { - bail!( - "The requested amount {} to be deposited is \ - less than the cycles ledger fee: {}", - amount, - crate::config::FEE - ) - } + // check that the amount is at least the fee plus one + let amount_to_mint = match amount.checked_sub(crate::config::FEE) { + None => { + bail!( + "The requested amount {} to be deposited is \ + less than the cycles ledger fee: {}", + amount, + crate::config::FEE + ) + } + Some(0) => { + bail!( + "Cannot deposit 0 cycles (amount: {}, cycles ledger fee: {})", + amount, + crate::config::FEE + ) + } + Some(amount_to_mint) => amount_to_mint, + }; - let block_index = mint(to, amount - crate::config::FEE, memo, now)?; + let block_index = mint(to, amount_to_mint, memo, now)?; prune(now); diff --git a/cycles-ledger/tests/tests.rs b/cycles-ledger/tests/tests.rs index 2799a1e..0d30c36 100644 --- a/cycles-ledger/tests/tests.rs +++ b/cycles-ledger/tests/tests.rs @@ -696,9 +696,19 @@ fn test_deposit_amount_below_fee() { // Attempt to deposit fewer than [config::FEE] cycles. This call should panic. let _deposit_result = env.deposit(account1, config::FEE - 1, None); +} + +#[test] +#[should_panic] +fn test_deposit_amount_same_as_fee() { + let env = TestEnv::setup(); + let account1 = account(1, None); - // check that no new block was created - assert_eq!(Nat::from(0u8), env.number_of_blocks()); + // The amount of cycles minted is the cycles attached - fee. + // If the amount of cycles attached is equal to the fee then + // the endpoint should panic because minting 0 cycles is + // forbidden. + let _deposit_result = env.deposit(account1, config::FEE, None); } #[test]