Skip to content
Merged
6 changes: 6 additions & 0 deletions crates/bridge-pot-currency-swap/src/existence_optional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,10 @@ impl<T: Config> primitives_currency_swap::CurrencySwap<T::AccountIdFrom, T::Acco

Ok(outgoing_imbalance)
}

fn estimate_swapped_balance(
balance: <Self::From as Currency<T::AccountIdFrom>>::Balance,
) -> <Self::To as Currency<T::AccountIdTo>>::Balance {
T::BalanceConverter::convert(balance)
}
}
6 changes: 6 additions & 0 deletions crates/bridge-pot-currency-swap/src/existence_required.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,10 @@ impl<T: Config> primitives_currency_swap::CurrencySwap<T::AccountIdFrom, T::Acco

Ok(outgoing_imbalance)
}

fn estimate_swapped_balance(
balance: <Self::From as Currency<T::AccountIdFrom>>::Balance,
) -> <Self::To as Currency<T::AccountIdTo>>::Balance {
T::BalanceConverter::convert(balance)
}
}
14 changes: 11 additions & 3 deletions crates/bridge-pot-currency-swap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use frame_support::{
sp_runtime::traits::Convert,
sp_std::marker::PhantomData,
traits::{Currency, Get},
traits::{fungible::Inspect, Currency, Get},
};

pub mod existence_optional;
Expand All @@ -24,10 +24,18 @@ pub trait Config {
type AccountIdTo;

/// The currency to swap from.
type CurrencyFrom: Currency<Self::AccountIdFrom>;
type CurrencyFrom: Currency<Self::AccountIdFrom>
+ Inspect<
Self::AccountIdFrom,
Balance = <Self::CurrencyFrom as Currency<Self::AccountIdFrom>>::Balance,
>;

/// The currency to swap to.
type CurrencyTo: Currency<Self::AccountIdTo>;
type CurrencyTo: Currency<Self::AccountIdTo>
+ Inspect<
Self::AccountIdTo,
Balance = <Self::CurrencyTo as Currency<Self::AccountIdTo>>::Balance,
>;

/// The converter to determine how the balance amount should be converted from one currency to
/// another.
Expand Down
12 changes: 10 additions & 2 deletions crates/pallet-currency-swap/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,19 @@ benchmarks! {
impl Interface for crate::mock::Test {
type Data = (
std::sync::MutexGuard<'static, ()>,
mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__estimate_swapped_balance::Context,
mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__swap::Context,
);

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

let estimate_swapped_balance_ctx =
mock::MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.times(1..)
.return_const(Self::to_balance());
let swap_ctx = mock::MockCurrencySwap::swap_context();
swap_ctx.expect().times(1..).return_once(move |_| {
Ok(
Expand All @@ -89,11 +96,12 @@ impl Interface for crate::mock::Test {
)
});

(mock_runtime_guard, swap_ctx)
(mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx)
}

fn verify(data: Self::Data) -> DispatchResult {
let (mock_runtime_guard, swap_ctx) = data;
let (mock_runtime_guard, estimate_swapped_balance_ctx, swap_ctx) = data;
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
drop(mock_runtime_guard);
Ok(())
Expand Down
7 changes: 5 additions & 2 deletions crates/pallet-currency-swap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#![cfg_attr(not(feature = "std"), no_std)]

use frame_support::traits::Currency;
use frame_support::traits::{fungible::Inspect, Currency};
pub use pallet::*;
use primitives_currency_swap::CurrencySwap as CurrencySwapT;
pub use weights::*;
Expand Down Expand Up @@ -140,6 +140,9 @@ pub mod pallet {
amount: FromBalanceOf<T>,
existence_requirement: ExistenceRequirement,
) -> DispatchResult {
let estimated_swapped_balance = T::CurrencySwap::estimate_swapped_balance(amount);
ToCurrencyOf::<T>::can_deposit(&to, estimated_swapped_balance, false).into_result()?;

let withdrawed_imbalance = FromCurrencyOf::<T>::withdraw(
&who,
amount,
Expand All @@ -150,7 +153,7 @@ pub mod pallet {

let deposited_imbalance =
T::CurrencySwap::swap(withdrawed_imbalance).map_err(|error| {
// Here we undo the withdrawl to avoid having a dangling imbalance.
// Here we undo the withdrawal to avoid having a dangling imbalance.
FromCurrencyOf::<T>::resolve_creating(&who, error.incoming_imbalance);
error.cause.into()
})?;
Expand Down
10 changes: 8 additions & 2 deletions crates/pallet-currency-swap/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use sp_core::{H160, H256};

use crate::{self as pallet_currency_swap};

pub(crate) const EXISTENTIAL_DEPOSIT: u64 = 10;

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;

Expand Down Expand Up @@ -69,7 +71,7 @@ impl pallet_balances::Config for Test {
type Balance = u64;
type RuntimeEvent = RuntimeEvent;
type DustRemoval = ();
type ExistentialDeposit = ConstU64<10>;
type ExistentialDeposit = ConstU64<EXISTENTIAL_DEPOSIT>;
type AccountStore = System;
type MaxLocks = ();
type MaxReserves = ();
Expand All @@ -90,7 +92,7 @@ impl pallet_evm_balances::Config for Test {
type RuntimeEvent = RuntimeEvent;
type AccountId = EvmAccountId;
type Balance = Balance;
type ExistentialDeposit = ConstU64<10>;
type ExistentialDeposit = ConstU64<EXISTENTIAL_DEPOSIT>;
type AccountStore = EvmSystem;
type DustRemoval = ();
}
Expand All @@ -109,6 +111,10 @@ mock! {
primitives_currency_swap::ToNegativeImbalanceFor<Self, AccountId, EvmAccountId>,
primitives_currency_swap::ErrorFor<Self, AccountId, EvmAccountId>
>;

fn estimate_swapped_balance(
balance: <Balances as Currency<AccountId>>::Balance,
) -> <EvmBalances as Currency<EvmAccountId>>::Balance;
}
}

Expand Down
115 changes: 114 additions & 1 deletion crates/pallet-currency-swap/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use frame_support::{assert_noop, assert_ok, traits::Currency};
use mockall::predicate;
use sp_core::H160;
use sp_runtime::DispatchError;
use sp_runtime::{DispatchError, TokenError};
use sp_std::str::FromStr;

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

// Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(swap_balance);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx
.expect()
Expand Down Expand Up @@ -61,6 +67,7 @@ fn swap_works() {
}));

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}
Expand All @@ -86,6 +93,12 @@ fn swap_works_kill_origin() {
System::set_block_number(1);

// Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(swap_balance);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx
.expect()
Expand Down Expand Up @@ -115,6 +128,7 @@ fn swap_works_kill_origin() {
}));

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}
Expand All @@ -139,6 +153,12 @@ fn swap_keep_alive_works() {
System::set_block_number(1);

// Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(swap_balance);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx
.expect()
Expand Down Expand Up @@ -171,6 +191,7 @@ fn swap_keep_alive_works() {
}));

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}
Expand All @@ -188,6 +209,12 @@ fn swap_fails() {
Balances::make_free_balance_be(&alice, alice_balance);

// Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(swap_balance);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx
.expect()
Expand All @@ -213,6 +240,46 @@ fn swap_fails() {
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}

/// This test verifies that swap call fails in case estimated swapped balance less or equal
/// than target currency existential deposit.
#[test]
fn swap_below_ed_fails() {
new_test_ext().execute_with_ext(|_| {
let alice = 42;
let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap();
let alice_balance = 1000;
let swap_balance = 100;

// Prepare the test state.
Balances::make_free_balance_be(&alice, alice_balance);

// // Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(EXISTENTIAL_DEPOSIT - 1);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx.expect().never();

// Invoke the function under test.
assert_noop!(
CurrencySwap::swap(RuntimeOrigin::signed(alice), alice_evm, swap_balance),
DispatchError::Token(TokenError::BelowMinimum)
);

// Assert state changes.
assert_eq!(Balances::total_balance(&alice), alice_balance);
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}
Expand All @@ -231,6 +298,12 @@ fn swap_keep_alive_fails() {
Balances::make_free_balance_be(&alice, alice_balance);

// Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(swap_balance);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx.expect().never();

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

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}

/// This test verifies that `swap_keep_alive` call fails in case estimated swapped balance less or equal
/// than target currency existential deposit.
#[test]
fn swap_keep_alive_below_ed_fails() {
new_test_ext().execute_with_ext(|_| {
let alice = 42;
let alice_evm = H160::from_str("1000000000000000000000000000000000000001").unwrap();
let alice_balance = 1000;
let swap_balance = 100;

// Prepare the test state.
Balances::make_free_balance_be(&alice, alice_balance);

// // Set mock expectations.
let estimate_swapped_balance_ctx = MockCurrencySwap::estimate_swapped_balance_context();
estimate_swapped_balance_ctx
.expect()
.once()
.with(predicate::eq(swap_balance))
.return_const(EXISTENTIAL_DEPOSIT - 1);
let swap_ctx = MockCurrencySwap::swap_context();
swap_ctx.expect().never();

// Invoke the function under test.
assert_noop!(
CurrencySwap::swap_keep_alive(RuntimeOrigin::signed(alice), alice_evm, swap_balance),
DispatchError::Token(TokenError::BelowMinimum)
);

// Assert state changes.
assert_eq!(Balances::total_balance(&alice), alice_balance);
assert_eq!(EvmBalances::total_balance(&alice_evm), 0);

// Assert mock invocations.
estimate_swapped_balance_ctx.checkpoint();
swap_ctx.checkpoint();
});
}
16 changes: 15 additions & 1 deletion crates/precompile-currency-swap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use frame_support::{
sp_runtime,
sp_std::{marker::PhantomData, prelude::*},
traits::tokens::currency::Currency,
traits::{fungible::Inspect, tokens::currency::Currency},
};
use pallet_evm::{
ExitError, ExitRevert, Precompile, PrecompileFailure, PrecompileHandle, PrecompileOutput,
Expand Down Expand Up @@ -117,6 +117,20 @@ where
});
}

let estimated_swapped_balance = CurrencySwapT::estimate_swapped_balance(value);
CurrencySwapT::To::can_deposit(&to, estimated_swapped_balance, false)
.into_result()
.map_err(|error| match error {
sp_runtime::DispatchError::Token(sp_runtime::TokenError::BelowMinimum) => {
PrecompileFailure::Error {
exit_status: ExitError::OutOfFund,
}
}
_ => PrecompileFailure::Error {
exit_status: ExitError::Other("unable to deposit funds".into()),
},
})?;

let imbalance = CurrencySwapT::From::withdraw(
&from,
value,
Expand Down
4 changes: 4 additions & 0 deletions crates/precompile-currency-swap/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ mock! {
primitives_currency_swap::ToNegativeImbalanceFor<Self, EvmAccountId, AccountId>,
primitives_currency_swap::ErrorFor<Self, EvmAccountId, AccountId>,
>;

fn estimate_swapped_balance(
balance: primitives_currency_swap::FromBalanceFor<Self, EvmAccountId, AccountId>,
) -> primitives_currency_swap::ToBalanceFor<Self, EvmAccountId, AccountId>;
}
}

Expand Down
Loading