diff --git a/pallets/ddc-clusters/src/benchmarking.rs b/pallets/ddc-clusters/src/benchmarking.rs index cb9b47397..2405330d7 100644 --- a/pallets/ddc-clusters/src/benchmarking.rs +++ b/pallets/ddc-clusters/src/benchmarking.rs @@ -7,7 +7,7 @@ pub use frame_benchmarking::{ }; use frame_system::RawOrigin; use pallet_contracts::chain_extension::UncheckedFrom; -use sp_runtime::{AccountId32, Perbill}; +use sp_runtime::{AccountId32, Perquintill}; use sp_std::prelude::*; use testing_utils::*; @@ -26,9 +26,9 @@ benchmarks! { let user = account::("user", USER_SEED, 0u32); let cluster_params = ClusterParams { node_provider_auth_contract: Some(user.clone()) }; let cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::default(), - validators_share: Perbill::default(), - cluster_reserve_share: Perbill::default(), + treasury_share: Perquintill::default(), + validators_share: Perquintill::default(), + cluster_reserve_share: Perquintill::default(), storage_bond_size: 100u32.into(), storage_chill_delay: 50u32.into(), storage_unbonding_delay: 50u32.into(), @@ -89,9 +89,9 @@ benchmarks! { let user = account::("user", USER_SEED, 0u32); let _ = config_cluster::(user, cluster_id); let new_cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::default(), - validators_share: Perbill::default(), - cluster_reserve_share: Perbill::default(), + treasury_share: Perquintill::default(), + validators_share: Perquintill::default(), + cluster_reserve_share: Perquintill::default(), storage_bond_size: 10u32.into(), storage_chill_delay: 5u32.into(), storage_unbonding_delay: 5u32.into(), diff --git a/pallets/ddc-clusters/src/mock.rs b/pallets/ddc-clusters/src/mock.rs index d06aa1837..911d4ce84 100644 --- a/pallets/ddc-clusters/src/mock.rs +++ b/pallets/ddc-clusters/src/mock.rs @@ -18,7 +18,7 @@ use sp_runtime::{ traits::{ BlakeTwo256, Convert, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify, }, - MultiSignature, Perbill, + MultiSignature, Perquintill, }; use crate::{self as pallet_ddc_clusters, *}; @@ -244,9 +244,9 @@ impl ExtBuilder { .assimilate_storage(&mut storage); let cluster_gov_params = ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, diff --git a/pallets/ddc-clusters/src/testing_utils.rs b/pallets/ddc-clusters/src/testing_utils.rs index cc08a264c..999a066ba 100644 --- a/pallets/ddc-clusters/src/testing_utils.rs +++ b/pallets/ddc-clusters/src/testing_utils.rs @@ -11,7 +11,7 @@ pub use frame_benchmarking::{ use frame_system::RawOrigin; use pallet_contracts::chain_extension::UncheckedFrom; use pallet_ddc_nodes::Node; -use sp_runtime::Perbill; +use sp_runtime::Perquintill; use sp_std::prelude::*; use crate::{Pallet as DdcClusters, *}; @@ -22,9 +22,9 @@ where { let cluster_params = ClusterParams { node_provider_auth_contract: Some(user.clone()) }; let cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::default(), - validators_share: Perbill::default(), - cluster_reserve_share: Perbill::default(), + treasury_share: Perquintill::default(), + validators_share: Perquintill::default(), + cluster_reserve_share: Perquintill::default(), storage_bond_size: 100u32.into(), storage_chill_delay: 50u32.into(), storage_unbonding_delay: 50u32.into(), @@ -62,9 +62,9 @@ where }; let cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::default(), - validators_share: Perbill::default(), - cluster_reserve_share: Perbill::default(), + treasury_share: Perquintill::default(), + validators_share: Perquintill::default(), + cluster_reserve_share: Perquintill::default(), storage_bond_size: 100u32.into(), storage_chill_delay: 50u32.into(), storage_unbonding_delay: 50u32.into(), diff --git a/pallets/ddc-clusters/src/tests.rs b/pallets/ddc-clusters/src/tests.rs index 86f4202de..fa7f148fd 100644 --- a/pallets/ddc-clusters/src/tests.rs +++ b/pallets/ddc-clusters/src/tests.rs @@ -8,7 +8,7 @@ use ddc_traits::cluster::ClusterManager; use frame_support::{assert_noop, assert_ok, error::BadOrigin}; use frame_system::Config; use hex_literal::hex; -use sp_runtime::{traits::Hash, Perbill}; +use sp_runtime::{traits::Hash, Perquintill}; use super::{mock::*, *}; @@ -23,9 +23,9 @@ fn create_cluster_works() { let auth_contract = AccountId::from([3; 32]); let cluster_gov_params = ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, @@ -109,9 +109,9 @@ fn add_and_delete_node_works() { cluster_reserve_id.clone(), ClusterParams { node_provider_auth_contract: Some(cluster_manager_id.clone()) }, ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, @@ -327,9 +327,9 @@ fn set_cluster_params_works() { cluster_reserve_id.clone(), ClusterParams { node_provider_auth_contract: Some(auth_contract_1) }, ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, @@ -372,9 +372,9 @@ fn set_cluster_gov_params_works() { let auth_contract = AccountId::from([3; 32]); let cluster_gov_params = ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, @@ -435,9 +435,9 @@ fn cluster_visitor_works() { let auth_contract = AccountId::from([3; 32]); let cluster_gov_params = ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, @@ -483,9 +483,9 @@ fn cluster_visitor_works() { assert_eq!( >::get_fees_params(&cluster_id).unwrap(), ClusterFeesParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02) + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02) } ); @@ -544,9 +544,9 @@ fn cluster_creator_works() { let auth_contract = AccountId::from([3; 32]); let cluster_gov_params = ClusterGovParams { - treasury_share: Perbill::from_float(0.05), - validators_share: Perbill::from_float(0.01), - cluster_reserve_share: Perbill::from_float(0.02), + treasury_share: Perquintill::from_float(0.05), + validators_share: Perquintill::from_float(0.01), + cluster_reserve_share: Perquintill::from_float(0.02), storage_bond_size: 100, storage_chill_delay: 50, storage_unbonding_delay: 50, diff --git a/pallets/ddc-customers/Cargo.toml b/pallets/ddc-customers/Cargo.toml index 7ba910a68..d24486c54 100644 --- a/pallets/ddc-customers/Cargo.toml +++ b/pallets/ddc-customers/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" frame-benchmarking = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", optional = true } frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } +sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } diff --git a/pallets/ddc-customers/src/benchmarking.rs b/pallets/ddc-customers/src/benchmarking.rs index 87da166fb..921c19bfb 100644 --- a/pallets/ddc-customers/src/benchmarking.rs +++ b/pallets/ddc-customers/src/benchmarking.rs @@ -4,7 +4,7 @@ use ddc_primitives::{ClusterGovParams, ClusterId, ClusterParams}; use frame_benchmarking::{account, benchmarks, whitelist_account}; use frame_support::traits::Currency; -use sp_runtime::Perbill; +use sp_runtime::Perquintill; use sp_std::prelude::*; use super::*; @@ -23,9 +23,9 @@ benchmarks! { let user = account::("user", USER_SEED, 0u32); let cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::default(), - validators_share: Perbill::default(), - cluster_reserve_share: Perbill::default(), + treasury_share: Perquintill::default(), + validators_share: Perquintill::default(), + cluster_reserve_share: Perquintill::default(), storage_bond_size: 100u32.into(), storage_chill_delay: 50u32.into(), storage_unbonding_delay: 50u32.into(), diff --git a/pallets/ddc-customers/src/lib.rs b/pallets/ddc-customers/src/lib.rs index 181821ea5..a26ef9858 100644 --- a/pallets/ddc-customers/src/lib.rs +++ b/pallets/ddc-customers/src/lib.rs @@ -12,9 +12,7 @@ pub(crate) mod mock; #[cfg(test)] mod tests; -use core::fmt::Debug; - -use codec::{Decode, Encode, HasCompact}; +use codec::{Decode, Encode}; use ddc_primitives::{BucketId, ClusterId}; use ddc_traits::{ cluster::{ClusterCreator, ClusterVisitor}, @@ -27,8 +25,9 @@ use frame_support::{ }; pub use pallet::*; use scale_info::TypeInfo; +use sp_io::hashing::blake2_128; use sp_runtime::{ - traits::{AccountIdConversion, AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Saturating, Zero}, + traits::{AccountIdConversion, CheckedAdd, CheckedSub, Saturating, Zero}, RuntimeDebug, SaturatedConversion, }; use sp_std::prelude::*; @@ -45,17 +44,13 @@ parameter_types! { /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct UnlockChunk -where - Balance: HasCompact, - BlockNumber: Clone, -{ +pub struct UnlockChunk { /// Amount of funds to be unlocked. #[codec(compact)] - value: Balance, + value: BalanceOf, /// Block number at which point it'll be unlocked. #[codec(compact)] - block: BlockNumber, + block: T::BlockNumber, } #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -73,33 +68,28 @@ pub struct BucketParams { #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct AccountsLedger { +pub struct AccountsLedger { /// The owner account whose balance is actually locked and can be used to pay for DDC network /// usage. - pub owner: AccountId, + pub owner: T::AccountId, /// The total amount of the owner's balance that we are currently accounting for. /// It's just `active` plus all the `unlocking` balances. #[codec(compact)] - pub total: Balance, + pub total: BalanceOf, /// The total amount of the owner's balance that will be accessible for DDC network payouts in /// any forthcoming rounds. #[codec(compact)] - pub active: Balance, + pub active: BalanceOf, /// Any balance that is becoming free, which may eventually be transferred out of the owner /// (assuming that the content owner has to pay for network usage). It is assumed that this /// will be treated as a first in, first out queue where the new (higher value) eras get pushed /// on the back. - pub unlocking: BoundedVec, MaxUnlockingChunks>, + pub unlocking: BoundedVec, MaxUnlockingChunks>, } -impl< - AccountId, - Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned + Zero + Debug, - T: Config, - > AccountsLedger -{ +impl AccountsLedger { /// Initializes the default object using the given owner. - pub fn default_from(owner: AccountId) -> Self { + pub fn default_from(owner: T::AccountId) -> Self { Self { owner, total: Zero::zero(), active: Zero::zero(), unlocking: Default::default() } } @@ -121,43 +111,13 @@ impl< }) .collect::>() .try_into() - .expect( - "filtering items from a bounded vec always leaves length less than bounds. qed", - ); + .map_err(|_| { + "filtering items from a bounded vec always leaves length less than bounds. qed" + }) + .unwrap(); Self { owner: self.owner, total, active: self.active, unlocking } } - - /// Charge funds that were scheduled for unlocking. - /// - /// Returns the updated ledger, and the amount actually charged. - fn charge_unlocking(mut self, value: Balance) -> Result<(Self, Balance), Error> { - let mut unlocking_balance = Balance::zero(); - - while let Some(last) = self.unlocking.last_mut() { - let temp = unlocking_balance - .checked_add(&last.value) - .ok_or(Error::::ArithmeticOverflow)?; - if temp <= value { - unlocking_balance = temp; - self.unlocking.pop(); - } else { - let diff = - value.checked_sub(&unlocking_balance).ok_or(Error::::ArithmeticUnderflow)?; - - unlocking_balance = - unlocking_balance.checked_add(&diff).ok_or(Error::::ArithmeticOverflow)?; - last.value = - last.value.checked_sub(&diff).ok_or(Error::::ArithmeticUnderflow)?; - } - - if unlocking_balance >= value { - break - } - } - - Ok((self, unlocking_balance)) - } } #[frame_support::pallet] @@ -190,12 +150,7 @@ pub mod pallet { /// Map from all (unlocked) "owner" accounts to the info regarding the staking. #[pallet::storage] #[pallet::getter(fn ledger)] - pub type Ledger = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - AccountsLedger, T>, - >; + pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, AccountsLedger>; #[pallet::type_value] pub fn DefaultBucketCount() -> BucketId { @@ -266,13 +221,14 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { + pub feeder_account: Option, pub buckets: Vec<(ClusterId, T::AccountId, BalanceOf, bool)>, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - GenesisConfig { buckets: Default::default() } + GenesisConfig { feeder_account: None, buckets: Default::default() } } } @@ -281,8 +237,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, ref is_public) in &self.buckets { @@ -300,7 +267,7 @@ pub mod pallet { }; >::insert(cur_bucket_id, bucket); - let ledger = AccountsLedger::, T> { + let ledger = AccountsLedger:: { owner: owner_id.clone(), total: *deposit, active: *deposit, @@ -481,7 +448,6 @@ pub mod pallet { // This was the consequence of a partial deposit unlock. just update the ledger and // move on. >::insert(&owner, &ledger); - // This is only an update, so we use less overall weight. Some(::WeightInfo::withdraw_unlocked_deposit_update()) }; @@ -497,13 +463,11 @@ pub mod pallet { let value = old_total.checked_sub(&ledger.total).ok_or(Error::::ArithmeticUnderflow)?; - let account_id = Self::account_id(); - ::Currency::transfer( - &account_id, + &Self::account_id(), &owner, value, - ExistenceRequirement::KeepAlive, + ExistenceRequirement::AllowDeath, )?; Self::deposit_event(Event::::Withdrawn(owner, value)); } @@ -538,18 +502,26 @@ pub mod pallet { pub fn account_id() -> T::AccountId { T::PalletId::get().into_account_truncating() } + + pub fn sub_account_id(account_id: &T::AccountId) -> T::AccountId { + let hash = blake2_128(&account_id.encode()); + + // hash is 28 bytes + T::PalletId::get().into_sub_account_truncating(hash) + } + /// Update the ledger for a owner. /// /// This will also deposit the funds to pallet. fn update_ledger_and_deposit( owner: &T::AccountId, - ledger: &AccountsLedger, T>, + ledger: &AccountsLedger, ) -> DispatchResult { ::Currency::transfer( owner, &Self::account_id(), ledger.total, - ExistenceRequirement::KeepAlive, + ExistenceRequirement::AllowDeath, )?; >::insert(owner, ledger); @@ -569,6 +541,42 @@ pub mod pallet { Ok(()) } + + /// Charge funds that were scheduled for unlocking. + /// + /// Returns the updated ledger, and the amount actually charged. + fn charge_unlocking( + mut ledger: AccountsLedger, + value: BalanceOf, + ) -> Result<(AccountsLedger, BalanceOf), Error> { + let mut unlocking_balance = BalanceOf::::zero(); + + while let Some(last) = ledger.unlocking.last_mut() { + let temp = unlocking_balance + .checked_add(&last.value) + .ok_or(Error::::ArithmeticOverflow)?; + if temp <= value { + unlocking_balance = temp; + ledger.unlocking.pop(); + } else { + let diff = value + .checked_sub(&unlocking_balance) + .ok_or(Error::::ArithmeticUnderflow)?; + + unlocking_balance = unlocking_balance + .checked_add(&diff) + .ok_or(Error::::ArithmeticOverflow)?; + last.value = + last.value.checked_sub(&diff).ok_or(Error::::ArithmeticUnderflow)?; + } + + if unlocking_balance >= value { + break + } + } + + Ok((ledger, unlocking_balance)) + } } impl CustomerCharger for Pallet { @@ -591,8 +599,6 @@ pub mod pallet { .total .checked_sub(&amount_to_deduct) .ok_or(Error::::ArithmeticUnderflow)?; - - >::insert(&content_owner, &ledger); } else { let diff = amount_to_deduct .checked_sub(&ledger.active) @@ -605,11 +611,10 @@ pub mod pallet { .ok_or(Error::::ArithmeticUnderflow)?; ledger.active = BalanceOf::::zero(); - let (ledger, charged) = ledger.charge_unlocking(diff)?; + let (_ledger, charged) = Self::charge_unlocking(ledger, diff)?; + ledger = _ledger; actually_charged.checked_add(&charged).ok_or(Error::::ArithmeticUnderflow)?; - - >::insert(&content_owner, &ledger); } ::Currency::transfer( @@ -619,6 +624,7 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; + >::insert(&content_owner, &ledger); // update state after successful transfer Self::deposit_event(Event::::Charged(content_owner, amount_to_deduct)); Ok(actually_charged.saturated_into::()) diff --git a/pallets/ddc-customers/src/mock.rs b/pallets/ddc-customers/src/mock.rs index 3e779f06c..7b07b6b06 100644 --- a/pallets/ddc-customers/src/mock.rs +++ b/pallets/ddc-customers/src/mock.rs @@ -9,7 +9,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}; @@ -18,13 +18,13 @@ use sp_io::TestExternalities; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - DispatchResult, Perbill, + DispatchResult, Perquintill, }; use crate::{self as pallet_ddc_customers, *}; /// The AccountId alias in this test module. -pub(crate) type AccountId = u64; +pub(crate) type AccountId = u128; pub(crate) type AccountIndex = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -147,9 +147,9 @@ impl ClusterVisitor for TestClusterVisitor { fn get_fees_params(_cluster_id: &ClusterId) -> Result { Ok(ClusterFeesParams { - treasury_share: Perbill::from_percent(1), - validators_share: Perbill::from_percent(10), - cluster_reserve_share: Perbill::from_percent(2), + treasury_share: Perquintill::from_percent(1), + validators_share: Perquintill::from_percent(10), + cluster_reserve_share: Perquintill::from_percent(2), }) } @@ -223,11 +223,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 e88846a5c..9a7738ca5 100644 --- a/pallets/ddc-customers/src/tests.rs +++ b/pallets/ddc-customers/src/tests.rs @@ -2,7 +2,6 @@ use ddc_primitives::ClusterId; use frame_support::{assert_noop, assert_ok}; -use frame_system::Config; use super::{mock::*, *}; @@ -164,8 +163,8 @@ fn charge_content_owner_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let account_3 = 3; - let vault = 4; + let account_3: u128 = 3; + let vault: u128 = 4; let deposit = 100_u128; let balance_before_deposit = Balances::free_balance(account_3); @@ -175,7 +174,7 @@ fn charge_content_owner_works() { assert_eq!(balance_before_deposit - deposit, balance_after_deposit); let pallet_balance = Balances::free_balance(DdcCustomers::account_id()); - assert_eq!(deposit, pallet_balance); + assert_eq!(deposit, pallet_balance - Balances::minimum_balance()); // Check storage assert_eq!( @@ -229,7 +228,10 @@ fn charge_content_owner_works() { }) ); - assert_eq!(0, Balances::free_balance(DdcCustomers::account_id())); + 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)); @@ -243,7 +245,10 @@ fn charge_content_owner_works() { }) ); - assert_eq!(deposit, Balances::free_balance(DdcCustomers::account_id())); + assert_eq!( + deposit, + Balances::free_balance(DdcCustomers::account_id()) - Balances::minimum_balance() + ); }) } @@ -264,14 +269,7 @@ fn unlock_and_withdraw_deposit_works() { assert_ok!(DdcCustomers::unlock_deposit(RuntimeOrigin::signed(account_1), 1_u128)); System::set_block_number(2); - let mut unlocking_chunks: BoundedVec< - UnlockChunk::BlockNumber>, - MaxUnlockingChunks, - > = Default::default(); - match unlocking_chunks.try_push(UnlockChunk { value: 1, block: 11 }) { - Ok(_) => (), - Err(_) => println!("No more chunks"), - }; + let unlocking_chunks = vec![UnlockChunk { value: 1, block: 11 }]; // Check storage assert_eq!( DdcCustomers::ledger(&1), @@ -279,7 +277,7 @@ fn unlock_and_withdraw_deposit_works() { owner: account_1, total: 35_u128, active: 34_u128, - unlocking: unlocking_chunks.clone(), + unlocking: BoundedVec::try_from(unlocking_chunks).unwrap(), }) ); diff --git a/pallets/ddc-payouts/src/benchmarking.rs b/pallets/ddc-payouts/src/benchmarking.rs index 4690b29ac..7aeac5042 100644 --- a/pallets/ddc-payouts/src/benchmarking.rs +++ b/pallets/ddc-payouts/src/benchmarking.rs @@ -61,9 +61,9 @@ fn create_default_cluster(cluster_id: ClusterId) { let cluster_reserve = create_account::("cr", 0, 0); let cluster_params = ClusterParams { node_provider_auth_contract: Default::default() }; let cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::from_percent(5), - validators_share: Perbill::from_percent(10), - cluster_reserve_share: Perbill::from_percent(15), + treasury_share: Perquintill::from_percent(5), + validators_share: Perquintill::from_percent(10), + cluster_reserve_share: Perquintill::from_percent(15), unit_per_mb_stored: CERE, unit_per_mb_streamed: CERE, unit_per_put_request: CERE, diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index 51eb7bc15..9ce58f2a1 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -25,7 +25,7 @@ pub(crate) mod mock; #[cfg(test)] mod tests; -use ddc_primitives::{ClusterId, DdcEra}; +use ddc_primitives::{ClusterId, DdcEra, MILLICENTS}; use ddc_traits::{ cluster::{ClusterCreator as ClusterCreatorType, ClusterVisitor as ClusterVisitorType}, customer::{ @@ -43,7 +43,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; pub use pallet::*; -use sp_runtime::{PerThing, Perbill}; +use sp_runtime::{traits::Convert, PerThing, Perquintill}; use sp_std::prelude::*; type BatchIndex = u16; @@ -96,8 +96,14 @@ pub struct CustomerCharge { pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type VoteScoreOf = + <::NominatorsAndValidatorsList as frame_election_provider_support::SortedListProvider< + ::AccountId, + >>::Score; + parameter_types! { pub MaxBatchesCount: u16 = 1000; + pub MaxDust: u128 = MILLICENTS; pub MaxBatchSize: u16 = 1000; } @@ -124,9 +130,10 @@ pub mod pallet { type CustomerDepositor: CustomerDepositorType; type TreasuryVisitor: PalletVisitorType; type ClusterVisitor: ClusterVisitorType; - type ValidatorList: SortedListProvider; + type NominatorsAndValidatorsList: SortedListProvider; type ClusterCreator: ClusterCreatorType>; type WeightInfo: WeightInfo; + type VoteScoreToU64: Convert, u64>; } #[pallet::event] @@ -190,6 +197,19 @@ pub mod pallet { node_provider_id: T::AccountId, amount: u128, }, + NotDistributedReward { + cluster_id: ClusterId, + era: DdcEra, + node_provider_id: T::AccountId, + expected_reward: u128, + distributed_reward: BalanceOf, + }, + NotDistributedOverallReward { + cluster_id: ClusterId, + era: DdcEra, + expected_reward: u128, + total_distributed_reward: u128, + }, RewardingFinished { cluster_id: ClusterId, era: DdcEra, @@ -212,12 +232,12 @@ pub mod pallet { BatchIndexAlreadyProcessed, BatchIndexIsOutOfRange, BatchesMissed, - NotDistributedBalance, BatchIndexOverflow, BoundedVecOverflow, ArithmeticOverflow, NotExpectedClusterState, BatchSizeIsOutOfBounds, + ScoreRetrievalError, } #[pallet::storage] @@ -240,6 +260,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 { @@ -343,13 +368,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 }); @@ -468,8 +491,10 @@ pub mod pallet { if amount_actually_charged > 0 { // something was charged and should be added // calculate ratio - let ratio = - Perbill::from_rational(amount_actually_charged, total_customer_charge); + let ratio = Perquintill::from_rational( + amount_actually_charged, + total_customer_charge, + ); customer_charge.storage = ratio * customer_charge.storage; customer_charge.transfer = ratio * customer_charge.transfer; @@ -682,6 +707,7 @@ pub mod pallet { Error::::BatchIndexAlreadyProcessed ); + let max_dust = MaxDust::get().saturated_into::>(); let mut updated_billing_report = billing_report.clone(); for payee in payees { let node_reward = get_node_reward::( @@ -700,19 +726,40 @@ pub mod pallet { .ok_or(Error::::ArithmeticOverflow)?; let node_provider_id = payee.0; - let reward: BalanceOf = amount_to_reward.saturated_into::>(); + if amount_to_reward > 0 { + let mut reward: BalanceOf = + amount_to_reward.saturated_into::>(); + + let vault_balance = ::Currency::free_balance( + &updated_billing_report.vault, + ) - ::Currency::minimum_balance(); + + if reward > vault_balance { + if reward - vault_balance > max_dust { + Self::deposit_event(Event::::NotDistributedReward { + cluster_id, + era, + node_provider_id: node_provider_id.clone(), + expected_reward: amount_to_reward, + distributed_reward: vault_balance, + }); + } + + reward = vault_balance; + } - ::Currency::transfer( - &updated_billing_report.vault, - &node_provider_id, - reward, - ExistenceRequirement::AllowDeath, - )?; + ::Currency::transfer( + &updated_billing_report.vault, + &node_provider_id, + reward, + ExistenceRequirement::AllowDeath, + )?; - updated_billing_report - .total_distributed_reward - .checked_add(amount_to_reward) - .ok_or(Error::::ArithmeticOverflow)?; + updated_billing_report.total_distributed_reward = updated_billing_report + .total_distributed_reward + .checked_add(amount_to_reward) + .ok_or(Error::::ArithmeticOverflow)?; + } Self::deposit_event(Event::::Rewarded { cluster_id, @@ -754,6 +801,26 @@ pub mod pallet { &billing_report.rewarding_max_batch_index, )?; + let expected_amount_to_reward = (|| -> Option { + billing_report + .total_customer_charge + .transfer + .checked_add(billing_report.total_customer_charge.storage)? + .checked_add(billing_report.total_customer_charge.puts)? + .checked_add(billing_report.total_customer_charge.gets) + })() + .ok_or(Error::::ArithmeticOverflow)?; + + if expected_amount_to_reward - billing_report.total_distributed_reward > MaxDust::get() + { + Self::deposit_event(Event::::NotDistributedOverallReward { + cluster_id, + era, + expected_reward: expected_amount_to_reward, + total_distributed_reward: billing_report.total_distributed_reward, + }); + } + billing_report.state = State::ProvidersRewarded; ActiveBillingReports::::insert(cluster_id, era, billing_report); @@ -775,20 +842,6 @@ pub mod pallet { .map_err(|_| Error::::BillingReportDoesNotExist)?; ensure!(billing_report.state == State::ProvidersRewarded, Error::::NotExpectedState); - let expected_amount_to_reward = (|| -> Option { - billing_report - .total_customer_charge - .transfer - .checked_add(billing_report.total_customer_charge.storage)? - .checked_add(billing_report.total_customer_charge.puts)? - .checked_add(billing_report.total_customer_charge.gets) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - ensure!( - expected_amount_to_reward == billing_report.total_distributed_reward, - Error::::NotDistributedBalance - ); billing_report.charging_processed_batches.clear(); billing_report.rewarding_processed_batches.clear(); @@ -829,20 +882,41 @@ pub mod pallet { ) } + fn get_current_exposure_ratios( + ) -> Result, DispatchError> { + let mut total_score = 0; + let mut individual_scores: Vec<(T::AccountId, u64)> = Vec::new(); + for staker_id in T::NominatorsAndValidatorsList::iter() { + let s = T::NominatorsAndValidatorsList::get_score(&staker_id) + .map_err(|_| Error::::ScoreRetrievalError)?; + let score = T::VoteScoreToU64::convert(s); + total_score += score; + + individual_scores.push((staker_id, score)); + } + + let mut result = Vec::new(); + for (staker_id, score) in individual_scores { + let ratio = Perquintill::from_rational(score, total_score); + result.push((staker_id, ratio)); + } + + Ok(result) + } + fn charge_validator_fees( validators_fee: u128, vault: &T::AccountId, ) -> DispatchResult { - let amount_to_deduct = validators_fee - .checked_div(T::ValidatorList::count().try_into().unwrap()) - .ok_or(Error::::ArithmeticOverflow)? - .saturated_into::>(); + let stakers = get_current_exposure_ratios::()?; + + for (staker_id, ratio) in stakers.iter() { + let amount_to_deduct = *ratio * validators_fee; - for validator_account_id in T::ValidatorList::iter() { ::Currency::transfer( vault, - &validator_account_id, - amount_to_deduct, + staker_id, + amount_to_deduct.saturated_into::>(), ExistenceRequirement::AllowDeath, )?; } @@ -857,20 +931,26 @@ pub mod pallet { ) -> Option { let mut node_reward = NodeReward::default(); - let mut ratio = Perbill::from_rational( - node_usage.transferred_bytes, - total_nodes_usage.transferred_bytes, + let mut ratio = Perquintill::from_rational( + node_usage.transferred_bytes as u128, + total_nodes_usage.transferred_bytes as u128, ); + // ratio multiplied by X will be > 0, < X no overflow node_reward.transfer = ratio * total_customer_charge.transfer; - ratio = Perbill::from_rational(node_usage.stored_bytes, total_nodes_usage.stored_bytes); + ratio = Perquintill::from_rational( + node_usage.stored_bytes as u128, + total_nodes_usage.stored_bytes as u128, + ); node_reward.storage = ratio * total_customer_charge.storage; - ratio = Perbill::from_rational(node_usage.number_of_puts, total_nodes_usage.number_of_puts); + ratio = + Perquintill::from_rational(node_usage.number_of_puts, total_nodes_usage.number_of_puts); node_reward.puts = ratio * total_customer_charge.puts; - ratio = Perbill::from_rational(node_usage.number_of_gets, total_nodes_usage.number_of_gets); + ratio = + Perquintill::from_rational(node_usage.number_of_gets, total_nodes_usage.number_of_gets); node_reward.gets = ratio * total_customer_charge.gets; Some(node_reward) @@ -928,7 +1008,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 3ef864f9a..c185fd212 100644 --- a/pallets/ddc-payouts/src/mock.rs +++ b/pallets/ddc-payouts/src/mock.rs @@ -13,9 +13,7 @@ use ddc_traits::{ }; use frame_election_provider_support::SortedListProvider; use frame_support::{ - construct_runtime, - dispatch::DispatchError, - parameter_types, + construct_runtime, parameter_types, traits::{ConstU32, ConstU64, Everything}, weights::constants::RocksDbWeight, PalletId, @@ -25,14 +23,15 @@ use sp_core::H256; use sp_io::TestExternalities; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, Identity, IdentityLookup}, + DispatchError, Perquintill, }; use sp_std::prelude::*; use crate::{self as pallet_ddc_payouts, *}; /// The AccountId alias in this test module. -pub type AccountId = u64; +pub type AccountId = u128; pub(crate) type AccountIndex = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -50,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 = 100; + parameter_types! { pub static ExistentialDeposit: Balance = 1; } @@ -109,8 +110,10 @@ impl crate::pallet::Config for Test { type CustomerDepositor = TestCustomerDepositor; type ClusterVisitor = TestClusterVisitor; type TreasuryVisitor = TestTreasuryVisitor; - type ValidatorList = TestValidatorVisitor; + type NominatorsAndValidatorsList = TestValidatorVisitor; type ClusterCreator = TestClusterCreator; + + type VoteScoreToU64 = Identity; type WeightInfo = (); } @@ -121,10 +124,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; - if amount_to_charge < 50_000_000 { + 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 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 } @@ -140,6 +161,11 @@ 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 { fn create_new_cluster( @@ -168,10 +194,16 @@ pub const TREASURY_ACCOUNT_ID: AccountId = 888; pub const VALIDATOR1_ACCOUNT_ID: AccountId = 111; pub const VALIDATOR2_ACCOUNT_ID: AccountId = 222; pub const VALIDATOR3_ACCOUNT_ID: AccountId = 333; + +pub const VALIDATOR1_SCORE: u64 = 30; +pub const VALIDATOR2_SCORE: u64 = 45; +pub const VALIDATOR3_SCORE: u64 = 25; + pub const PARTIAL_CHARGE: u128 = 100; 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 PRICING_PARAMS: ClusterPricingParams = ClusterPricingParams { unit_per_mb_streamed: 2_000_000, @@ -180,16 +212,23 @@ pub const PRICING_PARAMS: ClusterPricingParams = ClusterPricingParams { unit_per_get_request: 5_000_000, }; +pub const PRICING_PARAMS_ONE: ClusterPricingParams = ClusterPricingParams { + unit_per_mb_streamed: 10_000_000_000, + unit_per_mb_stored: 10_000_000_000, + unit_per_put_request: 10_000_000_000, + unit_per_get_request: 10_000_000_000, +}; + pub const PRICING_FEES: ClusterFeesParams = ClusterFeesParams { - treasury_share: Perbill::from_percent(1), - validators_share: Perbill::from_percent(10), - cluster_reserve_share: Perbill::from_percent(2), + treasury_share: Perquintill::from_percent(1), + validators_share: Perquintill::from_percent(10), + cluster_reserve_share: Perquintill::from_percent(2), }; pub const PRICING_FEES_ZERO: ClusterFeesParams = ClusterFeesParams { - treasury_share: Perbill::from_percent(0), - validators_share: Perbill::from_percent(0), - cluster_reserve_share: Perbill::from_percent(0), + treasury_share: Perquintill::from_percent(0), + validators_share: Perquintill::from_percent(0), + cluster_reserve_share: Perquintill::from_percent(0), }; pub struct TestTreasuryVisitor; @@ -200,7 +239,7 @@ impl PalletVisitor for TestTreasuryVisitor { } } -fn create_account_id_from_u64(id: u64) -> T::AccountId { +fn create_account_id_from_u128(id: u128) -> T::AccountId { let bytes = id.to_ne_bytes(); T::AccountId::decode(&mut &bytes[..]).unwrap() } @@ -214,9 +253,9 @@ impl SortedListProvider for TestValidator fn iter() -> Box> { Box::new( vec![ - create_account_id_from_u64::(VALIDATOR1_ACCOUNT_ID), - create_account_id_from_u64::(VALIDATOR2_ACCOUNT_ID), - create_account_id_from_u64::(VALIDATOR3_ACCOUNT_ID), + create_account_id_from_u128::(VALIDATOR1_ACCOUNT_ID), + create_account_id_from_u128::(VALIDATOR2_ACCOUNT_ID), + create_account_id_from_u128::(VALIDATOR3_ACCOUNT_ID), ] .into_iter(), ) @@ -236,8 +275,14 @@ impl SortedListProvider for TestValidator // nothing to do on insert. Ok(()) } - fn get_score(_id: &T::AccountId) -> Result { - unimplemented!() + fn get_score(validator_id: &T::AccountId) -> Result { + if *validator_id == create_account_id_from_u128::(VALIDATOR1_ACCOUNT_ID) { + Ok(VALIDATOR1_SCORE) + } else if *validator_id == create_account_id_from_u128::(VALIDATOR2_ACCOUNT_ID) { + Ok(VALIDATOR2_SCORE) + } else { + Ok(VALIDATOR3_SCORE) + } } fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. @@ -269,11 +314,19 @@ impl SortedListProvider for TestValidator } } -pub fn get_fees(cluster_id: &ClusterId) -> Result { - if *cluster_id == FREE_CLUSTER_ID { - Ok(PRICING_FEES_ZERO) +pub fn get_fees(cluster_id: &ClusterId) -> ClusterFeesParams { + if *cluster_id == FREE_CLUSTER_ID || *cluster_id == ONE_CLUSTER_ID { + PRICING_FEES_ZERO + } else { + 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 { - Ok(PRICING_FEES) + PRICING_PARAMS } } @@ -302,13 +355,13 @@ impl ClusterVisitor for TestClusterVisitor { } fn get_pricing_params( - _cluster_id: &ClusterId, + cluster_id: &ClusterId, ) -> Result { - 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( @@ -334,16 +387,20 @@ 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, 1000000000000000000000000), + (1, 10000000000000000000000000000), (2, 10), // < PARTIAL_CHARGE (3, USER3_BALANCE), // > PARTIAL_CHARGE (4, 1000000000000000000000000), + (5, 1000000000000000000000000), ], } .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 6cd8d2552..5f35f7043 100644 --- a/pallets/ddc-payouts/src/tests.rs +++ b/pallets/ddc-payouts/src/tests.rs @@ -2,16 +2,16 @@ use ddc_primitives::ClusterId; use frame_support::{assert_noop, assert_ok, error::BadOrigin}; +use sp_runtime::Perquintill; use super::{mock::*, *}; - #[test] fn set_authorised_caller_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let root_account = 1u64; - let dac_account = 2u64; + let root_account = 1u128; + let dac_account = 2u128; assert_noop!( DdcPayouts::set_authorised_caller(RuntimeOrigin::signed(root_account), dac_account), @@ -31,8 +31,8 @@ fn set_authorised_caller_works() { #[test] fn begin_billing_report_fails_for_unauthorised() { ExtBuilder.build_and_execute(|| { - let root_account = 1u64; - let dac_account = 2u64; + let root_account = 1u128; + let dac_account = 2u128; let cluster_id = ClusterId::from([1; 20]); let era = 100; @@ -59,7 +59,7 @@ fn begin_billing_report_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 2u64; + let dac_account = 2u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; @@ -81,7 +81,7 @@ fn begin_billing_report_works() { #[test] fn begin_charging_customers_fails_uninitialised() { ExtBuilder.build_and_execute(|| { - let dac_account = 2u64; + let dac_account = 2u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 2; @@ -125,7 +125,7 @@ fn begin_charging_customers_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 2u64; + let dac_account = 2u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 2; @@ -156,10 +156,10 @@ fn begin_charging_customers_works() { #[test] fn send_charging_customers_batch_fails_uninitialised() { ExtBuilder.build_and_execute(|| { - let root_account = 1u64; - let dac_account = 2u64; - let user1 = 3u64; - let user2 = 4u64; + let root_account = 1u128; + let dac_account = 2u128; + let user1 = 3u128; + let user2 = 4u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 2; @@ -258,32 +258,34 @@ fn send_charging_customers_batch_fails_uninitialised() { }) } -fn calculate_charge_parts(usage: CustomerUsage) -> CustomerCharge { +fn calculate_charge_parts(cluster_id: ClusterId, usage: CustomerUsage) -> CustomerCharge { + let pricing_params = get_pricing(&cluster_id); + CustomerCharge { - transfer: PRICING_PARAMS.unit_per_mb_streamed * (usage.transferred_bytes as u128) / + transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) / byte_unit::MEBIBYTE, - storage: (PRICING_PARAMS.unit_per_mb_stored * usage.stored_bytes as u128) / + 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, + gets: pricing_params.unit_per_get_request * usage.number_of_gets, } } -fn calculate_charge(usage: CustomerUsage) -> u128 { - let charge = calculate_charge_parts(usage); +fn calculate_charge(cluster_id: ClusterId, usage: CustomerUsage) -> u128 { + let charge = calculate_charge_parts(cluster_id, usage); charge.transfer + charge.storage + charge.puts + charge.gets } #[test] -fn send_charging_customers_batch_works() { +fn send_charging_customers_batch_works1() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 123u64; - let user1 = 1u64; - let user2_debtor = 2u64; - let user3_debtor = 3u64; - let user4 = 4u64; + let dac_account = 123u128; + let user1 = 1u128; + let user2_debtor = 2u128; + let user3_debtor = 3u128; + let user4 = 4u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 3; @@ -344,17 +346,17 @@ fn send_charging_customers_batch_works() { payers1, )); - let usage4_charge = calculate_charge(usage4.clone()); - let mut balance = Balances::free_balance(DdcPayouts::sub_account_id(cluster_id, era)); - assert_eq!(balance, usage4_charge); + let usage4_charge = calculate_charge(cluster_id, usage4.clone()); + 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(usage2.clone()); + let mut debt = calculate_charge(cluster_id, usage2.clone()); assert_eq!(user2_debt, debt); let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); - let charge2 = calculate_charge_parts(usage2); - let charge4 = calculate_charge_parts(usage4); + let charge2 = calculate_charge_parts(cluster_id, usage2); + let charge4 = calculate_charge_parts(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); assert_eq!(charge2.storage + charge4.storage, report.total_customer_charge.storage); @@ -392,7 +394,7 @@ fn send_charging_customers_batch_works() { .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(); @@ -411,13 +413,13 @@ fn send_charging_customers_batch_works() { era, batch_index, customer_id: user1, - amount: calculate_charge(usage1.clone()), + amount: calculate_charge(cluster_id, usage1.clone()), } .into(), ); report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); - let charge1 = calculate_charge_parts(usage1); + let charge1 = calculate_charge_parts(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -439,10 +441,10 @@ fn send_charging_customers_batch_works() { 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 += 2; + batch_index += 1; before_total_customer_charge = report.total_customer_charge.clone(); assert_ok!(DdcPayouts::send_charging_customers_batch( RuntimeOrigin::signed(dac_account), @@ -452,9 +454,9 @@ fn send_charging_customers_batch_works() { payers3, )); - let user3_charge = calculate_charge(usage3.clone()); - let charge3 = calculate_charge_parts(usage3); - let ratio = Perbill::from_rational(PARTIAL_CHARGE, user3_charge); + let user3_charge = calculate_charge(cluster_id, usage3.clone()); + let charge3 = calculate_charge_parts(cluster_id, usage3); + let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, @@ -473,7 +475,7 @@ fn send_charging_customers_batch_works() { 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(); @@ -504,12 +506,83 @@ fn send_charging_customers_batch_works() { }) } +#[test] +fn send_charging_customers_batch_works2() { + ExtBuilder.build_and_execute(|| { + System::set_block_number(1); + + let dac_account = 123u128; + let user5 = 5u128; + let cluster_id = ONE_CLUSTER_ID; + let era = 100; + let max_batch_index = 0; + let batch_index = 0; + let usage5 = CustomerUsage { + // should pass without debt + transferred_bytes: 1024, + stored_bytes: 1024, + number_of_puts: 1, + number_of_gets: 1, + }; + let payers5 = vec![(user5, usage5.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, + max_batch_index, + )); + assert_eq!(System::events().len(), 3); + + // 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::account_id()); + assert_ok!(DdcPayouts::send_charging_customers_batch( + RuntimeOrigin::signed(dac_account), + cluster_id, + era, + batch_index, + payers5, + )); + + let usage5_charge = calculate_charge(cluster_id, usage5.clone()); + let charge5 = calculate_charge_parts(cluster_id, usage5); + 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!( + charge5.puts + before_total_customer_charge.puts, + report.total_customer_charge.puts + ); + assert_eq!( + charge5.gets + before_total_customer_charge.gets, + report.total_customer_charge.gets + ); + assert_eq!( + charge5.storage + before_total_customer_charge.storage, + report.total_customer_charge.storage + ); + assert_eq!( + charge5.transfer + before_total_customer_charge.transfer, + report.total_customer_charge.transfer + ); + }) +} + #[test] fn end_charging_customers_fails_uninitialised() { ExtBuilder.build_and_execute(|| { - let root_account = 100u64; - let dac_account = 123u64; - let user1 = 1u64; + let root_account = 100u128; + let dac_account = 123u128; + let user1 = 1u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 2; @@ -580,8 +653,8 @@ fn end_charging_customers_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 123u64; - let user1 = 1u64; + let dac_account = 123u128; + let user1 = 1u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 0; @@ -618,15 +691,15 @@ fn end_charging_customers_works() { )); let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); - let charge = calculate_charge(usage1); + let charge = calculate_charge(cluster_id, usage1); System::assert_last_event( Event::Charged { cluster_id, era, batch_index, customer_id: user1, amount: charge } .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), @@ -636,9 +709,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(), @@ -653,30 +726,42 @@ 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); + let mut ratio = Perquintill::from_rational( + VALIDATOR1_SCORE, + VALIDATOR1_SCORE + VALIDATOR2_SCORE + VALIDATOR3_SCORE, + ); + assert_eq!(balance, get_fees(&cluster_id).validators_share * ratio * charge); balance = Balances::free_balance(VALIDATOR2_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.validators_share * charge / 3); + ratio = Perquintill::from_rational( + VALIDATOR2_SCORE, + VALIDATOR1_SCORE + VALIDATOR2_SCORE + VALIDATOR3_SCORE, + ); + assert_eq!(balance, get_fees(&cluster_id).validators_share * ratio * charge); balance = Balances::free_balance(VALIDATOR3_ACCOUNT_ID); - assert_eq!(balance, PRICING_FEES.validators_share * charge / 3); + ratio = Perquintill::from_rational( + VALIDATOR3_SCORE, + VALIDATOR1_SCORE + VALIDATOR2_SCORE + VALIDATOR3_SCORE, + ); + assert_eq!(balance, get_fees(&cluster_id).validators_share * ratio * charge); assert_eq!( report_after.total_customer_charge.transfer, @@ -702,8 +787,8 @@ fn end_charging_customers_works_zero_fees() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 123u64; - let user1 = 1u64; + let dac_account = 123u128; + let user1 = 1u128; let cluster_id = ClusterId::zero(); let era = 100; let max_batch_index = 0; @@ -711,8 +796,8 @@ fn end_charging_customers_works_zero_fees() { let usage1 = CustomerUsage { transferred_bytes: 23452345, stored_bytes: 3345234523, - number_of_puts: 4456456345234523, - number_of_gets: 523423, + number_of_puts: 1, + number_of_gets: 1, }; let payers = vec![(user1, usage1.clone())]; @@ -740,15 +825,15 @@ fn end_charging_customers_works_zero_fees() { )); let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); - let charge = calculate_charge(usage1); + let charge = calculate_charge(cluster_id, usage1); System::assert_last_event( Event::Charged { cluster_id, era, customer_id: user1, batch_index, amount: charge } .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), @@ -757,22 +842,22 @@ 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) .left_from_one(); - assert_eq!(total_left_from_one, Perbill::one()); + assert_eq!(total_left_from_one, Perquintill::one()); - assert_eq!(fees.treasury_share, Perbill::zero()); - assert_eq!(fees.validators_share, Perbill::zero()); - assert_eq!(fees.cluster_reserve_share, Perbill::zero()); + assert_eq!(fees.treasury_share, Perquintill::zero()); + assert_eq!(fees.validators_share, Perquintill::zero()); + assert_eq!(fees.cluster_reserve_share, Perquintill::zero()); balance = Balances::free_balance(TREASURY_ACCOUNT_ID); assert_eq!(balance, 0); @@ -811,9 +896,9 @@ fn end_charging_customers_works_zero_fees() { #[test] fn begin_rewarding_providers_fails_uninitialised() { ExtBuilder.build_and_execute(|| { - let root_account = 1u64; - let dac_account = 2u64; - let user1 = 3u64; + let root_account = 1u128; + let dac_account = 2u128; + let user1 = 3u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 2; @@ -936,8 +1021,8 @@ fn begin_rewarding_providers_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 123u64; - let user1 = 1u64; + let dac_account = 123u128; + let user1 = 1u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 0; @@ -995,11 +1080,11 @@ fn begin_rewarding_providers_works() { #[test] fn send_rewarding_providers_batch_fails_uninitialised() { ExtBuilder.build_and_execute(|| { - let root_account = 1u64; - let dac_account = 2u64; - let user1 = 3u64; - let user2 = 4u64; - let node1 = 33u64; + let root_account = 1u128; + let dac_account = 2u128; + let user1 = 3u128; + let user2 = 4u128; + let node1 = 33u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 1; @@ -1140,11 +1225,11 @@ fn send_rewarding_providers_batch_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 123u64; - let user1 = 1u64; - let node1 = 10u64; - let node2 = 11u64; - let node3 = 12u64; + let dac_account = 123u128; + let user1 = 1u128; + let node1 = 10u128; + let node2 = 11u128; + let node3 = 12u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 0; @@ -1232,9 +1317,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!( @@ -1270,46 +1355,59 @@ fn send_rewarding_providers_batch_works() { payees1, )); - let mut ratio = Perbill::from_rational( + let ratio1_transfer = Perquintill::from_rational( node_usage1.transferred_bytes, total_nodes_usage.transferred_bytes, ); - let mut transfer_charge = ratio * report_after.total_customer_charge.transfer; + let mut transfer_charge = ratio1_transfer * report_after.total_customer_charge.transfer; - ratio = Perbill::from_rational(node_usage1.stored_bytes, total_nodes_usage.stored_bytes); - let mut storage_charge = ratio * report_after.total_customer_charge.storage; + let ratio1_storage = + Perquintill::from_rational(node_usage1.stored_bytes, total_nodes_usage.stored_bytes); + let mut storage_charge = ratio1_storage * report_after.total_customer_charge.storage; - ratio = - Perbill::from_rational(node_usage1.number_of_puts, total_nodes_usage.number_of_puts); - let mut puts_charge = ratio * report_after.total_customer_charge.puts; + let ratio1_puts = Perquintill::from_rational( + node_usage1.number_of_puts, + total_nodes_usage.number_of_puts, + ); + let mut puts_charge = ratio1_puts * report_after.total_customer_charge.puts; - ratio = - Perbill::from_rational(node_usage1.number_of_gets, total_nodes_usage.number_of_gets); - let mut gets_charge = ratio * report_after.total_customer_charge.gets; + let ratio1_gets = Perquintill::from_rational( + node_usage1.number_of_gets, + total_nodes_usage.number_of_gets, + ); + let mut gets_charge = ratio1_gets * report_after.total_customer_charge.gets; - let mut balance = Balances::free_balance(node1); - assert_eq!(balance, transfer_charge + storage_charge + puts_charge + gets_charge); + let balance_node1 = Balances::free_balance(node1); + assert_eq!(balance_node1, transfer_charge + storage_charge + puts_charge + gets_charge); + let mut report_reward = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); - ratio = Perbill::from_rational( + let ratio2_transfer = Perquintill::from_rational( node_usage2.transferred_bytes, total_nodes_usage.transferred_bytes, ); - transfer_charge = ratio * report_after.total_customer_charge.transfer; + transfer_charge = ratio2_transfer * report_after.total_customer_charge.transfer; - ratio = Perbill::from_rational(node_usage2.stored_bytes, total_nodes_usage.stored_bytes); - storage_charge = ratio * report_after.total_customer_charge.storage; + let ratio2_storage = + Perquintill::from_rational(node_usage2.stored_bytes, total_nodes_usage.stored_bytes); + storage_charge = ratio2_storage * report_after.total_customer_charge.storage; - ratio = - Perbill::from_rational(node_usage2.number_of_puts, total_nodes_usage.number_of_puts); - puts_charge = ratio * report_after.total_customer_charge.puts; + let ratio2_puts = Perquintill::from_rational( + node_usage2.number_of_puts, + total_nodes_usage.number_of_puts, + ); + puts_charge = ratio2_puts * report_after.total_customer_charge.puts; - ratio = - Perbill::from_rational(node_usage2.number_of_gets, total_nodes_usage.number_of_gets); - gets_charge = ratio * report_after.total_customer_charge.gets; + let ratio2_gets = Perquintill::from_rational( + node_usage2.number_of_gets, + total_nodes_usage.number_of_gets, + ); + gets_charge = ratio2_gets * report_after.total_customer_charge.gets; - balance = Balances::free_balance(node2); - assert_eq!(balance, transfer_charge + storage_charge + puts_charge + gets_charge); + let balance_node2 = Balances::free_balance(node2); + assert_eq!(balance_node2, transfer_charge + storage_charge + puts_charge + gets_charge); + assert_eq!(report_reward.total_distributed_reward, balance_node1 + balance_node2); + // batch 2 assert_ok!(DdcPayouts::send_rewarding_providers_batch( RuntimeOrigin::signed(dac_account), cluster_id, @@ -1318,25 +1416,42 @@ fn send_rewarding_providers_batch_works() { payees2, )); - ratio = Perbill::from_rational( + let ratio3_transfer = Perquintill::from_rational( node_usage3.transferred_bytes, total_nodes_usage.transferred_bytes, ); - transfer_charge = ratio * report_after.total_customer_charge.transfer; + transfer_charge = ratio3_transfer * report_after.total_customer_charge.transfer; + + let ratio3_storage = + Perquintill::from_rational(node_usage3.stored_bytes, total_nodes_usage.stored_bytes); + storage_charge = ratio3_storage * report_after.total_customer_charge.storage; - ratio = Perbill::from_rational(node_usage3.stored_bytes, total_nodes_usage.stored_bytes); - storage_charge = ratio * report_after.total_customer_charge.storage; + let ratio3_puts = Perquintill::from_rational( + node_usage3.number_of_puts, + total_nodes_usage.number_of_puts, + ); + puts_charge = ratio3_puts * report_after.total_customer_charge.puts; + + let ratio3_gets = Perquintill::from_rational( + node_usage3.number_of_gets, + total_nodes_usage.number_of_gets, + ); + gets_charge = ratio3_gets * report_after.total_customer_charge.gets; - ratio = - Perbill::from_rational(node_usage3.number_of_puts, total_nodes_usage.number_of_puts); - puts_charge = ratio * report_after.total_customer_charge.puts; + report_reward = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let balance_node3 = Balances::free_balance(node3); + assert_eq!(balance_node3, transfer_charge + storage_charge + puts_charge + gets_charge); + assert_eq!( + report_reward.total_distributed_reward, + balance_node1 + balance_node2 + balance_node3 + ); - ratio = - Perbill::from_rational(node_usage3.number_of_gets, total_nodes_usage.number_of_gets); - gets_charge = ratio * report_after.total_customer_charge.gets; + let expected_amount_to_reward = report_reward.total_customer_charge.transfer + + report_reward.total_customer_charge.storage + + report_reward.total_customer_charge.puts + + report_reward.total_customer_charge.gets; - balance = Balances::free_balance(node3); - assert_eq!(balance, transfer_charge + storage_charge + puts_charge + gets_charge); + assert!(expected_amount_to_reward - report_reward.total_distributed_reward <= 20000); assert_ok!(DdcPayouts::end_rewarding_providers( RuntimeOrigin::signed(dac_account), @@ -1346,14 +1461,796 @@ fn send_rewarding_providers_batch_works() { }) } +#[test] +fn send_rewarding_providers_batch_100nodes_small_usage_works() { + 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 send_rewarding_providers_batch_100nodes_large_usage_works() { + 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 ratio = match i % 5 { + 0 => Perquintill::from_float(1_000_000.0), + 1 => Perquintill::from_float(10_000_000.0), + 2 => Perquintill::from_float(100_000_000.0), + 3 => Perquintill::from_float(1_000_000_000.0), + 4 => Perquintill::from_float(10_000_000_000.0), + _ => unreachable!(), + }; + let mut node_usage = match i % 3 { + 0 => node_usage1.clone(), + 1 => node_usage2.clone(), + 2 => node_usage3.clone(), + _ => unreachable!(), + }; + node_usage.transferred_bytes = ratio * node_usage.transferred_bytes; + node_usage.stored_bytes = ratio * node_usage.stored_bytes; + node_usage.number_of_puts = ratio * node_usage.number_of_puts; + node_usage.number_of_gets = ratio * node_usage.number_of_gets; + + 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::from_float(1_000_000.0), + 1 => Perquintill::from_float(10_000_000.0), + 2 => Perquintill::from_float(100_000_000.0), + 3 => Perquintill::from_float(1_000_000_000.0), + 4 => Perquintill::from_float(10_000_000_000.0), + _ => 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 send_rewarding_providers_batch_100nodes_small_large_usage_works() { + 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 ratio = match i % 5 { + 0 => Perquintill::from_float(1_000_000.0), + 1 => Perquintill::from_float(0.5), + 2 => Perquintill::from_float(100_000_000.0), + 3 => Perquintill::from_float(0.25), + 4 => Perquintill::from_float(10_000_000_000.0), + _ => unreachable!(), + }; + let mut node_usage = match i % 3 { + 0 => node_usage1.clone(), + 1 => node_usage2.clone(), + 2 => node_usage3.clone(), + _ => unreachable!(), + }; + node_usage.transferred_bytes = ratio * node_usage.transferred_bytes; + node_usage.stored_bytes = ratio * node_usage.stored_bytes; + node_usage.number_of_puts = ratio * node_usage.number_of_puts; + node_usage.number_of_gets = ratio * node_usage.number_of_gets; + + 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::from_float(1_000_000.0), + 1 => Perquintill::from_float(10_000_000.0), + 2 => Perquintill::from_float(100_000_000.0), + 3 => Perquintill::from_float(1_000_000_000.0), + 4 => Perquintill::from_float(10_000_000_000.0), + _ => 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(|| { - let root_account = 1u64; - let dac_account = 2u64; - let user1 = 3u64; - let user2 = 4u64; - let node1 = 33u64; + let root_account = 1u128; + let dac_account = 2u128; + let user1 = 3u128; + let user2 = 4u128; + let node1 = 33u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 1; @@ -1509,16 +2406,30 @@ fn end_rewarding_providers_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 2u64; - let user1 = 3u64; - let node1 = 33u64; + let dac_account = 2u128; + let user1 = 1u128; + let node1 = 33u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 0; let batch_index = 0; - let total_node_usage = NodeUsage::default(); - let payers = vec![(user1, CustomerUsage::default())]; - let payees = vec![(node1, NodeUsage::default())]; + let usage1 = CustomerUsage { + transferred_bytes: 23452345, + stored_bytes: 3345234523, + number_of_puts: 4456456345234523, + number_of_gets: 523423, + }; + + let node_usage1 = NodeUsage { + // CDN + Storage + transferred_bytes: usage1.transferred_bytes * 2 / 3, + stored_bytes: usage1.stored_bytes * 2 / 3, + number_of_puts: usage1.number_of_puts * 2 / 3, + number_of_gets: usage1.number_of_gets * 2 / 3, + }; + let total_node_usage = node_usage1.clone(); + let payers = vec![(user1, usage1)]; + let payees = vec![(node1, node_usage1)]; assert_ok!(DdcPayouts::set_authorised_caller(RuntimeOrigin::root(), dac_account)); @@ -1584,11 +2495,11 @@ fn end_rewarding_providers_works() { #[test] fn end_billing_report_fails_uninitialised() { ExtBuilder.build_and_execute(|| { - let root_account = 1u64; - let dac_account = 2u64; - let user1 = 3u64; - let user2 = 4u64; - let node1 = 33u64; + let root_account = 1u128; + let dac_account = 2u128; + let user1 = 3u128; + let user2 = 4u128; + let node1 = 33u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 1; @@ -1721,9 +2632,9 @@ fn end_billing_report_works() { ExtBuilder.build_and_execute(|| { System::set_block_number(1); - let dac_account = 2u64; - let user1 = 3u64; - let node1 = 33u64; + let dac_account = 2u128; + let user1 = 3u128; + let node1 = 33u128; let cluster_id = ClusterId::from([12; 20]); let era = 100; let max_batch_index = 0; diff --git a/pallets/ddc-staking/src/mock.rs b/pallets/ddc-staking/src/mock.rs index 7ff3fbd1e..9017e9ef5 100644 --- a/pallets/ddc-staking/src/mock.rs +++ b/pallets/ddc-staking/src/mock.rs @@ -26,7 +26,7 @@ use sp_io::TestExternalities; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, + Perquintill, }; use sp_std::collections::btree_map::BTreeMap; @@ -179,9 +179,9 @@ impl ClusterVisitor for TestClusterVisitor { fn get_fees_params(_cluster_id: &ClusterId) -> Result { Ok(ClusterFeesParams { - treasury_share: Perbill::from_percent(1), - validators_share: Perbill::from_percent(10), - cluster_reserve_share: Perbill::from_percent(2), + treasury_share: Perquintill::from_percent(1), + validators_share: Perquintill::from_percent(10), + cluster_reserve_share: Perquintill::from_percent(2), }) } diff --git a/pallets/ddc-staking/src/testing_utils.rs b/pallets/ddc-staking/src/testing_utils.rs index 7851ba072..fb63fa46a 100644 --- a/pallets/ddc-staking/src/testing_utils.rs +++ b/pallets/ddc-staking/src/testing_utils.rs @@ -7,7 +7,7 @@ use ddc_primitives::{ use frame_benchmarking::account; use frame_support::traits::Currency; use frame_system::RawOrigin; -use sp_runtime::{traits::StaticLookup, Perbill}; +use sp_runtime::{traits::StaticLookup, Perquintill}; use sp_std::prelude::*; use crate::{Pallet as DdcStaking, *}; @@ -109,9 +109,9 @@ pub fn create_stash_controller_node_with_balance( let cluster_id = ClusterId::from([1; 20]); let cluster_params = ClusterParams { node_provider_auth_contract: Some(stash.clone()) }; let cluster_gov_params: ClusterGovParams, T::BlockNumber> = ClusterGovParams { - treasury_share: Perbill::default(), - validators_share: Perbill::default(), - cluster_reserve_share: Perbill::default(), + treasury_share: Perquintill::default(), + validators_share: Perquintill::default(), + cluster_reserve_share: Perquintill::default(), storage_bond_size: 10u32.into(), storage_chill_delay: 50u32.into(), storage_unbonding_delay: 50u32.into(), diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index f0e9c1331..1c8fba03d 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -5,7 +5,11 @@ use scale_info::{prelude::vec::Vec, TypeInfo}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_core::hash::H160; -use sp_runtime::{AccountId32, Perbill, RuntimeDebug}; +use sp_runtime::{AccountId32, Perquintill, RuntimeDebug}; + +pub const MILLICENTS: u128 = 100_000; +pub const CENTS: u128 = 1_000 * MILLICENTS; // assume this is worth about a cent. +pub const DOLLARS: u128 = 100 * CENTS; pub type ClusterId = H160; pub type DdcEra = u32; pub type BucketId = u64; @@ -22,9 +26,9 @@ pub struct ClusterParams { #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Default)] #[scale_info(skip_type_params(Balance, BlockNumber, T))] pub struct ClusterGovParams { - pub treasury_share: Perbill, - pub validators_share: Perbill, - pub cluster_reserve_share: Perbill, + pub treasury_share: Perquintill, + pub validators_share: Perquintill, + pub cluster_reserve_share: Perquintill, pub storage_bond_size: Balance, pub storage_chill_delay: BlockNumber, pub storage_unbonding_delay: BlockNumber, @@ -44,12 +48,12 @@ pub struct ClusterPricingParams { #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] pub struct ClusterFeesParams { - pub treasury_share: Perbill, - pub validators_share: Perbill, - pub cluster_reserve_share: Perbill, + pub treasury_share: Perquintill, + pub validators_share: Perquintill, + pub cluster_reserve_share: Perquintill, } -#[derive(Debug, PartialEq)] +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] pub struct ClusterBondingParams { pub storage_bond_size: u128, pub storage_chill_delay: BlockNumber, diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index 509a1893b..deefc42e5 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -83,8 +83,8 @@ use sp_runtime::{ curve::PiecewiseLinear, generic, impl_opaque_keys, traits::{ - self, AccountIdConversion, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, NumberFor, - OpaqueKeys, SaturatedConversion, StaticLookup, + self, AccountIdConversion, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, + Identity as IdentityConvert, NumberFor, OpaqueKeys, SaturatedConversion, StaticLookup, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perbill, Percent, Permill, Perquintill, @@ -1382,9 +1382,10 @@ impl pallet_ddc_payouts::Config for Runtime { type CustomerDepositor = DdcCustomers; type ClusterVisitor = DdcClusters; type TreasuryVisitor = TreasuryWrapper; - type ValidatorList = pallet_staking::UseValidatorsMap; + type NominatorsAndValidatorsList = pallet_staking::UseNominatorsAndValidatorsMap; type ClusterCreator = DdcClusters; type WeightInfo = pallet_ddc_payouts::weights::SubstrateWeight; + type VoteScoreToU64 = IdentityConvert; // used for UseNominatorsAndValidatorsMap } construct_runtime!( diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 40fbf4fd3..413d054da 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -78,8 +78,8 @@ use sp_runtime::{ curve::PiecewiseLinear, generic, impl_opaque_keys, traits::{ - self, AccountIdConversion, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, NumberFor, - OpaqueKeys, SaturatedConversion, StaticLookup, + self, AccountIdConversion, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, + Identity as IdentityConvert, NumberFor, OpaqueKeys, SaturatedConversion, StaticLookup, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perbill, Percent, Permill, Perquintill, @@ -1367,9 +1367,10 @@ impl pallet_ddc_payouts::Config for Runtime { type CustomerDepositor = DdcCustomers; type ClusterVisitor = DdcClusters; type TreasuryVisitor = TreasuryWrapper; - type ValidatorList = pallet_staking::UseValidatorsMap; + type NominatorsAndValidatorsList = pallet_staking::UseNominatorsAndValidatorsMap; type ClusterCreator = DdcClusters; type WeightInfo = pallet_ddc_payouts::weights::SubstrateWeight; + type VoteScoreToU64 = IdentityConvert; // used for UseNominatorsAndValidatorsMap } impl pallet_ddc_staking::Config for Runtime {