Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e5c5f12
Make currency swap return the original imbalance on failure
MOZGIII Jul 1, 2023
5bc5451
Adjust the precompile-currency-swap
MOZGIII Jul 1, 2023
ad6806c
Add basic structure
dmitrylavrenov Jun 26, 2023
48c3eed
Define CurrencySwap interface
dmitrylavrenov Jun 26, 2023
40b0075
Define config with accounts and currencies types
dmitrylavrenov Jun 29, 2023
ccf0978
Basic swap call
dmitrylavrenov Jun 29, 2023
5c71251
Define custom error in CurrencySwap trait
dmitrylavrenov Jun 29, 2023
9ff039f
Draft working test
dmitrylavrenov Jun 29, 2023
66b514e
Working test with swap
dmitrylavrenov Jun 29, 2023
fd079fa
Refactor swap_works test
dmitrylavrenov Jun 29, 2023
cb72f97
Add swap_fails test
dmitrylavrenov Jun 29, 2023
8b2d3f1
Add docs for pallet
dmitrylavrenov Jun 29, 2023
bb7fe94
Add docs for currency swap interface
dmitrylavrenov Jun 29, 2023
3052831
Use negative imbalances logic at pallet level
dmitrylavrenov Jun 29, 2023
657e7c2
Fix docs
dmitrylavrenov Jun 29, 2023
f17e619
Mock CurrencySwap interface for tests
dmitrylavrenov Jun 29, 2023
e9908da
Use mocking in tests
dmitrylavrenov Jun 29, 2023
09891f7
Use AccountId and EvmAccountId to simplify their usage
dmitrylavrenov Jun 29, 2023
dae21e4
Rename utility aliases
dmitrylavrenov Jun 29, 2023
c376285
Add with_storage_layer
dmitrylavrenov Jun 29, 2023
1cf0136
Add BalancesSwapped event
dmitrylavrenov Jun 29, 2023
8f39367
Use currency swap primitives
dmitrylavrenov Jun 29, 2023
9114caf
Fix features
dmitrylavrenov Jun 29, 2023
30cfba7
Fix docs
dmitrylavrenov Jun 30, 2023
d1ccbaf
Implement benchmarking
dmitrylavrenov Jun 30, 2023
722a58a
Fix docs again
dmitrylavrenov Jul 1, 2023
bd41a36
Fix swap error processing
dmitrylavrenov Jul 1, 2023
3585811
Redesign benhcmark interface
dmitrylavrenov Jul 1, 2023
9396013
Improve docs and naming for benchmark interface
dmitrylavrenov Jul 2, 2023
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
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions crates/pallet-currency-swap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[package]
name = "pallet-currency-swap"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
primitives-currency-swap = { version = "0.1", path = "../primitives-currency-swap", default-features = false }

codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
frame-benchmarking = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38", optional = true }
frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
frame-system = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
sp-runtime = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
sp-std = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }

[dev-dependencies]
mockall = "0.11"
pallet-balances = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }
pallet-evm-balances = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
pallet-evm-system = { git = "https://github.com/humanode-network/frontier", branch = "locked/polkadot-v0.9.38" }
sp-core = { git = "https://github.com/humanode-network/substrate", branch = "locked/polkadot-v0.9.38" }

[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"primitives-currency-swap/std",
"scale-info/std",
"sp-runtime/std",
"sp-std/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-evm-balances/try-runtime",
"pallet-evm-system/try-runtime",
"primitives-currency-swap/try-runtime",
"sp-runtime/try-runtime",
]
119 changes: 119 additions & 0 deletions crates/pallet-currency-swap/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! The benchmarks for the pallet.

use frame_benchmarking::benchmarks;
use frame_support::{assert_ok, dispatch::DispatchResult, traits::Currency};
use frame_system::RawOrigin;

use crate::*;

/// The benchmark interface into the environment.
pub trait Interface: super::Config {
/// The data to be passed from `prepare` to `verify`.
type Data;

/// Prepare currency swap environment.
fn prepare() -> Self::Data;

/// Verify currency swap environment,
fn verify(data: Self::Data) -> DispatchResult;

/// Obtain the Account ID the balance is swapped from.
fn from_account_id() -> <Self as frame_system::Config>::AccountId;

/// Obtain the amount of balance to withdraw from the swap source account.
fn from_balance() -> FromBalanceOf<Self>;

/// Obtain the Account ID the balance is swapped to.
fn to_account_id() -> <Self as Config>::AccountIdTo;

/// Obtain the amount of balance to deposit to the swap destination account.
fn to_balance() -> ToBalanceOf<Self>;
}

benchmarks! {
where_clause {
where
T: Interface,
}

swap {
let from = <T as Interface>::from_account_id();
let from_balance = <T as Interface>::from_balance();
let to = <T as Interface>::to_account_id();
let to_balance = <T as Interface>::to_balance();
let init_balance: u32 = 1000;

let _ = <FromCurrencyOf<T>>::deposit_creating(&from, init_balance.into());

let from_balance_before = <FromCurrencyOf<T>>::total_balance(&from);
let to_balance_before = <ToCurrencyOf<T>>::total_balance(&to);

let currency_swap = <T as Interface>::prepare();

let origin = RawOrigin::Signed(from.clone());

}: _(origin, to.clone(), from_balance)
verify {
let from_balance_after = <FromCurrencyOf<T>>::total_balance(&from);
let to_balance_after = <ToCurrencyOf<T>>::total_balance(&to);

assert_eq!(from_balance_before - from_balance_after, from_balance);
assert_eq!(to_balance_after - to_balance_before, to_balance);

assert_ok!(<T as Interface>::verify(currency_swap));
}

impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(),
crate::mock::Test,
);
}

#[cfg(test)]
impl Interface for crate::mock::Test {
type Data = (
std::sync::MutexGuard<'static, ()>,
mock::__mock_MockCurrencySwap_CurrencySwap_9230394375286242749::__swap::Context,
);

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

let swap_ctx = mock::MockCurrencySwap::swap_context();
swap_ctx.expect().times(1..).return_once(move |_| {
Ok(
<mock::EvmBalances as Currency<mock::EvmAccountId>>::NegativeImbalance::new(
Self::to_balance().into(),
),
)
});

(mock_runtime_guard, swap_ctx)
}

fn verify(data: Self::Data) -> DispatchResult {
let (mock_runtime_guard, swap_ctx) = data;
swap_ctx.checkpoint();
drop(mock_runtime_guard);
Ok(())
}

fn from_account_id() -> <Self as frame_system::Config>::AccountId {
42
}

fn from_balance() -> FromBalanceOf<Self> {
100
}

fn to_account_id() -> <Self as Config>::AccountIdTo {
use sp_std::str::FromStr;

mock::EvmAccountId::from_str("1000000000000000000000000000000000000001").unwrap()
}

fn to_balance() -> ToBalanceOf<Self> {
100
}
}
140 changes: 140 additions & 0 deletions crates/pallet-currency-swap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//! A substrate pallet containing the currency swap integration.

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

use frame_support::traits::Currency;
pub use pallet::*;
use primitives_currency_swap::CurrencySwap as CurrencySwapT;
pub use weights::*;

pub mod weights;

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::From`] type from a given config.
type FromCurrencyOf<T> = <<T as Config>::CurrencySwap as CurrencySwapT<
<T as frame_system::Config>::AccountId,
<T as Config>::AccountIdTo,
>>::From;

/// Utility alias for easy access to the [`Currency::Balance`] of
/// the [`primitives_currency_swap::CurrencySwap::From`] type.
type FromBalanceOf<T> =
<FromCurrencyOf<T> as Currency<<T as frame_system::Config>::AccountId>>::Balance;

/// Utility alias for easy access to [`primitives_currency_swap::CurrencySwap::To`] type from a given config.
type ToCurrencyOf<T> = <<T as Config>::CurrencySwap as CurrencySwapT<
<T as frame_system::Config>::AccountId,
<T as Config>::AccountIdTo,
>>::To;

/// Utility alias for easy access to the [`Currency::Balance`] of
/// the [`primitives_currency_swap::CurrencySwap::To`] type.
type ToBalanceOf<T> = <ToCurrencyOf<T> as Currency<<T as Config>::AccountIdTo>>::Balance;

// We have to temporarily allow some clippy lints. Later on we'll send patches to substrate to
// fix them at their end.
#[allow(clippy::missing_docs_in_private_items)]
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
pallet_prelude::*,
storage::with_storage_layer,
traits::{ExistenceRequirement, Imbalance, WithdrawReasons},
};
use frame_system::pallet_prelude::*;
use sp_runtime::traits::MaybeDisplay;
use sp_std::fmt::Debug;

use super::*;

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

/// Configuration trait of this pallet.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// The user account identifier type to convert to.
type AccountIdTo: Parameter
+ Member
+ MaybeSerializeDeserialize
+ Debug
+ MaybeDisplay
+ Ord
+ MaxEncodedLen;

/// Interface into currency swap implementation.
type CurrencySwap: CurrencySwapT<Self::AccountId, Self::AccountIdTo>;

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Balances were swapped.
BalancesSwapped {
/// The account id balances withdrawed from.
from: T::AccountId,
/// The withdrawed balances amount.
withdrawed_amount: FromBalanceOf<T>,
/// The account id balances deposited to.
to: T::AccountIdTo,
/// The deposited balances amount.
deposited_amount: ToBalanceOf<T>,
},
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Swap balances.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::swap())]
pub fn swap(
origin: OriginFor<T>,
to: T::AccountIdTo,
amount: FromBalanceOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;

with_storage_layer(move || {
let withdrawed_imbalance = FromCurrencyOf::<T>::withdraw(
&who,
amount,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?;
let withdrawed_amount = withdrawed_imbalance.peek();

let deposited_imbalance =
T::CurrencySwap::swap(withdrawed_imbalance).map_err(|error| {
// Here we undo the withdrawl to avoid having a dangling imbalance.
FromCurrencyOf::<T>::resolve_creating(&who, error.incoming_imbalance);
error.cause.into()
})?;
let deposited_amount = deposited_imbalance.peek();

ToCurrencyOf::<T>::resolve_creating(&to, deposited_imbalance);

Self::deposit_event(Event::BalancesSwapped {
from: who,
withdrawed_amount,
to,
deposited_amount,
});

Ok(())
})
}
}
}
Loading