Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add prewitness deposit events #4745

Merged
merged 6 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions state-chain/pallets/cf-ingress-egress/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,13 @@ mod benchmarks {
));
}

let boost_id = PrewitnessedDepositIdCounter::<T, I>::get();
let prewitnessed_deposit_id = PrewitnessedDepositIdCounter::<T, I>::get();

#[block]
{
BoostPools::<T, I>::mutate(asset, FEE_TIER, |pool| {
// This depends on the number of boosters who contributed to it:
pool.as_mut().unwrap().on_lost_deposit(boost_id);
pool.as_mut().unwrap().on_lost_deposit(prewitnessed_deposit_id);
});
}
}
Expand Down
69 changes: 42 additions & 27 deletions state-chain/pallets/cf-ingress-egress/src/boost_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ pub struct BoostPool<AccountId, C: Chain> {
// Mapping from booster to the available amount they own in `available_amount`
amounts: BTreeMap<AccountId, ScaledAmount<C>>,
// Boosted deposits awaiting finalisation and how much of them is owed to which booster
pending_boosts: BTreeMap<BoostId, BTreeMap<AccountId, ScaledAmount<C>>>,
pending_boosts: BTreeMap<PrewitnessedDepositId, BTreeMap<AccountId, ScaledAmount<C>>>,
// Stores boosters who have indicated that they want to stop boosting along with
// the pending deposits that they have to wait to be finalised
pending_withdrawals: BTreeMap<AccountId, BTreeSet<BoostId>>,
pending_withdrawals: BTreeMap<AccountId, BTreeSet<PrewitnessedDepositId>>,
}

impl<AccountId, C: Chain> BoostPool<AccountId, C>
Expand Down Expand Up @@ -122,17 +122,17 @@ where
}
}

fn add_funds_inner(&mut self, account_id: AccountId, added_amount: ScaledAmount<C>) {
fn add_funds_inner(&mut self, booster_id: AccountId, added_amount: ScaledAmount<C>) {
// To keep things simple, we assume that the booster no longer wants to withdraw
// if they add more funds:
self.pending_withdrawals.remove(&account_id);
self.pending_withdrawals.remove(&booster_id);

self.amounts.entry(account_id).or_default().saturating_accrue(added_amount);
self.amounts.entry(booster_id).or_default().saturating_accrue(added_amount);
self.available_amount.saturating_accrue(added_amount);
}

pub(crate) fn add_funds(&mut self, account_id: AccountId, added_amount: C::ChainAmount) {
self.add_funds_inner(account_id, ScaledAmount::from_chain_amount(added_amount));
pub(crate) fn add_funds(&mut self, booster_id: AccountId, added_amount: C::ChainAmount) {
self.add_funds_inner(booster_id, ScaledAmount::from_chain_amount(added_amount));
}

pub(crate) fn get_available_amount(&self) -> C::ChainAmount {
Expand All @@ -141,7 +141,7 @@ where

pub(crate) fn provide_funds_for_boosting(
&mut self,
boost_id: BoostId,
prewitnessed_deposit_id: PrewitnessedDepositId,
amount_to_boost: C::ChainAmount,
) -> Result<(C::ChainAmount, C::ChainAmount), &'static str> {
let amount_to_boost = ScaledAmount::<C>::from_chain_amount(amount_to_boost);
Expand All @@ -158,7 +158,7 @@ where
(provided_amount, fee)
};

self.use_funds_for_boosting(boost_id, provided_amount, fee_amount)?;
self.use_funds_for_boosting(prewitnessed_deposit_id, provided_amount, fee_amount)?;

Ok((
provided_amount.saturating_add(fee_amount).into_chain_amount(),
Expand All @@ -170,7 +170,7 @@ where
/// among current boosters (along with the fee) upon finalisation
fn use_funds_for_boosting(
&mut self,
boost_id: BoostId,
prewitnessed_deposit_id: PrewitnessedDepositId,
required_amount: ScaledAmount<C>,
boost_fee: ScaledAmount<C>,
) -> Result<(), &'static str> {
Expand Down Expand Up @@ -237,7 +237,8 @@ where
// ensure that we correctly account for every single atomic unit even in presence
// of rounding errors:
use nanorand::{Rng, WyRand};
let lucky_index = WyRand::new_seed(boost_id).generate_range(0..self.amounts.len());
let lucky_index =
WyRand::new_seed(prewitnessed_deposit_id).generate_range(0..self.amounts.len());
if let Some((lucky_id, amount)) = self.amounts.iter_mut().nth(lucky_index) {
amount.saturating_accrue(excess_contributed);

Expand All @@ -249,17 +250,17 @@ where
// For every active booster, record how much of this particular deposit they are owed,
// (which is their pool share at the time of boosting):
self.pending_boosts
.try_insert(boost_id, boosters_to_receive)
.try_insert(prewitnessed_deposit_id, boosters_to_receive)
.map_err(|_| "Pending boost id already exists")?;

Ok(())
}

pub(crate) fn on_finalised_deposit(
&mut self,
boost_id: BoostId,
prewitnessed_deposit_id: PrewitnessedDepositId,
) -> Vec<(AccountId, C::ChainAmount)> {
let Some(boost_contributions) = self.pending_boosts.remove(&boost_id) else {
let Some(boost_contributions) = self.pending_boosts.remove(&prewitnessed_deposit_id) else {
// The deposit hadn't been boosted
return vec![];
};
Expand All @@ -270,8 +271,8 @@ where
// Depending on whether the booster is withdrawing, add deposits to
// their free balance or back to the available boost pool:
if let Some(pending_deposits) = self.pending_withdrawals.get_mut(&booster_id) {
if !pending_deposits.remove(&boost_id) {
log::warn!("Withdrawing booster contributed to boost {boost_id}, but it is not in pending withdrawals");
if !pending_deposits.remove(&prewitnessed_deposit_id) {
log::warn!("Withdrawing booster contributed to boost {prewitnessed_deposit_id}, but it is not in pending withdrawals");
}

if pending_deposits.is_empty() {
Expand All @@ -288,16 +289,19 @@ where
}

// Returns the number of boosters affected
pub fn on_lost_deposit(&mut self, boost_id: BoostId) -> usize {
let Some(booster_contributions) = self.pending_boosts.remove(&boost_id) else {
log_or_panic!("Failed to find boost record for a lost deposit: {boost_id}");
pub fn on_lost_deposit(&mut self, prewitnessed_deposit_id: PrewitnessedDepositId) -> usize {
let Some(booster_contributions) = self.pending_boosts.remove(&prewitnessed_deposit_id)
else {
log_or_panic!(
"Failed to find boost record for a lost deposit: {prewitnessed_deposit_id}"
);
return 0;
};

for booster_id in booster_contributions.keys() {
if let Some(pending_deposits) = self.pending_withdrawals.get_mut(booster_id) {
if !pending_deposits.remove(&boost_id) {
log::warn!("Withdrawing booster contributed to boost {boost_id}, but it is not in pending withdrawals");
if !pending_deposits.remove(&prewitnessed_deposit_id) {
log::warn!("Withdrawing booster contributed to boost {prewitnessed_deposit_id}, but it is not in pending withdrawals");
}

if pending_deposits.is_empty() {
Expand All @@ -309,8 +313,12 @@ where
booster_contributions.len()
}

// Return the amount immediately available for booster
pub fn stop_boosting(&mut self, booster_id: AccountId) -> Result<C::ChainAmount, &'static str> {
// Return the amount immediately unlocked for the booster and a list of all pending boosts that
// the booster is still a part of.
pub fn stop_boosting(
&mut self,
booster_id: AccountId,
) -> Result<(C::ChainAmount, BTreeSet<PrewitnessedDepositId>), &'static str> {
let Some(booster_active_amount) = self.amounts.remove(&booster_id) else {
return Err("Account not found in boost pool")
};
Expand All @@ -321,18 +329,25 @@ where
.pending_boosts
.iter()
.filter(|(_, owed_amounts)| owed_amounts.contains_key(&booster_id))
.map(|(boost_id, _)| *boost_id)
.map(|(prewitnessed_deposit_id, _)| *prewitnessed_deposit_id)
.collect();

if !pending_deposits.is_empty() {
self.pending_withdrawals.insert(booster_id, pending_deposits);
self.pending_withdrawals.insert(booster_id, pending_deposits.clone());
}

Ok(booster_active_amount.into_chain_amount())
Ok((booster_active_amount.into_chain_amount(), pending_deposits))
}

#[cfg(test)]
pub fn get_pending_boosts(&self) -> Vec<BoostId> {
pub fn get_pending_boosts(&self) -> Vec<PrewitnessedDepositId> {
self.pending_boosts.keys().copied().collect()
}
#[cfg(test)]
pub fn get_available_amount_for_account(
&self,
booster_id: &AccountId,
) -> Option<C::ChainAmount> {
self.amounts.get(booster_id).copied().map(|a| a.into_chain_amount())
}
}
46 changes: 24 additions & 22 deletions state-chain/pallets/cf-ingress-egress/src/boost_pool/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const BOOSTER_1: AccountId = 1;
const BOOSTER_2: AccountId = 2;
const BOOSTER_3: AccountId = 3;

const BOOST_1: BoostId = 1;
const BOOST_2: BoostId = 2;
const BOOST_1: PrewitnessedDepositId = 1;
const BOOST_2: PrewitnessedDepositId = 2;

#[track_caller]
pub fn check_pool(pool: &TestPool, amounts: impl IntoIterator<Item = (AccountId, Amount)>) {
Expand All @@ -34,7 +34,7 @@ pub fn check_pool(pool: &TestPool, amounts: impl IntoIterator<Item = (AccountId,
#[track_caller]
fn check_pending_boosts(
pool: &TestPool,
boosts: impl IntoIterator<Item = (BoostId, Vec<(AccountId, Amount)>)>,
boosts: impl IntoIterator<Item = (PrewitnessedDepositId, Vec<(AccountId, Amount)>)>,
) {
let expected_boosts: BTreeMap<_, _> = boosts.into_iter().collect();

Expand All @@ -44,8 +44,8 @@ fn check_pending_boosts(
"mismatch in pending boosts ids"
);

for (boost_id, boost_amounts) in &pool.pending_boosts {
let expected_amounts = &expected_boosts[boost_id];
for (prewitnessed_deposit_id, boost_amounts) in &pool.pending_boosts {
let expected_amounts = &expected_boosts[prewitnessed_deposit_id];

assert_eq!(
BTreeMap::from_iter(expected_amounts.iter().copied()),
Expand All @@ -59,11 +59,13 @@ fn check_pending_boosts(
#[track_caller]
fn check_pending_withdrawals(
pool: &TestPool,
withdrawals: impl IntoIterator<Item = (AccountId, Vec<BoostId>)>,
withdrawals: impl IntoIterator<Item = (AccountId, Vec<PrewitnessedDepositId>)>,
) {
let expected_withdrawals: BTreeMap<_, BTreeSet<_>> = withdrawals
.into_iter()
.map(|(account_id, boost_ids)| (account_id, boost_ids.into_iter().collect()))
.map(|(account_id, prewitnessed_deposit_ids)| {
(account_id, prewitnessed_deposit_ids.into_iter().collect())
})
.collect();

assert_eq!(pool.pending_withdrawals, expected_withdrawals, "mismatch in pending withdrawals");
Expand Down Expand Up @@ -105,14 +107,14 @@ fn withdrawing_funds() {
check_pool(&pool, [(BOOSTER_1, 1000), (BOOSTER_2, 900), (BOOSTER_3, 800)]);

// No pending to receive, should be able to withdraw in full
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(1000));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((1000, Default::default())));
check_pool(&pool, [(BOOSTER_2, 900), (BOOSTER_3, 800)]);
check_pending_withdrawals(&pool, []);

assert_eq!(pool.stop_boosting(BOOSTER_2), Ok(900));
assert_eq!(pool.stop_boosting(BOOSTER_2), Ok((900, Default::default())));
check_pool(&pool, [(BOOSTER_3, 800)]);

assert_eq!(pool.stop_boosting(BOOSTER_3), Ok(800));
assert_eq!(pool.stop_boosting(BOOSTER_3), Ok((800, Default::default())));
check_pool(&pool, []);
}

Expand All @@ -125,7 +127,7 @@ fn withdrawing_twice_is_no_op() {
pool.add_funds(BOOSTER_1, AMOUNT_1);
pool.add_funds(BOOSTER_2, AMOUNT_2);

assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(AMOUNT_1));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((AMOUNT_1, Default::default())));

check_pool(&pool, [(BOOSTER_2, AMOUNT_2)]);

Expand Down Expand Up @@ -170,7 +172,7 @@ fn adding_funds_during_pending_withdrawal_from_same_booster() {

check_pending_boosts(&pool, [(BOOST_1, vec![(BOOSTER_1, 500), (BOOSTER_2, 1500)])]);

assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(500));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((500, BTreeSet::from_iter([BOOST_1]))));

check_pool(&pool, [(BOOSTER_2, 1500)]);
check_pending_boosts(&pool, [(BOOST_1, vec![(BOOSTER_1, 500), (BOOSTER_2, 1500)])]);
Expand All @@ -197,7 +199,7 @@ fn withdrawing_funds_before_finalisation() {
check_pool(&pool, [(BOOSTER_1, 500), (BOOSTER_2, 500)]);

// Only some of the funds are available immediately, and some are in pending withdrawals:
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(500));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((500, BTreeSet::from_iter([BOOST_1]))));
check_pool(&pool, [(BOOSTER_2, 500)]);

assert_eq!(pool.on_finalised_deposit(BOOST_1), vec![(BOOSTER_1, 500)]);
Expand All @@ -215,7 +217,7 @@ fn adding_funds_with_pending_withdrawals() {
check_pool(&pool, [(BOOSTER_1, 500), (BOOSTER_2, 500)]);

// Only some of the funds are available immediately, and some are in pending withdrawals:
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(500));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((500, BTreeSet::from_iter([BOOST_1]))));
check_pool(&pool, [(BOOSTER_2, 500)]);

pool.add_funds(BOOSTER_3, 1000);
Expand Down Expand Up @@ -243,7 +245,7 @@ fn deposit_is_lost_while_withdrawing() {
pool.add_funds(BOOSTER_1, 1000);
pool.add_funds(BOOSTER_2, 1000);
assert_eq!(pool.provide_funds_for_boosting(BOOST_1, 1000), Ok((1000, 0)));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(500));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((500, BTreeSet::from_iter([BOOST_1]))));

check_pool(&pool, [(BOOSTER_2, 500)]);
check_pending_boosts(&pool, [(BOOST_1, vec![(BOOSTER_1, 500), (BOOSTER_2, 500)])]);
Expand All @@ -268,7 +270,7 @@ fn partially_losing_pending_withdrawals() {

check_pool(&pool, [(BOOSTER_1, 250), (BOOSTER_2, 250)]);

assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(250));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((250, BTreeSet::from_iter([BOOST_1, BOOST_2]))));

check_pending_withdrawals(&pool, [(BOOSTER_1, vec![BOOST_1, BOOST_2])]);

Expand Down Expand Up @@ -312,7 +314,7 @@ fn booster_joins_then_funds_lost() {
assert_eq!(pool.provide_funds_for_boosting(BOOST_1, 500), Ok((500, 0)));
assert_eq!(pool.provide_funds_for_boosting(BOOST_2, 1000), Ok((1000, 0)));

assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(250));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((250, BTreeSet::from_iter([BOOST_1, BOOST_2]))));
check_pool(&pool, [(BOOSTER_2, 250)]);

// New booster joins while we have a pending withdrawal:
Expand Down Expand Up @@ -340,7 +342,7 @@ fn booster_joins_between_boosts() {
check_pool(&pool, [(BOOSTER_1, 755), (BOOSTER_2, 755)]);
check_pending_boosts(&pool, [(BOOST_1, vec![(BOOSTER_1, 250), (BOOSTER_2, 250)])]);

assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(755));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((755, BTreeSet::from_iter([BOOST_1]))));
check_pool(&pool, [(BOOSTER_2, 755)]);

// New booster joins while we have a pending withdrawal:
Expand Down Expand Up @@ -404,12 +406,12 @@ fn small_rewards_accumulate() {
check_pool(&pool, [(BOOSTER_1, 1004), (BOOSTER_2, 50)]);

// 4 more boost like that and BOOSTER 2 should have withdrawable fees:
for boost_id in 1..=4 {
for prewitnessed_deposit_id in 1..=4 {
assert_eq!(
pool.provide_funds_for_boosting(boost_id, SMALL_DEPOSIT),
pool.provide_funds_for_boosting(prewitnessed_deposit_id, SMALL_DEPOSIT),
Ok((SMALL_DEPOSIT, 5))
);
assert_eq!(pool.on_finalised_deposit(boost_id), vec![]);
assert_eq!(pool.on_finalised_deposit(prewitnessed_deposit_id), vec![]);
}

// Note the increase in Booster 2's balance:
Expand All @@ -425,7 +427,7 @@ fn use_max_available_amount() {

check_pool(&pool, [(BOOSTER_1, 0)]);

assert_eq!(pool.stop_boosting(BOOSTER_1), Ok(0));
assert_eq!(pool.stop_boosting(BOOSTER_1), Ok((0, BTreeSet::from_iter([BOOST_1]))));

pool.add_funds(BOOSTER_1, 200);

Expand Down
Loading
Loading