-
Notifications
You must be signed in to change notification settings - Fork 19
Implement pallet-currency-swap logic
#679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
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 5bc5451
Adjust the precompile-currency-swap
MOZGIII ad6806c
Add basic structure
dmitrylavrenov 48c3eed
Define CurrencySwap interface
dmitrylavrenov 40b0075
Define config with accounts and currencies types
dmitrylavrenov ccf0978
Basic swap call
dmitrylavrenov 5c71251
Define custom error in CurrencySwap trait
dmitrylavrenov 9ff039f
Draft working test
dmitrylavrenov 66b514e
Working test with swap
dmitrylavrenov fd079fa
Refactor swap_works test
dmitrylavrenov cb72f97
Add swap_fails test
dmitrylavrenov 8b2d3f1
Add docs for pallet
dmitrylavrenov bb7fe94
Add docs for currency swap interface
dmitrylavrenov 3052831
Use negative imbalances logic at pallet level
dmitrylavrenov 657e7c2
Fix docs
dmitrylavrenov f17e619
Mock CurrencySwap interface for tests
dmitrylavrenov e9908da
Use mocking in tests
dmitrylavrenov 09891f7
Use AccountId and EvmAccountId to simplify their usage
dmitrylavrenov dae21e4
Rename utility aliases
dmitrylavrenov c376285
Add with_storage_layer
dmitrylavrenov 1cf0136
Add BalancesSwapped event
dmitrylavrenov 8f39367
Use currency swap primitives
dmitrylavrenov 9114caf
Fix features
dmitrylavrenov 30cfba7
Fix docs
dmitrylavrenov d1ccbaf
Implement benchmarking
dmitrylavrenov 722a58a
Fix docs again
dmitrylavrenov bd41a36
Fix swap error processing
dmitrylavrenov 3585811
Redesign benhcmark interface
dmitrylavrenov 9396013
Improve docs and naming for benchmark interface
dmitrylavrenov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 { | ||
MOZGIII marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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() | ||
MOZGIII marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| })?; | ||
| 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(()) | ||
| }) | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.