diff --git a/node/service/src/chain_spec.rs b/node/service/src/chain_spec.rs index cceef05ee..4aaf48b3f 100644 --- a/node/service/src/chain_spec.rs +++ b/node/service/src/chain_spec.rs @@ -237,6 +237,7 @@ pub fn cere_dev_genesis( nomination_pools: Default::default(), ddc_clusters: Default::default(), ddc_nodes: Default::default(), + ddc_payouts: Default::default(), } } diff --git a/pallets/ddc-customers/src/lib.rs b/pallets/ddc-customers/src/lib.rs index edbe62214..191af3ecc 100644 --- a/pallets/ddc-customers/src/lib.rs +++ b/pallets/ddc-customers/src/lib.rs @@ -217,13 +217,14 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { + pub feeder_account: Option, pub buckets: Vec<(ClusterId, T::AccountId, BalanceOf)>, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - GenesisConfig { buckets: Default::default() } + GenesisConfig { feeder_account: None, buckets: Default::default() } } } @@ -232,8 +233,19 @@ pub mod pallet { fn build(&self) { let account_id = >::account_id(); let min = ::Currency::minimum_balance(); - if ::Currency::free_balance(&account_id) < min { - let _ = ::Currency::make_free_balance_be(&account_id, min); + + let balance = ::Currency::free_balance(&account_id); + if balance < min { + if let Some(vault) = &self.feeder_account { + let _ = ::Currency::transfer( + vault, + &account_id, + min - balance, + ExistenceRequirement::AllowDeath, + ); + } else { + let _ = ::Currency::make_free_balance_be(&account_id, min); + } } for &(ref cluster_id, ref owner_id, ref deposit) in &self.buckets { @@ -438,7 +450,7 @@ pub mod pallet { old_total.checked_sub(&ledger.total).ok_or(Error::::ArithmeticUnderflow)?; ::Currency::transfer( - &Self::sub_account_id(&owner), + &Self::account_id(), &owner, value, ExistenceRequirement::AllowDeath, @@ -471,7 +483,7 @@ pub mod pallet { ) -> DispatchResult { ::Currency::transfer( owner, - &Self::sub_account_id(owner), + &Self::account_id(), ledger.total, ExistenceRequirement::AllowDeath, )?; @@ -570,7 +582,7 @@ pub mod pallet { } ::Currency::transfer( - &Self::sub_account_id(&content_owner), + &Self::account_id(), &billing_vault, actually_charged, ExistenceRequirement::AllowDeath, diff --git a/pallets/ddc-customers/src/mock.rs b/pallets/ddc-customers/src/mock.rs index eb25c239c..d9e847eb0 100644 --- a/pallets/ddc-customers/src/mock.rs +++ b/pallets/ddc-customers/src/mock.rs @@ -11,7 +11,7 @@ use ddc_traits::cluster::{ use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU32, ConstU64, Everything}, + traits::{ConstU32, ConstU64, Everything, GenesisBuild}, weights::constants::RocksDbWeight, }; use frame_system::mocking::{MockBlock, MockUncheckedExtrinsic}; @@ -238,11 +238,17 @@ impl ExtBuilder { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let _ = pallet_balances::GenesisConfig:: { + let _balance_genesis = pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 1000)], } .assimilate_storage(&mut storage); + let _customer_genesis = pallet_ddc_customers::GenesisConfig:: { + feeder_account: None, + buckets: Default::default(), + } + .assimilate_storage(&mut storage); + TestExternalities::new(storage) } pub fn build_and_execute(self, test: impl FnOnce()) { diff --git a/pallets/ddc-customers/src/tests.rs b/pallets/ddc-customers/src/tests.rs index a90d43482..c378a3961 100644 --- a/pallets/ddc-customers/src/tests.rs +++ b/pallets/ddc-customers/src/tests.rs @@ -142,8 +142,8 @@ fn charge_content_owner_works() { let balance_after_deposit = Balances::free_balance(account_3); assert_eq!(balance_before_deposit - deposit, balance_after_deposit); - let pallet_balance = Balances::free_balance(DdcCustomers::sub_account_id(&account_3)); - assert_eq!(deposit, pallet_balance); + let pallet_balance = Balances::free_balance(DdcCustomers::account_id()); + assert_eq!(deposit, pallet_balance - Balances::minimum_balance()); // Check storage assert_eq!( @@ -170,8 +170,7 @@ fn charge_content_owner_works() { let account_balance = Balances::free_balance(account_3); assert_eq!(balance_after_deposit, account_balance); - let pallet_balance_after_charge = - Balances::free_balance(DdcCustomers::sub_account_id(&account_3)); + let pallet_balance_after_charge = Balances::free_balance(DdcCustomers::account_id()); assert_eq!(pallet_balance - charged, pallet_balance_after_charge); // Check storage @@ -198,7 +197,10 @@ fn charge_content_owner_works() { }) ); - assert_eq!(0, Balances::free_balance(DdcCustomers::sub_account_id(&account_3))); + assert_eq!( + 0, + Balances::free_balance(DdcCustomers::account_id()) - Balances::minimum_balance() + ); assert_eq!(charge_result, deposit - charge1); assert_ok!(DdcCustomers::deposit_extra(RuntimeOrigin::signed(account_3), deposit)); @@ -212,7 +214,10 @@ fn charge_content_owner_works() { }) ); - assert_eq!(deposit, Balances::free_balance(DdcCustomers::sub_account_id(&account_3))); + assert_eq!( + deposit, + Balances::free_balance(DdcCustomers::account_id()) - Balances::minimum_balance() + ); }) } diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index d61d22059..42ed6080f 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -240,6 +240,11 @@ pub mod pallet { pub type DebtorCustomers = StorageDoubleMap<_, Blake2_128Concat, ClusterId, Blake2_128Concat, T::AccountId, u128>; + #[pallet::storage] + #[pallet::getter(fn owing_providers)] + pub type OwingProviders = + StorageDoubleMap<_, Blake2_128Concat, ClusterId, Blake2_128Concat, T::AccountId, u128>; + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] #[scale_info(skip_type_params(T))] pub struct BillingReport { @@ -316,13 +321,11 @@ pub mod pallet { Error::::NotExpectedState ); - let mut billing_report = BillingReport:: { - vault: Self::sub_account_id(cluster_id, era), + let billing_report = BillingReport:: { + vault: Self::account_id(), state: State::Initialized, ..Default::default() }; - billing_report.vault = Self::sub_account_id(cluster_id, era); - billing_report.state = State::Initialized; ActiveBillingReports::::insert(cluster_id, era, billing_report); Self::deposit_event(Event::::BillingReportInitialized { cluster_id, era }); @@ -398,12 +401,6 @@ pub mod pallet { .ok_or(Error::::ArithmeticOverflow)?; let customer_id = payer.0.clone(); - /*let amount_actually_charged = T::CustomerCharger::charge_content_owner( - customer_id.clone(), - updated_billing_report.vault.clone(), - total_customer_charge, - )?;*/ - let amount_actually_charged = match T::CustomerCharger::charge_content_owner( customer_id.clone(), updated_billing_report.vault.clone(), @@ -682,7 +679,21 @@ pub mod pallet { let node_provider_id = payee.0; if amount_to_reward > 0 { - let reward: BalanceOf = amount_to_reward.saturated_into::>(); + let mut reward: BalanceOf = + amount_to_reward.saturated_into::>(); + + let balance = ::Currency::free_balance( + &updated_billing_report.vault, + ) - ::Currency::minimum_balance(); + + if reward > balance { + ensure!( + reward - balance <= MaxDust::get().into(), + Error::::NotDistributedBalance + ); + + reward = balance; + } ::Currency::transfer( &updated_billing_report.vault, @@ -916,7 +927,44 @@ pub mod pallet { Ok(()) } + #[pallet::genesis_config] + pub struct GenesisConfig { + pub feeder_account: Option, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { feeder_account: None } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + let account_id = >::account_id(); + let min = ::Currency::minimum_balance(); + let balance = ::Currency::free_balance(&account_id); + if balance < min { + if let Some(vault) = &self.feeder_account { + let _ = ::Currency::transfer( + vault, + &account_id, + min - balance, + ExistenceRequirement::AllowDeath, + ); + } else { + let _ = ::Currency::make_free_balance_be(&account_id, min); + } + } + } + } + impl Pallet { + pub fn account_id() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + pub fn sub_account_id(cluster_id: ClusterId, era: DdcEra) -> T::AccountId { let mut bytes = Vec::new(); bytes.extend_from_slice(&cluster_id[..]); diff --git a/pallets/ddc-payouts/src/mock.rs b/pallets/ddc-payouts/src/mock.rs index b0f184a9b..00eba7232 100644 --- a/pallets/ddc-payouts/src/mock.rs +++ b/pallets/ddc-payouts/src/mock.rs @@ -49,10 +49,12 @@ construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - DdcPayouts: pallet_ddc_payouts::{Pallet, Call, Storage, Event}, + DdcPayouts: pallet_ddc_payouts::{Pallet, Call, Storage, Config, Event}, } ); +pub static MAX_DUST: u16 = 20000; + parameter_types! { pub static ExistentialDeposit: Balance = 1; } @@ -120,13 +122,28 @@ impl CustomerCharger for TestCustomerCharger { billing_vault: T::AccountId, amount: u128, ) -> Result { - ensure!(amount > 1_000_000, DispatchError::BadOrigin); // any error will do - let mut amount_to_charge = amount; - let temp = ACCOUNT_ID_5.to_ne_bytes(); + let mut temp = ACCOUNT_ID_1.to_ne_bytes(); + let account_1 = T::AccountId::decode(&mut &temp[..]).unwrap(); + temp = ACCOUNT_ID_2.to_ne_bytes(); + let account_2 = T::AccountId::decode(&mut &temp[..]).unwrap(); + temp = ACCOUNT_ID_3.to_ne_bytes(); + let account_3 = T::AccountId::decode(&mut &temp[..]).unwrap(); + temp = ACCOUNT_ID_4.to_ne_bytes(); + let account_4 = T::AccountId::decode(&mut &temp[..]).unwrap(); + temp = ACCOUNT_ID_5.to_ne_bytes(); let account_5 = T::AccountId::decode(&mut &temp[..]).unwrap(); - if amount_to_charge < 50_000_000 && content_owner != account_5 { + if content_owner == account_1 || + content_owner == account_2 || + content_owner == account_3 || + content_owner == account_4 || + content_owner == account_5 + { + ensure!(amount > 1_000_000, DispatchError::BadOrigin); // any error will do + } + + if amount_to_charge < 50_000_000 && content_owner == account_3 { amount_to_charge = PARTIAL_CHARGE; // for user 3 } @@ -142,6 +159,10 @@ impl CustomerCharger for TestCustomerCharger { } } +pub const ACCOUNT_ID_1: AccountId = 1; +pub const ACCOUNT_ID_2: AccountId = 2; +pub const ACCOUNT_ID_3: AccountId = 3; +pub const ACCOUNT_ID_4: AccountId = 4; pub const ACCOUNT_ID_5: AccountId = 5; pub struct TestClusterCreator; impl ClusterCreator for TestClusterCreator { @@ -280,11 +301,19 @@ impl SortedListProvider for TestValidator } } -pub fn get_fees(cluster_id: &ClusterId) -> Result { +pub fn get_fees(cluster_id: &ClusterId) -> ClusterFeesParams { if *cluster_id == FREE_CLUSTER_ID || *cluster_id == ONE_CLUSTER_ID { - Ok(PRICING_FEES_ZERO) + PRICING_FEES_ZERO } else { - Ok(PRICING_FEES) + PRICING_FEES + } +} + +pub fn get_pricing(cluster_id: &ClusterId) -> ClusterPricingParams { + if *cluster_id == FREE_CLUSTER_ID || *cluster_id == ONE_CLUSTER_ID { + PRICING_PARAMS_ONE + } else { + PRICING_PARAMS } } @@ -315,15 +344,11 @@ impl ClusterVisitor for TestClusterVisitor { fn get_pricing_params( cluster_id: &ClusterId, ) -> Result { - if *cluster_id == FREE_CLUSTER_ID || *cluster_id == ONE_CLUSTER_ID { - Ok(PRICING_PARAMS_ONE) - } else { - Ok(PRICING_PARAMS) - } + Ok(get_pricing(cluster_id)) } fn get_fees_params(cluster_id: &ClusterId) -> Result { - get_fees(cluster_id) + Ok(get_fees(cluster_id)) } fn get_reserve_account_id( @@ -349,7 +374,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let _ = pallet_balances::GenesisConfig:: { + let _balance_genesis = pallet_balances::GenesisConfig:: { balances: vec![ (1, 10000000000000000000000000000), (2, 10), // < PARTIAL_CHARGE @@ -360,6 +385,9 @@ impl ExtBuilder { } .assimilate_storage(&mut storage); + let _payout_genesis = pallet_ddc_payouts::GenesisConfig:: { feeder_account: None } + .assimilate_storage(&mut storage); + TestExternalities::new(storage) } pub fn build_and_execute(self, test: impl FnOnce()) { diff --git a/pallets/ddc-payouts/src/tests.rs b/pallets/ddc-payouts/src/tests.rs index 6bab56cce..f4d1732af 100644 --- a/pallets/ddc-payouts/src/tests.rs +++ b/pallets/ddc-payouts/src/tests.rs @@ -258,11 +258,7 @@ fn send_charging_customers_batch_fails_uninitialised() { } fn calculate_charge_parts(cluster_id: ClusterId, usage: CustomerUsage) -> CustomerCharge { - let pricing_params = if cluster_id == FREE_CLUSTER_ID || cluster_id == ONE_CLUSTER_ID { - PRICING_PARAMS_ONE - } else { - PRICING_PARAMS - }; + let pricing_params = get_pricing(&cluster_id); CustomerCharge { transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) / @@ -350,8 +346,8 @@ fn send_charging_customers_batch_works1() { )); let usage4_charge = calculate_charge(cluster_id, usage4.clone()); - let mut balance = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); - assert_eq!(balance, usage4_charge); + let mut balance = Balances::free_balance(DdcPayouts::account_id()); + assert_eq!(balance - Balances::minimum_balance(), usage4_charge); let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor).unwrap(); let mut debt = calculate_charge(cluster_id, usage2.clone()); @@ -397,7 +393,7 @@ fn send_charging_customers_batch_works1() { .into(), ); - assert_eq!(System::events().len(), 5 + 3 + 1); // 3 for Currency::transfer + assert_eq!(System::events().len(), 5 + 1 + 1); // 1 for Currency::transfer // batch 2 let mut before_total_customer_charge = report.total_customer_charge.clone(); @@ -444,7 +440,7 @@ fn send_charging_customers_batch_works1() { let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); - let balance_before = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); + let balance_before = Balances::free_balance(DdcPayouts::account_id()); // batch 3 batch_index += 1; @@ -478,7 +474,7 @@ fn send_charging_customers_batch_works1() { report.total_customer_charge.transfer ); - balance = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); + balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor).unwrap(); @@ -547,7 +543,7 @@ fn send_charging_customers_batch_works2() { // batch 1 let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let before_total_customer_charge = report.total_customer_charge.clone(); - let balance_before = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); + let balance_before = Balances::free_balance(DdcPayouts::account_id()); assert_ok!(DdcPayouts::send_charging_customers_batch( RuntimeOrigin::signed(dac_account), cluster_id, @@ -558,7 +554,7 @@ fn send_charging_customers_batch_works2() { let usage5_charge = calculate_charge(cluster_id, usage5.clone()); let charge5 = calculate_charge_parts(cluster_id, usage5); - let balance = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); + let balance = Balances::free_balance(DdcPayouts::account_id()); report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(balance, usage5_charge + balance_before); assert_eq!( @@ -700,9 +696,9 @@ fn end_charging_customers_works() { .into(), ); - let mut balance = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); - assert_eq!(balance, charge); - assert_eq!(System::events().len(), 4 + 3); // 3 for Currency::transfer + let mut balance = Balances::free_balance(DdcPayouts::account_id()); + assert_eq!(balance - Balances::minimum_balance(), charge); + assert_eq!(System::events().len(), 4 + 1); // 1 for Currency::transfer assert_ok!(DdcPayouts::end_charging_customers( RuntimeOrigin::signed(dac_account), @@ -712,9 +708,9 @@ fn end_charging_customers_works() { System::assert_has_event(Event::ChargingFinished { cluster_id, era }.into()); - let treasury_fee = PRICING_FEES.treasury_share * charge; - let reserve_fee = PRICING_FEES.cluster_reserve_share * charge; - let validator_fee = PRICING_FEES.validators_share * charge; + let treasury_fee = get_fees(&cluster_id).treasury_share * charge; + let reserve_fee = get_fees(&cluster_id).cluster_reserve_share * charge; + let validator_fee = get_fees(&cluster_id).validators_share * charge; System::assert_has_event( Event::TreasuryFeesCollected { cluster_id, era, amount: treasury_fee }.into(), @@ -729,30 +725,30 @@ fn end_charging_customers_works() { ); let transfers = 3 + 3 + 3 * 3; // for Currency::transfer - assert_eq!(System::events().len(), 7 + 1 + 3 + transfers); + assert_eq!(System::events().len(), 5 + 1 + 3 + transfers); let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report_after.state, State::CustomersChargedWithFees); - let total_left_from_one = (PRICING_FEES.treasury_share + - PRICING_FEES.validators_share + - PRICING_FEES.cluster_reserve_share) + 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(); balance = Balances::free_balance(TREASURY_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.treasury_share * charge); + assert_eq!(balance, get_fees(&cluster_id).treasury_share * charge); balance = Balances::free_balance(RESERVE_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.cluster_reserve_share * charge); + assert_eq!(balance, get_fees(&cluster_id).cluster_reserve_share * charge); balance = Balances::free_balance(VALIDATOR1_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.validators_share * charge / 3); + assert_eq!(balance, get_fees(&cluster_id).validators_share * charge / 3); balance = Balances::free_balance(VALIDATOR2_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.validators_share * charge / 3); + assert_eq!(balance, get_fees(&cluster_id).validators_share * charge / 3); balance = Balances::free_balance(VALIDATOR3_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.validators_share * charge / 3); + assert_eq!(balance, get_fees(&cluster_id).validators_share * charge / 3); assert_eq!( report_after.total_customer_charge.transfer, @@ -822,9 +818,9 @@ fn end_charging_customers_works_zero_fees() { .into(), ); - let mut balance = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); - assert_eq!(balance, charge); - assert_eq!(System::events().len(), 4 + 3); // 3 for Currency::transfer + let mut balance = Balances::free_balance(DdcPayouts::account_id()); + assert_eq!(balance - Balances::minimum_balance(), charge); + assert_eq!(System::events().len(), 4 + 1); // 1 for Currency::transfer assert_ok!(DdcPayouts::end_charging_customers( RuntimeOrigin::signed(dac_account), @@ -833,12 +829,12 @@ fn end_charging_customers_works_zero_fees() { )); System::assert_has_event(Event::ChargingFinished { cluster_id, era }.into()); - assert_eq!(System::events().len(), 7 + 1); + assert_eq!(System::events().len(), 5 + 1); let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report_after.state, State::CustomersChargedWithFees); - let fees = get_fees(&cluster_id).unwrap(); + let fees = get_fees(&cluster_id); let total_left_from_one = (fees.treasury_share + fees.validators_share + fees.cluster_reserve_share) @@ -1308,9 +1304,9 @@ fn send_rewarding_providers_batch_works() { )); let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); - let total_left_from_one = (PRICING_FEES.treasury_share + - PRICING_FEES.validators_share + - PRICING_FEES.cluster_reserve_share) + 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(); assert_eq!( @@ -1452,6 +1448,258 @@ fn send_rewarding_providers_batch_works() { }) } +#[test] +fn send_rewarding_providers_batch_works100nodes() { + ExtBuilder.build_and_execute(|| { + System::set_block_number(1); + + let num_nodes = 100; + let num_users = 5; + let dac_account = 123u128; + let bank = 1u128; + let cluster_id = ONE_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 usage1 = CustomerUsage { + transferred_bytes: 1024, + stored_bytes: 1024, + number_of_puts: 1, + number_of_gets: 1, + }; + + let node_usage1 = NodeUsage { + // CDN + transferred_bytes: Perquintill::from_float(0.75) * usage1.transferred_bytes, + stored_bytes: 0, + number_of_puts: Perquintill::from_float(0.75) * usage1.number_of_puts, + number_of_gets: Perquintill::from_float(0.75) * usage1.number_of_gets, + }; + + let node_usage2 = NodeUsage { + // Storage + transferred_bytes: 0, + stored_bytes: usage1.stored_bytes * 2, + number_of_puts: 0, + number_of_gets: 0, + }; + + let node_usage3 = NodeUsage { + // CDN + Storage + transferred_bytes: usage1.transferred_bytes * 2, + stored_bytes: usage1.stored_bytes * 3, + number_of_puts: usage1.number_of_puts * 2, + number_of_gets: usage1.number_of_gets * 2, + }; + + 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 = match i % 3 { + 0 => node_usage1.clone(), + 1 => node_usage2.clone(), + 2 => node_usage3.clone(), + _ => unreachable!(), + }; + 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 ratio = match user_id % 5 { + 0 => Perquintill::one(), + 1 => Perquintill::from_float(0.5), + 2 => Perquintill::from_float(2f64), + 3 => Perquintill::from_float(0.25), + 4 => Perquintill::from_float(0.001), + _ => unreachable!(), + }; + + let mut user_usage = usage1.clone(); + user_usage.transferred_bytes = ratio * user_usage.transferred_bytes; + user_usage.stored_bytes = ratio * user_usage.stored_bytes; + user_usage.number_of_puts = ratio * user_usage.number_of_puts; + user_usage.number_of_gets = ratio * user_usage.number_of_gets; + + 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(|| {