Skip to content

Commit

Permalink
TRN-397 swap out boundedVec for erc20-peg (#885)
Browse files Browse the repository at this point in the history
* add erc20-peg method for sudo to claim delayed payments

* migrate delayedPaymentSchedule to weakBoundedVec and benchmark process_deposit for erc20 peg

* write migration for delayed payment schedule

* fix up rebase

* address comments - add more tests, amend benchmarks, edit erc-20-peg config

* Update benchmarks for pallet-erc20-peg on TRN-397-delayed-payments-blocked

* change max delay per block in accordance with new weights

---------

Co-authored-by: GitHub Action <[email protected]>
  • Loading branch information
Nick95550 and actions-user authored Oct 15, 2024
1 parent 33c05f1 commit 8b2040a
Show file tree
Hide file tree
Showing 7 changed files with 575 additions and 66 deletions.
56 changes: 56 additions & 0 deletions pallet/erc20-peg/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use super::*;
use frame_benchmarking::{account as bench_account, benchmarks, impl_benchmark_test_suite};
use frame_support::{assert_ok, traits::fungibles::Inspect};
use frame_system::RawOrigin;
use sp_core::bounded::WeakBoundedVec;

use crate::Pallet as Erc20Peg;

Expand Down Expand Up @@ -99,6 +100,61 @@ benchmarks! {
assert_eq!(actual_balance, expected_balance);
}

process_deposit {
let token_address = account::<T>("TokenAddress").into();
let beneficiary: H160 = H160::from_low_u64_be(456);
let deposit_amount: Balance = 1_000_000;

assert!(AssetIdToErc20::<T>::iter_keys().next().is_none());
assert!(Erc20ToAssetId::<T>::get(token_address).is_none());
assert!(Erc20ToAssetId::<T>::iter_keys().next().is_none());
}: {
let _ = Erc20Peg::<T>::process_deposit(Erc20DepositEvent { token_address, amount: deposit_amount.into(), beneficiary});
} verify {
assert!(AssetIdToErc20::<T>::iter_keys().next().is_some());
assert!(Erc20ToAssetId::<T>::get(token_address).is_some());
assert!(Erc20ToAssetId::<T>::iter_keys().next().is_some());

let asset_id = Erc20ToAssetId::<T>::get(token_address).unwrap();
assert_eq!(T::MultiCurrency::balance(asset_id, &beneficiary.into()), deposit_amount);
}

claim_delayed_payment {
let alice = account::<T>("Alice");
let delay: BlockNumberFor<T> = 1000u32.into();
let withdraw_amount: Balance = 100u32.into();

let token_address = account::<T>("TokenAddress").into();
let beneficiary = account::<T>("Beneficiary").into();

let delayed_payment_id = NextDelayedPaymentId::<T>::get();
let payment_block = frame_system::Pallet::<T>::block_number() + delay;
Erc20ToAssetId::<T>::insert(token_address, 2); // XRP asset id

let message = WithdrawMessage {
token_address: token_address,
amount: withdraw_amount.into(),
beneficiary,
};

DelayedPaymentSchedule::<T>::insert(payment_block, WeakBoundedVec::try_from(vec![delayed_payment_id]).unwrap());
DelayedPayments::<T>::insert(delayed_payment_id, PendingPayment::Withdrawal((alice, message.clone())));

assert_eq!(DelayedPaymentSchedule::<T>::get(payment_block), vec![delayed_payment_id]);
assert_eq!(
DelayedPayments::<T>::get(delayed_payment_id),
Some(PendingPayment::Withdrawal((alice, message)))
);
}: _(RawOrigin::Root, payment_block, delayed_payment_id)
verify {
assert_eq!(
DelayedPaymentSchedule::<T>::get(payment_block),
vec![] as Vec<DelayedPaymentId>
);
assert!(DelayedPayments::<T>::get(delayed_payment_id).is_none());
}


set_erc20_peg_address {
let alice: EthAddress = account::<T>("Alice").into();
// Sanity check
Expand Down
71 changes: 54 additions & 17 deletions pallet/erc20-peg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use frame_support::{
use frame_system::pallet_prelude::*;
use seed_pallet_common::{CreateExt, EthereumBridge, EthereumEventSubscriber, OnEventResult};
use seed_primitives::{AccountId, AssetId, Balance, EthAddress};
use sp_core::{H160, U256};
use sp_core::{bounded::WeakBoundedVec, H160, U256};
use sp_runtime::{
traits::{AccountIdConversion, One, Saturating},
SaturatedConversion,
Expand Down Expand Up @@ -164,7 +164,7 @@ pub mod pallet {
_,
Twox64Concat,
BlockNumberFor<T>,
BoundedVec<DelayedPaymentId, T::MaxDelaysPerBlock>,
WeakBoundedVec<DelayedPaymentId, T::MaxDelaysPerBlock>,
ValueQuery,
>;

Expand Down Expand Up @@ -265,6 +265,8 @@ pub mod pallet {
EvmWithdrawalFailed,
/// The abi received does not match the encoding scheme
InvalidAbiEncoding,
/// Supplied payment id not in storage
PaymentIdNotFound,
}

#[pallet::hooks]
Expand Down Expand Up @@ -301,10 +303,17 @@ pub mod pallet {
let remaining_payments = (max_payments - processed_payment_count) as usize;
if payment_ids.len() > remaining_payments {
// Update storage with unprocessed payments
DelayedPaymentSchedule::<T>::insert(
block,
BoundedVec::truncate_from(payment_ids.split_off(remaining_payments)),
);
DelayedPaymentSchedule::<T>::mutate(block, |v| {
let split_payment_ids = payment_ids.split_off(remaining_payments);
let payment_ids_bounded = WeakBoundedVec::force_from(
split_payment_ids,
Some(
"Warning: There are more DelayedPaymentSchedule than expected. \
A runtime configuration adjustment may be needed.",
),
);
*v = payment_ids_bounded
})
} else {
processed_block_count += 1;
}
Expand Down Expand Up @@ -472,10 +481,39 @@ pub mod pallet {
Self::deposit_event(Event::<T>::PaymentDelaySet { asset_id, min_balance, delay });
Ok(())
}

#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::claim_delayed_payment())]
pub fn claim_delayed_payment(
origin: OriginFor<T>,
block_number: BlockNumberFor<T>,
payment_id: DelayedPaymentId,
) -> DispatchResult {
ensure_root(origin)?;
Self::remove_delayed_payment_entry(block_number, payment_id)
}
}
}

impl<T: Config> Pallet<T> {
pub fn remove_delayed_payment_entry(
block_number: BlockNumberFor<T>,
payment_id: DelayedPaymentId,
) -> DispatchResult {
DelayedPaymentSchedule::<T>::try_mutate(block_number, |payment_ids| {
if let Some(pos) = payment_ids.iter().position(|&id| id == payment_id) {
payment_ids.remove(pos);
Ok(())
} else {
Err(())
}
})
.map_err(|_| Error::<T>::PaymentIdNotFound)?;

Self::process_delayed_payment(payment_id);

Ok(())
}
/// Initiate the withdrawal
/// Can be called by the runtime or erc20-peg precompile
/// If a payment delay is in place for the asset, this will be handled when called from the
Expand Down Expand Up @@ -641,18 +679,17 @@ impl<T: Config> Pallet<T> {
let payment_block = <frame_system::Pallet<T>>::block_number().saturating_add(delay);
DelayedPayments::<T>::insert(payment_id, &pending_payment);
NextDelayedPaymentId::<T>::put(payment_id + 1);
let _ = DelayedPaymentSchedule::<T>::try_append(payment_block, payment_id).map_err(|_| {
// If we fail to append the payment_id to the schedule, log the error and throw an event
log::error!(
"ERC20-Peg: 📌 Failed to add delayed payment to DelayedPaymentSchedule: {:?}",
payment_id
DelayedPaymentSchedule::<T>::mutate(payment_block, |v| {
let mut payments = v.clone().into_inner();
payments.push(payment_id);
let payments_bounded = WeakBoundedVec::force_from(
payments,
Some(
"Warning: There are more DelayedPaymentSchedule than expected. \
A runtime configuration adjustment may be needed.",
),
);
Self::deposit_event(Event::<T>::Erc20DelayFailed {
payment_id,
scheduled_block: payment_block,
asset_id,
source,
});
*v = payments_bounded;
});

// Throw event for delayed payment
Expand Down
181 changes: 181 additions & 0 deletions pallet/erc20-peg/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,187 @@ fn withdraw_with_delay() {
});
}

#[test]
fn root_can_claim_delayed_payment() {
ExtBuilder::default().build().execute_with(|| {
let account: AccountId = create_account(123);
let asset_id: AssetId = 1;
let cennz_eth_address: EthAddress = H160::default();
let amount: Balance = 100;
let beneficiary: H160 = H160::from_slice(&hex!("a86e122EdbDcBA4bF24a2Abf89F5C230b37DF49d"));
let delay: u64 = 1000;
let _ = <Test as Config>::MultiCurrency::mint_into(asset_id, &account, amount);

<AssetIdToErc20<Test>>::insert(asset_id, cennz_eth_address);
<Erc20ToAssetId<Test>>::insert(cennz_eth_address, asset_id);

assert_ok!(Erc20Peg::activate_withdrawals(frame_system::RawOrigin::Root.into(), true));
// Activate withdrawal delays
assert_ok!(Erc20Peg::activate_withdrawals_delay(
frame_system::RawOrigin::Root.into(),
true
));

assert_ok!(Erc20Peg::set_payment_delay(
frame_system::RawOrigin::Root.into(),
asset_id,
amount,
delay
));

let delayed_payment_id = <NextDelayedPaymentId<Test>>::get();
let payment_block = <frame_system::Pallet<Test>>::block_number() + delay;
assert_ok!(Erc20Peg::withdraw(Some(account.clone()).into(), asset_id, amount, beneficiary));
let message = WithdrawMessage {
token_address: cennz_eth_address,
amount: amount.into(),
beneficiary,
};

assert_eq!(DelayedPaymentSchedule::<Test>::get(payment_block), vec![delayed_payment_id]);
assert_eq!(
DelayedPayments::<Test>::get(delayed_payment_id),
Some(PendingPayment::Withdrawal((account, message)))
);

assert_ok!(Erc20Peg::claim_delayed_payment(
frame_system::RawOrigin::Root.into(),
payment_block,
delayed_payment_id,
));

// Payment should be removed from storage
assert_eq!(
DelayedPaymentSchedule::<Test>::get(payment_block),
vec![] as Vec<DelayedPaymentId>
);
assert!(DelayedPayments::<Test>::get(delayed_payment_id).is_none());
})
}

#[test]
fn root_claim_on_delayed_payment_doesnt_effect_prior_delayed_payments() {
ExtBuilder::default().build().execute_with(|| {
let account: AccountId = create_account(123);
let asset_id: AssetId = 1;
let cennz_eth_address: EthAddress = H160::default();
let amount: Balance = 200;
let half_amount: Balance = 100;
let beneficiary: H160 = H160::from_slice(&hex!("a86e122EdbDcBA4bF24a2Abf89F5C230b37DF49d"));
let delay: u64 = 1000;
let _ = <Test as Config>::MultiCurrency::mint_into(asset_id, &account, amount);

<AssetIdToErc20<Test>>::insert(asset_id, cennz_eth_address);
<Erc20ToAssetId<Test>>::insert(cennz_eth_address, asset_id);

assert_ok!(Erc20Peg::activate_withdrawals(frame_system::RawOrigin::Root.into(), true));

// Activate withdrawal delays
assert_ok!(Erc20Peg::activate_withdrawals_delay(
frame_system::RawOrigin::Root.into(),
true
));

assert_ok!(Erc20Peg::set_payment_delay(
frame_system::RawOrigin::Root.into(),
asset_id,
half_amount,
delay
));

let payment_block = <frame_system::Pallet<Test>>::block_number() + delay;

let delayed_payment_id = <NextDelayedPaymentId<Test>>::get();
let delayed_payment_id_two = delayed_payment_id.saturating_add(1);

assert_ok!(Erc20Peg::withdraw(
Some(account.clone()).into(),
asset_id,
half_amount,
beneficiary
));
assert_ok!(Erc20Peg::withdraw(
Some(account.clone()).into(),
asset_id,
half_amount,
beneficiary
));

let message = WithdrawMessage {
token_address: cennz_eth_address,
amount: half_amount.into(),
beneficiary,
};

assert_eq!(
DelayedPaymentSchedule::<Test>::get(payment_block),
vec![delayed_payment_id, delayed_payment_id_two]
);
assert_eq!(
DelayedPayments::<Test>::get(delayed_payment_id),
Some(PendingPayment::Withdrawal((account, message.clone())))
);
assert_eq!(
DelayedPayments::<Test>::get(delayed_payment_id_two),
Some(PendingPayment::Withdrawal((account, message)))
);

assert_ok!(Erc20Peg::claim_delayed_payment(
frame_system::RawOrigin::Root.into(),
payment_block,
delayed_payment_id,
));

assert_eq!(
DelayedPaymentSchedule::<Test>::get(payment_block),
vec![delayed_payment_id_two]
);
assert!(DelayedPayments::<Test>::get(delayed_payment_id).is_none());
assert!(DelayedPayments::<Test>::get(delayed_payment_id_two).is_some());
})
}

#[test]
fn root_claim_fails_with_non_existant_block() {
ExtBuilder::default().build().execute_with(|| {
let payment_block = <frame_system::Pallet<Test>>::block_number();
let delayed_payment_id = <NextDelayedPaymentId<Test>>::get();

assert_noop!(
Erc20Peg::claim_delayed_payment(
frame_system::RawOrigin::Root.into(),
payment_block,
delayed_payment_id,
),
Error::<Test>::PaymentIdNotFound
);
})
}

#[test]
fn root_claim_fails_with_non_existant_payment_id() {
ExtBuilder::default().build().execute_with(|| {
let payment_block = <frame_system::Pallet<Test>>::block_number();
let delayed_payment_id = <NextDelayedPaymentId<Test>>::get();

let non_existant_payment_id: u64 = 999;

DelayedPaymentSchedule::<Test>::insert(
payment_block,
WeakBoundedVec::try_from(vec![delayed_payment_id]).unwrap(),
);

assert_noop!(
Erc20Peg::claim_delayed_payment(
frame_system::RawOrigin::Root.into(),
payment_block,
non_existant_payment_id, // This payment id doesn't exist at this key
),
Error::<Test>::PaymentIdNotFound
);
})
}

#[test]
fn withdraw_less_than_delay_goes_through() {
ExtBuilder::default().build().execute_with(|| {
Expand Down
Loading

0 comments on commit 8b2040a

Please sign in to comment.