Skip to content

Commit

Permalink
Payout dust (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
aie0 authored Dec 14, 2023
1 parent e24b417 commit 276c4df
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 19 deletions.
59 changes: 42 additions & 17 deletions pallets/ddc-payouts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,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<T>,
},
NotDistributedOverallReward {
cluster_id: ClusterId,
era: DdcEra,
expected_reward: u128,
total_distributed_reward: u128,
},
RewardingFinished {
cluster_id: ClusterId,
era: DdcEra,
Expand All @@ -212,7 +225,6 @@ pub mod pallet {
BatchIndexAlreadyProcessed,
BatchIndexIsOutOfRange,
BatchesMissed,
NotDistributedBalance,
BatchIndexOverflow,
BoundedVecOverflow,
ArithmeticOverflow,
Expand Down Expand Up @@ -682,17 +694,22 @@ pub mod pallet {
let mut reward: BalanceOf<T> =
amount_to_reward.saturated_into::<BalanceOf<T>>();

let balance = <T as pallet::Config>::Currency::free_balance(
let vault_balance = <T as pallet::Config>::Currency::free_balance(
&updated_billing_report.vault,
) - <T as pallet::Config>::Currency::minimum_balance();

if reward > balance {
ensure!(
reward - balance <= MaxDust::get().into(),
Error::<T>::NotDistributedBalance
);

reward = balance;
if reward > vault_balance {
if reward - vault_balance > MaxDust::get().into() {
Self::deposit_event(Event::<T>::NotDistributedReward {
cluster_id,
era,
node_provider_id: node_provider_id.clone(),
expected_reward: amount_to_reward,
distributed_reward: vault_balance,
});
}

reward = vault_balance;
}

<T as pallet::Config>::Currency::transfer(
Expand Down Expand Up @@ -758,11 +775,16 @@ pub mod pallet {
})()
.ok_or(Error::<T>::ArithmeticOverflow)?;

ensure!(
expected_amount_to_reward - billing_report.total_distributed_reward <=
MaxDust::get().into(),
Error::<T>::NotDistributedBalance
);
if expected_amount_to_reward - billing_report.total_distributed_reward >
MaxDust::get().into()
{
Self::deposit_event(Event::<T>::NotDistributedOverallReward {
cluster_id,
era,
expected_reward: expected_amount_to_reward,
total_distributed_reward: billing_report.total_distributed_reward,
});
}

billing_report.state = State::ProvidersRewarded;
ActiveBillingReports::<T>::insert(cluster_id, era, billing_report);
Expand Down Expand Up @@ -854,14 +876,17 @@ pub mod pallet {
let mut node_reward = NodeReward::default();

let mut ratio = Perquintill::from_rational(
node_usage.transferred_bytes,
total_nodes_usage.transferred_bytes,
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 = Perquintill::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 =
Expand Down
2 changes: 1 addition & 1 deletion pallets/ddc-payouts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ construct_runtime!(
}
);

pub static MAX_DUST: u16 = 20000;
pub static MAX_DUST: u16 = 100;

parameter_types! {
pub static ExistentialDeposit: Balance = 1;
Expand Down
267 changes: 266 additions & 1 deletion pallets/ddc-payouts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1449,7 +1449,7 @@ fn send_rewarding_providers_batch_works() {
}

#[test]
fn send_rewarding_providers_batch_works100nodes() {
fn send_rewarding_providers_batch_100nodes_small_usage_works() {
ExtBuilder.build_and_execute(|| {
System::set_block_number(1);

Expand Down Expand Up @@ -1700,6 +1700,271 @@ fn send_rewarding_providers_batch_works100nodes() {
})
}

#[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<(u128, NodeUsage)>> = 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<(u128, CustomerUsage)>> = 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(|| {
Expand Down

0 comments on commit 276c4df

Please sign in to comment.