Skip to content

Commit

Permalink
Automate OB withdrawals (#925)
Browse files Browse the repository at this point in the history
## Describe your changes

Automate Ob withdrawals to a safe limit, then make it claimable

## Issue ticket number and link

## Checklist before requesting a review
- [x] I have performed a self-review of my code.
- [x] If it is a core feature, I have added thorough tests.
- [x] I removed all Clippy and Formatting Warnings. 
- [x] I added required Copyrights.
  • Loading branch information
Gauthamastro authored Mar 13, 2024
2 parents 1d4b403 + 4915188 commit 2ece554
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 139 deletions.
2 changes: 2 additions & 0 deletions pallets/liquidity-mining/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ parameter_types! {
pub const TresuryPalletId: PalletId = PalletId(*b"OCEX_TRE");
pub const LMPRewardsPalletId: PalletId = PalletId(*b"OCEX_TMP");
pub const MsPerDay: u64 = 86_400_000;
pub const OBWithdrawalLimit: u32 = 50;
}

impl crate::pallet::Config for Test {
Expand All @@ -142,6 +143,7 @@ impl ocex::Config for Test {
type AuthorityId = ocex::sr25519::AuthorityId;
type GovernanceOrigin = EnsureRoot<sp_runtime::AccountId32>;
type CrowdSourceLiqudityMining = LiqudityMining;
type OBWithdrawalLimit = OBWithdrawalLimit;
type WeightInfo = ocex::weights::WeightInfo<Test>;
}

Expand Down
5 changes: 4 additions & 1 deletion pallets/ocex/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ benchmarks! {
}: _(RawOrigin::Signed(main.clone()), x as u64, main.clone())
verify {
assert_last_event::<T>(Event::WithdrawalClaimed {
snapshot_id: x as u64,
main,
withdrawals: vec_withdrawals,
}.into());
Expand Down Expand Up @@ -572,7 +573,9 @@ fn create_trade_metrics<T: Config>() -> TradingPairMetricsMap<T::AccountId> {

fn get_dummy_snapshot<T: Config>() -> SnapshotSummary<T::AccountId> {
let mut withdrawals = Vec::new();
for _ in 0..20 {
let pallet_account = Ocex::<T>::get_pallet_account();
T::NativeCurrency::deposit_creating(&pallet_account, (1000u128 * UNIT_BALANCE).saturated_into());
for _ in 0..50 {
withdrawals.push(Withdrawal {
main_account: T::AccountId::decode(&mut &[0u8; 32][..]).unwrap(),
amount: Decimal::one(),
Expand Down
118 changes: 69 additions & 49 deletions pallets/ocex/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ pub mod pallet {
#[pallet::constant]
type LMPRewardsPalletId: Get<PalletId>;

/// Orderbook withdrawal Limit
#[pallet::constant]
type OBWithdrawalLimit: Get<u32>;

/// Balances Pallet
type NativeCurrency: Currency<Self::AccountId>
+ ReservableCurrency<Self::AccountId>
Expand Down Expand Up @@ -810,9 +814,6 @@ pub mod pallet {
// Anyone can claim the withdrawal for any user
// This is to build services that can enable free withdrawals similar to CEXes.
let _ = ensure_signed(origin)?;
// This vector will keep track of withdrawals processed already
let mut processed_withdrawals = vec![];
let mut failed_withdrawals = vec![];
ensure!(
<Withdrawals<T>>::contains_key(snapshot_id),
Error::<T>::InvalidWithdrawalIndex
Expand All @@ -822,50 +823,28 @@ pub mod pallet {
// return Err
<Withdrawals<T>>::mutate(snapshot_id, |btree_map| {
// Get mutable reference to the withdrawals vector
if let Some(withdrawal_vector) = btree_map.get_mut(&account) {
while !withdrawal_vector.is_empty() {
// Perform pop operation to ensure we do not leave any withdrawal left
// for a double spend
if let Some(withdrawal) = withdrawal_vector.pop() {
if Self::on_idle_withdrawal_processor(withdrawal.clone()) {
processed_withdrawals.push(withdrawal.to_owned());
} else {
// Storing the failed withdrawals back into the storage item
failed_withdrawals.push(withdrawal.to_owned());
Self::deposit_event(Event::WithdrawalFailed(withdrawal.to_owned()));
}
} else {
return Err(Error::<T>::InvalidWithdrawalAmount);
}
}
if let Some(withdrawal_vector) = btree_map.remove(&account) {
let (failed_withdrawals, processed_withdrawals) =
Self::do_withdraw(snapshot_id, withdrawal_vector);
// Not removing key from BtreeMap so that failed withdrawals can still be
// tracked
btree_map.insert(account.clone(), failed_withdrawals);

if !processed_withdrawals.is_empty() {
Self::deposit_event(Event::WithdrawalClaimed {
snapshot_id,
main: account.clone(),
withdrawals: processed_withdrawals.clone(),
});
}
Ok(())
} else {
// This allows us to ensure we do not have someone with an invalid account
Err(Error::<T>::InvalidWithdrawalIndex)
}
})?;
if !processed_withdrawals.is_empty() {
Self::deposit_event(Event::WithdrawalClaimed {
main: account.clone(),
withdrawals: processed_withdrawals.clone(),
});
<OnChainEvents<T>>::mutate(|onchain_events| {
onchain_events.push(
orderbook_primitives::ocex::OnChainEvents::OrderBookWithdrawalClaimed(
snapshot_id,
account.clone(),
processed_withdrawals,
),
);
});
Ok(Pays::No.into())
} else {
// If someone withdraws nothing successfully - should pay for such transaction
Ok(Pays::Yes.into())
}

Ok(Pays::Yes.into())
}

/// Allowlist Token
Expand Down Expand Up @@ -904,6 +883,7 @@ pub mod pallet {
_signatures: Vec<(u16, <T::AuthorityId as RuntimeAppPublic>::Signature)>,
) -> DispatchResult {
ensure_none(origin)?;
let snapshot_id = summary.snapshot_id;
// Update the trader's performance on-chain
if let Some(ref metrics) = summary.trader_metrics {
Self::update_lmp_scores(metrics)?;
Expand All @@ -912,17 +892,26 @@ pub mod pallet {
Self::process_egress_msg(summary.egress_messages.as_ref())?;
if !summary.withdrawals.is_empty() {
let withdrawal_map = Self::create_withdrawal_tree(&summary.withdrawals);
<Withdrawals<T>>::insert(summary.snapshot_id, withdrawal_map);
let mut failed_withdrawal_map = crate::pallet::WithdrawalsMap::<T>::new();
for (account, withdrawals) in withdrawal_map {
let (failed_withdraws, successful_withdraws) =
Self::do_withdraw(snapshot_id, withdrawals);
if !failed_withdraws.is_empty() {
failed_withdrawal_map.insert(account.clone(), failed_withdraws);
}
if !successful_withdraws.is_empty() {
Self::deposit_event(Event::WithdrawalClaimed {
snapshot_id,
main: account.clone(),
withdrawals: successful_withdraws.clone(),
});
}
}
if !failed_withdrawal_map.is_empty() {
<Withdrawals<T>>::insert(summary.snapshot_id, failed_withdrawal_map);
}
let fees = summary.get_fees();
Self::settle_withdrawal_fees(fees)?;
<OnChainEvents<T>>::mutate(|onchain_events| {
onchain_events.push(
orderbook_primitives::ocex::OnChainEvents::OrderbookWithdrawalProcessed(
summary.snapshot_id,
summary.withdrawals.clone(),
),
);
});
}
let id = summary.snapshot_id;
<SnapshotNonce<T>>::put(id);
Expand Down Expand Up @@ -1103,6 +1092,7 @@ pub mod pallet {
EnclaveCleanup(Vec<T::AccountId>),
TradingPairIsNotOperational,
WithdrawalClaimed {
snapshot_id: u64,
main: T::AccountId,
withdrawals: Vec<Withdrawal<T::AccountId>>,
},
Expand All @@ -1118,8 +1108,8 @@ pub mod pallet {
TokenAllowlisted(AssetId),
/// AllowlistedTokenRemoved
AllowlistedTokenRemoved(AssetId),
/// Withdrawal failed
WithdrawalFailed(Withdrawal<T::AccountId>),
/// Withdrawal ready to claim
WithdrawalReady(u64, Withdrawal<T::AccountId>),
/// Exchange state has been updated
ExchangeStateUpdated(bool),
/// DisputePeriod has been updated
Expand Down Expand Up @@ -1318,6 +1308,32 @@ pub mod pallet {
StorageValue<_, AuctionInfo<T::AccountId, BalanceOf<T>>, OptionQuery>;

impl<T: crate::pallet::Config> crate::pallet::Pallet<T> {
pub fn do_withdraw(
snapshot_id: u64,
mut withdrawal_vector: Vec<Withdrawal<T::AccountId>>,
) -> (Vec<Withdrawal<T::AccountId>>, Vec<Withdrawal<T::AccountId>>) {
let mut failed_withdrawals = Vec::new();
let mut processed_withdrawals = Vec::new();
while !withdrawal_vector.is_empty() {
// Perform pop operation to ensure we do not leave any withdrawal left
// for a double spend
if let Some(withdrawal) = withdrawal_vector.pop() {
if Self::on_idle_withdrawal_processor(withdrawal.clone()) {
processed_withdrawals.push(withdrawal.to_owned());
} else {
// Storing the failed withdrawals back into the storage item
failed_withdrawals.push(withdrawal.to_owned());
Self::deposit_event(Event::WithdrawalReady(
snapshot_id,
withdrawal.to_owned(),
));
}
}
}

(failed_withdrawals, processed_withdrawals)
}

pub fn do_claim_lmp_rewards(
main: T::AccountId,
epoch: u16,
Expand Down Expand Up @@ -2144,6 +2160,10 @@ impl<T: Config + frame_system::offchain::SendTransactionTypes<Call<T>>> Pallet<T
return InvalidTransaction::Custom(10).into();
}

if T::OBWithdrawalLimit::get() < snapshot_summary.withdrawals.len() as u32 {
return InvalidTransaction::Custom(13).into();
}

// Check if this validator was part of that authority set
let authorities = <Authorities<T>>::get(snapshot_summary.validator_set_id).validators;

Expand Down
2 changes: 2 additions & 0 deletions pallets/ocex/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ parameter_types! {
pub const TreasuryPalletId: PalletId = PalletId(*b"OCEX_TRS");
//pub const TreasuryPalletId: PalletId = PalletId(*b"OCEX_CRW");
pub const MsPerDay: u64 = 86_400_000;
pub const OBWithdrawalLimit: u32 = 50;
}

impl pallet_lmp::pallet::Config for Test {
Expand All @@ -147,6 +148,7 @@ impl Config for Test {
type GovernanceOrigin = EnsureRoot<sp_runtime::AccountId32>;
type CrowdSourceLiqudityMining = LiqudityMining;
type WeightInfo = crate::weights::WeightInfo<Test>;
type OBWithdrawalLimit = OBWithdrawalLimit;
}

parameter_types! {
Expand Down
30 changes: 10 additions & 20 deletions pallets/ocex/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use std::str::FromStr;
// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required.
use crate::mock::*;
use frame_support::traits::fungibles::Mutate as MutateAsset;
use frame_support::{testing_prelude::bounded_vec, BoundedVec};
use frame_support::BoundedVec;
use frame_system::EventRecord;
use orderbook_primitives::ingress::{EgressMessages, IngressMessages};
use orderbook_primitives::ocex::AccountInfo;
Expand Down Expand Up @@ -2036,16 +2036,10 @@ fn test_submit_snapshot() {
assert_ok!(OCEX::submit_snapshot(RuntimeOrigin::none(), snapshot.clone(), Vec::new()));

assert_eq!(Withdrawals::<Test>::contains_key(1), true);
assert_eq!(Withdrawals::<Test>::get(1), withdrawal_map.clone());
assert_eq!(Withdrawals::<Test>::get(1), withdrawal_map);
assert_eq!(Snapshots::<Test>::contains_key(1), true);
assert_eq!(Snapshots::<Test>::get(1).unwrap(), snapshot.clone());
assert_eq!(SnapshotNonce::<Test>::get(), 1);
let onchain_events =
vec![orderbook_primitives::ocex::OnChainEvents::OrderbookWithdrawalProcessed(
1,
snapshot.withdrawals.clone(),
)];
assert_eq!(OnChainEvents::<Test>::get(), onchain_events);
// Checking for redundant data inside snapshot
assert_eq!(Snapshots::<Test>::get(1).unwrap().withdrawals, snapshot.withdrawals);
})
Expand Down Expand Up @@ -2102,11 +2096,14 @@ fn test_withdrawal() {
new_block();
new_block();

assert_ok!(OCEX::claim_withdraw(
RuntimeOrigin::signed(account_id.clone().into()),
1,
account_id.clone()
));
assert_noop!(
OCEX::claim_withdraw(
RuntimeOrigin::signed(account_id.clone().into()),
1,
account_id.clone()
),
Error::<Test>::InvalidWithdrawalIndex
);
// Balances after withdrawal
assert_eq!(
<Test as Config>::NativeCurrency::free_balance(account_id.clone()),
Expand All @@ -2116,13 +2113,6 @@ fn test_withdrawal() {
<Test as Config>::NativeCurrency::free_balance(custodian_account.clone()),
initial_balance - UNIT_BALANCE, // Dec
);
let withdrawal_claimed: orderbook_primitives::ocex::OnChainEvents<AccountId> =
orderbook_primitives::ocex::OnChainEvents::OrderBookWithdrawalClaimed(
1,
account_id.clone().into(),
bounded_vec![snapshot.withdrawals[0].clone()],
);
assert_eq!(OnChainEvents::<Test>::get()[1], withdrawal_claimed);
});
}

Expand Down
Loading

0 comments on commit 2ece554

Please sign in to comment.