From ff776f0be638acf746ac8cc07f05fd08aa0195d2 Mon Sep 17 00:00:00 2001 From: aie0 <149175774+aie0@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:05:32 +0200 Subject: [PATCH] Payouts random tests (#213) --- pallets/ddc-payouts/src/benchmarking.rs | 4 +- pallets/ddc-payouts/src/lib.rs | 14 +- pallets/ddc-payouts/src/mock.rs | 34 +++- pallets/ddc-payouts/src/tests.rs | 238 +++++++++++++++++++++++- 4 files changed, 271 insertions(+), 19 deletions(-) diff --git a/pallets/ddc-payouts/src/benchmarking.rs b/pallets/ddc-payouts/src/benchmarking.rs index 80c6528cb..3967a85cd 100644 --- a/pallets/ddc-payouts/src/benchmarking.rs +++ b/pallets/ddc-payouts/src/benchmarking.rs @@ -350,8 +350,8 @@ benchmarks! { let total_node_usage = NodeUsage { transferred_bytes: 200000000u64.saturating_mul(b.into()), // 200 mb per provider stored_bytes: 100000000u64.saturating_mul(b.into()), // 100 mb per provider - number_of_gets: 10u128.saturating_mul(b.into()), // 10 gets per provider - number_of_puts: 5u128.saturating_mul(b.into()), // 5 puts per provider + number_of_gets: 10u64.saturating_mul(b.into()), // 10 gets per provider + number_of_puts: 10u64.saturating_mul(b.into()), // 5 puts per provider }; let charging_max_batch_index = 0; let mut charging_processed_batches : BoundedBTreeSet = BoundedBTreeSet::default(); diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index 7c329162f..a883a27f3 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -53,8 +53,8 @@ type BatchIndex = u16; pub struct CustomerUsage { pub transferred_bytes: u64, pub stored_bytes: u64, - pub number_of_puts: u128, - pub number_of_gets: u128, + pub number_of_puts: u64, + pub number_of_gets: u64, } /// Stores usage of node provider @@ -62,8 +62,8 @@ pub struct CustomerUsage { pub struct NodeUsage { pub transferred_bytes: u64, pub stored_bytes: u64, - pub number_of_puts: u128, - pub number_of_gets: u128, + pub number_of_puts: u64, + pub number_of_gets: u64, } /// Stores reward in tokens(units) of node provider as per NodeUsage @@ -951,13 +951,11 @@ pub mod pallet { })() .ok_or(Error::::ArithmeticOverflow)?; - total.gets = usage - .number_of_gets + total.gets = (usage.number_of_gets as u128) .checked_mul(pricing.unit_per_get_request) .ok_or(Error::::ArithmeticOverflow)?; - total.puts = usage - .number_of_puts + total.puts = (usage.number_of_puts as u128) .checked_mul(pricing.unit_per_put_request) .ok_or(Error::::ArithmeticOverflow)?; diff --git a/pallets/ddc-payouts/src/mock.rs b/pallets/ddc-payouts/src/mock.rs index e81e40e08..8230c6508 100644 --- a/pallets/ddc-payouts/src/mock.rs +++ b/pallets/ddc-payouts/src/mock.rs @@ -5,7 +5,7 @@ use crate::{self as pallet_ddc_payouts, *}; use ddc_primitives::{ ClusterBondingParams, ClusterFeesParams, ClusterGovParams, ClusterParams, ClusterPricingParams, - NodeType, + NodeType, DOLLARS, }; use ddc_traits::{ cluster::{ClusterCreator, ClusterVisitor, ClusterVisitorError}, @@ -16,7 +16,7 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU32, ConstU64, Everything}, + traits::{ConstU32, ConstU64, Everything, Randomness}, weights::constants::RocksDbWeight, PalletId, }; @@ -59,6 +59,21 @@ parameter_types! { pub static ExistentialDeposit: Balance = 1; } +#[derive(Default, Clone)] +pub struct MockRandomness(H256); + +impl Randomness for MockRandomness { + fn random(subject: &[u8]) -> (H256, BlockNumber) { + let (mut r, b) = Self::random_seed(); + r.as_mut()[0..subject.len()].copy_from_slice(subject); + (r, b) + } + + fn random_seed() -> (H256, BlockNumber) { + (H256::default(), BlockNumber::default()) + } +} + impl frame_system::Config for Test { type BaseCallFilter = Everything; type BlockWeights = (); @@ -204,6 +219,7 @@ pub const USER3_BALANCE: u128 = 1000; pub const FREE_CLUSTER_ID: ClusterId = ClusterId::zero(); pub const ONE_CLUSTER_ID: ClusterId = ClusterId::repeat_byte(5u8); +pub const CERE_CLUSTER_ID: ClusterId = ClusterId::repeat_byte(10u8); pub const PRICING_PARAMS: ClusterPricingParams = ClusterPricingParams { unit_per_mb_streamed: 2_000_000, @@ -219,6 +235,13 @@ pub const PRICING_PARAMS_ONE: ClusterPricingParams = ClusterPricingParams { unit_per_get_request: 10_000_000_000, }; +pub const PRICING_PARAMS_CERE: ClusterPricingParams = ClusterPricingParams { + unit_per_mb_streamed: DOLLARS, + unit_per_mb_stored: DOLLARS, + unit_per_put_request: DOLLARS, + unit_per_get_request: DOLLARS, +}; + pub const PRICING_FEES: ClusterFeesParams = ClusterFeesParams { treasury_share: Perquintill::from_percent(1), validators_share: Perquintill::from_percent(10), @@ -315,7 +338,10 @@ impl SortedListProvider for TestValidator } pub fn get_fees(cluster_id: &ClusterId) -> ClusterFeesParams { - if *cluster_id == FREE_CLUSTER_ID || *cluster_id == ONE_CLUSTER_ID { + if *cluster_id == FREE_CLUSTER_ID || + *cluster_id == ONE_CLUSTER_ID || + *cluster_id == CERE_CLUSTER_ID + { PRICING_FEES_ZERO } else { PRICING_FEES @@ -325,6 +351,8 @@ pub fn get_fees(cluster_id: &ClusterId) -> ClusterFeesParams { pub fn get_pricing(cluster_id: &ClusterId) -> ClusterPricingParams { if *cluster_id == FREE_CLUSTER_ID || *cluster_id == ONE_CLUSTER_ID { PRICING_PARAMS_ONE + } else if *cluster_id == CERE_CLUSTER_ID { + PRICING_PARAMS_CERE } else { PRICING_PARAMS } diff --git a/pallets/ddc-payouts/src/tests.rs b/pallets/ddc-payouts/src/tests.rs index 3aa413af1..d5f70e8ea 100644 --- a/pallets/ddc-payouts/src/tests.rs +++ b/pallets/ddc-payouts/src/tests.rs @@ -2,8 +2,10 @@ use super::{mock::*, *}; use ddc_primitives::ClusterId; -use frame_support::{assert_noop, assert_ok, error::BadOrigin}; +use frame_support::{assert_noop, assert_ok, error::BadOrigin, traits::Randomness}; +use sp_core::H256; use sp_runtime::Perquintill; + #[test] fn set_authorised_caller_works() { ExtBuilder.build_and_execute(|| { @@ -265,8 +267,8 @@ fn calculate_charge_parts(cluster_id: ClusterId, usage: CustomerUsage) -> Custom byte_unit::MEBIBYTE, storage: (pricing_params.unit_per_mb_stored * usage.stored_bytes as u128) / byte_unit::MEBIBYTE, - puts: pricing_params.unit_per_put_request * usage.number_of_puts, - gets: pricing_params.unit_per_get_request * usage.number_of_gets, + puts: pricing_params.unit_per_put_request * (usage.number_of_puts as u128), + gets: pricing_params.unit_per_get_request * (usage.number_of_gets as u128), } } @@ -1461,7 +1463,7 @@ fn send_rewarding_providers_batch_works() { } #[test] -fn send_rewarding_providers_batch_100nodes_small_usage_works() { +fn send_rewarding_providers_batch_100_nodes_small_usage_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); @@ -1713,7 +1715,7 @@ fn send_rewarding_providers_batch_100nodes_small_usage_works() { } #[test] -fn send_rewarding_providers_batch_100nodes_large_usage_works() { +fn send_rewarding_providers_batch_100_nodes_large_usage_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); @@ -1978,7 +1980,7 @@ fn send_rewarding_providers_batch_100nodes_large_usage_works() { } #[test] -fn send_rewarding_providers_batch_100nodes_small_large_usage_works() { +fn send_rewarding_providers_batch_100_nodes_small_large_usage_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); @@ -2242,6 +2244,230 @@ fn send_rewarding_providers_batch_100nodes_small_large_usage_works() { }) } +fn generate_random_u64>(_: &T, min: u64, max: u64) -> u64 { + let (random_seed, _) = T::random_seed(); + let random_raw = u64::from_be_bytes(random_seed.as_bytes()[0..8].try_into().unwrap()); + + min.saturating_add(random_raw % (max.saturating_sub(min).saturating_add(1))) +} + +#[test] +fn send_rewarding_providers_batch_100_nodes_random_usage_works() { + ExtBuilder.build_and_execute(|| { + System::set_block_number(1); + + let mock_randomness = MockRandomness::default(); + let min: u64 = 1024; + let max: u64 = 1024 * 1024; + let num_nodes = 100; + let num_users = 100; + let dac_account = 123u128; + let bank = 1u128; + let cluster_id = CERE_CLUSTER_ID; + let era = 100; + let user_batch_size = 10; + let node_batch_size = 10; + let mut batch_user_index = 0; + let mut batch_node_index = 0; + let mut payees: Vec> = Vec::new(); + let mut node_batch: Vec<(u128, NodeUsage)> = Vec::new(); + let mut total_nodes_usage = NodeUsage::default(); + for i in 10..10 + num_nodes { + let node_usage = NodeUsage { + transferred_bytes: generate_random_u64(&mock_randomness, min, max), + stored_bytes: generate_random_u64(&mock_randomness, min, max), + number_of_puts: generate_random_u64(&mock_randomness, min, max), + number_of_gets: generate_random_u64(&mock_randomness, min, max), + }; + + total_nodes_usage.transferred_bytes += node_usage.transferred_bytes; + total_nodes_usage.stored_bytes += node_usage.stored_bytes; + total_nodes_usage.number_of_puts += node_usage.number_of_puts; + total_nodes_usage.number_of_gets += node_usage.number_of_gets; + + node_batch.push((i, node_usage)); + if node_batch.len() == node_batch_size { + payees.push(node_batch.clone()); + node_batch.clear(); + } + } + if !node_batch.is_empty() { + payees.push(node_batch.clone()); + } + + let mut total_charge = 0u128; + let mut payers: Vec> = Vec::new(); + let mut user_batch: Vec<(u128, CustomerUsage)> = Vec::new(); + for user_id in 1000..1000 + num_users { + let user_usage = CustomerUsage { + transferred_bytes: generate_random_u64(&mock_randomness, min, max), + stored_bytes: generate_random_u64(&mock_randomness, min, max), + number_of_puts: generate_random_u64(&mock_randomness, min, max), + number_of_gets: generate_random_u64(&mock_randomness, min, max), + }; + + let expected_charge = calculate_charge(cluster_id, user_usage.clone()); + Balances::transfer( + RuntimeOrigin::signed(bank), + user_id, + (expected_charge * 2).max(Balances::minimum_balance()), + ) + .unwrap(); + total_charge += expected_charge; + + user_batch.push((user_id, user_usage)); + if user_batch.len() == user_batch_size { + payers.push(user_batch.clone()); + user_batch.clear(); + } + } + if !user_batch.is_empty() { + payers.push(user_batch.clone()); + } + + assert_ok!(DdcPayouts::set_authorised_caller(RuntimeOrigin::root(), dac_account)); + assert_ok!(DdcPayouts::begin_billing_report( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + )); + assert_ok!(DdcPayouts::begin_charging_customers( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + (payers.len() - 1) as u16, + )); + + for batch in payers.iter() { + assert_ok!(DdcPayouts::send_charging_customers_batch( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + batch_user_index, + batch.to_vec(), + )); + + for (customer_id, usage) in batch.iter() { + let charge = calculate_charge(cluster_id, usage.clone()); + + System::assert_has_event( + Event::Charged { + cluster_id, + era, + customer_id: *customer_id, + batch_index: batch_user_index, + amount: charge, + } + .into(), + ); + } + batch_user_index += 1; + } + + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let balance1 = Balances::free_balance(report_before.vault); + let balance2 = Balances::free_balance(DdcPayouts::account_id()); + assert_eq!(balance1, balance2); + assert_eq!(report_before.vault, DdcPayouts::account_id()); + assert_eq!(balance1 - Balances::minimum_balance(), total_charge); + + assert_ok!(DdcPayouts::end_charging_customers( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + )); + + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) + .left_from_one(); + + let total_charge = report_after.total_customer_charge.transfer + + report_before.total_customer_charge.storage + + report_before.total_customer_charge.puts + + report_before.total_customer_charge.gets; + let balance_after = Balances::free_balance(DdcPayouts::account_id()); + assert_eq!(total_charge, balance_after - Balances::minimum_balance()); + + assert_eq!( + report_after.total_customer_charge.transfer, + total_left_from_one * report_before.total_customer_charge.transfer + ); + assert_eq!( + report_after.total_customer_charge.storage, + total_left_from_one * report_before.total_customer_charge.storage + ); + assert_eq!( + report_after.total_customer_charge.puts, + total_left_from_one * report_before.total_customer_charge.puts + ); + assert_eq!( + report_after.total_customer_charge.gets, + total_left_from_one * report_before.total_customer_charge.gets + ); + + assert_ok!(DdcPayouts::begin_rewarding_providers( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + (payees.len() - 1) as u16, + total_nodes_usage.clone(), + )); + + for batch in payees.iter() { + let before_batch = Balances::free_balance(DdcPayouts::account_id()); + assert_ok!(DdcPayouts::send_rewarding_providers_batch( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + batch_node_index, + batch.to_vec(), + )); + + let mut batch_charge = 0; + for (node1, node_usage1) in batch.iter() { + let ratio1_transfer = Perquintill::from_rational( + node_usage1.transferred_bytes, + total_nodes_usage.transferred_bytes, + ); + let transfer_charge = ratio1_transfer * report_after.total_customer_charge.transfer; + + let ratio1_storage = Perquintill::from_rational( + node_usage1.stored_bytes, + total_nodes_usage.stored_bytes, + ); + let storage_charge = ratio1_storage * report_after.total_customer_charge.storage; + + let ratio1_puts = Perquintill::from_rational( + node_usage1.number_of_puts, + total_nodes_usage.number_of_puts, + ); + let puts_charge = ratio1_puts * report_after.total_customer_charge.puts; + + let ratio1_gets = Perquintill::from_rational( + node_usage1.number_of_gets, + total_nodes_usage.number_of_gets, + ); + let gets_charge = ratio1_gets * report_after.total_customer_charge.gets; + + let balance_node1 = Balances::free_balance(node1); + assert!( + (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 < + MAX_DUST.into() + ); + + batch_charge += transfer_charge + storage_charge + puts_charge + gets_charge; + } + let after_batch = Balances::free_balance(DdcPayouts::account_id()); + assert!(batch_charge + after_batch - before_batch < MAX_DUST.into()); + + batch_node_index += 1; + } + assert!(Balances::free_balance(DdcPayouts::account_id()) < MAX_DUST.into()); + }) +} + #[test] fn end_rewarding_providers_fails_uninitialised() { ExtBuilder.build_and_execute(|| {