Skip to content

Commit aeb2ab6

Browse files
Check existential deposit when creating an account during swap (#694)
* Add swap_keep_alive call * Add tests * Fix docs * Add estimated swapped balances logic into CurrencySwap interface * Apply required changes into bridge pot currency swap logic * Fix depositting with balance below ed at pallet-currency-swap * Fix depositting with balance below ed at precompile-currency-swap * Fix tests * Add more tests to check introduced flow * Fix benchmarking
1 parent 318aeab commit aeb2ab6

File tree

11 files changed

+325
-14
lines changed

11 files changed

+325
-14
lines changed

crates/bridge-pot-currency-swap/src/existence_optional.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,10 @@ impl<T: Config> primitives_currency_swap::CurrencySwap<T::AccountIdFrom, T::Acco
4545

4646
Ok(outgoing_imbalance)
4747
}
48+
49+
fn estimate_swapped_balance(
50+
balance: <Self::From as Currency<T::AccountIdFrom>>::Balance,
51+
) -> <Self::To as Currency<T::AccountIdTo>>::Balance {
52+
T::BalanceConverter::convert(balance)
53+
}
4854
}

crates/bridge-pot-currency-swap/src/existence_required.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,10 @@ impl<T: Config> primitives_currency_swap::CurrencySwap<T::AccountIdFrom, T::Acco
7373

7474
Ok(outgoing_imbalance)
7575
}
76+
77+
fn estimate_swapped_balance(
78+
balance: <Self::From as Currency<T::AccountIdFrom>>::Balance,
79+
) -> <Self::To as Currency<T::AccountIdTo>>::Balance {
80+
T::BalanceConverter::convert(balance)
81+
}
7682
}

crates/bridge-pot-currency-swap/src/lib.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use frame_support::{
77
sp_runtime::traits::Convert,
88
sp_std::marker::PhantomData,
9-
traits::{Currency, Get},
9+
traits::{fungible::Inspect, Currency, Get},
1010
};
1111

1212
pub mod existence_optional;
@@ -24,10 +24,18 @@ pub trait Config {
2424
type AccountIdTo;
2525

2626
/// The currency to swap from.
27-
type CurrencyFrom: Currency<Self::AccountIdFrom>;
27+
type CurrencyFrom: Currency<Self::AccountIdFrom>
28+
+ Inspect<
29+
Self::AccountIdFrom,
30+
Balance = <Self::CurrencyFrom as Currency<Self::AccountIdFrom>>::Balance,
31+
>;
2832

2933
/// The currency to swap to.
30-
type CurrencyTo: Currency<Self::AccountIdTo>;
34+
type CurrencyTo: Currency<Self::AccountIdTo>
35+
+ Inspect<
36+
Self::AccountIdTo,
37+
Balance = <Self::CurrencyTo as Currency<Self::AccountIdTo>>::Balance,
38+
>;
3139

3240
/// The converter to determine how the balance amount should be converted from one currency to
3341
/// another.

crates/pallet-currency-swap/src/benchmarking.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,19 @@ benchmarks! {
7474
impl Interface for crate::mock::Test {
7575
type Data = (
7676
std::sync::MutexGuard<'static, ()>,
77+
mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__estimate_swapped_balance::Context,
7778
mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__swap::Context,
7879
);
7980

8081
fn prepare() -> Self::Data {
8182
let mock_runtime_guard = mock::runtime_lock();
8283

84+
let estimate_swapped_balance_ctx =
85+
mock::MockCurrencySwap::estimate_swapped_balance_context();
86+
estimate_swapped_balance_ctx
87+
.expect()
88+
.times(1..)
89+
.return_const(Self::to_balance());
8390
let swap_ctx = mock::MockCurrencySwap::swap_context();
8491
swap_ctx.expect().times(1..).return_once(move |_| {
8592
Ok(
@@ -89,11 +96,12 @@ impl Interface for crate::mock::Test {
8996
)
9097
});
9198

92-
(mock_runtime_guard, swap_ctx)
99+
(mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx)
93100
}
94101

95102
fn verify(data: Self::Data) -> DispatchResult {
96-
let (mock_runtime_guard, swap_ctx) = data;
103+
let (mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx) = data;
104+
estimate_swapped_balance_ctx.checkpoint();
97105
swap_ctx.checkpoint();
98106
drop(mock_runtime_guard);
99107
Ok(())

crates/pallet-currency-swap/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
#![cfg_attr(not(feature = "std"), no_std)]
44

5-
use frame_support::traits::Currency;
5+
use frame_support::traits::{fungible::Inspect, Currency};
66
pub use pallet::*;
77
use primitives_currency_swap::CurrencySwap as CurrencySwapT;
88
pub use weights::*;
@@ -140,6 +140,9 @@ pub mod pallet {
140140
amount: FromBalanceOf<T>,
141141
existence_requirement: ExistenceRequirement,
142142
) -> DispatchResult {
143+
let estimated_swapped_balance = T::CurrencySwap::estimate_swapped_balance(amount);
144+
ToCurrencyOf::<T>::can_deposit(&to, estimated_swapped_balance, false).into_result()?;
145+
143146
let withdrawed_imbalance = FromCurrencyOf::<T>::withdraw(
144147
&who,
145148
amount,
@@ -150,7 +153,7 @@ pub mod pallet {
150153

151154
let deposited_imbalance =
152155
T::CurrencySwap::swap(withdrawed_imbalance).map_err(|error| {
153-
// Here we undo the withdrawl to avoid having a dangling imbalance.
156+
// Here we undo the withdrawal to avoid having a dangling imbalance.
154157
FromCurrencyOf::<T>::resolve_creating(&who, error.incoming_imbalance);
155158
error.cause.into()
156159
})?;

crates/pallet-currency-swap/src/mock.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use sp_core::{H160, H256};
1717

1818
use crate::{self as pallet_currency_swap};
1919

20+
pub(crate) const EXISTENTIAL_DEPOSIT: u64 = 10;
21+
2022
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
2123
type Block = frame_system::mocking::MockBlock<Test>;
2224

@@ -69,7 +71,7 @@ impl pallet_balances::Config for Test {
6971
type Balance = u64;
7072
type RuntimeEvent = RuntimeEvent;
7173
type DustRemoval = ();
72-
type ExistentialDeposit = ConstU64<10>;
74+
type ExistentialDeposit = ConstU64<EXISTENTIAL_DEPOSIT>;
7375
type AccountStore = System;
7476
type MaxLocks = ();
7577
type MaxReserves = ();
@@ -90,7 +92,7 @@ impl pallet_evm_balances::Config for Test {
9092
type RuntimeEvent = RuntimeEvent;
9193
type AccountId = EvmAccountId;
9294
type Balance = Balance;
93-
type ExistentialDeposit = ConstU64<10>;
95+
type ExistentialDeposit = ConstU64<EXISTENTIAL_DEPOSIT>;
9496
type AccountStore = EvmSystem;
9597
type DustRemoval = ();
9698
}
@@ -109,6 +111,10 @@ mock! {
109111
primitives_currency_swap::ToNegativeImbalanceFor<Self, AccountId, EvmAccountId>,
110112
primitives_currency_swap::ErrorFor<Self, AccountId, EvmAccountId>
111113
>;
114+
115+
fn estimate_swapped_balance(
116+
balance: <Balances as Currency<AccountId>>::Balance,
117+
) -> <EvmBalances as Currency<EvmAccountId>>::Balance;
112118
}
113119
}
114120

crates/pallet-currency-swap/src/tests.rs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use frame_support::{assert_noop, assert_ok, traits::Currency};
44
use mockall::predicate;
55
use sp_core::H160;
6-
use sp_runtime::DispatchError;
6+
use sp_runtime::{DispatchError, TokenError};
77
use sp_std::str::FromStr;
88

99
use crate::{mock::*, *};
@@ -29,6 +29,12 @@ fn swap_works() {
2929
System::set_block_number(1);
3030

3131
// Set mock expectations.
32+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
33+
estimate_swapped_balance_ctx
34+
.expect()
35+
.once()
36+
.with(predicate::eq(swap_balance))
37+
.return_const(swap_balance);
3238
let swap_ctx = MockCurrencySwap::swap_context();
3339
swap_ctx
3440
.expect()
@@ -61,6 +67,7 @@ fn swap_works() {
6167
}));
6268

6369
// Assert mock invocations.
70+
estimate_swapped_balance_ctx.checkpoint();
6471
swap_ctx.checkpoint();
6572
});
6673
}
@@ -86,6 +93,12 @@ fn swap_works_kill_origin() {
8693
System::set_block_number(1);
8794

8895
// Set mock expectations.
96+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
97+
estimate_swapped_balance_ctx
98+
.expect()
99+
.once()
100+
.with(predicate::eq(swap_balance))
101+
.return_const(swap_balance);
89102
let swap_ctx = MockCurrencySwap::swap_context();
90103
swap_ctx
91104
.expect()
@@ -115,6 +128,7 @@ fn swap_works_kill_origin() {
115128
}));
116129

117130
// Assert mock invocations.
131+
estimate_swapped_balance_ctx.checkpoint();
118132
swap_ctx.checkpoint();
119133
});
120134
}
@@ -139,6 +153,12 @@ fn swap_keep_alive_works() {
139153
System::set_block_number(1);
140154

141155
// Set mock expectations.
156+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
157+
estimate_swapped_balance_ctx
158+
.expect()
159+
.once()
160+
.with(predicate::eq(swap_balance))
161+
.return_const(swap_balance);
142162
let swap_ctx = MockCurrencySwap::swap_context();
143163
swap_ctx
144164
.expect()
@@ -171,6 +191,7 @@ fn swap_keep_alive_works() {
171191
}));
172192

173193
// Assert mock invocations.
194+
estimate_swapped_balance_ctx.checkpoint();
174195
swap_ctx.checkpoint();
175196
});
176197
}
@@ -188,6 +209,12 @@ fn swap_fails() {
188209
Balances::make_free_balance_be(&alice, alice_balance);
189210

190211
// Set mock expectations.
212+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
213+
estimate_swapped_balance_ctx
214+
.expect()
215+
.once()
216+
.with(predicate::eq(swap_balance))
217+
.return_const(swap_balance);
191218
let swap_ctx = MockCurrencySwap::swap_context();
192219
swap_ctx
193220
.expect()
@@ -213,6 +240,46 @@ fn swap_fails() {
213240
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);
214241

215242
// Assert mock invocations.
243+
estimate_swapped_balance_ctx.checkpoint();
244+
swap_ctx.checkpoint();
245+
});
246+
}
247+
248+
/// This test verifies that swap call fails in case estimated swapped balance less or equal
249+
/// than target currency existential deposit.
250+
#[test]
251+
fn swap_below_ed_fails() {
252+
new_test_ext().execute_with_ext(|_| {
253+
let alice = 42;
254+
let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap();
255+
let alice_balance = 1000;
256+
let swap_balance = 100;
257+
258+
// Prepare the test state.
259+
Balances::make_free_balance_be(&alice, alice_balance);
260+
261+
// // Set mock expectations.
262+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
263+
estimate_swapped_balance_ctx
264+
.expect()
265+
.once()
266+
.with(predicate::eq(swap_balance))
267+
.return_const(EXISTENTIAL_DEPOSIT - 1);
268+
let swap_ctx = MockCurrencySwap::swap_context();
269+
swap_ctx.expect().never();
270+
271+
// Invoke the function under test.
272+
assert_noop!(
273+
CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance),
274+
DispatchError::Token(TokenError::BelowMinimum)
275+
);
276+
277+
// Assert state changes.
278+
assert_eq!(Balances::total_balance(&alice), alice_balance);
279+
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);
280+
281+
// Assert mock invocations.
282+
estimate_swapped_balance_ctx.checkpoint();
216283
swap_ctx.checkpoint();
217284
});
218285
}
@@ -231,6 +298,12 @@ fn swap_keep_alive_fails() {
231298
Balances::make_free_balance_be(&alice, alice_balance);
232299

233300
// Set mock expectations.
301+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
302+
estimate_swapped_balance_ctx
303+
.expect()
304+
.once()
305+
.with(predicate::eq(swap_balance))
306+
.return_const(swap_balance);
234307
let swap_ctx = MockCurrencySwap::swap_context();
235308
swap_ctx.expect().never();
236309

@@ -245,6 +318,46 @@ fn swap_keep_alive_fails() {
245318
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);
246319

247320
// Assert mock invocations.
321+
estimate_swapped_balance_ctx.checkpoint();
322+
swap_ctx.checkpoint();
323+
});
324+
}
325+
326+
/// This test verifies that `swap_keep_alive` call fails in case estimated swapped balance less or equal
327+
/// than target currency existential deposit.
328+
#[test]
329+
fn swap_keep_alive_below_ed_fails() {
330+
new_test_ext().execute_with_ext(|_| {
331+
let alice = 42;
332+
let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap();
333+
let alice_balance = 1000;
334+
let swap_balance = 100;
335+
336+
// Prepare the test state.
337+
Balances::make_free_balance_be(&alice, alice_balance);
338+
339+
// // Set mock expectations.
340+
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
341+
estimate_swapped_balance_ctx
342+
.expect()
343+
.once()
344+
.with(predicate::eq(swap_balance))
345+
.return_const(EXISTENTIAL_DEPOSIT - 1);
346+
let swap_ctx = MockCurrencySwap::swap_context();
347+
swap_ctx.expect().never();
348+
349+
// Invoke the function under test.
350+
assert_noop!(
351+
CurrencySwap::swap_keep_alive(RuntimeOrigin::signed(alice), alice_evm, swap_balance),
352+
DispatchError::Token(TokenError::BelowMinimum)
353+
);
354+
355+
// Assert state changes.
356+
assert_eq!(Balances::total_balance(&alice), alice_balance);
357+
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);
358+
359+
// Assert mock invocations.
360+
estimate_swapped_balance_ctx.checkpoint();
248361
swap_ctx.checkpoint();
249362
});
250363
}

crates/precompile-currency-swap/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use frame_support::{
66
sp_runtime,
77
sp_std::{marker::PhantomData, prelude::*},
8-
traits::tokens::currency::Currency,
8+
traits::{fungible::Inspect, tokens::currency::Currency},
99
};
1010
use pallet_evm::{
1111
ExitError, ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
@@ -117,6 +117,20 @@ where
117117
});
118118
}
119119

120+
let estimated_swapped_balance = CurrencySwapT::estimate_swapped_balance(value);
121+
CurrencySwapT::To::can_deposit(&to, estimated_swapped_balance, false)
122+
.into_result()
123+
.map_err(|error| match error {
124+
sp_runtime::DispatchError::Token(sp_runtime::TokenError::BelowMinimum) => {
125+
PrecompileFailure::Error {
126+
exit_status: ExitError::OutOfFund,
127+
}
128+
}
129+
_ => PrecompileFailure::Error {
130+
exit_status: ExitError::Other("unable to deposit funds".into()),
131+
},
132+
})?;
133+
120134
let imbalance = CurrencySwapT::From::withdraw(
121135
&from,
122136
value,

crates/precompile-currency-swap/src/mock.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ mock! {
195195
primitives_currency_swap::ToNegativeImbalanceFor<Self, EvmAccountId, AccountId>,
196196
primitives_currency_swap::ErrorFor<Self, EvmAccountId, AccountId>,
197197
>;
198+
199+
fn estimate_swapped_balance(
200+
balance: primitives_currency_swap::FromBalanceFor<Self, EvmAccountId, AccountId>,
201+
) -> primitives_currency_swap::ToBalanceFor<Self, EvmAccountId, AccountId>;
198202
}
199203
}
200204

0 commit comments

Comments
 (0)