From 98c88bc172312375247f7f32c5ca045f02a07a35 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 3 Sep 2024 21:28:42 +0200 Subject: [PATCH 01/46] feat: init bonded coins pallet --- Cargo.lock | 15 +++ Cargo.toml | 1 + pallets/pallet-bonded-coins/Cargo.toml | 48 ++++++++ pallets/pallet-bonded-coins/README.md | 4 + .../pallet-bonded-coins/src/benchmarking.rs | 20 ++++ pallets/pallet-bonded-coins/src/lib.rs | 106 ++++++++++++++++++ pallets/pallet-bonded-coins/src/mock.rs | 59 ++++++++++ pallets/pallet-bonded-coins/src/tests.rs | 23 ++++ 8 files changed, 276 insertions(+) create mode 100644 pallets/pallet-bonded-coins/Cargo.toml create mode 100644 pallets/pallet-bonded-coins/README.md create mode 100644 pallets/pallet-bonded-coins/src/benchmarking.rs create mode 100644 pallets/pallet-bonded-coins/src/lib.rs create mode 100644 pallets/pallet-bonded-coins/src/mock.rs create mode 100644 pallets/pallet-bonded-coins/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 32419e473..e65fa914f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7127,6 +7127,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-bonded-coins" +version = "0.8.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-bounties" version = "28.0.0" diff --git a/Cargo.toml b/Cargo.toml index f6a775c14..3d3c5f502 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ pallet-relay-store = { path = "pallets/pallet-relay-store", default-features pallet-web3-names = { path = "pallets/pallet-web3-names", default-features = false } parachain-staking = { path = "pallets/parachain-staking", default-features = false } public-credentials = { path = "pallets/public-credentials", default-features = false } +pallet-bonded-coins = { path = "pallets/pallet-bonded-coins", default-features = false } # Internal support (with default disabled) kilt-asset-dids = { path = "crates/assets", default-features = false } diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml new file mode 100644 index 000000000..59964de3b --- /dev/null +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-bonded-coins" +authors = ["Anonymous"] +description = "FRAME pallet template for defining custom runtime logic." +version = "0.8.0" +license = "Unlicense" +homepage = "https://substrate.io" +repository.workspace = true +edition.workspace = true + + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true, features = ["derive"]} +scale-info = { workspace = true, features = ["derive"]} +# Substrate +frame-benchmarking = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +[dev-dependencies] +serde = { workspace = true} + +# Substrate +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "parity-scale-codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/pallets/pallet-bonded-coins/README.md b/pallets/pallet-bonded-coins/README.md new file mode 100644 index 000000000..f7d669f51 --- /dev/null +++ b/pallets/pallet-bonded-coins/README.md @@ -0,0 +1,4 @@ +# pallet bonded coins + +Create currencies which can be minted by locking up tokens of an existing currency as collateral. +The exchange rate of collateral to tokens minted is determined by a bonding curve linking total issuance of the new currency to mint price. diff --git a/pallets/pallet-bonded-coins/src/benchmarking.rs b/pallets/pallet-bonded-coins/src/benchmarking.rs new file mode 100644 index 000000000..8bba2a098 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/benchmarking.rs @@ -0,0 +1,20 @@ +//! Benchmarking setup for pallet-parachain-template + +use super::*; + +#[allow(unused)] +use crate::Pallet as Template; +use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_system::RawOrigin; + +benchmarks! { + do_something { + let s in 0 .. 100; + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), s) + verify { + assert_eq!(Something::::get(), Some(s)); + } +} + +impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs new file mode 100644 index 000000000..5f3252bfc --- /dev/null +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -0,0 +1,106 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +/// Edit this file to define custom logic or remove it if it is not needed. +/// Learn more about FRAME and the core library of Substrate FRAME pallets: +/// +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // The pallet's runtime storage items. + // https://docs.substrate.io/v3/runtime/storage + #[pallet::storage] + #[pallet::getter(fn something)] + // Learn more about declaring storage items: + // https://docs.substrate.io/v3/runtime/storage#declaring-storage-items + pub type Something = StorageValue<_, u32>; + + // Pallets use events to inform users when important changes are made. + // https://docs.substrate.io/v3/runtime/events-and-errors + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event documentation should end with an array that provides descriptive names for event + /// parameters. [something, who] + SomethingStored(u32, T::AccountId), + } + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + /// Error names should be descriptive. + NoneValue, + /// Errors should have helpful documentation associated with them. + StorageOverflow, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + // Dispatchable functions allows users to interact with the pallet and invoke state changes. + // These functions materialize as "extrinsics", which are often compared to transactions. + // Dispatchable functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// An example dispatchable that takes a singles value as a parameter, writes the value to + /// storage and emits an event. This function must be dispatched by a signed extrinsic. + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + pub fn do_something(origin: OriginFor, something: u32) -> DispatchResultWithPostInfo { + // Check that the extrinsic was signed and get the signer. + // This function will return an error if the extrinsic is not signed. + // https://docs.substrate.io/v3/runtime/origins + let who = ensure_signed(origin)?; + + // Update storage. + >::put(something); + + // Emit an event. + Self::deposit_event(Event::SomethingStored(something, who)); + // Return a successful DispatchResultWithPostInfo + Ok(().into()) + } + + /// An example dispatchable that may throw a custom error. + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().reads_writes(1,1))] + pub fn cause_error(origin: OriginFor) -> DispatchResultWithPostInfo { + let _who = ensure_signed(origin)?; + + // Read a value from storage. + match >::get() { + // Return an error if the value has not been set. + None => Err(Error::::NoneValue)?, + Some(old) => { + // Increment the value read from storage; will error in the event of overflow. + let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; + // Update the value in storage with the incremented result. + >::put(new); + Ok(().into()) + }, + } + } + } +} diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs new file mode 100644 index 000000000..411a16b11 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -0,0 +1,59 @@ +use frame_support::{derive_impl, parameter_types, traits::Everything}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + TemplateModule: crate::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/pallets/pallet-bonded-coins/src/tests.rs b/pallets/pallet-bonded-coins/src/tests.rs new file mode 100644 index 000000000..527aec8ed --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests.rs @@ -0,0 +1,23 @@ +use crate::{mock::*, Error}; +use frame_support::{assert_noop, assert_ok}; + +#[test] +fn it_works_for_default_value() { + new_test_ext().execute_with(|| { + // Dispatch a signed extrinsic. + assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); + // Read pallet storage and assert an expected result. + assert_eq!(TemplateModule::something(), Some(42)); + }); +} + +#[test] +fn correct_error_for_none_value() { + new_test_ext().execute_with(|| { + // Ensure the expected error is thrown when no value is present. + assert_noop!( + TemplateModule::cause_error(RuntimeOrigin::signed(1)), + Error::::NoneValue + ); + }); +} From 10f3cb83a40aa4bbe09b3137edc6fd4382a71b59 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 4 Sep 2024 17:08:08 +0200 Subject: [PATCH 02/46] feat: add config --- pallets/pallet-bonded-coins/src/lib.rs | 27 ++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 5f3252bfc..35e6c446f 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -16,7 +16,11 @@ mod benchmarking; #[frame_support::pallet] pub mod pallet { - use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; + use frame_support::{ + dispatch::DispatchResultWithPostInfo, + pallet_prelude::*, + traits::fungible::{Mutate, MutateHold}, + }; use frame_system::pallet_prelude::*; /// Configure the pallet by specifying the parameters and types on which it depends. @@ -24,6 +28,25 @@ pub mod pallet { pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The currency used for storage deposits. + type DepositCurrency: MutateHold; + /// The currency used as collateral for minting bonded tokens. + type CollateralCurrency: Mutate; + /// The maximum amount that a user can place on a bet. + /// TODO: Type should be derived from Self::CollateralCurrency + #[pallet::constant] + type MaxStake: Get; + /// The maximum length allowed for an event name. + /// TODO: is there a better type for a length? + #[pallet::constant] + type MaxNameLength: Get; + /// The maximum number of outcomes allowed for an event. + #[pallet::constant] + type MaxOutcomes: Get; + /// Who can create new events. + type CreateOrigin: EnsureOrigin; + /// Who can mint coins. + type MintOrigin: EnsureOrigin; } #[pallet::pallet] @@ -99,7 +122,7 @@ pub mod pallet { // Update the value in storage with the incremented result. >::put(new); Ok(().into()) - }, + } } } } From fbcbdffe05a2c5b27a05cb7e6fae55bf0133d42c Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 4 Sep 2024 17:28:06 +0200 Subject: [PATCH 03/46] feat: add storage --- Cargo.lock | 1 + pallets/pallet-bonded-coins/Cargo.toml | 3 +- pallets/pallet-bonded-coins/src/lib.rs | 25 +++++++++------ pallets/pallet-bonded-coins/src/types.rs | 39 ++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index e65fa914f..f2698fefd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7134,6 +7134,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "pallet-assets", "parity-scale-codec", "scale-info", "serde", diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 59964de3b..bdee1b280 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -19,12 +19,13 @@ scale-info = { workspace = true, features = ["derive"]} frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-core = { workspace = true } +pallet-assets = { workspace = true } [dev-dependencies] serde = { workspace = true} # Substrate -sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 35e6c446f..1564c86e7 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -14,18 +14,22 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +mod types; + #[frame_support::pallet] pub mod pallet { + use crate::types::EventData; use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::*, traits::fungible::{Mutate, MutateHold}, }; use frame_system::pallet_prelude::*; + use pallet_assets as assets; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + assets::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency used for storage deposits. @@ -39,10 +43,10 @@ pub mod pallet { /// The maximum length allowed for an event name. /// TODO: is there a better type for a length? #[pallet::constant] - type MaxNameLength: Get; + type MaxNameLength: Get + TypeInfo; /// The maximum number of outcomes allowed for an event. #[pallet::constant] - type MaxOutcomes: Get; + type MaxOutcomes: Get + TypeInfo; /// Who can create new events. type CreateOrigin: EnsureOrigin; /// Who can mint coins. @@ -52,13 +56,16 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); - // The pallet's runtime storage items. - // https://docs.substrate.io/v3/runtime/storage + /// Predictable Events #[pallet::storage] - #[pallet::getter(fn something)] - // Learn more about declaring storage items: - // https://docs.substrate.io/v3/runtime/storage#declaring-storage-items - pub type Something = StorageValue<_, u32>; + #[pallet::getter(fn events)] + pub(crate) type Events = StorageMap< + _, + Twox64Concat, + T::AccountId, + EventData, + OptionQuery, + >; // Pallets use events to inform users when important changes are made. // https://docs.substrate.io/v3/runtime/events-and-errors diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs new file mode 100644 index 000000000..74fc111a3 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -0,0 +1,39 @@ +use frame_support::BoundedVec; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::Get; + +#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum EventState { + #[default] + Active, + Frozen, + Sealed, /// TODO: save block number where seal happened here + Decided(CurrencyId), + Destroying, +} + +#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct EventData, MaxOptions: Get> { + pub creator: AccountId, + pub name: BoundedVec, + pub linked_currencies: BoundedVec, + pub state: EventState, +} + +impl, MaxOptions: Get> + EventData +{ + pub fn new( + creator: AccountId, + name: BoundedVec, + linked_currencies: BoundedVec, + ) -> Self { + Self { + creator, + name, + linked_currencies, + state: EventState::default(), + } + } +} From 37a9647413b66a7f5410b76c647e1ef232d09f3f Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 5 Sep 2024 21:45:46 +0200 Subject: [PATCH 04/46] feat: implement create extrinsic --- Cargo.lock | 1 - pallets/pallet-bonded-coins/Cargo.toml | 3 +- pallets/pallet-bonded-coins/src/lib.rs | 173 +++++++++++++++++-------- 3 files changed, 123 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2698fefd..e65fa914f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7134,7 +7134,6 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "pallet-assets", "parity-scale-codec", "scale-info", "serde", diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index bdee1b280..130951158 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -20,14 +20,13 @@ frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } sp-core = { workspace = true } -pallet-assets = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] serde = { workspace = true} # Substrate sp-io = { workspace = true } -sp-runtime = { workspace = true } [features] default = ["std"] diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 1564c86e7..66ae99682 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -21,36 +21,70 @@ pub mod pallet { use crate::types::EventData; use frame_support::{ dispatch::DispatchResultWithPostInfo, - pallet_prelude::*, - traits::fungible::{Mutate, MutateHold}, + pallet_prelude::{ValueQuery, *}, + traits::{ + fungible::{Inspect as InspectFungible, Mutate, MutateHold}, + fungibles::{ + metadata::Mutate as FungiblesMetadata, Create as CreateFungibles, Destroy as DestroyFungibles, + }, + tokens::{AssetId, Balance}, + }, }; use frame_system::pallet_prelude::*; - use pallet_assets as assets; + use parity_scale_codec::EncodeLike; + use sp_runtime::{ + traits::{EnsureAddAssign, One, Saturating}, + SaturatedConversion, + }; + + pub type BalanceOf = >::Balance; + pub type DepositCurrencyHoldReasonOf = + <::DepositCurrency as frame_support::traits::fungible::InspectHold< + ::AccountId, + >>::Reason; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config + assets::Config { + pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency used for storage deposits. type DepositCurrency: MutateHold; /// The currency used as collateral for minting bonded tokens. type CollateralCurrency: Mutate; + /// Implementation of creating and managing new fungibles + type Fungibles: CreateFungibles + + DestroyFungibles + + FungiblesMetadata; /// The maximum amount that a user can place on a bet. - /// TODO: Type should be derived from Self::CollateralCurrency #[pallet::constant] - type MaxStake: Get; + type MaxStake: Get>; /// The maximum length allowed for an event name. - /// TODO: is there a better type for a length? #[pallet::constant] - type MaxNameLength: Get + TypeInfo; + type MaxNameLength: Get + TypeInfo; // TODO: is there a better type for a length? /// The maximum number of outcomes allowed for an event. #[pallet::constant] type MaxOutcomes: Get + TypeInfo; + /// The deposit required for each outcome currency. + #[pallet::constant] + type DepositPerOutcome: Get>; /// Who can create new events. - type CreateOrigin: EnsureOrigin; + type EventCreateOrigin: EnsureOrigin; /// Who can mint coins. - type MintOrigin: EnsureOrigin; + type WagerOrigin: EnsureOrigin; + /// The type used for event ids + type EventId: EncodeLike + + Decode + + TypeInfo + + Clone + + MaxEncodedLen + + From + + Into + + Into>; + /// + type AssetId: AssetId + Default + EnsureAddAssign + One; + /// + type Balance: Balance; } #[pallet::pallet] @@ -62,75 +96,112 @@ pub mod pallet { pub(crate) type Events = StorageMap< _, Twox64Concat, - T::AccountId, + T::EventId, EventData, OptionQuery, >; - // Pallets use events to inform users when important changes are made. - // https://docs.substrate.io/v3/runtime/events-and-errors + /// The asset id to be used for the next event creation. + #[pallet::storage] + #[pallet::getter(fn next_asset_id)] + pub(crate) type NextAssetId = StorageValue<_, T::AssetId, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Event documentation should end with an array that provides descriptive names for event - /// parameters. [something, who] - SomethingStored(u32, T::AccountId), + /// A new predictable event has been initiated. [event_id] + EventCreated(T::AccountId), + /// Prediction registration for a predictable event has been paused. [event_id] + EventPaused(T::AccountId), + /// Prediction registration for a predictable event has been resumed. [event_id] + EventResumed(T::AccountId), + /// Prediction registration for a predictable event has been resumed. [event_id, selected_outcome] + EventDecided(T::AccountId, u32), // TODO: outcome index type should be configurable + /// A predictable event has fully deleted. [event_id] + EventDestroyed(T::AccountId), } // Errors inform users that something went wrong. #[pallet::error] pub enum Error { - /// Error names should be descriptive. - NoneValue, - /// Errors should have helpful documentation associated with them. - StorageOverflow, + /// The number of outcomes is either lower than 2 or greater than MaxOutcomes. + OutcomesLength, + /// A wager cannot be placed or modified on a predictable event whose status is not Active. + Inactive, + /// The event id is not currently registered. + EventUnknown, + /// The event has no outcome with the given index. + OutcomeUnknown, + /// An account has exceeded the maximum allowed wager value. + MaxStakeExceeded, } #[pallet::hooks] impl Hooks> for Pallet {} + #[derive(Debug, Encode, Decode, Clone, PartialEq, TypeInfo)] + pub struct TokenMeta { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub min_balance: Balance, + } + // Dispatchable functions allows users to interact with the pallet and invoke state changes. // These functions materialize as "extrinsics", which are often compared to transactions. // Dispatchable functions must be annotated with a weight and must return a DispatchResult. #[pallet::call] impl Pallet { - /// An example dispatchable that takes a singles value as a parameter, writes the value to - /// storage and emits an event. This function must be dispatched by a signed extrinsic. #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] - pub fn do_something(origin: OriginFor, something: u32) -> DispatchResultWithPostInfo { - // Check that the extrinsic was signed and get the signer. - // This function will return an error if the extrinsic is not signed. - // https://docs.substrate.io/v3/runtime/origins - let who = ensure_signed(origin)?; + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] // TODO: properly configure weights + pub fn create_event( + origin: OriginFor, + event_name: BoundedVec, + outcomes: BoundedVec, T::MaxOutcomes>, + ) -> DispatchResultWithPostInfo { + // ensure origin is EventCreateOrigin + let who = T::EventCreateOrigin::ensure_origin(origin)?; + + ensure!( + (2..=T::MaxOutcomes::get().saturated_into()).contains(&outcomes.len()), + Error::::OutcomesLength + ); + + let mut asset_id = Self::next_asset_id(); + let event_id = T::EventId::from(asset_id.clone()); + + T::DepositCurrency::hold( + &event_id.clone().into(), // TODO: just assumed that you can use an event id as hold reason, not sure that's true though + &who, + T::DepositPerOutcome::get() + .saturating_mul(outcomes.len().saturated_into()) + .saturated_into(), + )?; + + let mut currency_ids = BoundedVec::with_bounded_capacity(outcomes.len()); + + for (idx, entry) in outcomes.iter().enumerate() { + T::Fungibles::create(asset_id.clone(), event_id.clone().into(), true, entry.min_balance)?; + currency_ids[idx] = asset_id.clone(); + asset_id.ensure_add_assign(T::AssetId::one())?; + + T::Fungibles::set( + asset_id.clone(), + &event_id.clone().into(), + entry.name.clone(), + entry.symbol.clone(), + entry.decimals, + )?; + } + + >::put(asset_id); - // Update storage. - >::put(something); + >::set(event_id, Some(EventData::new(who.clone(), event_name, currency_ids))); // Emit an event. - Self::deposit_event(Event::SomethingStored(something, who)); + Self::deposit_event(Event::EventCreated(who)); // Return a successful DispatchResultWithPostInfo Ok(().into()) } - - /// An example dispatchable that may throw a custom error. - #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().reads_writes(1,1))] - pub fn cause_error(origin: OriginFor) -> DispatchResultWithPostInfo { - let _who = ensure_signed(origin)?; - - // Read a value from storage. - match >::get() { - // Return an error if the value has not been set. - None => Err(Error::::NoneValue)?, - Some(old) => { - // Increment the value read from storage; will error in the event of overflow. - let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; - // Update the value in storage with the incremented result. - >::put(new); - Ok(().into()) - } - } - } } } From f6acfa3e596aeee8a7989fd5fe1be790f0274aba Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 9 Sep 2024 22:23:37 +0200 Subject: [PATCH 05/46] docs: update docstrings --- pallets/pallet-bonded-coins/src/lib.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 66ae99682..d18eb1eed 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -81,9 +81,9 @@ pub mod pallet { + From + Into + Into>; - /// + /// Type of an asset id in the Fungibles implementation type AssetId: AssetId + Default + EnsureAddAssign + One; - /// + /// The balance of assets in the Fungibles implementation type Balance: Balance; } @@ -147,9 +147,6 @@ pub mod pallet { pub min_balance: Balance, } - // Dispatchable functions allows users to interact with the pallet and invoke state changes. - // These functions materialize as "extrinsics", which are often compared to transactions. - // Dispatchable functions must be annotated with a weight and must return a DispatchResult. #[pallet::call] impl Pallet { #[pallet::call_index(0)] From d2f4b34ac2451fbf0989979d1659bc1f8061dddb Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 10 Sep 2024 22:12:33 +0200 Subject: [PATCH 06/46] refactor: move to more generic pallet design --- pallets/pallet-bonded-coins/src/lib.rs | 125 +++++++++++------------ pallets/pallet-bonded-coins/src/types.rs | 50 ++++++--- 2 files changed, 93 insertions(+), 82 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index d18eb1eed..b963ee216 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -18,7 +18,7 @@ mod types; #[frame_support::pallet] pub mod pallet { - use crate::types::EventData; + use crate::types::{Curve, PoolDetails, TokenMeta}; use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::{ValueQuery, *}, @@ -56,24 +56,16 @@ pub mod pallet { type Fungibles: CreateFungibles + DestroyFungibles + FungiblesMetadata; - /// The maximum amount that a user can place on a bet. + /// The maximum number of currencies allowed for a single pool. #[pallet::constant] - type MaxStake: Get>; - /// The maximum length allowed for an event name. + type MaxCurrencies: Get + TypeInfo; + /// The deposit required for each bonded currency. #[pallet::constant] - type MaxNameLength: Get + TypeInfo; // TODO: is there a better type for a length? - /// The maximum number of outcomes allowed for an event. - #[pallet::constant] - type MaxOutcomes: Get + TypeInfo; - /// The deposit required for each outcome currency. - #[pallet::constant] - type DepositPerOutcome: Get>; - /// Who can create new events. - type EventCreateOrigin: EnsureOrigin; - /// Who can mint coins. - type WagerOrigin: EnsureOrigin; - /// The type used for event ids - type EventId: EncodeLike + type DepositPerCurrency: Get>; + /// Who can create new bonded currency pools. + type PoolCreateOrigin: EnsureOrigin; + /// The type used for pool ids + type PoolId: EncodeLike + Decode + TypeInfo + Clone @@ -90,18 +82,18 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); - /// Predictable Events + /// Bonded Currency Swapping Pools #[pallet::storage] - #[pallet::getter(fn events)] - pub(crate) type Events = StorageMap< + #[pallet::getter(fn pools)] + pub(crate) type Pools = StorageMap< _, Twox64Concat, - T::EventId, - EventData, + T::PoolId, + PoolDetails, OptionQuery, >; - /// The asset id to be used for the next event creation. + /// The asset id to be used for the next pool creation. #[pallet::storage] #[pallet::getter(fn next_asset_id)] pub(crate) type NextAssetId = StorageValue<_, T::AssetId, ValueQuery>; @@ -109,94 +101,91 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// A new predictable event has been initiated. [event_id] - EventCreated(T::AccountId), - /// Prediction registration for a predictable event has been paused. [event_id] - EventPaused(T::AccountId), - /// Prediction registration for a predictable event has been resumed. [event_id] - EventResumed(T::AccountId), - /// Prediction registration for a predictable event has been resumed. [event_id, selected_outcome] - EventDecided(T::AccountId, u32), // TODO: outcome index type should be configurable - /// A predictable event has fully deleted. [event_id] - EventDestroyed(T::AccountId), + /// A new bonded token pool has been initiated. [pool_id] + PoolCreated(T::AccountId), + /// Trading locks on a pool have been removed. [pool_id] + Unlocked(T::AccountId), + /// Trading locks on a pool have been set or changed. [pool_id] + LockSet(T::AccountId), + /// A bonded token pool has been moved to destroying state. [pool_id] + DestructionStarted(T::AccountId), + /// A bonded token pool has been fully destroyed and all collateral and deposits have been refunded. [pool_id] + Destroyed(T::AccountId), } // Errors inform users that something went wrong. #[pallet::error] pub enum Error { - /// The number of outcomes is either lower than 2 or greater than MaxOutcomes. - OutcomesLength, - /// A wager cannot be placed or modified on a predictable event whose status is not Active. - Inactive, - /// The event id is not currently registered. - EventUnknown, - /// The event has no outcome with the given index. - OutcomeUnknown, - /// An account has exceeded the maximum allowed wager value. - MaxStakeExceeded, + /// The number of bonded currencies on a new pool is either lower than 1 or greater than MaxCurrencies. + CurrenciesOutOfBounds, + /// A token swap cannot be executed due to a lock placed on this operation. + Locked, + /// The pool id is not currently registered. + PoolUnknown, + /// The pool has no associated bonded currency with the given index. + IndexOutOfBounds, } #[pallet::hooks] impl Hooks> for Pallet {} - #[derive(Debug, Encode, Decode, Clone, PartialEq, TypeInfo)] - pub struct TokenMeta { - pub name: Vec, - pub symbol: Vec, - pub decimals: u8, - pub min_balance: Balance, - } - #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] // TODO: properly configure weights - pub fn create_event( + pub fn create_pool( origin: OriginFor, - event_name: BoundedVec, - outcomes: BoundedVec, T::MaxOutcomes>, + curve: Curve, + currencies: BoundedVec, T::MaxCurrencies>, + frozen: bool, + // currency_admin: Option TODO: use this to set currency admin ) -> DispatchResultWithPostInfo { - // ensure origin is EventCreateOrigin - let who = T::EventCreateOrigin::ensure_origin(origin)?; + // ensure origin is PoolCreateOrigin + let who = T::PoolCreateOrigin::ensure_origin(origin)?; ensure!( - (2..=T::MaxOutcomes::get().saturated_into()).contains(&outcomes.len()), - Error::::OutcomesLength + (2..=T::MaxCurrencies::get().saturated_into()).contains(¤cies.len()), + Error::::CurrenciesOutOfBounds ); let mut asset_id = Self::next_asset_id(); - let event_id = T::EventId::from(asset_id.clone()); + let pool_id = T::PoolId::from(asset_id.clone()); T::DepositCurrency::hold( - &event_id.clone().into(), // TODO: just assumed that you can use an event id as hold reason, not sure that's true though + &pool_id.clone().into(), // TODO: just assumed that you can use a pool id as hold reason, not sure that's true though &who, - T::DepositPerOutcome::get() - .saturating_mul(outcomes.len().saturated_into()) + T::DepositPerCurrency::get() + .saturating_mul(currencies.len().saturated_into()) .saturated_into(), )?; - let mut currency_ids = BoundedVec::with_bounded_capacity(outcomes.len()); + let mut currency_ids = BoundedVec::with_bounded_capacity(currencies.len()); - for (idx, entry) in outcomes.iter().enumerate() { - T::Fungibles::create(asset_id.clone(), event_id.clone().into(), true, entry.min_balance)?; + for (idx, entry) in currencies.iter().enumerate() { + T::Fungibles::create(asset_id.clone(), pool_id.clone().into(), false, entry.min_balance)?; currency_ids[idx] = asset_id.clone(); asset_id.ensure_add_assign(T::AssetId::one())?; T::Fungibles::set( asset_id.clone(), - &event_id.clone().into(), + &pool_id.clone().into(), entry.name.clone(), entry.symbol.clone(), entry.decimals, )?; + + // TODO: use fungibles::roles::ResetTeam to update currency admin } >::put(asset_id); - >::set(event_id, Some(EventData::new(who.clone(), event_name, currency_ids))); + >::set( + pool_id, + Some(PoolDetails::new(who.clone(), curve, currency_ids, !frozen)), + ); // Emit an event. - Self::deposit_event(Event::EventCreated(who)); + Self::deposit_event(Event::PoolCreated(who)); // Return a successful DispatchResultWithPostInfo Ok(().into()) } diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 74fc111a3..84e0169f1 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -4,36 +4,58 @@ use scale_info::TypeInfo; use sp_core::Get; #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub enum EventState { +pub struct Locks { + pub allow_mint: bool, + pub allow_burn: bool, + pub allow_swap: bool, +} + +#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum EventState { #[default] Active, - Frozen, - Sealed, /// TODO: save block number where seal happened here - Decided(CurrencyId), + Frozen(LockType), Destroying, } #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct EventData, MaxOptions: Get> { +pub struct PoolDetails> { pub creator: AccountId, - pub name: BoundedVec, - pub linked_currencies: BoundedVec, - pub state: EventState, + pub curve: ParametrizedCurve, + pub bonded_currencies: BoundedVec, + pub state: EventState, + pub transferable: bool, } -impl, MaxOptions: Get> - EventData +impl> + PoolDetails { pub fn new( creator: AccountId, - name: BoundedVec, - linked_currencies: BoundedVec, + curve: ParametrizedCurve, + bonded_currencies: BoundedVec, + transferable: bool, ) -> Self { Self { creator, - name, - linked_currencies, + curve, + bonded_currencies, + transferable, state: EventState::default(), } } } + +#[derive(Debug, Encode, Decode, Clone, PartialEq, TypeInfo)] +pub struct TokenMeta { + pub name: Vec, + pub symbol: Vec, + pub decimals: u8, + pub min_balance: Balance, +} + +#[derive(Default, Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum Curve { + #[default] + LinearRatioCurve, +} From 08277e11c303107964be5e072f4a4c7e6933102a Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 11 Sep 2024 00:12:42 +0200 Subject: [PATCH 07/46] refactor: make currency ids user-specified --- pallets/pallet-bonded-coins/src/lib.rs | 60 +++++++++++------------- pallets/pallet-bonded-coins/src/types.rs | 9 ++-- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index b963ee216..db1011a60 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -21,28 +21,32 @@ pub mod pallet { use crate::types::{Curve, PoolDetails, TokenMeta}; use frame_support::{ dispatch::DispatchResultWithPostInfo, - pallet_prelude::{ValueQuery, *}, + pallet_prelude::*, traits::{ fungible::{Inspect as InspectFungible, Mutate, MutateHold}, fungibles::{ metadata::Mutate as FungiblesMetadata, Create as CreateFungibles, Destroy as DestroyFungibles, + Inspect as InspectFungibles, }, - tokens::{AssetId, Balance}, }, + Hashable, }; use frame_system::pallet_prelude::*; use parity_scale_codec::EncodeLike; - use sp_runtime::{ - traits::{EnsureAddAssign, One, Saturating}, - SaturatedConversion, - }; + use sp_runtime::{traits::Saturating, SaturatedConversion}; - pub type BalanceOf = >::Balance; + pub type DepositCurrencyBalanceOf = + <::DepositCurrency as InspectFungible<::AccountId>>::Balance; pub type DepositCurrencyHoldReasonOf = <::DepositCurrency as frame_support::traits::fungible::InspectHold< ::AccountId, >>::Reason; - + pub type CollateralCurrencyBalanceOf = + <::CollateralCurrency as InspectFungible<::AccountId>>::Balance; + pub type FungiblesBalanceOf = + <::Fungibles as InspectFungibles<::AccountId>>::Balance; + pub type FungiblesAssetIdOf = + <::Fungibles as InspectFungibles<::AccountId>>::AssetId; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -53,15 +57,15 @@ pub mod pallet { /// The currency used as collateral for minting bonded tokens. type CollateralCurrency: Mutate; /// Implementation of creating and managing new fungibles - type Fungibles: CreateFungibles - + DestroyFungibles - + FungiblesMetadata; + type Fungibles: CreateFungibles + + DestroyFungibles + + FungiblesMetadata; /// The maximum number of currencies allowed for a single pool. #[pallet::constant] type MaxCurrencies: Get + TypeInfo; /// The deposit required for each bonded currency. #[pallet::constant] - type DepositPerCurrency: Get>; + type DepositPerCurrency: Get>; /// Who can create new bonded currency pools. type PoolCreateOrigin: EnsureOrigin; /// The type used for pool ids @@ -70,13 +74,9 @@ pub mod pallet { + TypeInfo + Clone + MaxEncodedLen - + From + + From<[u8; 32]> + Into + Into>; - /// Type of an asset id in the Fungibles implementation - type AssetId: AssetId + Default + EnsureAddAssign + One; - /// The balance of assets in the Fungibles implementation - type Balance: Balance; } #[pallet::pallet] @@ -89,15 +89,10 @@ pub mod pallet { _, Twox64Concat, T::PoolId, - PoolDetails, + PoolDetails, Curve, T::MaxCurrencies>, OptionQuery, >; - /// The asset id to be used for the next pool creation. - #[pallet::storage] - #[pallet::getter(fn next_asset_id)] - pub(crate) type NextAssetId = StorageValue<_, T::AssetId, ValueQuery>; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -136,7 +131,7 @@ pub mod pallet { pub fn create_pool( origin: OriginFor, curve: Curve, - currencies: BoundedVec, T::MaxCurrencies>, + currencies: BoundedVec, FungiblesAssetIdOf>, T::MaxCurrencies>, frozen: bool, // currency_admin: Option TODO: use this to set currency admin ) -> DispatchResultWithPostInfo { @@ -148,8 +143,9 @@ pub mod pallet { Error::::CurrenciesOutOfBounds ); - let mut asset_id = Self::next_asset_id(); - let pool_id = T::PoolId::from(asset_id.clone()); + let currency_ids = BoundedVec::truncate_from(currencies.iter().map(|c| c.id.clone()).collect()); + + let pool_id = T::PoolId::from(currency_ids.blake2_256()); T::DepositCurrency::hold( &pool_id.clone().into(), // TODO: just assumed that you can use a pool id as hold reason, not sure that's true though @@ -159,15 +155,15 @@ pub mod pallet { .saturated_into(), )?; - let mut currency_ids = BoundedVec::with_bounded_capacity(currencies.len()); + for entry in currencies { + let asset_id = entry.id.clone(); - for (idx, entry) in currencies.iter().enumerate() { + // create new assset class; fail if it already exists T::Fungibles::create(asset_id.clone(), pool_id.clone().into(), false, entry.min_balance)?; - currency_ids[idx] = asset_id.clone(); - asset_id.ensure_add_assign(T::AssetId::one())?; + // set metadata for new asset class T::Fungibles::set( - asset_id.clone(), + asset_id, &pool_id.clone().into(), entry.name.clone(), entry.symbol.clone(), @@ -177,8 +173,6 @@ pub mod pallet { // TODO: use fungibles::roles::ResetTeam to update currency admin } - >::put(asset_id); - >::set( pool_id, Some(PoolDetails::new(who.clone(), curve, currency_ids, !frozen)), diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 84e0169f1..b1fc8b2c8 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -11,7 +11,7 @@ pub struct Locks { } #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub enum EventState { +pub enum PoolStatus { #[default] Active, Frozen(LockType), @@ -23,7 +23,7 @@ pub struct PoolDetails, - pub state: EventState, + pub state: PoolStatus, pub transferable: bool, } @@ -41,13 +41,14 @@ impl> curve, bonded_currencies, transferable, - state: EventState::default(), + state: PoolStatus::default(), } } } #[derive(Debug, Encode, Decode, Clone, PartialEq, TypeInfo)] -pub struct TokenMeta { +pub struct TokenMeta { + pub id: AssetId, pub name: Vec, pub symbol: Vec, pub decimals: u8, From 76f651e3ab87db957e89f957e4eb34886f8be244 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 11 Sep 2024 18:41:03 +0200 Subject: [PATCH 08/46] feat: add mint_into extrinsic --- pallets/pallet-bonded-coins/src/lib.rs | 98 ++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 12 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index db1011a60..f33942505 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -16,9 +16,9 @@ mod benchmarking; mod types; -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] pub mod pallet { - use crate::types::{Curve, PoolDetails, TokenMeta}; + use crate::types::{Curve, PoolDetails, PoolStatus, TokenMeta}; use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::*, @@ -26,14 +26,17 @@ pub mod pallet { fungible::{Inspect as InspectFungible, Mutate, MutateHold}, fungibles::{ metadata::Mutate as FungiblesMetadata, Create as CreateFungibles, Destroy as DestroyFungibles, - Inspect as InspectFungibles, + Inspect as InspectFungibles, Mutate as MutateFungibles, }, + tokens::Preservation, }, Hashable, }; use frame_system::pallet_prelude::*; - use parity_scale_codec::EncodeLike; - use sp_runtime::{traits::Saturating, SaturatedConversion}; + use sp_runtime::{ + traits::{Saturating, Zero}, + ArithmeticError, SaturatedConversion, + }; pub type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; @@ -59,7 +62,8 @@ pub mod pallet { /// Implementation of creating and managing new fungibles type Fungibles: CreateFungibles + DestroyFungibles - + FungiblesMetadata; + + FungiblesMetadata + + MutateFungibles; /// The maximum number of currencies allowed for a single pool. #[pallet::constant] type MaxCurrencies: Get + TypeInfo; @@ -69,16 +73,17 @@ pub mod pallet { /// Who can create new bonded currency pools. type PoolCreateOrigin: EnsureOrigin; /// The type used for pool ids - type PoolId: EncodeLike - + Decode - + TypeInfo - + Clone + type PoolId: Parameter + MaxEncodedLen + From<[u8; 32]> + Into + Into>; } + fn mock_curve_impl(_: Curve, active_pre: T, active_post: T, __: T) -> T { + active_post.saturating_sub(active_pre) + } + #[pallet::pallet] pub struct Pallet(_); @@ -119,15 +124,20 @@ pub mod pallet { PoolUnknown, /// The pool has no associated bonded currency with the given index. IndexOutOfBounds, + /// The cost or returns for a mint, burn, or swap operation is outside the user-defined slippage tolerance. + Slippage, } #[pallet::hooks] impl Hooks> for Pallet {} #[pallet::call] - impl Pallet { + impl Pallet + where + FungiblesBalanceOf: TryInto>, + { #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] // TODO: properly configure weights + // #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] TODO: properly configure weights pub fn create_pool( origin: OriginFor, curve: Curve, @@ -183,5 +193,69 @@ pub mod pallet { // Return a successful DispatchResultWithPostInfo Ok(().into()) } + + #[pallet::call_index(1)] + pub fn mint_into( + origin: OriginFor, + pool_id: T::PoolId, + currency_idx: u32, + amount_to_mint: FungiblesBalanceOf, + max_cost: CollateralCurrencyBalanceOf, + beneficiary: T::AccountId, + ) -> DispatchResultWithPostInfo { + let signer = ensure_signed(origin)?; + + let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + let mint_enabled = match pool_details.state { + // if mint is locked, then operation is priviledged + PoolStatus::Frozen(locks) => locks.allow_mint || signer == pool_details.creator, + PoolStatus::Active => true, + _ => false, + }; + ensure!(mint_enabled, Error::::Locked); + + // get id of the currency we want to mint + // this also serves as a validation of the currency_idx parameter + let mint_currency_id = pool_details + .bonded_currencies + .get::(currency_idx.saturated_into()) + .ok_or(Error::::IndexOutOfBounds)?; + + let mut total_issuances: Vec> = pool_details + .bonded_currencies + .iter() + .map(|id| T::Fungibles::total_issuance(id.clone())) + .collect(); + + // calculate parameters for bonding curve + // we've checked the vector length before + let active_issuance_pre = total_issuances.swap_remove(currency_idx.saturated_into()); + let active_issuance_post = active_issuance_pre.saturating_add(amount_to_mint); // TODO: use checked_add and fail on overflow? + let passive_issuance: FungiblesBalanceOf = total_issuances + .iter() + .fold(Zero::zero(), |sum, x| sum.saturating_add(*x)); + + // calculate cost + let cost: CollateralCurrencyBalanceOf = mock_curve_impl( + pool_details.curve, + active_issuance_pre, + active_issuance_post, + passive_issuance, + ) + .try_into() + .map_err(|_| ArithmeticError::Overflow)?; + + // fail if cost > max_cost + ensure!(!cost.gt(&max_cost), Error::::Slippage); + + // withdraw the collateral and put it in the deposit account + T::CollateralCurrency::transfer(&signer, &pool_id.into(), cost, Preservation::Preserve)?; + + // mint tokens into beneficiary account + T::Fungibles::mint_into(mint_currency_id.clone(), &beneficiary, amount_to_mint)?; + + Ok(().into()) + } } } From bd227e1303ed874bd3803075319525fc8da7cc79 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 11 Sep 2024 18:58:25 +0200 Subject: [PATCH 09/46] refactor: use account lookups --- pallets/pallet-bonded-coins/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index f33942505..5ea417bc6 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -34,10 +34,11 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use sp_runtime::{ - traits::{Saturating, Zero}, + traits::{Saturating, Zero, StaticLookup}, ArithmeticError, SaturatedConversion, }; + type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; pub type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; pub type DepositCurrencyHoldReasonOf = @@ -143,7 +144,7 @@ pub mod pallet { curve: Curve, currencies: BoundedVec, FungiblesAssetIdOf>, T::MaxCurrencies>, frozen: bool, - // currency_admin: Option TODO: use this to set currency admin + // currency_admin: Option> TODO: use this to set currency admin ) -> DispatchResultWithPostInfo { // ensure origin is PoolCreateOrigin let who = T::PoolCreateOrigin::ensure_origin(origin)?; @@ -201,9 +202,10 @@ pub mod pallet { currency_idx: u32, amount_to_mint: FungiblesBalanceOf, max_cost: CollateralCurrencyBalanceOf, - beneficiary: T::AccountId, + beneficiary: AccountIdLookupOf, ) -> DispatchResultWithPostInfo { let signer = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; @@ -255,6 +257,8 @@ pub mod pallet { // mint tokens into beneficiary account T::Fungibles::mint_into(mint_currency_id.clone(), &beneficiary, amount_to_mint)?; + // TODO: apply lock if pool_details.transferable != true + Ok(().into()) } } From 9ae15d2310bb6a7f53b8e57cf7e972820f5f674b Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 11 Sep 2024 19:06:15 +0200 Subject: [PATCH 10/46] chore: make Frozen the default PoolStatus --- pallets/pallet-bonded-coins/src/types.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index b1fc8b2c8..896f44179 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -10,13 +10,17 @@ pub struct Locks { pub allow_swap: bool, } -#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub enum PoolStatus { - #[default] Active, Frozen(LockType), Destroying, } +impl Default for PoolStatus { + fn default() -> Self { + Self::Frozen(LockType::default()) + } +} #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct PoolDetails> { From ea44040cfcada115b62f76f965280c3904bfa027 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 11 Sep 2024 20:07:37 +0200 Subject: [PATCH 11/46] refactor: implement cost calculation in designated function --- pallets/pallet-bonded-coins/src/lib.rs | 64 +++++++++++++++--------- pallets/pallet-bonded-coins/src/types.rs | 18 +++++++ 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 5ea417bc6..9c72bbd0d 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -18,7 +18,7 @@ mod types; #[frame_support::pallet(dev_mode)] pub mod pallet { - use crate::types::{Curve, PoolDetails, PoolStatus, TokenMeta}; + use crate::types::{Curve, MockCurve, PoolDetails, PoolStatus, TokenMeta}; use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::*, @@ -34,7 +34,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use sp_runtime::{ - traits::{Saturating, Zero, StaticLookup}, + traits::{CheckedAdd, CheckedConversion, Saturating, StaticLookup, Zero}, ArithmeticError, SaturatedConversion, }; @@ -81,10 +81,6 @@ pub mod pallet { + Into>; } - fn mock_curve_impl(_: Curve, active_pre: T, active_post: T, __: T) -> T { - active_post.saturating_sub(active_pre) - } - #[pallet::pallet] pub struct Pallet(_); @@ -217,36 +213,22 @@ pub mod pallet { }; ensure!(mint_enabled, Error::::Locked); + let currency_idx_usize: usize = currency_idx.checked_into().ok_or(Error::::IndexOutOfBounds)?; + // get id of the currency we want to mint // this also serves as a validation of the currency_idx parameter let mint_currency_id = pool_details .bonded_currencies - .get::(currency_idx.saturated_into()) + .get(currency_idx_usize) .ok_or(Error::::IndexOutOfBounds)?; - let mut total_issuances: Vec> = pool_details + let total_issuances: Vec> = pool_details .bonded_currencies .iter() .map(|id| T::Fungibles::total_issuance(id.clone())) .collect(); - // calculate parameters for bonding curve - // we've checked the vector length before - let active_issuance_pre = total_issuances.swap_remove(currency_idx.saturated_into()); - let active_issuance_post = active_issuance_pre.saturating_add(amount_to_mint); // TODO: use checked_add and fail on overflow? - let passive_issuance: FungiblesBalanceOf = total_issuances - .iter() - .fold(Zero::zero(), |sum, x| sum.saturating_add(*x)); - - // calculate cost - let cost: CollateralCurrencyBalanceOf = mock_curve_impl( - pool_details.curve, - active_issuance_pre, - active_issuance_post, - passive_issuance, - ) - .try_into() - .map_err(|_| ArithmeticError::Overflow)?; + let cost = Self::get_cost(pool_details.curve, &amount_to_mint, total_issuances, currency_idx_usize)?; // fail if cost > max_cost ensure!(!cost.gt(&max_cost), Error::::Slippage); @@ -262,4 +244,36 @@ pub mod pallet { Ok(().into()) } } + + impl Pallet + where + FungiblesBalanceOf: TryInto>, + { + pub fn get_cost( + curve: Curve, + amount_to_mint: &FungiblesBalanceOf, + mut total_issuances: Vec>, + mint_into_idx: usize, + ) -> Result, ArithmeticError> { + // calculate parameters for bonding curve + // we've checked the vector length before + let active_issuance_pre = total_issuances.swap_remove(mint_into_idx); + let active_issuance_post = active_issuance_pre + .checked_add(amount_to_mint) + .ok_or(ArithmeticError::Overflow)?; + let passive_issuance: FungiblesBalanceOf = total_issuances + .iter() + .fold(Zero::zero(), |sum, x| sum.saturating_add(*x)); + + // match curve implementation + let curve_impl = match curve { + Curve::LinearRatioCurve => MockCurve::new(), + }; + + let cost = curve_impl.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance); + + // Try conversion to Collateral Balance type + return cost.try_into().map_err(|_| ArithmeticError::Overflow); + } + } } diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 896f44179..d8d8bcdbb 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -2,6 +2,7 @@ use frame_support::BoundedVec; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::Get; +use sp_runtime::traits::Saturating; #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct Locks { @@ -64,3 +65,20 @@ pub enum Curve { #[default] LinearRatioCurve, } + +pub struct MockCurve {} + +impl MockCurve { + pub fn new() -> Self { + Self { } + } + + pub fn calculate_cost( + self, + active_issuance_pre: Balance, + active_issuance_post: Balance, + _: Balance, + ) -> Balance { + active_issuance_pre.saturating_sub(active_issuance_post) + } +} From b0fb4321736e7b9b8d1379112c2f0801a1c36b79 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 10:30:10 +0200 Subject: [PATCH 12/46] chore: fmt cargo.toml --- pallets/pallet-bonded-coins/Cargo.toml | 38 ++++++++++++-------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 130951158..1dabd3da4 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -1,29 +1,28 @@ [package] -name = "pallet-bonded-coins" -authors = ["Anonymous"] -description = "FRAME pallet template for defining custom runtime logic." -version = "0.8.0" -license = "Unlicense" -homepage = "https://substrate.io" +authors = ["Anonymous"] +description = "FRAME pallet template for defining custom runtime logic." +edition.workspace = true +homepage = "https://substrate.io" +license = "Unlicense" +name = "pallet-bonded-coins" repository.workspace = true -edition.workspace = true - +version = "0.8.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parity-scale-codec = { workspace = true, features = ["derive"]} -scale-info = { workspace = true, features = ["derive"]} +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } # Substrate -frame-benchmarking = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } +frame-benchmarking = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] -serde = { workspace = true} +serde = { workspace = true } # Substrate sp-io = { workspace = true } @@ -36,13 +35,10 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", ] std = [ - "parity-scale-codec/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "parity-scale-codec/std", "scale-info/std", ] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", -] +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] From 11b37c5467a4ec5d5e60b472c3f84ac959fde89e Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 10:53:16 +0200 Subject: [PATCH 13/46] chore: update package info --- Cargo.lock | 2 +- pallets/pallet-bonded-coins/Cargo.toml | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e65fa914f..49bb3763c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7129,7 +7129,7 @@ dependencies = [ [[package]] name = "pallet-bonded-coins" -version = "0.8.0" +version = "1.15.0-dev" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 1dabd3da4..05efaa63f 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -1,12 +1,14 @@ [package] -authors = ["Anonymous"] -description = "FRAME pallet template for defining custom runtime logic." -edition.workspace = true -homepage = "https://substrate.io" -license = "Unlicense" -name = "pallet-bonded-coins" -repository.workspace = true -version = "0.8.0" +authors = { workspace = true } +description = "A pallet for creating bonded coins that can be minted by placing a collateral." +documentation = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license-file = { workspace = true } +name = "pallet-bonded-coins" +readme = "README.md" +repository = { workspace = true } +version = { workspace = true } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] From 8b326349713814278db62333a8a56af4ca5fd7a6 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 13:52:56 +0200 Subject: [PATCH 14/46] chore: review suggestions --- pallets/pallet-bonded-coins/src/lib.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 9c72bbd0d..87d94a865 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -18,7 +18,6 @@ mod types; #[frame_support::pallet(dev_mode)] pub mod pallet { - use crate::types::{Curve, MockCurve, PoolDetails, PoolStatus, TokenMeta}; use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::*, @@ -34,23 +33,26 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use sp_runtime::{ - traits::{CheckedAdd, CheckedConversion, Saturating, StaticLookup, Zero}, + traits::{CheckedAdd, Saturating, StaticLookup, Zero}, ArithmeticError, SaturatedConversion, }; + use crate::types::{Curve, MockCurve, PoolDetails, PoolStatus, TokenMeta}; + type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; - pub type DepositCurrencyBalanceOf = + type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; - pub type DepositCurrencyHoldReasonOf = + type DepositCurrencyHoldReasonOf = <::DepositCurrency as frame_support::traits::fungible::InspectHold< ::AccountId, >>::Reason; - pub type CollateralCurrencyBalanceOf = + type CollateralCurrencyBalanceOf = <::CollateralCurrency as InspectFungible<::AccountId>>::Balance; - pub type FungiblesBalanceOf = + type FungiblesBalanceOf = <::Fungibles as InspectFungibles<::AccountId>>::Balance; - pub type FungiblesAssetIdOf = + type FungiblesAssetIdOf = <::Fungibles as InspectFungibles<::AccountId>>::AssetId; + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -213,7 +215,7 @@ pub mod pallet { }; ensure!(mint_enabled, Error::::Locked); - let currency_idx_usize: usize = currency_idx.checked_into().ok_or(Error::::IndexOutOfBounds)?; + let currency_idx_usize: usize = currency_idx.saturated_into(); // get id of the currency we want to mint // this also serves as a validation of the currency_idx parameter From 434afd9ad3b41609ef8f877aa0fff0afd3264350 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 15:11:24 +0200 Subject: [PATCH 15/46] feat: add parameter to curve enum --- pallets/pallet-bonded-coins/src/lib.rs | 10 ++++++---- pallets/pallet-bonded-coins/src/types.rs | 19 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 87d94a865..45f5c9829 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -83,6 +83,8 @@ pub mod pallet { + Into>; } + type CurveParameterType = u32; + #[pallet::pallet] pub struct Pallet(_); @@ -93,7 +95,7 @@ pub mod pallet { _, Twox64Concat, T::PoolId, - PoolDetails, Curve, T::MaxCurrencies>, + PoolDetails, Curve, T::MaxCurrencies>, OptionQuery, >; @@ -139,7 +141,7 @@ pub mod pallet { // #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] TODO: properly configure weights pub fn create_pool( origin: OriginFor, - curve: Curve, + curve: Curve, currencies: BoundedVec, FungiblesAssetIdOf>, T::MaxCurrencies>, frozen: bool, // currency_admin: Option> TODO: use this to set currency admin @@ -252,7 +254,7 @@ pub mod pallet { FungiblesBalanceOf: TryInto>, { pub fn get_cost( - curve: Curve, + curve: Curve, amount_to_mint: &FungiblesBalanceOf, mut total_issuances: Vec>, mint_into_idx: usize, @@ -269,7 +271,7 @@ pub mod pallet { // match curve implementation let curve_impl = match curve { - Curve::LinearRatioCurve => MockCurve::new(), + Curve::LinearRatioCurve(_) => MockCurve::new(), }; let cost = curve_impl.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance); diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index d8d8bcdbb..586f6873f 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -60,25 +60,28 @@ pub struct TokenMeta { pub min_balance: Balance, } -#[derive(Default, Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub enum Curve { - #[default] - LinearRatioCurve, +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum Curve { + /// Price scales linearly with the ratio of the total issuance of the active currency to the sum of all total issuances. + /// `f(i_active) = s * i_active / (i_active + i_passive)`, where s is a scaling factor. + /// Parameters: + /// - Scaling Factor + LinearRatioCurve(ParamType), } pub struct MockCurve {} impl MockCurve { pub fn new() -> Self { - Self { } - } - + Self {} + } + pub fn calculate_cost( self, active_issuance_pre: Balance, active_issuance_post: Balance, _: Balance, ) -> Balance { - active_issuance_pre.saturating_sub(active_issuance_post) + active_issuance_pre.saturating_sub(active_issuance_post) } } From 771bb33ede3c47260cf81f4b0512b051aba9931e Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 17:56:13 +0200 Subject: [PATCH 16/46] fix: currencies number check --- pallets/pallet-bonded-coins/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 45f5c9829..96dad2b31 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -118,7 +118,7 @@ pub mod pallet { #[pallet::error] pub enum Error { /// The number of bonded currencies on a new pool is either lower than 1 or greater than MaxCurrencies. - CurrenciesOutOfBounds, + CurrenciesNumber, /// A token swap cannot be executed due to a lock placed on this operation. Locked, /// The pool id is not currently registered. @@ -150,8 +150,8 @@ pub mod pallet { let who = T::PoolCreateOrigin::ensure_origin(origin)?; ensure!( - (2..=T::MaxCurrencies::get().saturated_into()).contains(¤cies.len()), - Error::::CurrenciesOutOfBounds + (1..=T::MaxCurrencies::get().saturated_into()).contains(¤cies.len()), + Error::::CurrenciesNumber ); let currency_ids = BoundedVec::truncate_from(currencies.iter().map(|c| c.id.clone()).collect()); From cccae241496cd8be67a7f6687013e8c1ad457b5d Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 18:06:43 +0200 Subject: [PATCH 17/46] refactor: curve parameters are a struct --- pallets/pallet-bonded-coins/src/types.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 586f6873f..f60335242 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -60,13 +60,17 @@ pub struct TokenMeta { pub min_balance: Balance, } +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct LinearRatioCurveParams { + pub scaling_factor: ParamType, +} #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub enum Curve { /// Price scales linearly with the ratio of the total issuance of the active currency to the sum of all total issuances. /// `f(i_active) = s * i_active / (i_active + i_passive)`, where s is a scaling factor. /// Parameters: /// - Scaling Factor - LinearRatioCurve(ParamType), + LinearRatioCurve(LinearRatioCurveParams), } pub struct MockCurve {} From 3026f9778cdafce801977e617ccd9b61dc4adb83 Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 12 Sep 2024 18:10:41 +0200 Subject: [PATCH 18/46] chore: handle parameters in MockCurve --- pallets/pallet-bonded-coins/src/lib.rs | 2 +- pallets/pallet-bonded-coins/src/types.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 96dad2b31..7210f678f 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -271,7 +271,7 @@ pub mod pallet { // match curve implementation let curve_impl = match curve { - Curve::LinearRatioCurve(_) => MockCurve::new(), + Curve::LinearRatioCurve(params) => MockCurve::new(params), }; let cost = curve_impl.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance); diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index f60335242..67bfde88a 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -76,7 +76,7 @@ pub enum Curve { pub struct MockCurve {} impl MockCurve { - pub fn new() -> Self { + pub fn new(_: T) -> Self { Self {} } From 53f0614f6e7b812dc6bc571d3bf7455deb6b4e2a Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Fri, 13 Sep 2024 10:24:29 +0200 Subject: [PATCH 19/46] first draft bonding curves --- Cargo.lock | 2 + Cargo.toml | 3 +- pallets/pallet-bonded-coins/Cargo.toml | 40 ++++---- pallets/pallet-bonded-coins/src/curves.rs | 111 ++++++++++++++++++++++ pallets/pallet-bonded-coins/src/mock.rs | 59 ------------ pallets/pallet-bonded-coins/src/tests.rs | 24 +---- 6 files changed, 135 insertions(+), 104 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/curves.rs diff --git a/Cargo.lock b/Cargo.lock index e65fa914f..beb318555 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7137,9 +7137,11 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", + "sp-std", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3d3c5f502..4b74c6021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ ctype = { path = "pallets/ctype", default-features = false } delegation = { path = "pallets/delegation", default-features = false } did = { path = "pallets/did", default-features = false } pallet-asset-switch = { path = "pallets/pallet-asset-switch", default-features = false } +pallet-bonded-coins = { path = "pallets/pallet-bonded-coins", default-features = false } pallet-configuration = { path = "pallets/pallet-configuration", default-features = false } pallet-deposit-storage = { path = "pallets/pallet-deposit-storage", default-features = false } pallet-did-lookup = { path = "pallets/pallet-did-lookup", default-features = false } @@ -69,7 +70,6 @@ pallet-relay-store = { path = "pallets/pallet-relay-store", default-features pallet-web3-names = { path = "pallets/pallet-web3-names", default-features = false } parachain-staking = { path = "pallets/parachain-staking", default-features = false } public-credentials = { path = "pallets/public-credentials", default-features = false } -pallet-bonded-coins = { path = "pallets/pallet-bonded-coins", default-features = false } # Internal support (with default disabled) kilt-asset-dids = { path = "crates/assets", default-features = false } @@ -162,6 +162,7 @@ pallet-treasury = { git = "https://github.com/parityt pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } pallet-vesting = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } sp-api = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } +sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } sp-authority-discovery = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-crates-io-v1.7.0" } diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 130951158..23a9844bd 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -1,29 +1,30 @@ [package] -name = "pallet-bonded-coins" -authors = ["Anonymous"] -description = "FRAME pallet template for defining custom runtime logic." -version = "0.8.0" -license = "Unlicense" -homepage = "https://substrate.io" +authors = ["Anonymous"] +description = "FRAME pallet template for defining custom runtime logic." +edition.workspace = true +homepage = "https://substrate.io" +license = "Unlicense" +name = "pallet-bonded-coins" repository.workspace = true -edition.workspace = true - +version = "0.8.0" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parity-scale-codec = { workspace = true, features = ["derive"]} -scale-info = { workspace = true, features = ["derive"]} +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } # Substrate -frame-benchmarking = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } +frame-benchmarking = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } [dev-dependencies] -serde = { workspace = true} +serde = { workspace = true } # Substrate sp-io = { workspace = true } @@ -36,13 +37,10 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", ] std = [ - "parity-scale-codec/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "parity-scale-codec/std", "scale-info/std", ] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", -] +try-runtime = ["frame-support/try-runtime", "frame-system/try-runtime"] diff --git a/pallets/pallet-bonded-coins/src/curves.rs b/pallets/pallet-bonded-coins/src/curves.rs new file mode 100644 index 000000000..fee90b92d --- /dev/null +++ b/pallets/pallet-bonded-coins/src/curves.rs @@ -0,0 +1,111 @@ +use sp_arithmetic::{fixed_point::FixedPointNumber, ArithmeticError}; + +pub trait BondingFunction +{ + fn get_value(&self, x: F) -> Result; + + fn get_power_2(x: F) -> Result { + Ok(x.saturating_mul(x)) + } + fn get_power_3(x: F) -> Result { + Ok(Self::get_power_2(x)?.saturating_mul(x)) + } + + // Change naming + fn calculate_integral(&self, low: F, high: F) -> Result { + let high_val = self.get_value(high)?; + let low_val = self.get_value(low)?; + Ok(high_val.saturating_sub(low_val)) + } +} + +#[derive(Debug, Clone)] +pub struct LinearBondingFunctionParameters { + m: F, + n: F, +} + +impl BondingFunction for LinearBondingFunctionParameters +where + F: FixedPointNumber, +{ + // f(x) = m * x^2 + n * x + fn get_value(&self, x: F) -> Result { + let x2 = Self::get_power_2(x)?; + // can also be a Underflow. TODO: CHECK how I can figure out the error + let mx2 = self.m.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; + + let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; + + let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; + + // we do not need the fractions here. So we truncate the result + Ok(result.trunc()) + } +} + + + + + +#[cfg(test)] +mod tests { + use super::*; + + use frame_support::assert_err; + use sp_arithmetic::FixedU128; + + #[test] + fn test_linear_bonding_function_basic_test() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + + let curve = LinearBondingFunctionParameters { m, n }; + + // 1*1^2 + 2*1 = 3 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(3)); + } + + #[test] + fn test_linear_bonding_function_fraction() { + + let m = FixedU128::from_rational(1, 2); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = LinearBondingFunctionParameters { m, n }; + + // 0.5*1^2 + 2*1 = 2 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(2)); + } + + #[test] + fn test_linear_bonding_overflow_n() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_inner(u128::MAX); + let x = FixedU128::from_u32(2); + + let curve = LinearBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); + } + + #[test] + fn test_linear_bonding_overflow_m() { + let m = FixedU128::from_inner(u128::MAX); + let n = FixedU128::from_inner(1); + let x = FixedU128::from_u32(2); + + let curve = LinearBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); + } +} + + diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 411a16b11..e69de29bb 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1,59 +0,0 @@ -use frame_support::{derive_impl, parameter_types, traits::Everything}; -use frame_system as system; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; - -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TemplateModule: crate::{Pallet, Call, Storage, Event}, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] -impl system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -impl crate::Config for Test { - type RuntimeEvent = RuntimeEvent; -} - -// Build genesis storage according to the mock runtime. -pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::::default().build_storage().unwrap().into() -} diff --git a/pallets/pallet-bonded-coins/src/tests.rs b/pallets/pallet-bonded-coins/src/tests.rs index 527aec8ed..0519ecba6 100644 --- a/pallets/pallet-bonded-coins/src/tests.rs +++ b/pallets/pallet-bonded-coins/src/tests.rs @@ -1,23 +1 @@ -use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok}; - -#[test] -fn it_works_for_default_value() { - new_test_ext().execute_with(|| { - // Dispatch a signed extrinsic. - assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); - // Read pallet storage and assert an expected result. - assert_eq!(TemplateModule::something(), Some(42)); - }); -} - -#[test] -fn correct_error_for_none_value() { - new_test_ext().execute_with(|| { - // Ensure the expected error is thrown when no value is present. - assert_noop!( - TemplateModule::cause_error(RuntimeOrigin::signed(1)), - Error::::NoneValue - ); - }); -} + \ No newline at end of file From c2c6914de1adefbc288f49e2ed6fc3546a4d4945 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Mon, 16 Sep 2024 15:18:51 +0200 Subject: [PATCH 20/46] unit tests with linear bonding curve --- .../pallet-bonded-coins/src/benchmarking.rs | 19 -- pallets/pallet-bonded-coins/src/curves.rs | 111 ---------- .../src/curves_parameters.rs | 76 +++++++ pallets/pallet-bonded-coins/src/lib.rs | 95 +++++++-- pallets/pallet-bonded-coins/src/mock.rs | 1 + pallets/pallet-bonded-coins/src/tests.rs | 1 - .../src/tests/curves_parameters.rs | 102 +++++++++ pallets/pallet-bonded-coins/src/tests/mod.rs | 2 + .../pallet-bonded-coins/src/tests/types.rs | 195 ++++++++++++++++++ pallets/pallet-bonded-coins/src/types.rs | 40 ++-- 10 files changed, 470 insertions(+), 172 deletions(-) delete mode 100644 pallets/pallet-bonded-coins/src/curves.rs create mode 100644 pallets/pallet-bonded-coins/src/curves_parameters.rs delete mode 100644 pallets/pallet-bonded-coins/src/tests.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/curves_parameters.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/mod.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/types.rs diff --git a/pallets/pallet-bonded-coins/src/benchmarking.rs b/pallets/pallet-bonded-coins/src/benchmarking.rs index 8bba2a098..8b1378917 100644 --- a/pallets/pallet-bonded-coins/src/benchmarking.rs +++ b/pallets/pallet-bonded-coins/src/benchmarking.rs @@ -1,20 +1 @@ -//! Benchmarking setup for pallet-parachain-template -use super::*; - -#[allow(unused)] -use crate::Pallet as Template; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_system::RawOrigin; - -benchmarks! { - do_something { - let s in 0 .. 100; - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), s) - verify { - assert_eq!(Something::::get(), Some(s)); - } -} - -impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/pallet-bonded-coins/src/curves.rs b/pallets/pallet-bonded-coins/src/curves.rs deleted file mode 100644 index fee90b92d..000000000 --- a/pallets/pallet-bonded-coins/src/curves.rs +++ /dev/null @@ -1,111 +0,0 @@ -use sp_arithmetic::{fixed_point::FixedPointNumber, ArithmeticError}; - -pub trait BondingFunction -{ - fn get_value(&self, x: F) -> Result; - - fn get_power_2(x: F) -> Result { - Ok(x.saturating_mul(x)) - } - fn get_power_3(x: F) -> Result { - Ok(Self::get_power_2(x)?.saturating_mul(x)) - } - - // Change naming - fn calculate_integral(&self, low: F, high: F) -> Result { - let high_val = self.get_value(high)?; - let low_val = self.get_value(low)?; - Ok(high_val.saturating_sub(low_val)) - } -} - -#[derive(Debug, Clone)] -pub struct LinearBondingFunctionParameters { - m: F, - n: F, -} - -impl BondingFunction for LinearBondingFunctionParameters -where - F: FixedPointNumber, -{ - // f(x) = m * x^2 + n * x - fn get_value(&self, x: F) -> Result { - let x2 = Self::get_power_2(x)?; - // can also be a Underflow. TODO: CHECK how I can figure out the error - let mx2 = self.m.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; - - let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; - - let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; - - // we do not need the fractions here. So we truncate the result - Ok(result.trunc()) - } -} - - - - - -#[cfg(test)] -mod tests { - use super::*; - - use frame_support::assert_err; - use sp_arithmetic::FixedU128; - - #[test] - fn test_linear_bonding_function_basic_test() { - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(2); - let x = FixedU128::from_u32(1); - - - let curve = LinearBondingFunctionParameters { m, n }; - - // 1*1^2 + 2*1 = 3 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_u32(3)); - } - - #[test] - fn test_linear_bonding_function_fraction() { - - let m = FixedU128::from_rational(1, 2); - let n = FixedU128::from_u32(2); - let x = FixedU128::from_u32(1); - - let curve = LinearBondingFunctionParameters { m, n }; - - // 0.5*1^2 + 2*1 = 2 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_u32(2)); - } - - #[test] - fn test_linear_bonding_overflow_n() { - let m = FixedU128::from_u32(1); - let n = FixedU128::from_inner(u128::MAX); - let x = FixedU128::from_u32(2); - - let curve = LinearBondingFunctionParameters { m, n }; - - let result = curve.get_value(x); - assert_err!(result, ArithmeticError::Overflow); - } - - #[test] - fn test_linear_bonding_overflow_m() { - let m = FixedU128::from_inner(u128::MAX); - let n = FixedU128::from_inner(1); - let x = FixedU128::from_u32(2); - - let curve = LinearBondingFunctionParameters { m, n }; - - let result = curve.get_value(x); - assert_err!(result, ArithmeticError::Overflow); - } -} - - diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs new file mode 100644 index 000000000..ae4dc8d07 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -0,0 +1,76 @@ +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_arithmetic::{fixed_point::FixedPointNumber, ArithmeticError}; +use sp_runtime::FixedU128; + +/// A trait to define the bonding curve functions +pub trait BondingFunction { + /// returns the value of the curve at x. + /// The bonding curve is already the primitive integral of f(x). + /// Therefore the costs can be calculated by the difference of the values of the curve at two points. + fn get_value(&self, x: F) -> Result; + + /// static function to calculate the power of 2 of x + fn get_power_2(x: F) -> Result { + Ok(x.saturating_mul(x)) + } + + /// static function to calculate the power of 3 of x + fn get_power_3(x: F) -> Result { + Ok(Self::get_power_2(x)?.saturating_mul(x)) + } + + /// calculates the cost of the curve between low and high + fn calculate_costs(&self, low: F, high: F) -> Result { + let high_val = self.get_value(high)?; + let low_val = self.get_value(low)?; + Ok(high_val.saturating_sub(low_val)) + } +} + +/// A linear bonding function with the shape of f(x) = mx + n, +/// which results in the primitive integral F(x) = m' * x^2 + n * x. +/// It is expected that the user provides the correct parameters for the curve. +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct LinearBondingFunctionParameters { + pub m: F, + pub n: F, +} + +impl BondingFunction for LinearBondingFunctionParameters +where + F: FixedPointNumber, +{ + /// we + fn get_value(&self, x: F) -> Result { + let x2 = Self::get_power_2(x)?; + + let mx2 = self.m.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; + let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; + + let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; + + // we do not need the fractions here. So we truncate the result + Ok(result) + } +} + +pub fn transform_denomination_currency_amount( + amount: u128, + current_denomination: u8, + target_denomination: u8, +) -> Result { + let diff = target_denomination as i8 - current_denomination as i8; + let value = { + if diff > 0 { + let factor = 10u128.pow(diff as u32); + amount.checked_mul(factor).ok_or(ArithmeticError::Overflow) + } else { + let factor = 10u128.pow(diff.abs() as u32); + // Dividing by zero can never happen 10^0 = 1. Lets just be save. + amount.checked_div(factor).ok_or(ArithmeticError::DivisionByZero) + } + }?; + + Ok(FixedU128::from_inner(value)) +} diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 7210f678f..152f0b2ed 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -16,6 +16,7 @@ mod benchmarking; mod types; +mod curves_parameters; #[frame_support::pallet(dev_mode)] pub mod pallet { use frame_support::{ @@ -24,8 +25,9 @@ pub mod pallet { traits::{ fungible::{Inspect as InspectFungible, Mutate, MutateHold}, fungibles::{ - metadata::Mutate as FungiblesMetadata, Create as CreateFungibles, Destroy as DestroyFungibles, - Inspect as InspectFungibles, Mutate as MutateFungibles, + metadata::Inspect as FungiblesInspect, metadata::Mutate as FungiblesMetadata, + Create as CreateFungibles, Destroy as DestroyFungibles, Inspect as InspectFungibles, + Mutate as MutateFungibles, }, tokens::Preservation, }, @@ -34,10 +36,13 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_runtime::{ traits::{CheckedAdd, Saturating, StaticLookup, Zero}, - ArithmeticError, SaturatedConversion, + ArithmeticError, FixedU128, SaturatedConversion, }; - use crate::types::{Curve, MockCurve, PoolDetails, PoolStatus, TokenMeta}; + use crate::{ + curves_parameters::transform_denomination_currency_amount, + types::{Curve, PoolDetails, PoolStatus, TokenMeta}, + }; type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; type DepositCurrencyBalanceOf = @@ -66,6 +71,7 @@ pub mod pallet { type Fungibles: CreateFungibles + DestroyFungibles + FungiblesMetadata + + FungiblesInspect + MutateFungibles; /// The maximum number of currencies allowed for a single pool. #[pallet::constant] @@ -83,7 +89,7 @@ pub mod pallet { + Into>; } - type CurveParameterType = u32; + type CurveParameterType = FixedU128; #[pallet::pallet] pub struct Pallet(_); @@ -226,13 +232,23 @@ pub mod pallet { .get(currency_idx_usize) .ok_or(Error::::IndexOutOfBounds)?; - let total_issuances: Vec> = pool_details + let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details .bonded_currencies .iter() - .map(|id| T::Fungibles::total_issuance(id.clone())) + .map(|id| { + ( + T::Fungibles::total_issuance(id.clone()), + T::Fungibles::decimals(id.clone()), + ) + }) .collect(); - let cost = Self::get_cost(pool_details.curve, &amount_to_mint, total_issuances, currency_idx_usize)?; + let cost = Self::get_cost( + pool_details.curve, + &amount_to_mint, + currencies_metadata, + currency_idx_usize, + )?; // fail if cost > max_cost ensure!(!cost.gt(&max_cost), Error::::Slippage); @@ -252,32 +268,67 @@ pub mod pallet { impl Pallet where FungiblesBalanceOf: TryInto>, + CollateralCurrencyBalanceOf: TryInto, { + /// Calculate the cost of minting a given amount of a currency into a pool. + /// The pool currency might have all different denominations. Therefore, + /// the cost is calculated by normalizing all currencies to the same denomination. pub fn get_cost( curve: Curve, amount_to_mint: &FungiblesBalanceOf, - mut total_issuances: Vec>, + total_issuances: Vec<(FungiblesBalanceOf, u8)>, mint_into_idx: usize, ) -> Result, ArithmeticError> { - // calculate parameters for bonding curve - // we've checked the vector length before - let active_issuance_pre = total_issuances.swap_remove(mint_into_idx); + // todo: change that. We have also to restrict the denomination of the pool currencies maybe? + let target_denomination_normalization = 18; + let target_denomination_costs = 10; + + // normalize all issuances to the same denomination representation + let mut normalized_issuances = total_issuances + .clone() + .into_iter() + .map(|(x, d)| { + transform_denomination_currency_amount( + x.saturated_into::(), + d, + target_denomination_normalization, + ) + }) + .collect::, ArithmeticError>>()?; + + // normalize the amount to mint + let normalized_amount_to_mint = transform_denomination_currency_amount( + amount_to_mint.clone().saturated_into(), + total_issuances[mint_into_idx].1, + target_denomination_normalization, + )?; + + // remove the target currency from the normalized total issuances + let active_issuance_pre = normalized_issuances.swap_remove(mint_into_idx); + + // add the normalized amount to mint to the active target issuance let active_issuance_post = active_issuance_pre - .checked_add(amount_to_mint) + .checked_add(&normalized_amount_to_mint) .ok_or(ArithmeticError::Overflow)?; - let passive_issuance: FungiblesBalanceOf = total_issuances + + // calculate the passive issuance + let passive_issuance = normalized_issuances .iter() - .fold(Zero::zero(), |sum, x| sum.saturating_add(*x)); + .fold(FixedU128::zero(), |sum, x| sum.saturating_add(*x)); - // match curve implementation - let curve_impl = match curve { - Curve::LinearRatioCurve(params) => MockCurve::new(params), - }; + let normalize_cost = curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance)?; - let cost = curve_impl.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance); + // transform the cost back to the target denomination of the collateral currency + let real_costs = transform_denomination_currency_amount( + normalize_cost.into_inner(), + target_denomination_normalization, + target_denomination_costs, + )?; - // Try conversion to Collateral Balance type - return cost.try_into().map_err(|_| ArithmeticError::Overflow); + real_costs + .into_inner() + .try_into() + .map_err(|_| ArithmeticError::Overflow) } } } diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index e69de29bb..8b1378917 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -0,0 +1 @@ + diff --git a/pallets/pallet-bonded-coins/src/tests.rs b/pallets/pallet-bonded-coins/src/tests.rs deleted file mode 100644 index 0519ecba6..000000000 --- a/pallets/pallet-bonded-coins/src/tests.rs +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pallets/pallet-bonded-coins/src/tests/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/curves_parameters.rs new file mode 100644 index 000000000..e91bddb68 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/curves_parameters.rs @@ -0,0 +1,102 @@ +use frame_support::assert_err; +use sp_arithmetic::{ArithmeticError, FixedU128}; +use sp_runtime::traits::Zero; + +use crate::curves_parameters::{ + transform_denomination_currency_amount, BondingFunction, LinearBondingFunctionParameters, +}; + +#[test] +fn test_linear_bonding_function_basic_test() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = LinearBondingFunctionParameters { m, n }; + + // 1*1^2 + 2*1 = 3 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(3)); +} + +#[test] +fn test_linear_bonding_function_fraction() { + let m = FixedU128::from_rational(1, 2); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = LinearBondingFunctionParameters { m, n }; + + // 0.5*1^2 + 2*1 = 2 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(2)); +} + +#[test] +fn test_linear_bonding_overflow_n() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_inner(u128::MAX); + let x = FixedU128::from_u32(2); + + let curve = LinearBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_linear_bonding_overflow_m() { + let m = FixedU128::from_inner(u128::MAX); + let n = FixedU128::from_inner(1); + let x = FixedU128::from_u32(2); + + let curve = LinearBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_increase_denomination_currency_amount() { + let amount = 100; + let current_denomination = 2; + let target_denomination = 3; + + let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination).unwrap(); + assert_eq!(result, FixedU128::from_inner(1000)); +} + +#[test] +fn test_decrease_denomination_currency_amount() { + let amount = 1000; + let current_denomination = 3; + let target_denomination = 2; + + let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination).unwrap(); + assert_eq!(result, FixedU128::from_inner(100)); +} + +#[test] +fn test_increase_denomination_overflow() { + let amount = u128::MAX; + let current_denomination = 10; + + // just increase denomination by one. This should overflow + let target_denomination = 11; + + let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_decrease_denomination_underflow() { + let amount = 1; + let current_denomination = 5; + + // just increase + let target_denomination = 4; + + // we should have dropped all relevant bits. This should gives use an Ok with zero + let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination).unwrap(); + assert_eq!(result, FixedU128::zero()) +} diff --git a/pallets/pallet-bonded-coins/src/tests/mod.rs b/pallets/pallet-bonded-coins/src/tests/mod.rs new file mode 100644 index 000000000..0451a780e --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod curves_parameters; +mod types; diff --git a/pallets/pallet-bonded-coins/src/tests/types.rs b/pallets/pallet-bonded-coins/src/tests/types.rs new file mode 100644 index 000000000..245a3e03a --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types.rs @@ -0,0 +1,195 @@ +use sp_arithmetic::FixedU128; +use sp_runtime::traits::Zero; + +use crate::{ + curves_parameters::{transform_denomination_currency_amount, LinearBondingFunctionParameters}, + types::Curve, +}; + +// target denomination for collateral currency +const CURRENT_DENOMINATION: u8 = 15; +const NORMALIZED_DENOMINATION: u8 = 18; +const ONE_COIN: u128 = 10u128.pow(CURRENT_DENOMINATION as u32); + +#[test] +fn test_mint_first_coin() { + // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(3); + let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + + // Create supply, where denomination is 15. Active issuance is zero. + let active_issuance_pre: u128 = 0; + let active_issuance_post: u128 = ONE_COIN; + + // single coin in pool. Passive issuance is zero. + let passive_issuance = FixedU128::zero(); + + let normalized_active_issuance_pre = + transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_active_issuance_post = + transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + + // The cost to mint the first coin should be 4. + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + passive_issuance, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_u32(4)); +} + +#[test] +fn test_mint_coin_with_existing_supply() { + // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(3); + let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + + // Create supply. Active issuance is 100. We want to mint 10 additional coins. + let active_issuance_pre: u128 = ONE_COIN * 100; + let active_issuance_post: u128 = ONE_COIN * 110; + + // single coin in pool. Passive issuance is zero. + let passive_issuance = FixedU128::zero(); + + let normalized_active_issuance_pre = + transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_active_issuance_post = + transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + + // Existing supply: 100^2 + 3*100 = 10300 + // New supply: 110^2 + 3*110 = 12130 + // Cost to mint 10 coins: 12130 - 10300 = 2130 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + passive_issuance, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_u32(2130)); +} + +#[test] +fn test_mint_coin_with_existing_passive_supply() { + // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(3); + let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + + // Create supply. Active issuance is Zero. We only mint a single coin. + let active_issuance_pre: u128 = 0; + let active_issuance_post: u128 = ONE_COIN; + + // Multiple coins in pool. Passive issuance is 10. + let passive_issuance = ONE_COIN * 10; + + let normalized_active_issuance_pre = + transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_active_issuance_post = + transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_passive_issuance = + transform_denomination_currency_amount(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + + // The passive issuance should influence the price of the new selected currency. + // Existing supply: (10)^2 + (10 )*3 = 130 + // New supply: (10 + 1)^2 + (10 + 1)*3 = 154 + // Cost to mint 1 coin: 154 - 130 = 24 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + normalized_passive_issuance, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_u32(24)); +} + +#[test] +fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { + // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(3); + let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + + // Create supply. Active issuance is 10. We mint 10 additional coins. + let active_issuance_pre: u128 = ONE_COIN * 10; + let active_issuance_post: u128 = ONE_COIN * 20; + + // Multiple coins in pool. Passive issuance is 10. + let passive_issuance = ONE_COIN * 10; + + let normalized_active_issuance_pre = + transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_active_issuance_post = + transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_passive_issuance = + transform_denomination_currency_amount(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + + // The passive issuance should influence the price of the new selected currency. + // Existing supply: (20)^2 + (20)*3 = 460 + // New supply: (30)^2 + (30)*3 = 990 + // Cost to mint 10 coin: 990 - 460 = 530 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + normalized_passive_issuance, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_u32(530)); +} + +#[test] +fn test_mint_first_coin_frac_bonding_curve() { + // Create curve with shape f(x) = x + 3, resulting into integral function F(x) = 1/2*x^2 + 3x + let m = FixedU128::from_rational(1, 2); + let n = FixedU128::from_u32(3); + let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + + // Create supply, where denomination is 15. Active issuance is zero. + let active_issuance_pre: u128 = 0; + let active_issuance_post: u128 = ONE_COIN; + + // single coin in pool. Passive issuance is zero. + let passive_issuance = FixedU128::zero(); + + let normalized_active_issuance_pre = + transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + let normalized_active_issuance_post = + transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + .unwrap(); + + // Existing supply: 1/2*(0)^2 + (0)*3 = 0 + // New supply: 1/2*(1)^2 + (1)*3 = 3.5 + // Cost to mint 10 coin: 3.5 - 0 = 0 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + passive_issuance, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_rational(7, 2)); +} + +// TODO add more tests for passive and existing active supply. diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 67bfde88a..f3053efd5 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -2,7 +2,9 @@ use frame_support::BoundedVec; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::Get; -use sp_runtime::traits::Saturating; +use sp_runtime::{ArithmeticError, FixedPointNumber}; + +use crate::curves_parameters::{self, BondingFunction}; #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct Locks { @@ -61,31 +63,31 @@ pub struct TokenMeta { } #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct LinearRatioCurveParams { - pub scaling_factor: ParamType, -} -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub enum Curve { +pub enum Curve { /// Price scales linearly with the ratio of the total issuance of the active currency to the sum of all total issuances. /// `f(i_active) = s * i_active / (i_active + i_passive)`, where s is a scaling factor. /// Parameters: /// - Scaling Factor - LinearRatioCurve(LinearRatioCurveParams), + LinearRatioCurve(curves_parameters::LinearBondingFunctionParameters), } -pub struct MockCurve {} +impl Curve +where + F: FixedPointNumber, +{ + pub fn calculate_cost( + &self, + active_issuance_pre: F, + active_issuance_post: F, + passive_issuance: F, + ) -> Result { + let calculation_param = match self { + Curve::LinearRatioCurve(params) => params, + }; -impl MockCurve { - pub fn new(_: T) -> Self { - Self {} - } + let active_issuance_pre_with_passive = active_issuance_pre.saturating_add(passive_issuance); + let active_issuance_post_with_passive = active_issuance_post.saturating_add(passive_issuance); - pub fn calculate_cost( - self, - active_issuance_pre: Balance, - active_issuance_post: Balance, - _: Balance, - ) -> Balance { - active_issuance_pre.saturating_sub(active_issuance_post) + calculation_param.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) } } From e2a749710b63f17b294e83a1aef2c5a2ee5c949f Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Mon, 16 Sep 2024 17:34:00 +0200 Subject: [PATCH 21/46] feat: more bonding curves --- Cargo.lock | 1 + pallets/pallet-bonded-coins/Cargo.toml | 1 + .../src/curves_parameters.rs | 131 +++++++++++++++++- pallets/pallet-bonded-coins/src/types.rs | 31 +++-- 4 files changed, 148 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5eb0db16c..f9069831d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7141,6 +7141,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "sp-std", ] [[package]] diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 24facfab1..272dab1d0 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -23,6 +23,7 @@ frame-system = { workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } [dev-dependencies] serde = { workspace = true } diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index ae4dc8d07..ddcb001e2 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -1,7 +1,23 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_arithmetic::{fixed_point::FixedPointNumber, ArithmeticError}; -use sp_runtime::FixedU128; +use sp_arithmetic::ArithmeticError; +use sp_runtime::{FixedPointNumber, FixedU128}; +use sp_std::marker::PhantomData; +/// Little helper trait to calculate the square root of Fixed, in order to maintain the generic. +pub trait SquareRoot: Sized { + fn try_sqrt(self) -> Option; + fn sqrt(self) -> Self; +} + +impl SquareRoot for FixedU128 { + fn try_sqrt(self) -> Option { + self.clone().try_sqrt() + } + + fn sqrt(self) -> Self { + self.clone().sqrt() + } +} /// A trait to define the bonding curve functions pub trait BondingFunction { @@ -41,7 +57,7 @@ impl BondingFunction for LinearBondingFunctionParameters where F: FixedPointNumber, { - /// we + /// F(x) = m * x^2 + n * x fn get_value(&self, x: F) -> Result { let x2 = Self::get_power_2(x)?; @@ -49,12 +65,117 @@ where let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; + Ok(result) + } +} + +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct QuadraticBondingFunctionParameters { + pub m: F, + pub n: F, + pub o: F, +} + +impl BondingFunction for QuadraticBondingFunctionParameters +where + F: FixedPointNumber, +{ + /// F(x) = m * x^3 + n * x^2 + o * x + fn get_value(&self, x: F) -> Result { + let x2 = Self::get_power_2(x)?; + let x3 = Self::get_power_3(x)?; + + let mx3 = self.m.clone().checked_mul(&x3).ok_or(ArithmeticError::Overflow)?; + let nx2 = self.n.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; + let ox = self.o.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; - // we do not need the fractions here. So we truncate the result + let result = mx3 + .checked_add(&nx2) + .ok_or(ArithmeticError::Overflow)? + .checked_add(&ox) + .ok_or(ArithmeticError::Overflow)?; Ok(result) } } +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct SquareRootBondingFunctionParameters { + pub m: F, + pub n: F, +} + +impl BondingFunction for SquareRootBondingFunctionParameters +where + F: FixedPointNumber + SquareRoot, +{ + /// F(x) = m * sqrt(x^2) + n * x + fn get_value(&self, x: F) -> Result { + let x3 = Self::get_power_3(x)?; + let sqrt_x3 = x3.try_sqrt().ok_or(ArithmeticError::Overflow)?; + let mx3 = self.m.clone().checked_mul(&sqrt_x3).ok_or(ArithmeticError::Overflow)?; + let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; + + mx3.checked_add(&nx).ok_or(ArithmeticError::Overflow) + } +} + +#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub struct RationalBondingFunctionParameters(PhantomData); + +impl RationalBondingFunctionParameters +where + F: FixedPointNumber, +{ + fn get_power_2(x: F) -> Result { + Ok(x.saturating_mul(x)) + } + + pub fn calculate_costs(&self, low: (F, F), high: (F, F)) -> Result { + let high_val = self.calculate_ration(high.0, high.1)?; + let low_val = self.calculate_ration(low.0, high.1)?; + Ok(high_val.saturating_sub(low_val)) + } + + /// F(a) = 0.5 * (a / (a + b))**2 + 0.5 * (b / (a + b))**2 * (a+b) , where b is the supply of the other assets. + fn calculate_ration(&self, a: F, b: F) -> Result { + // for the case, that a and b has no supply, we return 0. + if a.is_zero() && b.is_zero() { + return Ok(F::zero()); + } + + // Should never happen, but lets be save. If 0.5 can not be represented as a fixed point number, we have an underflow. + let constant = F::checked_from_rational(1, 2).ok_or(ArithmeticError::Underflow)?; + + let sum_a_b = a.checked_add(&b).ok_or(ArithmeticError::Overflow)?; + + // Should never happen. + let a_divided_sum = a.checked_div(&sum_a_b).ok_or(ArithmeticError::DivisionByZero)?; + + // Should never happen. + let b_divided_sum = a.checked_div(&sum_a_b).ok_or(ArithmeticError::DivisionByZero)?; + + let a_divided_sum_squared = Self::get_power_2(a_divided_sum)?; + + let b_divided_sum_squared = Self::get_power_2(b_divided_sum)?; + + let a_divided_sum_squared_multiplied = a_divided_sum_squared + .checked_mul(&constant) + .ok_or(ArithmeticError::Overflow)?; + + let b_divided_sum_squared_multiplied = b_divided_sum_squared + .checked_mul(&constant) + .ok_or(ArithmeticError::Overflow)?; + + let b_divided_sum_squared_multiplied_multiplied = b_divided_sum_squared_multiplied + .checked_mul(&sum_a_b) + .ok_or(ArithmeticError::Overflow)?; + + a_divided_sum_squared_multiplied + .checked_add(&b_divided_sum_squared_multiplied_multiplied) + .ok_or(ArithmeticError::Overflow) + } +} + pub fn transform_denomination_currency_amount( amount: u128, current_denomination: u8, @@ -67,7 +188,7 @@ pub fn transform_denomination_currency_amount( amount.checked_mul(factor).ok_or(ArithmeticError::Overflow) } else { let factor = 10u128.pow(diff.abs() as u32); - // Dividing by zero can never happen 10^0 = 1. Lets just be save. + // Dividing by zero can never happen 10^0 = 1. Lets be save. amount.checked_div(factor).ok_or(ArithmeticError::DivisionByZero) } }?; diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index f3053efd5..03101c59d 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -4,7 +4,7 @@ use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{ArithmeticError, FixedPointNumber}; -use crate::curves_parameters::{self, BondingFunction}; +use crate::curves_parameters::{self, BondingFunction, SquareRoot}; #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct Locks { @@ -64,16 +64,15 @@ pub struct TokenMeta { #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub enum Curve { - /// Price scales linearly with the ratio of the total issuance of the active currency to the sum of all total issuances. - /// `f(i_active) = s * i_active / (i_active + i_passive)`, where s is a scaling factor. - /// Parameters: - /// - Scaling Factor LinearRatioCurve(curves_parameters::LinearBondingFunctionParameters), + QuadraticRatioCurve(curves_parameters::QuadraticBondingFunctionParameters), + SquareRootBondingFunction(curves_parameters::SquareRootBondingFunctionParameters), + RationalBondingFunction(curves_parameters::RationalBondingFunctionParameters), } impl Curve where - F: FixedPointNumber, + F: FixedPointNumber + SquareRoot, { pub fn calculate_cost( &self, @@ -81,13 +80,23 @@ where active_issuance_post: F, passive_issuance: F, ) -> Result { - let calculation_param = match self { - Curve::LinearRatioCurve(params) => params, - }; - let active_issuance_pre_with_passive = active_issuance_pre.saturating_add(passive_issuance); let active_issuance_post_with_passive = active_issuance_post.saturating_add(passive_issuance); - calculation_param.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) + match self { + Curve::LinearRatioCurve(params) => { + params.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) + } + Curve::QuadraticRatioCurve(params) => { + params.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) + } + Curve::SquareRootBondingFunction(params) => { + params.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) + } + Curve::RationalBondingFunction(params) => params.calculate_costs( + (active_issuance_pre, passive_issuance), + (active_issuance_post, passive_issuance), + ), + } } } From 16a8ca38142f6238dafc0dca7fea1fe4d94df1ae Mon Sep 17 00:00:00 2001 From: Raphael Flechtner <39338561+rflechtner@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:10:17 -0400 Subject: [PATCH 22/46] feat: implement burn_into extrinsic (#728) Implements bonding curve based token burning. Refactors the get_cost associated function to do so. Co-authored-by: Adel Golghalyani --- pallets/pallet-bonded-coins/src/lib.rs | 125 +++++++++++++++++++++-- pallets/pallet-bonded-coins/src/types.rs | 5 + 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 7210f678f..d7cf5ac84 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -27,17 +27,17 @@ pub mod pallet { metadata::Mutate as FungiblesMetadata, Create as CreateFungibles, Destroy as DestroyFungibles, Inspect as InspectFungibles, Mutate as MutateFungibles, }, - tokens::Preservation, + tokens::{Fortitude, Precision, Preservation}, }, Hashable, }; use frame_system::pallet_prelude::*; use sp_runtime::{ - traits::{CheckedAdd, Saturating, StaticLookup, Zero}, + traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, ArithmeticError, SaturatedConversion, }; - use crate::types::{Curve, MockCurve, PoolDetails, PoolStatus, TokenMeta}; + use crate::types::{Curve, DiffKind, MockCurve, PoolDetails, PoolStatus, TokenMeta}; type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; type DepositCurrencyBalanceOf = @@ -127,6 +127,8 @@ pub mod pallet { IndexOutOfBounds, /// The cost or returns for a mint, burn, or swap operation is outside the user-defined slippage tolerance. Slippage, + /// The amount to mint, burn, or swap is zero. + ZeroAmount, } #[pallet::hooks] @@ -217,6 +219,8 @@ pub mod pallet { }; ensure!(mint_enabled, Error::::Locked); + ensure!(amount_to_mint.is_zero(), Error::::ZeroAmount); + let currency_idx_usize: usize = currency_idx.saturated_into(); // get id of the currency we want to mint @@ -232,7 +236,13 @@ pub mod pallet { .map(|id| T::Fungibles::total_issuance(id.clone())) .collect(); - let cost = Self::get_cost(pool_details.curve, &amount_to_mint, total_issuances, currency_idx_usize)?; + let cost = Self::get_collateral_diff( + DiffKind::Mint, + pool_details.curve, + &amount_to_mint, + total_issuances, + currency_idx_usize, + )?; // fail if cost > max_cost ensure!(!cost.gt(&max_cost), Error::::Slippage); @@ -247,24 +257,99 @@ pub mod pallet { Ok(().into()) } + + #[pallet::call_index(2)] + pub fn burn_into( + origin: OriginFor, + pool_id: T::PoolId, + currency_idx: u32, + amount_to_burn: FungiblesBalanceOf, + min_return: CollateralCurrencyBalanceOf, + beneficiary: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { + let signer = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + let burn_enabled = match pool_details.state { + // if mint is locked, then operation is priviledged + PoolStatus::Frozen(locks) => locks.allow_burn || signer == pool_details.creator, + PoolStatus::Active => true, + _ => false, + }; + ensure!(burn_enabled, Error::::Locked); + + ensure!(amount_to_burn.is_zero(), Error::::ZeroAmount); + + let currency_idx_usize: usize = currency_idx.saturated_into(); + + // get id of the currency we want to burn + // this also serves as a validation of the currency_idx parameter + let burn_currency_id = pool_details + .bonded_currencies + .get(currency_idx_usize) + .ok_or(Error::::IndexOutOfBounds)?; + + // TODO: remove lock if one exists / if pool_details.transferable != true + + // Debit from caller to ensure sufficient funds before making expensive cost calculations + let burnt_amount = T::Fungibles::burn_from( + burn_currency_id.clone(), + &signer, + amount_to_burn, + Precision::Exact, + Fortitude::Polite, + )?; + + let total_issuances: Vec> = pool_details + .bonded_currencies + .iter() + .map(|id| T::Fungibles::total_issuance(id.clone())) + .collect(); + + // + let returns = Self::get_collateral_diff( + DiffKind::Burn, + pool_details.curve, + &burnt_amount, + total_issuances, + currency_idx_usize, + )?; + + // fail if returns < min_return + ensure!(!returns.lt(&min_return), Error::::Slippage); + + // withdraw collateral from deposit and transfer to beneficiary account; deposit account may be drained + T::CollateralCurrency::transfer(&pool_id.into(), &beneficiary, returns, Preservation::Expendable)?; + + Ok(().into()) + } } impl Pallet where FungiblesBalanceOf: TryInto>, { - pub fn get_cost( + pub fn get_collateral_diff( + kind: DiffKind, curve: Curve, - amount_to_mint: &FungiblesBalanceOf, + amount: &FungiblesBalanceOf, mut total_issuances: Vec>, - mint_into_idx: usize, + currency_idx: usize, ) -> Result, ArithmeticError> { // calculate parameters for bonding curve // we've checked the vector length before - let active_issuance_pre = total_issuances.swap_remove(mint_into_idx); - let active_issuance_post = active_issuance_pre - .checked_add(amount_to_mint) - .ok_or(ArithmeticError::Overflow)?; + + let (active_issuance_pre, active_issuance_post) = + Self::calculate_pre_post_issuances(kind, amount, &total_issuances, currency_idx)?; + + let passive_issuance: FungiblesBalanceOf = total_issuances + .iter() + .enumerate() + .filter(|&(idx, _)| idx != currency_idx) + .fold(Zero::zero(), |sum, (_, x)| sum.saturating_add(*x)); + let passive_issuance: FungiblesBalanceOf = total_issuances .iter() .fold(Zero::zero(), |sum, x| sum.saturating_add(*x)); @@ -279,5 +364,23 @@ pub mod pallet { // Try conversion to Collateral Balance type return cost.try_into().map_err(|_| ArithmeticError::Overflow); } + + fn calculate_pre_post_issuances( + kind: DiffKind, + amount: &FungiblesBalanceOf, + total_issuances: &[FungiblesBalanceOf], + currency_idx: usize, + ) -> Result<(FungiblesBalanceOf, FungiblesBalanceOf), ArithmeticError> { + let active_issuance_pre = total_issuances[currency_idx]; + let active_issuance_post = match kind { + DiffKind::Mint => active_issuance_pre + .checked_add(amount) + .ok_or(ArithmeticError::Overflow)?, + DiffKind::Burn => active_issuance_pre + .checked_sub(amount) + .ok_or(ArithmeticError::Underflow)?, + }; + Ok((active_issuance_pre, active_issuance_post)) + } } } diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 67bfde88a..7486490e9 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -73,6 +73,11 @@ pub enum Curve { LinearRatioCurve(LinearRatioCurveParams), } +pub enum DiffKind { + Mint, + Burn, +} + pub struct MockCurve {} impl MockCurve { From b58738a89bf4a8d1e7a5ef0f461d358e40acfd90 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Tue, 17 Sep 2024 13:10:36 +0200 Subject: [PATCH 23/46] merge conflicts --- pallets/pallet-bonded-coins/src/lib.rs | 47 ++++++++++++-------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 15b9631f7..cc423582e 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -35,22 +35,12 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use sp_runtime::{ -<<<<<<< HEAD - traits::{CheckedAdd, Saturating, StaticLookup, Zero}, - ArithmeticError, FixedU128, SaturatedConversion, - }; - - use crate::{ - curves_parameters::transform_denomination_currency_amount, - types::{Curve, PoolDetails, PoolStatus, TokenMeta}, - }; -======= traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, ArithmeticError, SaturatedConversion, }; + use sp_arithmetic::FixedU128; - use crate::types::{Curve, DiffKind, MockCurve, PoolDetails, PoolStatus, TokenMeta}; ->>>>>>> feat-bonded-coins + use crate::{types::{Curve, DiffKind, PoolDetails, PoolStatus, TokenMeta}, curves_parameters::transform_denomination_currency_amount}; type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; type DepositCurrencyBalanceOf = @@ -322,10 +312,13 @@ pub mod pallet { Fortitude::Polite, )?; - let total_issuances: Vec> = pool_details + let total_issuances: Vec<(FungiblesBalanceOf, u8)> = pool_details .bonded_currencies .iter() - .map(|id| T::Fungibles::total_issuance(id.clone())) + .map(|id| ( + T::Fungibles::total_issuance(id.clone()), + T::Fungibles::decimals(id.clone()), + )) .collect(); // @@ -352,11 +345,13 @@ pub mod pallet { FungiblesBalanceOf: TryInto>, CollateralCurrencyBalanceOf: TryInto, { + + /// save usage of currency_ids. pub fn get_collateral_diff( kind: DiffKind, curve: Curve, amount: &FungiblesBalanceOf, - mut total_issuances: Vec>, + total_issuances: Vec<(FungiblesBalanceOf, u8)>, currency_idx: usize, ) -> Result, ArithmeticError> { @@ -364,10 +359,9 @@ pub mod pallet { let target_denomination_normalization = 18; let target_denomination_costs = 10; - // calculate parameters for bonding curve - // we've checked the vector length before + - let mut normalized_issuances = total_issuances + let normalized_issuances = total_issuances .clone() .into_iter() .map(|(x, d)| { @@ -379,20 +373,21 @@ pub mod pallet { }) .collect::, ArithmeticError>>()?; + // normalize the amount to mint let normalized_amount_to_mint = transform_denomination_currency_amount( - amount_to_mint.clone().saturated_into(), - total_issuances[mint_into_idx].1, + amount.clone().saturated_into(), + total_issuances[currency_idx].1, target_denomination_normalization, )?; let (active_issuance_pre, active_issuance_post) = - Self::calculate_pre_post_issuances(kind, amount, &normalized_issuances, currency_idx)?; + Self::calculate_pre_post_issuances(kind, &normalized_amount_to_mint, &normalized_issuances, currency_idx)?; - let passive_issuance: FungiblesBalanceOf = total_issuances + let passive_issuance = normalized_issuances .iter() .enumerate() .filter(|&(idx, _)| idx != currency_idx) - .fold(Zero::zero(), |sum, (_, x)| sum.saturating_add(*x)); + .fold(FixedU128::zero(), |sum, (_, x)| sum.saturating_add(*x)); let normalize_cost = curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance)?; @@ -412,10 +407,10 @@ pub mod pallet { fn calculate_pre_post_issuances( kind: DiffKind, - amount: &FungiblesBalanceOf, - total_issuances: &[FungiblesBalanceOf], + amount: &FixedU128, + total_issuances: &[FixedU128], currency_idx: usize, - ) -> Result<(FungiblesBalanceOf, FungiblesBalanceOf), ArithmeticError> { + ) -> Result<(FixedU128, FixedU128), ArithmeticError> { let active_issuance_pre = total_issuances[currency_idx]; let active_issuance_post = match kind { DiffKind::Mint => active_issuance_pre From 28e9dcc65ff6777ec7b422e9a10be725b05a108b Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Tue, 17 Sep 2024 13:15:26 +0200 Subject: [PATCH 24/46] remove file content --- .../pallet-bonded-coins/src/benchmarking.rs | 20 ------- pallets/pallet-bonded-coins/src/mock.rs | 59 ------------------- pallets/pallet-bonded-coins/src/tests.rs | 23 -------- 3 files changed, 102 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/benchmarking.rs b/pallets/pallet-bonded-coins/src/benchmarking.rs index 8bba2a098..e69de29bb 100644 --- a/pallets/pallet-bonded-coins/src/benchmarking.rs +++ b/pallets/pallet-bonded-coins/src/benchmarking.rs @@ -1,20 +0,0 @@ -//! Benchmarking setup for pallet-parachain-template - -use super::*; - -#[allow(unused)] -use crate::Pallet as Template; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -use frame_system::RawOrigin; - -benchmarks! { - do_something { - let s in 0 .. 100; - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), s) - verify { - assert_eq!(Something::::get(), Some(s)); - } -} - -impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 411a16b11..e69de29bb 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1,59 +0,0 @@ -use frame_support::{derive_impl, parameter_types, traits::Everything}; -use frame_system as system; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; - -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TemplateModule: crate::{Pallet, Call, Storage, Event}, - } -); - -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] -impl system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -impl crate::Config for Test { - type RuntimeEvent = RuntimeEvent; -} - -// Build genesis storage according to the mock runtime. -pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::::default().build_storage().unwrap().into() -} diff --git a/pallets/pallet-bonded-coins/src/tests.rs b/pallets/pallet-bonded-coins/src/tests.rs index 527aec8ed..e69de29bb 100644 --- a/pallets/pallet-bonded-coins/src/tests.rs +++ b/pallets/pallet-bonded-coins/src/tests.rs @@ -1,23 +0,0 @@ -use crate::{mock::*, Error}; -use frame_support::{assert_noop, assert_ok}; - -#[test] -fn it_works_for_default_value() { - new_test_ext().execute_with(|| { - // Dispatch a signed extrinsic. - assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); - // Read pallet storage and assert an expected result. - assert_eq!(TemplateModule::something(), Some(42)); - }); -} - -#[test] -fn correct_error_for_none_value() { - new_test_ext().execute_with(|| { - // Ensure the expected error is thrown when no value is present. - assert_noop!( - TemplateModule::cause_error(RuntimeOrigin::signed(1)), - Error::::NoneValue - ); - }); -} From 1c053c7318fd77d0171b7f5e4575d5042eb01fc4 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Tue, 17 Sep 2024 13:16:14 +0200 Subject: [PATCH 25/46] clippy --- pallets/pallet-bonded-coins/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index d7cf5ac84..90d2ae2ae 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -335,7 +335,7 @@ pub mod pallet { kind: DiffKind, curve: Curve, amount: &FungiblesBalanceOf, - mut total_issuances: Vec>, + total_issuances: Vec>, currency_idx: usize, ) -> Result, ArithmeticError> { // calculate parameters for bonding curve @@ -350,10 +350,6 @@ pub mod pallet { .filter(|&(idx, _)| idx != currency_idx) .fold(Zero::zero(), |sum, (_, x)| sum.saturating_add(*x)); - let passive_issuance: FungiblesBalanceOf = total_issuances - .iter() - .fold(Zero::zero(), |sum, x| sum.saturating_add(*x)); - // match curve implementation let curve_impl = match curve { Curve::LinearRatioCurve(params) => MockCurve::new(params), From 708e09004e30c7f7d4795d0c66fa3396ce8aabc4 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Tue, 17 Sep 2024 13:47:03 +0200 Subject: [PATCH 26/46] first draft swap --- .../pallet-bonded-coins/src/benchmarking.rs | 1 + pallets/pallet-bonded-coins/src/lib.rs | 84 +++++++++++++++++-- pallets/pallet-bonded-coins/src/mock.rs | 1 + pallets/pallet-bonded-coins/src/tests.rs | 1 + 4 files changed, 79 insertions(+), 8 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/benchmarking.rs b/pallets/pallet-bonded-coins/src/benchmarking.rs index e69de29bb..8b1378917 100644 --- a/pallets/pallet-bonded-coins/src/benchmarking.rs +++ b/pallets/pallet-bonded-coins/src/benchmarking.rs @@ -0,0 +1 @@ + diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 90d2ae2ae..cd10d59a9 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -19,7 +19,7 @@ mod types; #[frame_support::pallet(dev_mode)] pub mod pallet { use frame_support::{ - dispatch::DispatchResultWithPostInfo, + dispatch::{DispatchResult, DispatchResultWithPostInfo}, pallet_prelude::*, traits::{ fungible::{Inspect as InspectFungible, Mutate, MutateHold}, @@ -293,7 +293,12 @@ pub mod pallet { // TODO: remove lock if one exists / if pool_details.transferable != true - // Debit from caller to ensure sufficient funds before making expensive cost calculations + let total_issuances: Vec> = pool_details + .bonded_currencies + .iter() + .map(|id| T::Fungibles::total_issuance(id.clone())) + .collect(); + let burnt_amount = T::Fungibles::burn_from( burn_currency_id.clone(), &signer, @@ -302,12 +307,6 @@ pub mod pallet { Fortitude::Polite, )?; - let total_issuances: Vec> = pool_details - .bonded_currencies - .iter() - .map(|id| T::Fungibles::total_issuance(id.clone())) - .collect(); - // let returns = Self::get_collateral_diff( DiffKind::Burn, @@ -322,9 +321,78 @@ pub mod pallet { // withdraw collateral from deposit and transfer to beneficiary account; deposit account may be drained T::CollateralCurrency::transfer(&pool_id.into(), &beneficiary, returns, Preservation::Expendable)?; + Ok(().into()) } + + #[pallet::call_index(3)] + pub fn swap_into( + origin: OriginFor, + pool_id: T::PoolId, + from_idx: u32, + to_idx: u32, + amount_to_swap: FungiblesBalanceOf, + beneficiary: AccountIdLookupOf, + min_return: FungiblesBalanceOf, + ) -> DispatchResult { + + // + let signer = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + let swap_enabled = match pool_details.state { + // if swap is locked, then operation is priviledged + PoolStatus::Frozen(locks) => locks.allow_swap || signer == pool_details.creator, + PoolStatus::Active => true, + _ => false, + }; + + ensure!(swap_enabled, Error::::Locked); + ensure!(amount_to_swap.is_zero(), Error::::ZeroAmount); + + let from_idx_usize: usize = from_idx.saturated_into(); + let to_idx_usize: usize = to_idx.saturated_into(); + + let burn_currency_id = pool_details + .bonded_currencies + .get(from_idx_usize) + .ok_or(Error::::IndexOutOfBounds)?; + + let mint_currency_id = pool_details.bonded_currencies.get(to_idx_usize).ok_or(Error::::IndexOutOfBounds)?; + + + // 1. calculate the total issuances of the pool. + let total_issuances: Vec> = pool_details + .bonded_currencies + .iter() + .map(|id| T::Fungibles::total_issuance(id.clone())) + .collect(); + + // 2. burn tokens from signer. Burned amount is used to mint new tokens. + let burned_amount = T::Fungibles::burn_from( + burn_currency_id.clone(), + &signer, + amount_to_swap, + Precision::Exact, + Fortitude::Polite, + )?; + + // 3. calculate collatoral diff + + let returns = Self::get_collateral_diff( + DiffKind::Burn, + pool_details.curve, + &burned_amount, + total_issuances, + from_idx_usize, + )?; + + + Ok(()) + } } impl Pallet diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index e69de29bb..8b1378917 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -0,0 +1 @@ + diff --git a/pallets/pallet-bonded-coins/src/tests.rs b/pallets/pallet-bonded-coins/src/tests.rs index e69de29bb..8b1378917 100644 --- a/pallets/pallet-bonded-coins/src/tests.rs +++ b/pallets/pallet-bonded-coins/src/tests.rs @@ -0,0 +1 @@ + From 2570c5ca758d88d2f867c4f49525dda6bf6a98c5 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 18 Sep 2024 09:38:52 +0200 Subject: [PATCH 27/46] swap tx --- .../src/curves_parameters.rs | 10 +- pallets/pallet-bonded-coins/src/lib.rs | 346 +++++++++++------- pallets/pallet-bonded-coins/src/types.rs | 1 - 3 files changed, 216 insertions(+), 141 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index ddcb001e2..c035c3c3c 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -57,14 +57,11 @@ impl BondingFunction for LinearBondingFunctionParameters where F: FixedPointNumber, { - /// F(x) = m * x^2 + n * x + /// F(x) = m * x + n fn get_value(&self, x: F) -> Result { - let x2 = Self::get_power_2(x)?; - - let mx2 = self.m.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; - let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; + let mx = self.m.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; - let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; + let result = mx.checked_add(&self.n).ok_or(ArithmeticError::Overflow)?; Ok(result) } } @@ -174,6 +171,7 @@ where .checked_add(&b_divided_sum_squared_multiplied_multiplied) .ok_or(ArithmeticError::Overflow) } + } pub fn transform_denomination_currency_amount( diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 4819fbae7..4d503d4b5 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -34,15 +34,19 @@ pub mod pallet { Hashable, }; use frame_system::pallet_prelude::*; + use sp_arithmetic::{traits::CheckedDiv, FixedU128}; use sp_runtime::{ traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, ArithmeticError, SaturatedConversion, }; - use sp_arithmetic::FixedU128; - use crate::{types::{Curve, DiffKind, PoolDetails, PoolStatus, TokenMeta}, curves_parameters::transform_denomination_currency_amount}; + use crate::{ + curves_parameters::transform_denomination_currency_amount, + types::{Curve, DiffKind, PoolDetails, PoolStatus, TokenMeta}, + }; type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; + type AccountIdOf = ::AccountId; type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; type DepositCurrencyHoldReasonOf = @@ -56,6 +60,13 @@ pub mod pallet { type FungiblesAssetIdOf = <::Fungibles as InspectFungibles<::AccountId>>::AssetId; + type PoolDetailsOf = PoolDetails< + ::AccountId, + FungiblesAssetIdOf, + Curve, + ::MaxCurrencies, + >; + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -95,13 +106,7 @@ pub mod pallet { /// Bonded Currency Swapping Pools #[pallet::storage] #[pallet::getter(fn pools)] - pub(crate) type Pools = StorageMap< - _, - Twox64Concat, - T::PoolId, - PoolDetails, Curve, T::MaxCurrencies>, - OptionQuery, - >; + pub(crate) type Pools = StorageMap<_, Twox64Concat, T::PoolId, PoolDetailsOf, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -190,7 +195,7 @@ pub mod pallet { // TODO: use fungibles::roles::ResetTeam to update currency admin } - >::set( + Pools::::set( pool_id, Some(PoolDetails::new(who.clone(), curve, currency_ids, !frozen)), ); @@ -215,7 +220,7 @@ pub mod pallet { let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - let mint_enabled = match pool_details.state { + let mint_enabled = match &pool_details.state { // if mint is locked, then operation is priviledged PoolStatus::Frozen(locks) => locks.allow_mint || signer == pool_details.creator, PoolStatus::Active => true, @@ -227,43 +232,18 @@ pub mod pallet { let currency_idx_usize: usize = currency_idx.saturated_into(); - // get id of the currency we want to mint - // this also serves as a validation of the currency_idx parameter - let mint_currency_id = pool_details - .bonded_currencies - .get(currency_idx_usize) - .ok_or(Error::::IndexOutOfBounds)?; - - let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details - .bonded_currencies - .iter() - .map(|id| { - ( - T::Fungibles::total_issuance(id.clone()), - T::Fungibles::decimals(id.clone()), - ) - }) - .collect(); - - - let cost = Self::get_collateral_diff( - DiffKind::Mint, - pool_details.curve, - &amount_to_mint, - currencies_metadata, + let cost = Self::mint_pool_currency_and_calculate_collateral( + &pool_details, currency_idx_usize, + beneficiary, + amount_to_mint, )?; - // fail if cost > max_cost - ensure!(!cost.gt(&max_cost), Error::::Slippage); - // withdraw the collateral and put it in the deposit account - T::CollateralCurrency::transfer(&signer, &pool_id.into(), cost, Preservation::Preserve)?; - - // mint tokens into beneficiary account - T::Fungibles::mint_into(mint_currency_id.clone(), &beneficiary, amount_to_mint)?; + let real_costs = T::CollateralCurrency::transfer(&signer, &pool_id.into(), cost, Preservation::Preserve)?; - // TODO: apply lock if pool_details.transferable != true + // fail if cost > max_cost + ensure!(!real_costs.gt(&max_cost), Error::::Slippage); Ok(().into()) } @@ -276,13 +256,13 @@ pub mod pallet { amount_to_burn: FungiblesBalanceOf, min_return: CollateralCurrencyBalanceOf, beneficiary: AccountIdLookupOf, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { let signer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - let burn_enabled = match pool_details.state { + let burn_enabled = match &pool_details.state { // if mint is locked, then operation is priviledged PoolStatus::Frozen(locks) => locks.allow_burn || signer == pool_details.creator, PoolStatus::Active => true, @@ -294,57 +274,28 @@ pub mod pallet { let currency_idx_usize: usize = currency_idx.saturated_into(); - // get id of the currency we want to burn - // this also serves as a validation of the currency_idx parameter - let burn_currency_id = pool_details - .bonded_currencies - .get(currency_idx_usize) - .ok_or(Error::::IndexOutOfBounds)?; - - // TODO: remove lock if one exists / if pool_details.transferable != true - - let total_issuances: Vec> = pool_details - .bonded_currencies - .iter() - .map(|id| T::Fungibles::total_issuance(id.clone())) - .collect(); - - let burnt_amount = T::Fungibles::burn_from( - burn_currency_id.clone(), - &signer, + let collateral_return = Self::burn_pool_currency_and_calculate_collateral( + &pool_details, + currency_idx_usize, + signer, amount_to_burn, - Precision::Exact, - Fortitude::Polite, )?; - - let total_issuances: Vec<(FungiblesBalanceOf, u8)> = pool_details - .bonded_currencies - .iter() - .map(|id| ( - T::Fungibles::total_issuance(id.clone()), - T::Fungibles::decimals(id.clone()), - )) - .collect(); - - - // - let returns = Self::get_collateral_diff( - DiffKind::Burn, - pool_details.curve, - &burnt_amount, - total_issuances, - currency_idx_usize, + // withdraw collateral from deposit and transfer to beneficiary account; deposit account may be drained + let returns = T::CollateralCurrency::transfer( + &pool_id.into(), + &beneficiary, + collateral_return, + Preservation::Expendable, )?; + // get id of the currency we want to burn + // this also serves as a validation of the currency_idx parameter + // fail if returns < min_return ensure!(!returns.lt(&min_return), Error::::Slippage); - // withdraw collateral from deposit and transfer to beneficiary account; deposit account may be drained - T::CollateralCurrency::transfer(&pool_id.into(), &beneficiary, returns, Preservation::Expendable)?; - - - Ok(().into()) + Ok(()) } #[pallet::call_index(3)] @@ -357,14 +308,13 @@ pub mod pallet { beneficiary: AccountIdLookupOf, min_return: FungiblesBalanceOf, ) -> DispatchResult { - - // + let signer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - let swap_enabled = match pool_details.state { + let swap_enabled = match &pool_details.state { // if swap is locked, then operation is priviledged PoolStatus::Frozen(locks) => locks.allow_swap || signer == pool_details.creator, PoolStatus::Active => true, @@ -377,40 +327,91 @@ pub mod pallet { let from_idx_usize: usize = from_idx.saturated_into(); let to_idx_usize: usize = to_idx.saturated_into(); - let burn_currency_id = pool_details - .bonded_currencies - .get(from_idx_usize) - .ok_or(Error::::IndexOutOfBounds)?; - - let mint_currency_id = pool_details.bonded_currencies.get(to_idx_usize).ok_or(Error::::IndexOutOfBounds)?; - - - // 1. calculate the total issuances of the pool. - let total_issuances: Vec> = pool_details - .bonded_currencies - .iter() - .map(|id| T::Fungibles::total_issuance(id.clone())) - .collect(); - - // 2. burn tokens from signer. Burned amount is used to mint new tokens. - let burned_amount = T::Fungibles::burn_from( - burn_currency_id.clone(), - &signer, - amount_to_swap, - Precision::Exact, - Fortitude::Polite, - )?; - - // 3. calculate collatoral diff - - let returns = Self::get_collateral_diff( - DiffKind::Burn, - pool_details.curve, - &burned_amount, - total_issuances, - from_idx_usize, - )?; + match &pool_details.curve { + Curve::RationalBondingFunction(_) => { + let target_denomination_normalization = 18; + + let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details + .bonded_currencies + .iter() + .map(|id| { + ( + T::Fungibles::total_issuance(id.clone()), + T::Fungibles::decimals(id.clone()), + ) + }) + .collect(); + + let collateral = Self::burn_pool_currency_and_calculate_collateral( + &pool_details, + from_idx_usize, + signer, + amount_to_swap, + )?; + + let normalized_collateral = transform_denomination_currency_amount( + collateral.saturated_into::(), + currencies_metadata[from_idx_usize].1, + target_denomination_normalization, + )?; + let normalized_target_issuance = transform_denomination_currency_amount( + amount_to_swap.clone().saturated_into(), + currencies_metadata[to_idx_usize].1, + target_denomination_normalization, + )?; + + let normalized_passive_issuances = currencies_metadata + .clone() + .into_iter() + .enumerate() + .filter(|(idx, _)| *idx != to_idx_usize) + .map(|(_, (x, d))| { + transform_denomination_currency_amount( + x.saturated_into::(), + d, + target_denomination_normalization, + ) + }) + .try_fold(FixedU128::zero(), |sum, result| result.map(|x| sum.saturating_add(x)))?; + + let ratio = normalized_target_issuance + .checked_div(&normalized_passive_issuances) + .ok_or(ArithmeticError::DivisionByZero)?; + + let supply_to_mint = normalized_collateral + .checked_div(&ratio) + .ok_or(ArithmeticError::DivisionByZero)?; + + let raw_supply = transform_denomination_currency_amount( + supply_to_mint.into_inner(), + target_denomination_normalization, + currencies_metadata[to_idx_usize].1, + )?; + + Self::mint_pool_currency_and_calculate_collateral( + &pool_details, + to_idx_usize, + beneficiary, + raw_supply.into_inner().saturated_into(), + )?; + } + // The price for burning and minting in the pool is the same, if the bonding curve is not [RationalBondingFunction]. + _ => { + Self::burn_pool_currency_and_calculate_collateral( + &pool_details, + from_idx_usize, + signer, + amount_to_swap, + )?; + Self::mint_pool_currency_and_calculate_collateral( + &pool_details, + to_idx_usize, + beneficiary, + min_return, + )?; + } + }; Ok(()) } @@ -421,22 +422,18 @@ pub mod pallet { FungiblesBalanceOf: TryInto>, CollateralCurrencyBalanceOf: TryInto, { - - /// save usage of currency_ids. + /// save usage of currency_ids. pub fn get_collateral_diff( kind: DiffKind, - curve: Curve, + curve: &Curve, amount: &FungiblesBalanceOf, total_issuances: Vec<(FungiblesBalanceOf, u8)>, currency_idx: usize, ) -> Result, ArithmeticError> { - // todo: change that. We have also to restrict the denomination of the pool currencies maybe? let target_denomination_normalization = 18; let target_denomination_costs = 10; - - let normalized_issuances = total_issuances .clone() .into_iter() @@ -456,8 +453,12 @@ pub mod pallet { target_denomination_normalization, )?; - let (active_issuance_pre, active_issuance_post) = - Self::calculate_pre_post_issuances(kind, &normalized_amount_to_mint, &normalized_issuances, currency_idx)?; + let (active_issuance_pre, active_issuance_post) = Self::calculate_pre_post_issuances( + kind, + &normalized_amount_to_mint, + &normalized_issuances, + currency_idx, + )?; let passive_issuance = normalized_issuances .iter() @@ -465,8 +466,6 @@ pub mod pallet { .filter(|&(idx, _)| idx != currency_idx) .fold(FixedU128::zero(), |sum, (_, x)| sum.saturating_add(*x)); - - let normalize_cost = curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance)?; // transform the cost back to the target denomination of the collateral currency @@ -499,5 +498,84 @@ pub mod pallet { }; Ok((active_issuance_pre, active_issuance_post)) } + + fn burn_pool_currency_and_calculate_collateral( + pool_details: &PoolDetailsOf, + currency_idx: usize, + payer: AccountIdOf, + amount: FungiblesBalanceOf, + ) -> Result, DispatchError> { + let burn_currency_id = pool_details + .bonded_currencies + .get(currency_idx) + .ok_or(Error::::IndexOutOfBounds)?; + + let real_amount = T::Fungibles::burn_from( + burn_currency_id.clone(), + &payer, + amount, + Precision::Exact, + Fortitude::Polite, + )?; + + let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details + .bonded_currencies + .iter() + .map(|id| { + ( + T::Fungibles::total_issuance(id.clone()), + T::Fungibles::decimals(id.clone()), + ) + }) + .collect(); + + // + let returns = Self::get_collateral_diff( + DiffKind::Burn, + &pool_details.curve, + &real_amount, + currencies_metadata, + currency_idx, + )?; + + Ok(returns) + } + + fn mint_pool_currency_and_calculate_collateral( + pool_details: &PoolDetailsOf, + currency_idx: usize, + beneficiary: AccountIdOf, + amount: FungiblesBalanceOf, + ) -> Result, DispatchError> { + // get id of the currency we want to mint + // this also serves as a validation of the currency_idx parameter + let mint_currency_id = pool_details + .bonded_currencies + .get(currency_idx) + .ok_or(Error::::IndexOutOfBounds)?; + + let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details + .bonded_currencies + .iter() + .map(|id| { + ( + T::Fungibles::total_issuance(id.clone()), + T::Fungibles::decimals(id.clone()), + ) + }) + .collect(); + + let cost = Self::get_collateral_diff( + DiffKind::Mint, + &pool_details.curve, + &amount, + currencies_metadata, + currency_idx, + )?; + + T::Fungibles::mint_into(mint_currency_id.clone(), &beneficiary, amount)?; + + Ok(cost) + } } } diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 304ad5138..47b420cbc 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -75,7 +75,6 @@ pub enum DiffKind { Burn, } - impl Curve where F: FixedPointNumber + SquareRoot, From eefb5358d56437c98b2c98d99d1d992295264be7 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 18 Sep 2024 10:14:54 +0200 Subject: [PATCH 28/46] lock tx --- .../src/curves_parameters.rs | 1 - pallets/pallet-bonded-coins/src/lib.rs | 35 +++++++++++++++++-- pallets/pallet-bonded-coins/src/types.rs | 2 +- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index c035c3c3c..0c0278ab3 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -171,7 +171,6 @@ where .checked_add(&b_divided_sum_squared_multiplied_multiplied) .ok_or(ArithmeticError::Overflow) } - } pub fn transform_denomination_currency_amount( diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 4d503d4b5..32062049c 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -19,6 +19,8 @@ mod types; mod curves_parameters; #[frame_support::pallet(dev_mode)] pub mod pallet { + use core::f32::consts::E; + use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo}, pallet_prelude::*, @@ -42,7 +44,7 @@ pub mod pallet { use crate::{ curves_parameters::transform_denomination_currency_amount, - types::{Curve, DiffKind, PoolDetails, PoolStatus, TokenMeta}, + types::{Curve, DiffKind, Locks, PoolDetails, PoolStatus, TokenMeta}, }; type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; @@ -308,7 +310,6 @@ pub mod pallet { beneficiary: AccountIdLookupOf, min_return: FungiblesBalanceOf, ) -> DispatchResult { - let signer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; @@ -415,6 +416,36 @@ pub mod pallet { Ok(()) } + + #[pallet::call_index(4)] + pub fn set_lock(origin: OriginFor, pool_id: T::PoolId, lock: Locks) -> DispatchResult { + let who = ensure_signed(origin)?; + + Pools::::try_mutate(pool_id, |pool| -> DispatchResult { + if let Some(pool) = pool { + ensure!(who == pool.creator, Error::::PoolUnknown); + pool.state = PoolStatus::Frozen(lock); + Ok(()) + } else { + Err(Error::::PoolUnknown.into()) + } + }) + } + + #[pallet::call_index(5)] + pub fn unlock(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + Pools::::try_mutate(pool_id, |pool| -> DispatchResult { + if let Some(pool) = pool { + ensure!(who == pool.creator, Error::::PoolUnknown); + pool.state = PoolStatus::Active; + Ok(()) + } else { + Err(Error::::PoolUnknown.into()) + } + }) + } } impl Pallet diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 47b420cbc..4c1ce69f4 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -6,7 +6,7 @@ use sp_runtime::{ArithmeticError, FixedPointNumber}; use crate::curves_parameters::{self, BondingFunction, SquareRoot}; -#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen, Debug)] pub struct Locks { pub allow_mint: bool, pub allow_burn: bool, From 00a35c4ff81995636653ecee64ddc2f31cb7f069 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 18 Sep 2024 11:27:02 +0200 Subject: [PATCH 29/46] first mvp --- pallets/pallet-bonded-coins/src/lib.rs | 97 ++++++++++++++++++++++-- pallets/pallet-bonded-coins/src/types.rs | 26 +++++++ 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 32062049c..87af56101 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -19,15 +19,15 @@ mod types; mod curves_parameters; #[frame_support::pallet(dev_mode)] pub mod pallet { - use core::f32::consts::E; use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo}, + ensure, pallet_prelude::*, traits::{ fungible::{Inspect as InspectFungible, Mutate, MutateHold}, fungibles::{ - metadata::Inspect as FungiblesInspect, metadata::Mutate as FungiblesMetadata, + metadata::{Inspect as FungiblesInspect, Mutate as FungiblesMetadata}, Create as CreateFungibles, Destroy as DestroyFungibles, Inspect as InspectFungibles, Mutate as MutateFungibles, }, @@ -35,7 +35,7 @@ pub mod pallet { }, Hashable, }; - use frame_system::pallet_prelude::*; + use frame_system::{ensure_root, pallet_prelude::*}; use sp_arithmetic::{traits::CheckedDiv, FixedU128}; use sp_runtime::{ traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, @@ -140,6 +140,8 @@ pub mod pallet { Slippage, /// The amount to mint, burn, or swap is zero. ZeroAmount, + /// The pool is already in the process of being destroyed. + Destroying, } #[pallet::hooks] @@ -182,7 +184,7 @@ pub mod pallet { for entry in currencies { let asset_id = entry.id.clone(); - // create new assset class; fail if it already exists + // create new asset class; fail if it already exists T::Fungibles::create(asset_id.clone(), pool_id.clone().into(), false, entry.min_balance)?; // set metadata for new asset class @@ -429,7 +431,9 @@ pub mod pallet { } else { Err(Error::::PoolUnknown.into()) } - }) + })?; + + Ok(()) } #[pallet::call_index(5)] @@ -444,7 +448,77 @@ pub mod pallet { } else { Err(Error::::PoolUnknown.into()) } - }) + })?; + + Ok(()) + } + + #[pallet::call_index(6)] + pub fn start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + let mut pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + ensure!(who == pool_details.creator, Error::::PoolUnknown); + + Self::do_start_destroy_pool(&mut pool_details) + } + + #[pallet::call_index(7)] + pub fn force_start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { + ensure_root(origin)?; + + let mut pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + Self::do_start_destroy_pool(&mut pool_details) + } + + #[pallet::call_index(8)] + pub fn destroy_accounts(origin: OriginFor, pool_id: T::PoolId, max_accounts: u32) -> DispatchResult { + ensure_signed(origin)?; + + let mut pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + ensure!(pool_details.state == PoolStatus::Destroying, Error::::Destroying); + + let max_accounts_to_destroy_per_currency = + max_accounts.saturating_div(pool_details.bonded_currencies.len().saturated_into()); + + for (idx, currency_id) in pool_details.bonded_currencies.clone().iter().enumerate() { + let destroyed_accounts = + T::Fungibles::destroy_accounts(currency_id.clone(), max_accounts_to_destroy_per_currency)?; + if destroyed_accounts == 0 { + T::Fungibles::finish_destroy(currency_id.clone())?; + pool_details.bonded_currencies.swap_remove(idx); + } + } + + Ok(()) + } + + #[pallet::call_index(9)] + pub fn finish_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { + ensure_signed(origin)?; + + let pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + + ensure!(pool_details.state == PoolStatus::Destroying, Error::::Destroying); + ensure!(pool_details.bonded_currencies.is_empty(), Error::::Destroying); + + let pool_account = pool_id.clone().into(); + + let total_collateral_issuance = T::CollateralCurrency::total_balance(&pool_account); + + T::CollateralCurrency::transfer( + &pool_account, + &pool_details.creator, + total_collateral_issuance, + Preservation::Expendable, + )?; + + Pools::::remove(pool_id); + + Ok(()) } } @@ -608,5 +682,16 @@ pub mod pallet { Ok(cost) } + + fn do_start_destroy_pool(pool_details: &mut PoolDetailsOf) -> DispatchResult { + ensure!(pool_details.state != PoolStatus::Destroying, Error::::Destroying); + + pool_details.state.destroy(); + + for currency_id in pool_details.bonded_currencies.iter() { + T::Fungibles::start_destroy(currency_id.clone(), None)?; + } + Ok(()) + } } } diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 4c1ce69f4..6c301e8bb 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -19,6 +19,32 @@ pub enum PoolStatus { Frozen(LockType), Destroying, } + +impl PoolStatus +where + LockType: Eq, +{ + pub fn is_active(&self) -> bool { + matches!(self, Self::Active) + } + + pub fn is_frozen(&self, lock: LockType) -> bool { + matches!(self, Self::Frozen(ref l) if l == &lock) + } + + pub fn is_destroying(&self) -> bool { + matches!(self, Self::Destroying) + } + + pub fn freeze(&mut self, lock: LockType) { + *self = Self::Frozen(lock); + } + + pub fn destroy(&mut self) { + *self = Self::Destroying; + } +} + impl Default for PoolStatus { fn default() -> Self { Self::Frozen(LockType::default()) From 388e015b6349b1c27b2a39bb932a9eedf5671a4a Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 18 Sep 2024 14:18:07 +0200 Subject: [PATCH 30/46] minor refactoring --- pallets/pallet-bonded-coins/src/lib.rs | 136 +++++++++++------------ pallets/pallet-bonded-coins/src/types.rs | 65 +++++++---- 2 files changed, 109 insertions(+), 92 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 87af56101..84ad6da3a 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -21,7 +21,7 @@ mod curves_parameters; pub mod pallet { use frame_support::{ - dispatch::{DispatchResult, DispatchResultWithPostInfo}, + dispatch::DispatchResult, ensure, pallet_prelude::*, traits::{ @@ -51,10 +51,6 @@ pub mod pallet { type AccountIdOf = ::AccountId; type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; - type DepositCurrencyHoldReasonOf = - <::DepositCurrency as frame_support::traits::fungible::InspectHold< - ::AccountId, - >>::Reason; type CollateralCurrencyBalanceOf = <::CollateralCurrency as InspectFungible<::AccountId>>::Balance; type FungiblesBalanceOf = @@ -75,7 +71,7 @@ pub mod pallet { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The currency used for storage deposits. - type DepositCurrency: MutateHold; + type DepositCurrency: MutateHold; /// The currency used as collateral for minting bonded tokens. type CollateralCurrency: Mutate; /// Implementation of creating and managing new fungibles @@ -93,11 +89,9 @@ pub mod pallet { /// Who can create new bonded currency pools. type PoolCreateOrigin: EnsureOrigin; /// The type used for pool ids - type PoolId: Parameter - + MaxEncodedLen - + From<[u8; 32]> - + Into - + Into>; + type PoolId: Parameter + MaxEncodedLen + From<[u8; 32]> + Into; + + type RuntimeHoldReason: From; } type CurveParameterType = FixedU128; @@ -114,15 +108,15 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A new bonded token pool has been initiated. [pool_id] - PoolCreated(T::AccountId), + PoolCreated(T::PoolId), /// Trading locks on a pool have been removed. [pool_id] - Unlocked(T::AccountId), + Unlocked(T::PoolId), /// Trading locks on a pool have been set or changed. [pool_id] - LockSet(T::AccountId), + LockSet(T::PoolId), /// A bonded token pool has been moved to destroying state. [pool_id] - DestructionStarted(T::AccountId), + DestructionStarted(T::PoolId), /// A bonded token pool has been fully destroyed and all collateral and deposits have been refunded. [pool_id] - Destroyed(T::AccountId), + Destroyed(T::PoolId), } // Errors inform users that something went wrong. @@ -142,6 +136,13 @@ pub mod pallet { ZeroAmount, /// The pool is already in the process of being destroyed. Destroying, + /// The user is not privileged to perform the requested operation. + Unauthorized, + } + + #[pallet::composite_enum] + pub enum HoldReason { + Deposit, } #[pallet::hooks] @@ -158,9 +159,11 @@ pub mod pallet { origin: OriginFor, curve: Curve, currencies: BoundedVec, FungiblesAssetIdOf>, T::MaxCurrencies>, - frozen: bool, - // currency_admin: Option> TODO: use this to set currency admin - ) -> DispatchResultWithPostInfo { + tradable: bool, // Todo: why do we need that? + state: PoolStatus, + pool_manager: AccountIdOf, // Todo: maybe change that back to owner. + // currency_admin: Option> TODO: use this to set currency admin + ) -> DispatchResult { // ensure origin is PoolCreateOrigin let who = T::PoolCreateOrigin::ensure_origin(origin)?; @@ -174,7 +177,7 @@ pub mod pallet { let pool_id = T::PoolId::from(currency_ids.blake2_256()); T::DepositCurrency::hold( - &pool_id.clone().into(), // TODO: just assumed that you can use a pool id as hold reason, not sure that's true though + &T::RuntimeHoldReason::from(HoldReason::Deposit), &who, T::DepositPerCurrency::get() .saturating_mul(currencies.len().saturated_into()) @@ -200,14 +203,13 @@ pub mod pallet { } Pools::::set( - pool_id, - Some(PoolDetails::new(who.clone(), curve, currency_ids, !frozen)), + &pool_id, + Some(PoolDetails::new(pool_manager, curve, currency_ids, tradable, state)), ); - // Emit an event. - Self::deposit_event(Event::PoolCreated(who)); - // Return a successful DispatchResultWithPostInfo - Ok(().into()) + Self::deposit_event(Event::PoolCreated(pool_id)); + + Ok(()) } #[pallet::call_index(1)] @@ -218,19 +220,13 @@ pub mod pallet { amount_to_mint: FungiblesBalanceOf, max_cost: CollateralCurrencyBalanceOf, beneficiary: AccountIdLookupOf, - ) -> DispatchResultWithPostInfo { - let signer = ensure_signed(origin)?; + ) -> DispatchResult { + let who = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - let mint_enabled = match &pool_details.state { - // if mint is locked, then operation is priviledged - PoolStatus::Frozen(locks) => locks.allow_mint || signer == pool_details.creator, - PoolStatus::Active => true, - _ => false, - }; - ensure!(mint_enabled, Error::::Locked); + ensure!(pool_details.is_minting_authorized(&who), Error::::Locked); ensure!(amount_to_mint.is_zero(), Error::::ZeroAmount); @@ -244,12 +240,14 @@ pub mod pallet { )?; // withdraw the collateral and put it in the deposit account - let real_costs = T::CollateralCurrency::transfer(&signer, &pool_id.into(), cost, Preservation::Preserve)?; + let real_costs = T::CollateralCurrency::transfer(&who, &pool_id.into(), cost, Preservation::Preserve)?; // fail if cost > max_cost - ensure!(!real_costs.gt(&max_cost), Error::::Slippage); + ensure!(real_costs <= max_cost, Error::::Slippage); - Ok(().into()) + // TODO: apply lock if pool_details.transferable != true + + Ok(()) } #[pallet::call_index(2)] @@ -261,18 +259,12 @@ pub mod pallet { min_return: CollateralCurrencyBalanceOf, beneficiary: AccountIdLookupOf, ) -> DispatchResult { - let signer = ensure_signed(origin)?; + let who = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - let burn_enabled = match &pool_details.state { - // if mint is locked, then operation is priviledged - PoolStatus::Frozen(locks) => locks.allow_burn || signer == pool_details.creator, - PoolStatus::Active => true, - _ => false, - }; - ensure!(burn_enabled, Error::::Locked); + ensure!(pool_details.is_burning_authorized(&who), Error::::Locked); ensure!(amount_to_burn.is_zero(), Error::::ZeroAmount); @@ -281,7 +273,7 @@ pub mod pallet { let collateral_return = Self::burn_pool_currency_and_calculate_collateral( &pool_details, currency_idx_usize, - signer, + who, amount_to_burn, )?; @@ -297,7 +289,7 @@ pub mod pallet { // this also serves as a validation of the currency_idx parameter // fail if returns < min_return - ensure!(!returns.lt(&min_return), Error::::Slippage); + ensure!(returns >= min_return, Error::::Slippage); Ok(()) } @@ -312,19 +304,12 @@ pub mod pallet { beneficiary: AccountIdLookupOf, min_return: FungiblesBalanceOf, ) -> DispatchResult { - let signer = ensure_signed(origin)?; + let who = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - let swap_enabled = match &pool_details.state { - // if swap is locked, then operation is priviledged - PoolStatus::Frozen(locks) => locks.allow_swap || signer == pool_details.creator, - PoolStatus::Active => true, - _ => false, - }; - - ensure!(swap_enabled, Error::::Locked); + ensure!(pool_details.is_swapping_authorized(&who), Error::::Locked); ensure!(amount_to_swap.is_zero(), Error::::ZeroAmount); let from_idx_usize: usize = from_idx.saturated_into(); @@ -348,7 +333,7 @@ pub mod pallet { let collateral = Self::burn_pool_currency_and_calculate_collateral( &pool_details, from_idx_usize, - signer, + who, amount_to_swap, )?; @@ -404,7 +389,7 @@ pub mod pallet { Self::burn_pool_currency_and_calculate_collateral( &pool_details, from_idx_usize, - signer, + who, amount_to_swap, )?; Self::mint_pool_currency_and_calculate_collateral( @@ -423,16 +408,18 @@ pub mod pallet { pub fn set_lock(origin: OriginFor, pool_id: T::PoolId, lock: Locks) -> DispatchResult { let who = ensure_signed(origin)?; - Pools::::try_mutate(pool_id, |pool| -> DispatchResult { + Pools::::try_mutate(&pool_id, |pool| -> DispatchResult { if let Some(pool) = pool { - ensure!(who == pool.creator, Error::::PoolUnknown); - pool.state = PoolStatus::Frozen(lock); + ensure!(pool.is_manager(&who), Error::::Unauthorized); + pool.state = PoolStatus::Locked(lock); Ok(()) } else { Err(Error::::PoolUnknown.into()) } })?; + Self::deposit_event(Event::LockSet(pool_id)); + Ok(()) } @@ -440,9 +427,9 @@ pub mod pallet { pub fn unlock(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { let who = ensure_signed(origin)?; - Pools::::try_mutate(pool_id, |pool| -> DispatchResult { + Pools::::try_mutate(&pool_id, |pool| -> DispatchResult { if let Some(pool) = pool { - ensure!(who == pool.creator, Error::::PoolUnknown); + ensure!(pool.is_manager(&who), Error::::Unauthorized); pool.state = PoolStatus::Active; Ok(()) } else { @@ -450,6 +437,8 @@ pub mod pallet { } })?; + Self::deposit_event(Event::Unlocked(pool_id)); + Ok(()) } @@ -459,9 +448,12 @@ pub mod pallet { let mut pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - ensure!(who == pool_details.creator, Error::::PoolUnknown); + ensure!(pool_details.is_manager(&who), Error::::Unauthorized); - Self::do_start_destroy_pool(&mut pool_details) + Self::do_start_destroy_pool(&mut pool_details)?; + + Self::deposit_event(Event::DestructionStarted(pool_id)); + Ok(()) } #[pallet::call_index(7)] @@ -470,9 +462,13 @@ pub mod pallet { let mut pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; - Self::do_start_destroy_pool(&mut pool_details) + Self::do_start_destroy_pool(&mut pool_details)?; + + Self::deposit_event(Event::DestructionStarted(pool_id)); + Ok(()) } + // todo: check if we really need that tx. #[pallet::call_index(8)] pub fn destroy_accounts(origin: OriginFor, pool_id: T::PoolId, max_accounts: u32) -> DispatchResult { ensure_signed(origin)?; @@ -511,12 +507,14 @@ pub mod pallet { T::CollateralCurrency::transfer( &pool_account, - &pool_details.creator, + &pool_details.manager, total_collateral_issuance, Preservation::Expendable, )?; - Pools::::remove(pool_id); + Pools::::remove(&pool_id); + + Self::deposit_event(Event::Destroyed(pool_id)); Ok(()) } diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 6c301e8bb..30e408d50 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -13,31 +13,24 @@ pub struct Locks { pub allow_swap: bool, } -#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen, Debug)] pub enum PoolStatus { Active, - Frozen(LockType), + Locked(LockType), Destroying, } -impl PoolStatus -where - LockType: Eq, -{ +impl PoolStatus { pub fn is_active(&self) -> bool { matches!(self, Self::Active) } - pub fn is_frozen(&self, lock: LockType) -> bool { - matches!(self, Self::Frozen(ref l) if l == &lock) - } - pub fn is_destroying(&self) -> bool { matches!(self, Self::Destroying) } pub fn freeze(&mut self, lock: LockType) { - *self = Self::Frozen(lock); + *self = Self::Locked(lock); } pub fn destroy(&mut self) { @@ -45,36 +38,62 @@ where } } -impl Default for PoolStatus { - fn default() -> Self { - Self::Frozen(LockType::default()) - } -} - -#[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct PoolDetails> { - pub creator: AccountId, + pub manager: AccountId, pub curve: ParametrizedCurve, pub bonded_currencies: BoundedVec, pub state: PoolStatus, pub transferable: bool, } -impl> +impl PoolDetails +where + AccountId: PartialEq, + MaxOptions: Get, { pub fn new( - creator: AccountId, + manager: AccountId, curve: ParametrizedCurve, bonded_currencies: BoundedVec, transferable: bool, + state: PoolStatus, ) -> Self { Self { - creator, + manager, curve, bonded_currencies, transferable, - state: PoolStatus::default(), + state, + } + } + + pub fn is_manager(&self, who: &AccountId) -> bool { + who == &self.manager + } + + pub fn is_minting_authorized(&self, who: &AccountId) -> bool { + match &self.state { + PoolStatus::Locked(locks) => locks.allow_mint || self.is_manager(&who), + PoolStatus::Active => true, + _ => false, + } + } + + pub fn is_swapping_authorized(&self, who: &AccountId) -> bool { + match &self.state { + PoolStatus::Locked(locks) => locks.allow_swap || self.is_manager(&who), + PoolStatus::Active => true, + _ => false, + } + } + + pub fn is_burning_authorized(&self, who: &AccountId) -> bool { + match &self.state { + PoolStatus::Locked(locks) => locks.allow_burn || self.is_manager(&who), + PoolStatus::Active => true, + _ => false, } } } From 87fcea8cdc8ae693cc8e6d16cbbed6a13e00b26e Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Thu, 19 Sep 2024 09:33:24 +0200 Subject: [PATCH 31/46] Add testing framework --- Cargo.lock | 1 + chopsticks/chain.yaml | 20 + chopsticks/db/package.json | 10 + chopsticks/package.json | 10 + chopsticks/yarn.lock | 2410 ++++++++++++++++++++++ pallets/pallet-bonded-coins/src/lib.rs | 64 +- pallets/pallet-bonded-coins/src/types.rs | 19 +- runtimes/peregrine/Cargo.toml | 1 + runtimes/peregrine/src/lib.rs | 56 + 9 files changed, 2562 insertions(+), 29 deletions(-) create mode 100644 chopsticks/chain.yaml create mode 100644 chopsticks/db/package.json create mode 100644 chopsticks/package.json create mode 100644 chopsticks/yarn.lock diff --git a/Cargo.lock b/Cargo.lock index f9069831d..7df346445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8741,6 +8741,7 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-bonded-coins", "pallet-collective", "pallet-democracy", "pallet-deposit-storage", diff --git a/chopsticks/chain.yaml b/chopsticks/chain.yaml new file mode 100644 index 000000000..169976b81 --- /dev/null +++ b/chopsticks/chain.yaml @@ -0,0 +1,20 @@ + +endpoint: + # We fetch relevant storage elements from Spiritnet even tho we are running Peregrine (with sudo). + - wss://kilt.ibp.network +db: db/peregrine.sqlite +runtime-log-level: 5 +port: 50002 +wasm-override: /home/adel/projects/kilt-node/target/debug/wbuild/peregrine-runtime/peregrine_runtime.wasm + +import-storage: + System: + Account: + - - - 5HeHMRTEabP45PkFugDAy5hRnagQmmQJMfUFffWhAmDUw8TR + - providers: 1 + data: + free: "100000000000000000000000" + Sudo: + Key: 5HeHMRTEabP45PkFugDAy5hRnagQmmQJMfUFffWhAmDUw8TR + + diff --git a/chopsticks/db/package.json b/chopsticks/db/package.json new file mode 100644 index 000000000..b22e447f2 --- /dev/null +++ b/chopsticks/db/package.json @@ -0,0 +1,10 @@ +{ + "name": "kilt-chopsticks", + "private": "true", + "devDependencies": { + "@acala-network/chopsticks": "^0.14.1" + }, + "scripts": { + "spawn:": "cargo build -p peregrine-runtime && yarn chopsticks -c chain.yaml" + } +} diff --git a/chopsticks/package.json b/chopsticks/package.json new file mode 100644 index 000000000..0968df9c1 --- /dev/null +++ b/chopsticks/package.json @@ -0,0 +1,10 @@ +{ + "name": "kilt-chopsticks", + "private": "true", + "devDependencies": { + "@acala-network/chopsticks": "^0.14.1" + }, + "scripts": { + "spawn": "cargo build -p peregrine-runtime && yarn chopsticks -c chain.yaml" + } +} diff --git a/chopsticks/yarn.lock b/chopsticks/yarn.lock new file mode 100644 index 000000000..d533f4d30 --- /dev/null +++ b/chopsticks/yarn.lock @@ -0,0 +1,2410 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@acala-network/chopsticks-core@0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@acala-network/chopsticks-core/-/chopsticks-core-0.14.1.tgz#25ee8bfe4eae547092f275fefdf70cdcd9edc95c" + integrity sha512-ckvYXpQxevVOQBg/pWrlUnFx4zzMge5LBa565x/j6AyrS8PJOtpMqZ4tYnxxFOnuHgl4ztqy7PltnaUW9p0kBQ== + dependencies: + "@acala-network/chopsticks-executor" "0.14.1" + "@polkadot/rpc-provider" "^12.3.1" + "@polkadot/types" "^12.3.1" + "@polkadot/types-codec" "^12.3.1" + "@polkadot/types-known" "^12.3.1" + "@polkadot/util" "^13.0.2" + "@polkadot/util-crypto" "^13.0.2" + comlink "^4.4.1" + eventemitter3 "^5.0.1" + lodash "^4.17.21" + lru-cache "^10.2.0" + pino "^8.19.0" + pino-pretty "^11.0.0" + rxjs "^7.8.1" + zod "^3.22.4" + +"@acala-network/chopsticks-db@0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@acala-network/chopsticks-db/-/chopsticks-db-0.14.1.tgz#a3e14f6931a4625b957c651c61f0a5e6b51bc480" + integrity sha512-Q9CgEgz26rVJDHj6iNIgz1N3WF3cyFyUxpQl6QiiYVMTrKbzSgMeb/2Y+IW6i4FMcwdipyJBM2JwC0Pxj/yKuw== + dependencies: + "@acala-network/chopsticks-core" "0.14.1" + "@polkadot/util" "^13.0.2" + idb "^8.0.0" + sqlite3 "^5.1.7" + typeorm "^0.3.20" + +"@acala-network/chopsticks-executor@0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@acala-network/chopsticks-executor/-/chopsticks-executor-0.14.1.tgz#f6929e5032b367931ee9d08021a134c4d38eb0e6" + integrity sha512-TYIf7pyNqTveAdfLNZ8lj9a8+tCZM1wsMuF5Pi3glGUIY7i0z/vQblA41n9cD/A/hr1EpX+9MUkxZBVOKc5o5Q== + dependencies: + "@polkadot/util" "^13.0.2" + "@polkadot/wasm-util" "^7.3.2" + +"@acala-network/chopsticks@^0.14.1": + version "0.14.1" + resolved "https://registry.yarnpkg.com/@acala-network/chopsticks/-/chopsticks-0.14.1.tgz#e8cfe4650629cd600968772d1b7580d9e3b14b2e" + integrity sha512-JEUDQ4sHNw3CnhtM3mtfJFU2+pjQJZsp3eY+jziN85njpkQt/nOJiHvtyYEolTJwJmAwKCcbAQu38vFAgn3vvA== + dependencies: + "@acala-network/chopsticks-core" "0.14.1" + "@acala-network/chopsticks-db" "0.14.1" + "@pnpm/npm-conf" "^2.2.2" + "@polkadot/api" "^12.3.1" + "@polkadot/api-augment" "^12.3.1" + "@polkadot/rpc-provider" "^12.3.1" + "@polkadot/types" "^12.3.1" + "@polkadot/util" "^13.0.2" + "@polkadot/util-crypto" "^13.0.2" + axios "^1.7.4" + comlink "^4.4.1" + dotenv "^16.4.5" + global-agent "^3.0.0" + js-yaml "^4.1.0" + jsondiffpatch "^0.5.0" + lodash "^4.17.21" + ws "^8.17.1" + yargs "^17.7.2" + zod "^3.22.4" + +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + +"@noble/curves@^1.3.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + +"@noble/hashes@1.5.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@pnpm/config.env-replace@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz#ab29da53df41e8948a00f2433f085f54de8b3a4c" + integrity sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w== + +"@pnpm/network.ca-file@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz#2ab05e09c1af0cdf2fcf5035bea1484e222f7983" + integrity sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA== + dependencies: + graceful-fs "4.2.10" + +"@pnpm/npm-conf@^2.2.2": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz#bb375a571a0bd63ab0a23bece33033c683e9b6b0" + integrity sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw== + dependencies: + "@pnpm/config.env-replace" "^1.1.0" + "@pnpm/network.ca-file" "^1.0.1" + config-chain "^1.1.11" + +"@polkadot-api/json-rpc-provider-proxy@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.1.0.tgz#6e191f28e7d0fbbe8b540fc51d12a0adaeba297e" + integrity sha512-8GSFE5+EF73MCuLQm8tjrbCqlgclcHBSRaswvXziJ0ZW7iw3UEMsKkkKvELayWyBuOPa2T5i1nj6gFOeIsqvrg== + +"@polkadot-api/json-rpc-provider@0.0.1", "@polkadot-api/json-rpc-provider@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz#333645d40ccd9bccfd1f32503f17e4e63e76e297" + integrity sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA== + +"@polkadot-api/metadata-builders@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@polkadot-api/metadata-builders/-/metadata-builders-0.3.2.tgz#007f158c9e0546cf79ba440befc0c753ab1a6629" + integrity sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg== + dependencies: + "@polkadot-api/substrate-bindings" "0.6.0" + "@polkadot-api/utils" "0.1.0" + +"@polkadot-api/observable-client@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@polkadot-api/observable-client/-/observable-client-0.3.2.tgz#fd91efee350595a6e0ecfd3f294cc80de86c0cf7" + integrity sha512-HGgqWgEutVyOBXoGOPp4+IAq6CNdK/3MfQJmhCJb8YaJiaK4W6aRGrdQuQSTPHfERHCARt9BrOmEvTXAT257Ug== + dependencies: + "@polkadot-api/metadata-builders" "0.3.2" + "@polkadot-api/substrate-bindings" "0.6.0" + "@polkadot-api/utils" "0.1.0" + +"@polkadot-api/substrate-bindings@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-bindings/-/substrate-bindings-0.6.0.tgz#889b0c3ba19dc95282286506bf6e370a43ce119a" + integrity sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw== + dependencies: + "@noble/hashes" "^1.3.1" + "@polkadot-api/utils" "0.1.0" + "@scure/base" "^1.1.1" + scale-ts "^1.6.0" + +"@polkadot-api/substrate-client@^0.1.2": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@polkadot-api/substrate-client/-/substrate-client-0.1.4.tgz#7a808e5cb85ecb9fa2b3a43945090a6c807430ce" + integrity sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A== + dependencies: + "@polkadot-api/json-rpc-provider" "0.0.1" + "@polkadot-api/utils" "0.1.0" + +"@polkadot-api/utils@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@polkadot-api/utils/-/utils-0.1.0.tgz#d36937cdc465c2ea302f3278cf53157340ab33a0" + integrity sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA== + +"@polkadot/api-augment@12.4.2", "@polkadot/api-augment@^12.3.1": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-12.4.2.tgz#6c52a6d04f398e786a834839948b0bc0069b391c" + integrity sha512-BkG2tQpUUO0iUm65nSqP8hwHkNfN8jQw8apqflJNt9H8EkEL6v7sqwbLvGqtlxM9wzdxbg7lrWp3oHg4rOP31g== + dependencies: + "@polkadot/api-base" "12.4.2" + "@polkadot/rpc-augment" "12.4.2" + "@polkadot/types" "12.4.2" + "@polkadot/types-augment" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/util" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/api-base@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-12.4.2.tgz#c8312bae8dfc70c0fdec0c1366e405906e66575f" + integrity sha512-XYI7Po8i6C4lYZah7Xo0v7zOAawBUfkmtx0YxsLY/665Sup8oqzEj666xtV9qjBzR9coNhQonIFOn+9fh27Ncw== + dependencies: + "@polkadot/rpc-core" "12.4.2" + "@polkadot/types" "12.4.2" + "@polkadot/util" "^13.0.2" + rxjs "^7.8.1" + tslib "^2.6.3" + +"@polkadot/api-derive@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-12.4.2.tgz#2a1fbd79ff4842edc20378957374000ca6f7bd70" + integrity sha512-R0AMANEnqs5AiTaiQX2FXCxUlOibeDSgqlkyG1/0KDsdr6PO/l3dJOgEO+grgAwh4hdqzk4I9uQpdKxG83f2Gw== + dependencies: + "@polkadot/api" "12.4.2" + "@polkadot/api-augment" "12.4.2" + "@polkadot/api-base" "12.4.2" + "@polkadot/rpc-core" "12.4.2" + "@polkadot/types" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/util" "^13.0.2" + "@polkadot/util-crypto" "^13.0.2" + rxjs "^7.8.1" + tslib "^2.6.3" + +"@polkadot/api@12.4.2", "@polkadot/api@^12.3.1": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-12.4.2.tgz#c13dc1ad7fab670c5fd6d8d970a8462b999d7c1d" + integrity sha512-e1KS048471iBWZU10TJNEYOZqLO+8h8ajmVqpaIBOVkamN7tmacBxmHgq0+IA8VrGxjxtYNa1xF5Sqrg76uBEg== + dependencies: + "@polkadot/api-augment" "12.4.2" + "@polkadot/api-base" "12.4.2" + "@polkadot/api-derive" "12.4.2" + "@polkadot/keyring" "^13.0.2" + "@polkadot/rpc-augment" "12.4.2" + "@polkadot/rpc-core" "12.4.2" + "@polkadot/rpc-provider" "12.4.2" + "@polkadot/types" "12.4.2" + "@polkadot/types-augment" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/types-create" "12.4.2" + "@polkadot/types-known" "12.4.2" + "@polkadot/util" "^13.0.2" + "@polkadot/util-crypto" "^13.0.2" + eventemitter3 "^5.0.1" + rxjs "^7.8.1" + tslib "^2.6.3" + +"@polkadot/keyring@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-13.1.1.tgz#14b85d4e73ebfa8ccb0fadcdee127e102624dc11" + integrity sha512-Wm+9gn946GIPjGzvueObLGBBS9s541HE6mvKdWGEmPFMzH93ESN931RZlOd67my5MWryiSP05h5SHTp7bSaQTA== + dependencies: + "@polkadot/util" "13.1.1" + "@polkadot/util-crypto" "13.1.1" + tslib "^2.7.0" + +"@polkadot/networks@13.1.1", "@polkadot/networks@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-13.1.1.tgz#e1a05ef6f78ffc37272c6474df7b55244b311f9c" + integrity sha512-eEQ4+Mfl1xFtApeU5PdXZ2XBhxNSvUz9yW+YQVGUCkXRjWFbqNRsTOYWGd9uFbiAOXiiiXbtqfZpxSDzIm4XOg== + dependencies: + "@polkadot/util" "13.1.1" + "@substrate/ss58-registry" "^1.50.0" + tslib "^2.7.0" + +"@polkadot/rpc-augment@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-12.4.2.tgz#fbe310260f3e5159fc2fa924c1a7c52f69162f9c" + integrity sha512-IEco5pnso+fYkZNMlMAN5i4XAxdXPv0PZ0HNuWlCwF/MmRvWl8pq5JFtY1FiByHEbeuHwMIUhHM5SDKQ85q9Hg== + dependencies: + "@polkadot/rpc-core" "12.4.2" + "@polkadot/types" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/util" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/rpc-core@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-12.4.2.tgz#d20cfdadd932978d58037a213115844a0c49464b" + integrity sha512-yaveqxNcmyluyNgsBT5tpnCa/md0CGbOtRK7K82LWsz7gsbh0x80GBbJrQGxsUybg1gPeZbO1q9IigwA6fY8ag== + dependencies: + "@polkadot/rpc-augment" "12.4.2" + "@polkadot/rpc-provider" "12.4.2" + "@polkadot/types" "12.4.2" + "@polkadot/util" "^13.0.2" + rxjs "^7.8.1" + tslib "^2.6.3" + +"@polkadot/rpc-provider@12.4.2", "@polkadot/rpc-provider@^12.3.1": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-12.4.2.tgz#c6a4f9b9194a5227e4355cef026aac2b07430162" + integrity sha512-cAhfN937INyxwW1AdjABySdCKhC7QCIONRDHDea1aLpiuxq/w+QwjxauR9fCNGh3lTaAwwnmZ5WfFU2PtkDMGQ== + dependencies: + "@polkadot/keyring" "^13.0.2" + "@polkadot/types" "12.4.2" + "@polkadot/types-support" "12.4.2" + "@polkadot/util" "^13.0.2" + "@polkadot/util-crypto" "^13.0.2" + "@polkadot/x-fetch" "^13.0.2" + "@polkadot/x-global" "^13.0.2" + "@polkadot/x-ws" "^13.0.2" + eventemitter3 "^5.0.1" + mock-socket "^9.3.1" + nock "^13.5.4" + tslib "^2.6.3" + optionalDependencies: + "@substrate/connect" "0.8.11" + +"@polkadot/types-augment@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-12.4.2.tgz#8b8e278f4cbecbdd586470d31c27576e06e87074" + integrity sha512-3fDCOy2BEMuAtMYl4crKg76bv/0pDNEuzpAzV4EBUMIlJwypmjy5sg3gUPCMcA+ckX3xb8DhkWU4ceUdS7T2KQ== + dependencies: + "@polkadot/types" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/util" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/types-codec@12.4.2", "@polkadot/types-codec@^12.3.1": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-12.4.2.tgz#aa601ddbbe4bb28ef751e4565bd87037dee9a49b" + integrity sha512-DiPGRFWtVMepD9i05eC3orSbGtpN7un/pXOrXu0oriU+oxLkpvZH68ZsPNtJhKdQy03cAYtvB8elJOFJZYqoqQ== + dependencies: + "@polkadot/util" "^13.0.2" + "@polkadot/x-bigint" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/types-create@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-12.4.2.tgz#1113746ab92694c3402abc59feb3e3fcaf980997" + integrity sha512-nOpeAKZLdSqNMfzS3waQXgyPPaNt8rUHEmR5+WNv6c/Ke/vyf710wjxiTewfp0wpBgtdrimlgG4DLX1J9Ms1LA== + dependencies: + "@polkadot/types-codec" "12.4.2" + "@polkadot/util" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/types-known@12.4.2", "@polkadot/types-known@^12.3.1": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-12.4.2.tgz#c47ffac0a0bcc544e120f09e92f39e6f61ed94c8" + integrity sha512-bvhO4KQu/dgPmdwQXsweSMRiRisJ7Bp38lZVEIFykfd2qYyRW3OQEbIPKYpx9raD+fDATU0bTiKQnELrSGhYXw== + dependencies: + "@polkadot/networks" "^13.0.2" + "@polkadot/types" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/types-create" "12.4.2" + "@polkadot/util" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/types-support@12.4.2": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-12.4.2.tgz#22df5c22a002aca271507355123aeb68e1399c03" + integrity sha512-bz6JSt23UEZ2eXgN4ust6z5QF9pO5uNH7UzCP+8I/Nm85ZipeBYj2Wu6pLlE3Hw30hWZpuPxMDOKoEhN5bhLgw== + dependencies: + "@polkadot/util" "^13.0.2" + tslib "^2.6.3" + +"@polkadot/types@12.4.2", "@polkadot/types@^12.3.1": + version "12.4.2" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-12.4.2.tgz#948e0191b30c37f0d89b8e120174d6be31ed9f9d" + integrity sha512-ivYtt7hYcRvo69ULb1BJA9BE1uefijXcaR089Dzosr9+sMzvsB1yslNQReOq+Wzq6h6AQj4qex6qVqjWZE6Z4A== + dependencies: + "@polkadot/keyring" "^13.0.2" + "@polkadot/types-augment" "12.4.2" + "@polkadot/types-codec" "12.4.2" + "@polkadot/types-create" "12.4.2" + "@polkadot/util" "^13.0.2" + "@polkadot/util-crypto" "^13.0.2" + rxjs "^7.8.1" + tslib "^2.6.3" + +"@polkadot/util-crypto@13.1.1", "@polkadot/util-crypto@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-13.1.1.tgz#26960046a9bd6b3b63dc9b006c1a24dc6391b875" + integrity sha512-FG68rrLPdfLcscEyH10vnGkakM4O2lqr71S3GDhgc9WXaS8y9jisLgMPg8jbMHiQBJ3iKYkmtPKiLBowRslj2w== + dependencies: + "@noble/curves" "^1.3.0" + "@noble/hashes" "^1.3.3" + "@polkadot/networks" "13.1.1" + "@polkadot/util" "13.1.1" + "@polkadot/wasm-crypto" "^7.3.2" + "@polkadot/wasm-util" "^7.3.2" + "@polkadot/x-bigint" "13.1.1" + "@polkadot/x-randomvalues" "13.1.1" + "@scure/base" "^1.1.7" + tslib "^2.7.0" + +"@polkadot/util@13.1.1", "@polkadot/util@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-13.1.1.tgz#9cbf81e8c48e2ac549dbe2a40384624870016658" + integrity sha512-M4iQ5Um8tFdDmD7a96nPzfrEt+kxyWOqQDPqXyaax4QBnq/WCbq0jo8IO61uz55mdMQnGZvq8jd8uge4V6JzzQ== + dependencies: + "@polkadot/x-bigint" "13.1.1" + "@polkadot/x-global" "13.1.1" + "@polkadot/x-textdecoder" "13.1.1" + "@polkadot/x-textencoder" "13.1.1" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + tslib "^2.7.0" + +"@polkadot/wasm-bridge@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz#e1b01906b19e06cbca3d94f10f5666f2ae0baadc" + integrity sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g== + dependencies: + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-crypto-asmjs@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz#c6d41bc4b48b5359d57a24ca3066d239f2d70a34" + integrity sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q== + dependencies: + tslib "^2.6.2" + +"@polkadot/wasm-crypto-init@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz#7e1fe79ba978fb0a4a0f74a92d976299d38bc4b8" + integrity sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g== + dependencies: + "@polkadot/wasm-bridge" "7.3.2" + "@polkadot/wasm-crypto-asmjs" "7.3.2" + "@polkadot/wasm-crypto-wasm" "7.3.2" + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-crypto-wasm@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz#44e08ed5cf6499ce4a3aa7247071a5d01f6a74f4" + integrity sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw== + dependencies: + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-crypto@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz#61bbcd9e591500705c8c591e6aff7654bdc8afc9" + integrity sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw== + dependencies: + "@polkadot/wasm-bridge" "7.3.2" + "@polkadot/wasm-crypto-asmjs" "7.3.2" + "@polkadot/wasm-crypto-init" "7.3.2" + "@polkadot/wasm-crypto-wasm" "7.3.2" + "@polkadot/wasm-util" "7.3.2" + tslib "^2.6.2" + +"@polkadot/wasm-util@7.3.2", "@polkadot/wasm-util@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz#4fe6370d2b029679b41a5c02cd7ebf42f9b28de1" + integrity sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg== + dependencies: + tslib "^2.6.2" + +"@polkadot/x-bigint@13.1.1", "@polkadot/x-bigint@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-13.1.1.tgz#1a9036c9529ce15deab808bee7333bcbd3ab0078" + integrity sha512-Cq4Y6fd9UWtRBZz8RX2tWEBL1IFwUtY6cL8p6HC9yhZtUR6OPjKZe6RIZQa9gSOoIuqZWd6PmtvSNGVH32yfkQ== + dependencies: + "@polkadot/x-global" "13.1.1" + tslib "^2.7.0" + +"@polkadot/x-fetch@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-13.1.1.tgz#df05a3405537accab76000d99aa32cbea790aed9" + integrity sha512-qA6mIUUebJbS+oWzq/EagZflmaoa9b25WvsxSFn7mCvzKngXzr+GYCY4XiDwKY/S+/pr/kvSCKZ1ia8BDqPBYQ== + dependencies: + "@polkadot/x-global" "13.1.1" + node-fetch "^3.3.2" + tslib "^2.7.0" + +"@polkadot/x-global@13.1.1", "@polkadot/x-global@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-13.1.1.tgz#1db0c16e45a20eddf682c98b1d3487619203c8a9" + integrity sha512-DViIMmmEs29Qlsp058VTg2Mn7e3/CpGazNnKJrsBa0o1Ptxl13/4Z0fjqCpNi2GB+kaOsnREzxUORrHcU+PqcQ== + dependencies: + tslib "^2.7.0" + +"@polkadot/x-randomvalues@13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-13.1.1.tgz#e3fc6e77cdfe6f345fca7433dd92a914807a7e4f" + integrity sha512-cXj4omwbgzQQSiBtV1ZBw+XhJUU3iz/DS6ghUnGllSZEK+fGqiyaNgeFQzDY0tKjm6kYaDpvtOHR3mHsbzDuTg== + dependencies: + "@polkadot/x-global" "13.1.1" + tslib "^2.7.0" + +"@polkadot/x-textdecoder@13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-13.1.1.tgz#305e9a1be38aa435942bc2a73b088a2ca1c1c89b" + integrity sha512-LpZ9KYc6HdBH+i86bCmun4g4GWMiWN/1Pzs0hNdanlQMfqp3UGzl1Dqp0nozMvjWAlvyG7ip235VgNMd8HEbqg== + dependencies: + "@polkadot/x-global" "13.1.1" + tslib "^2.7.0" + +"@polkadot/x-textencoder@13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-13.1.1.tgz#2588c57c1fae68493a5588a156313d25b91a577e" + integrity sha512-w1mT15B9ptN5CJNgN/A0CmBqD5y9OePjBdU6gmAd8KRhwXCF0MTBKcEZk1dHhXiXtX+28ULJWLrfefC5gxy69Q== + dependencies: + "@polkadot/x-global" "13.1.1" + tslib "^2.7.0" + +"@polkadot/x-ws@^13.0.2": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-13.1.1.tgz#cff0356c75e64f0221706e34f831126287354ac1" + integrity sha512-E/xFmJTiFzu+IK5M3/8W/9fnvNJFelcnunPv/IgO6UST94SDaTsN/Gbeb6SqPb6CsrTHRl3WD+AZ3ErGGwQfEA== + dependencies: + "@polkadot/x-global" "13.1.1" + tslib "^2.7.0" + ws "^8.16.0" + +"@scure/base@^1.1.1", "@scure/base@^1.1.7": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + +"@sqltools/formatter@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" + integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== + +"@substrate/connect-extension-protocol@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.1.0.tgz#7df153f704702b98559e7e5e8a2ce17881fe1d1d" + integrity sha512-Wz5Cbn6S6P4vWfHyrsnPW7g15IAViMaXCk+jYkq4nNEMmzPtTKIEbtxrdDMBKrouOFtYKKp0znx5mh9KTCNqlA== + +"@substrate/connect-known-chains@^1.1.5": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@substrate/connect-known-chains/-/connect-known-chains-1.4.0.tgz#ee0562056cf98a3ee1103a64fa33ff21d86c69fd" + integrity sha512-p/mxn1GobtxJ+7xbIkUH4+/njH1neRHHKTcSGHNOC78Cf6Ch1Xzp082+nMjOBDLQLmraK5PF74AKV3WXHGuALw== + +"@substrate/connect@0.8.11": + version "0.8.11" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.8.11.tgz#983ec69a05231636e217b573b8130a6b942af69f" + integrity sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw== + dependencies: + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.5" + "@substrate/light-client-extension-helpers" "^1.0.0" + smoldot "2.0.26" + +"@substrate/light-client-extension-helpers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-1.0.0.tgz#7b60368c57e06e5cf798c6557422d12e6d81f1ff" + integrity sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg== + dependencies: + "@polkadot-api/json-rpc-provider" "^0.0.1" + "@polkadot-api/json-rpc-provider-proxy" "^0.1.0" + "@polkadot-api/observable-client" "^0.3.0" + "@polkadot-api/substrate-client" "^0.1.2" + "@substrate/connect-extension-protocol" "^2.0.0" + "@substrate/connect-known-chains" "^1.1.5" + rxjs "^7.8.1" + +"@substrate/ss58-registry@^1.50.0": + version "1.50.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.50.0.tgz#2d2a3d060cd4eadd200e4538078461ba73e13d6d" + integrity sha512-mkmlMlcC+MSd9rA+PN8ljGAm5fVZskvVwkXIsbx4NFwaT8kt38r7e9cyDWscG3z2Zn40POviZvEMrJSk+r2SgQ== + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@types/bn.js@^5.1.5": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.6.tgz#9ba818eec0c85e4d3c679518428afdf611d03203" + integrity sha512-Xh8vSwUeMKeYYrj3cX4lGQgFSF/N03r+tv4AiLl1SucqV+uTQpxRcnM8AkXKHwYP9ZPXOYXRr2KPXpVlIvqh9w== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "22.5.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.5.tgz#52f939dd0f65fc552a4ad0b392f3c466cc5d7a44" + integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA== + dependencies: + undici-types "~6.19.2" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +app-root-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" + integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + +axios@^1.7.4: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +boolean@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^2.0.7: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +comlink@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/comlink/-/comlink-4.4.1.tgz#e568b8e86410b809e8600eb2cf40c189371ef981" + integrity sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +config-chain@^1.1.11: + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +dateformat@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +dayjs@^1.11.9: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + +debug@4, debug@^4.1.0, debug@^4.3.3, debug@^4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-data-property@^1.0.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +diff-match-patch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37" + integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== + +dotenv@^16.0.3, dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +fast-copy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35" + integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ== + +fast-redact@^3.1.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" + integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== + +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +foreground-child@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +glob@^10.3.10: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" + integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== + dependencies: + boolean "^3.0.1" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" + +globalthis@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@4.2.10: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +help-me@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" + integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== + +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +http-cache-semantics@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +idb@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/idb/-/idb-8.0.0.tgz#33d7ed894ed36e23bcb542fb701ad579bfaad41f" + integrity sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw== + +ieee754@^1.1.13, ieee754@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.4, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +jsondiffpatch@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.5.0.tgz#f9795416022685a3ba7eced11a338c5cb0cf66f4" + integrity sha512-Quz3MvAwHxVYNXsOByL7xI5EB2WYOeFswqaHIA3qOK3isRWTxiplBEocmmru6XmxDB2L7jDNYtYA4FyimoAFEw== + dependencies: + chalk "^3.0.0" + diff-match-patch "^1.0.0" + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@^2.1.3: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + +mock-socket@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.3.1.tgz#24fb00c2f573c84812aa4a24181bb025de80cc8e" + integrity sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw== + +ms@^2.0.0, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +negotiator@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +nock@^13.5.4: + version "13.5.5" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.5.tgz#cd1caaca281d42be17d51946367a3d53a6af3e78" + integrity sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + propagate "^2.0.0" + +node-abi@^3.3.0: + version "3.67.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.67.0.tgz#1d159907f18d18e18809dbbb5df47ed2426a08df" + integrity sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw== + dependencies: + semver "^7.3.5" + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + +pino-abstract-transport@^1.0.0, pino-abstract-transport@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" + integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + +pino-pretty@^11.0.0: + version "11.2.2" + resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-11.2.2.tgz#5e8ec69b31e90eb187715af07b1d29a544e60d39" + integrity sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^3.0.2" + fast-safe-stringify "^2.1.1" + help-me "^5.0.0" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.0.0" + pump "^3.0.0" + readable-stream "^4.0.0" + secure-json-parse "^2.4.0" + sonic-boom "^4.0.1" + strip-json-comments "^3.1.1" + +pino-std-serializers@^6.0.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" + integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== + +pino@^8.19.0: + version "8.21.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d" + integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.2.0" + pino-std-serializers "^6.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.7.0" + thread-stream "^2.6.0" + +prebuild-install@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + +reflect-metadata@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" + integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +scale-ts@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/scale-ts/-/scale-ts-1.6.0.tgz#e9641093c5a9e50f964ddb1607139034e3e932e9" + integrity sha512-Ja5VCjNZR8TGKhUumy9clVVxcDpM+YFjAnkMuwQy68Hixio3VRRvWdE3g8T/yC+HXA0ZDQl2TGyUmtmbcVl40Q== + +secure-json-parse@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== + +semver@^7.3.2, semver@^7.3.5: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +sha.js@^2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +smoldot@2.0.26: + version "2.0.26" + resolved "https://registry.yarnpkg.com/smoldot/-/smoldot-2.0.26.tgz#0e64c7fcd26240fbe4c8d6b6e4b9a9aca77e00f6" + integrity sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig== + dependencies: + ws "^8.8.1" + +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +sonic-boom@^3.7.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422" + integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg== + dependencies: + atomic-sleep "^1.0.0" + +sonic-boom@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.1.0.tgz#4f039663ba191fac5cfe4f1dc330faac079e4342" + integrity sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw== + dependencies: + atomic-sleep "^1.0.0" + +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + +sprintf-js@^1.1.2, sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +thread-stream@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" + integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== + dependencies: + real-require "^0.2.0" + +tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.2, tslib@^2.6.3, tslib@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + +typeorm@^0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.20.tgz#4b61d737c6fed4e9f63006f88d58a5e54816b7ab" + integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q== + dependencies: + "@sqltools/formatter" "^1.2.5" + app-root-path "^3.1.0" + buffer "^6.0.3" + chalk "^4.1.2" + cli-highlight "^2.1.11" + dayjs "^1.11.9" + debug "^4.3.4" + dotenv "^16.0.3" + glob "^10.3.10" + mkdirp "^2.1.3" + reflect-metadata "^0.2.1" + sha.js "^2.4.11" + tslib "^2.5.0" + uuid "^9.0.0" + yargs "^17.6.2" + +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.16.0, ws@^8.17.1, ws@^8.8.1: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^16.0.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.6.2, yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +zod@^3.22.4: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 84ad6da3a..c4e9d02d6 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -17,7 +17,7 @@ mod benchmarking; mod types; mod curves_parameters; -#[frame_support::pallet(dev_mode)] +#[frame_support::pallet] pub mod pallet { use frame_support::{ @@ -25,7 +25,7 @@ pub mod pallet { ensure, pallet_prelude::*, traits::{ - fungible::{Inspect as InspectFungible, Mutate, MutateHold}, + fungible::{Inspect as InspectFungible, MutateHold}, fungibles::{ metadata::{Inspect as FungiblesInspect, Mutate as FungiblesMetadata}, Create as CreateFungibles, Destroy as DestroyFungibles, Inspect as InspectFungibles, @@ -35,11 +35,11 @@ pub mod pallet { }, Hashable, }; + use sp_std::vec::Vec; use frame_system::{ensure_root, pallet_prelude::*}; use sp_arithmetic::{traits::CheckedDiv, FixedU128}; use sp_runtime::{ - traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, - ArithmeticError, SaturatedConversion, + traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, ArithmeticError, BoundedVec, SaturatedConversion }; use crate::{ @@ -52,7 +52,7 @@ pub mod pallet { type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; type CollateralCurrencyBalanceOf = - <::CollateralCurrency as InspectFungible<::AccountId>>::Balance; + <::CollateralCurrency as InspectFungibles<::AccountId>>::Balance; type FungiblesBalanceOf = <::Fungibles as InspectFungibles<::AccountId>>::Balance; type FungiblesAssetIdOf = @@ -60,11 +60,18 @@ pub mod pallet { type PoolDetailsOf = PoolDetails< ::AccountId, - FungiblesAssetIdOf, Curve, - ::MaxCurrencies, + BoundedCurrencyVec >; + type CollateralAssetIdOf = <::CollateralCurrency as InspectFungibles<::AccountId>>::AssetId; + + type BoundedCurrencyVec = BoundedVec, ::MaxCurrencies>; + type BoundedNameVec = BoundedVec::MaxNameLength>; + type BoundedSymbolVec = BoundedVec::MaxSymbolLength>; + type TokenMetaOf = TokenMeta, FungiblesAssetIdOf, BoundedSymbolVec, BoundedNameVec>; + + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -73,7 +80,7 @@ pub mod pallet { /// The currency used for storage deposits. type DepositCurrency: MutateHold; /// The currency used as collateral for minting bonded tokens. - type CollateralCurrency: Mutate; + type CollateralCurrency: MutateFungibles; /// Implementation of creating and managing new fungibles type Fungibles: CreateFungibles + DestroyFungibles @@ -82,10 +89,20 @@ pub mod pallet { + MutateFungibles; /// The maximum number of currencies allowed for a single pool. #[pallet::constant] - type MaxCurrencies: Get + TypeInfo; + type MaxCurrencies: Get; + /// The deposit required for each bonded currency. + + #[pallet::constant] + type MaxSymbolLength: Get; + + #[pallet::constant] + type MaxNameLength: Get; /// The deposit required for each bonded currency. #[pallet::constant] type DepositPerCurrency: Get>; + + /// The asset id of the collateral currency. + type CollateralAssetId: Get>; /// Who can create new bonded currency pools. type PoolCreateOrigin: EnsureOrigin; /// The type used for pool ids @@ -154,11 +171,11 @@ pub mod pallet { FungiblesBalanceOf: TryInto>, { #[pallet::call_index(0)] - // #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] TODO: properly configure weights + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn create_pool( origin: OriginFor, curve: Curve, - currencies: BoundedVec, FungiblesAssetIdOf>, T::MaxCurrencies>, + currencies: BoundedVec, T::MaxCurrencies>, tradable: bool, // Todo: why do we need that? state: PoolStatus, pool_manager: AccountIdOf, // Todo: maybe change that back to owner. @@ -194,8 +211,8 @@ pub mod pallet { T::Fungibles::set( asset_id, &pool_id.clone().into(), - entry.name.clone(), - entry.symbol.clone(), + entry.name.clone().into(), + entry.symbol.clone().into(), entry.decimals, )?; @@ -213,6 +230,7 @@ pub mod pallet { } #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn mint_into( origin: OriginFor, pool_id: T::PoolId, @@ -228,7 +246,7 @@ pub mod pallet { ensure!(pool_details.is_minting_authorized(&who), Error::::Locked); - ensure!(amount_to_mint.is_zero(), Error::::ZeroAmount); + ensure!(!amount_to_mint.is_zero(), Error::::ZeroAmount); let currency_idx_usize: usize = currency_idx.saturated_into(); @@ -240,7 +258,7 @@ pub mod pallet { )?; // withdraw the collateral and put it in the deposit account - let real_costs = T::CollateralCurrency::transfer(&who, &pool_id.into(), cost, Preservation::Preserve)?; + let real_costs = T::CollateralCurrency::transfer(T::CollateralAssetId::get(), &who, &pool_id.into(), cost, Preservation::Preserve)?; // fail if cost > max_cost ensure!(real_costs <= max_cost, Error::::Slippage); @@ -251,6 +269,7 @@ pub mod pallet { } #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn burn_into( origin: OriginFor, pool_id: T::PoolId, @@ -266,7 +285,7 @@ pub mod pallet { ensure!(pool_details.is_burning_authorized(&who), Error::::Locked); - ensure!(amount_to_burn.is_zero(), Error::::ZeroAmount); + ensure!(!amount_to_burn.is_zero(), Error::::ZeroAmount); let currency_idx_usize: usize = currency_idx.saturated_into(); @@ -279,6 +298,7 @@ pub mod pallet { // withdraw collateral from deposit and transfer to beneficiary account; deposit account may be drained let returns = T::CollateralCurrency::transfer( + T::CollateralAssetId::get(), &pool_id.into(), &beneficiary, collateral_return, @@ -295,6 +315,7 @@ pub mod pallet { } #[pallet::call_index(3)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn swap_into( origin: OriginFor, pool_id: T::PoolId, @@ -310,7 +331,7 @@ pub mod pallet { let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; ensure!(pool_details.is_swapping_authorized(&who), Error::::Locked); - ensure!(amount_to_swap.is_zero(), Error::::ZeroAmount); + ensure!(!amount_to_swap.is_zero(), Error::::ZeroAmount); let from_idx_usize: usize = from_idx.saturated_into(); let to_idx_usize: usize = to_idx.saturated_into(); @@ -405,6 +426,7 @@ pub mod pallet { } #[pallet::call_index(4)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn set_lock(origin: OriginFor, pool_id: T::PoolId, lock: Locks) -> DispatchResult { let who = ensure_signed(origin)?; @@ -424,6 +446,7 @@ pub mod pallet { } #[pallet::call_index(5)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn unlock(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { let who = ensure_signed(origin)?; @@ -443,6 +466,7 @@ pub mod pallet { } #[pallet::call_index(6)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { let who = ensure_signed(origin)?; @@ -457,6 +481,7 @@ pub mod pallet { } #[pallet::call_index(7)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn force_start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { ensure_root(origin)?; @@ -470,6 +495,7 @@ pub mod pallet { // todo: check if we really need that tx. #[pallet::call_index(8)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn destroy_accounts(origin: OriginFor, pool_id: T::PoolId, max_accounts: u32) -> DispatchResult { ensure_signed(origin)?; @@ -493,6 +519,7 @@ pub mod pallet { } #[pallet::call_index(9)] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn finish_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { ensure_signed(origin)?; @@ -503,9 +530,10 @@ pub mod pallet { let pool_account = pool_id.clone().into(); - let total_collateral_issuance = T::CollateralCurrency::total_balance(&pool_account); + let total_collateral_issuance = T::CollateralCurrency::total_balance(T::CollateralAssetId::get(), &pool_account); T::CollateralCurrency::transfer( + T::CollateralAssetId::get(), &pool_account, &pool_details.manager, total_collateral_issuance, diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 30e408d50..e2e07bc94 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -1,7 +1,5 @@ -use frame_support::BoundedVec; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_core::Get; use sp_runtime::{ArithmeticError, FixedPointNumber}; use crate::curves_parameters::{self, BondingFunction, SquareRoot}; @@ -39,24 +37,23 @@ impl PoolStatus { } #[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct PoolDetails> { +pub struct PoolDetails { pub manager: AccountId, pub curve: ParametrizedCurve, - pub bonded_currencies: BoundedVec, + pub bonded_currencies: Currencies, pub state: PoolStatus, pub transferable: bool, } -impl - PoolDetails +impl + PoolDetails where AccountId: PartialEq, - MaxOptions: Get, { pub fn new( manager: AccountId, curve: ParametrizedCurve, - bonded_currencies: BoundedVec, + bonded_currencies: Currencies, transferable: bool, state: PoolStatus, ) -> Self { @@ -99,10 +96,10 @@ where } #[derive(Debug, Encode, Decode, Clone, PartialEq, TypeInfo)] -pub struct TokenMeta { +pub struct TokenMeta { pub id: AssetId, - pub name: Vec, - pub symbol: Vec, + pub name: Name, + pub symbol: Symbol, pub decimals: u8, pub min_balance: Balance, } diff --git a/runtimes/peregrine/Cargo.toml b/runtimes/peregrine/Cargo.toml index f8b46c79b..ef228aa4e 100644 --- a/runtimes/peregrine/Cargo.toml +++ b/runtimes/peregrine/Cargo.toml @@ -42,6 +42,7 @@ did = { workspace = true } kilt-runtime-api-dip-provider = { workspace = true } kilt-support = { workspace = true } pallet-asset-switch = { workspace = true } +pallet-bonded-coins = { workspace = true } pallet-deposit-storage = { workspace = true } pallet-did-lookup = { workspace = true } pallet-dip-provider = { workspace = true } diff --git a/runtimes/peregrine/src/lib.rs b/runtimes/peregrine/src/lib.rs index b80211976..d9d728fc2 100644 --- a/runtimes/peregrine/src/lib.rs +++ b/runtimes/peregrine/src/lib.rs @@ -704,6 +704,57 @@ impl pallet_web3_names::Config for Runtime { type BalanceMigrationManager = Migration; } +pub type BondedCoinsAssets = pallet_assets::Instance2; +impl pallet_assets::Config for Runtime { + type ApprovalDeposit = runtime_common::constants::assets::ApprovalDeposit; + type AssetAccountDeposit = runtime_common::constants::assets::AssetAccountDeposit; + type AssetDeposit = runtime_common::constants::assets::AssetDeposit; + type AssetId = u32; + type AssetIdParameter = u32; + type Balance = Balance; + type CallbackHandle = (); + type CreateOrigin = AsEnsureOriginWithArg>; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRoot; + type Freezer = (); + type MetadataDepositBase = runtime_common::constants::assets::MetaDepositBase; + type MetadataDepositPerByte = runtime_common::constants::assets::MetaDepositPerByte; + type RemoveItemsLimit = runtime_common::constants::assets::RemoveItemsLimit; + type RuntimeEvent = RuntimeEvent; + type StringLimit = runtime_common::constants::assets::StringLimit; + type WeightInfo = weights::pallet_assets::WeightInfo; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + pub MaxCurrenciesLength: u32 = 100; + pub MaxNameLength: u32 = 100; + pub MaxSymbolLength: u32 = 100; + pub AssetLocation: Location = xcm::v4::Junctions::Here.into(); + +} + + +impl pallet_bonded_coins::Config for Runtime { + type CollateralCurrency = Fungibles; + type DepositCurrency = Balances; + type DepositPerCurrency = runtime_common::constants::assets::MetaDepositBase; + type Fungibles = BondedFungibles; + type MaxCurrencies = MaxCurrenciesLength; + type PoolCreateOrigin = EnsureSigned; + type PoolId = AccountId; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + type CollateralAssetId = AssetLocation; + type MaxNameLength = MaxNameLength; + type MaxSymbolLength = MaxSymbolLength; +} + + + impl pallet_inflation::Config for Runtime { type Currency = Balances; type InitialPeriodLength = constants::treasury::InitialPeriodLength; @@ -1085,6 +1136,11 @@ construct_runtime! { AssetSwitchPool1: pallet_asset_switch:: = 48, Fungibles: pallet_assets = 49, + BondedFungibles: pallet_assets:: = 50, + BondingCurvesPallet: pallet_bonded_coins = 51, + + + // KILT Pallets. Start indices 60 to leave room // DELETED: KiltLaunch: kilt_launch = 60, Ctype: ctype = 61, From be943ea6a31d2669a4445426dcfa4f6505c4df3a Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Thu, 19 Sep 2024 10:52:26 +0200 Subject: [PATCH 32/46] mocks --- Cargo.lock | 3 + pallets/pallet-bonded-coins/Cargo.toml | 5 +- pallets/pallet-bonded-coins/src/lib.rs | 72 ++++---- pallets/pallet-bonded-coins/src/mock.rs | 213 +++++++++++++++++++++++ pallets/pallet-bonded-coins/src/types.rs | 3 +- runtimes/peregrine/src/lib.rs | 8 +- 6 files changed, 265 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7df346445..f6c78dcb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7134,12 +7134,15 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "pallet-assets", + "pallet-balances", "parity-scale-codec", "scale-info", "serde", "sp-arithmetic", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", "sp-std", ] diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 272dab1d0..87ceec160 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -26,7 +26,10 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -serde = { workspace = true } +pallet-assets = { workspace = true, features = ["std"] } +pallet-balances = { workspace = true, features = ["std"] } +serde = { workspace = true } +sp-keystore = { workspace = true, features = ["std"] } # Substrate sp-io = { workspace = true } diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index c4e9d02d6..ac4dc5d04 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -25,7 +25,7 @@ pub mod pallet { ensure, pallet_prelude::*, traits::{ - fungible::{Inspect as InspectFungible, MutateHold}, + fungible::{Inspect as InspectFungible, MutateHold}, fungibles::{ metadata::{Inspect as FungiblesInspect, Mutate as FungiblesMetadata}, Create as CreateFungibles, Destroy as DestroyFungibles, Inspect as InspectFungibles, @@ -35,12 +35,13 @@ pub mod pallet { }, Hashable, }; - use sp_std::vec::Vec; use frame_system::{ensure_root, pallet_prelude::*}; use sp_arithmetic::{traits::CheckedDiv, FixedU128}; use sp_runtime::{ - traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, ArithmeticError, BoundedVec, SaturatedConversion + traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, + ArithmeticError, BoundedVec, SaturatedConversion, }; + use sp_std::vec::Vec; use crate::{ curves_parameters::transform_denomination_currency_amount, @@ -48,29 +49,35 @@ pub mod pallet { }; type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; + type AccountIdOf = ::AccountId; - type DepositCurrencyBalanceOf = + + pub(crate) type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; + type CollateralCurrencyBalanceOf = <::CollateralCurrency as InspectFungibles<::AccountId>>::Balance; + type FungiblesBalanceOf = <::Fungibles as InspectFungibles<::AccountId>>::Balance; + type FungiblesAssetIdOf = <::Fungibles as InspectFungibles<::AccountId>>::AssetId; - type PoolDetailsOf = PoolDetails< - ::AccountId, - Curve, - BoundedCurrencyVec - >; + type PoolDetailsOf = + PoolDetails<::AccountId, Curve, BoundedCurrencyVec>; - type CollateralAssetIdOf = <::CollateralCurrency as InspectFungibles<::AccountId>>::AssetId; + type CollateralAssetIdOf = + <::CollateralCurrency as InspectFungibles<::AccountId>>::AssetId; type BoundedCurrencyVec = BoundedVec, ::MaxCurrencies>; - type BoundedNameVec = BoundedVec::MaxNameLength>; - type BoundedSymbolVec = BoundedVec::MaxSymbolLength>; - type TokenMetaOf = TokenMeta, FungiblesAssetIdOf, BoundedSymbolVec, BoundedNameVec>; + type CurrencyNameOf = BoundedVec::MaxStringLength>; + + type CurrencySymbolOf = BoundedVec::MaxStringLength>; + + type TokenMetaOf = + TokenMeta, FungiblesAssetIdOf, CurrencySymbolOf, CurrencyNameOf>; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -91,16 +98,14 @@ pub mod pallet { #[pallet::constant] type MaxCurrencies: Get; /// The deposit required for each bonded currency. - - #[pallet::constant] - type MaxSymbolLength: Get; #[pallet::constant] - type MaxNameLength: Get; + type MaxStringLength: Get; + /// The deposit required for each bonded currency. #[pallet::constant] type DepositPerCurrency: Get>; - + /// The asset id of the collateral currency. type CollateralAssetId: Get>; /// Who can create new bonded currency pools. @@ -171,7 +176,7 @@ pub mod pallet { FungiblesBalanceOf: TryInto>, { #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn create_pool( origin: OriginFor, curve: Curve, @@ -230,7 +235,7 @@ pub mod pallet { } #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn mint_into( origin: OriginFor, pool_id: T::PoolId, @@ -258,7 +263,13 @@ pub mod pallet { )?; // withdraw the collateral and put it in the deposit account - let real_costs = T::CollateralCurrency::transfer(T::CollateralAssetId::get(), &who, &pool_id.into(), cost, Preservation::Preserve)?; + let real_costs = T::CollateralCurrency::transfer( + T::CollateralAssetId::get(), + &who, + &pool_id.into(), + cost, + Preservation::Preserve, + )?; // fail if cost > max_cost ensure!(real_costs <= max_cost, Error::::Slippage); @@ -269,7 +280,7 @@ pub mod pallet { } #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn burn_into( origin: OriginFor, pool_id: T::PoolId, @@ -315,7 +326,7 @@ pub mod pallet { } #[pallet::call_index(3)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn swap_into( origin: OriginFor, pool_id: T::PoolId, @@ -426,7 +437,7 @@ pub mod pallet { } #[pallet::call_index(4)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn set_lock(origin: OriginFor, pool_id: T::PoolId, lock: Locks) -> DispatchResult { let who = ensure_signed(origin)?; @@ -446,7 +457,7 @@ pub mod pallet { } #[pallet::call_index(5)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn unlock(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { let who = ensure_signed(origin)?; @@ -466,7 +477,7 @@ pub mod pallet { } #[pallet::call_index(6)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { let who = ensure_signed(origin)?; @@ -481,7 +492,7 @@ pub mod pallet { } #[pallet::call_index(7)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn force_start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { ensure_root(origin)?; @@ -495,7 +506,7 @@ pub mod pallet { // todo: check if we really need that tx. #[pallet::call_index(8)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn destroy_accounts(origin: OriginFor, pool_id: T::PoolId, max_accounts: u32) -> DispatchResult { ensure_signed(origin)?; @@ -519,7 +530,7 @@ pub mod pallet { } #[pallet::call_index(9)] - #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] + #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn finish_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { ensure_signed(origin)?; @@ -530,7 +541,8 @@ pub mod pallet { let pool_account = pool_id.clone().into(); - let total_collateral_issuance = T::CollateralCurrency::total_balance(T::CollateralAssetId::get(), &pool_account); + let total_collateral_issuance = + T::CollateralCurrency::total_balance(T::CollateralAssetId::get(), &pool_account); T::CollateralCurrency::transfer( T::CollateralAssetId::get(), diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 8b1378917..7424e9534 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1 +1,214 @@ +#[cfg(test)] +pub mod runtime { + use crate::{Config, DepositCurrencyBalanceOf}; + use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU32}, + weights::constants::RocksDbWeight, + }; + use frame_system::{EnsureRoot, EnsureSigned}; + use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, + }; + pub type Block = frame_system::mocking::MockBlock; + pub type Hash = sp_core::H256; + pub type Balance = u128; + pub type Signature = MultiSignature; + pub type AccountPublic = ::Signer; + pub type AccountId = ::AccountId; + + pub(crate) const ACCOUNT_00: AccountId = AccountId::new([1u8; 32]); + pub(super) const DEFAULT_COLLATERAL_CURRENCY: (u32, AccountId, Balance, [u8; 4], u8) = + (0, ACCOUNT_00, 1_000_000_000_000, [85, 83, 68, 84], 10); + + frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets, + BondingPallet: crate, + } + ); + + parameter_types! { + pub const SS58Prefix: u8 = 38; + pub const BlockHashCount: u64 = 250; + } + + impl frame_system::Config for Test { + type RuntimeTask = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Block = Block; + type Nonce = u64; + + type Hash = Hash; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = (); + type BlockHashCount = BlockHashCount; + type DbWeight = RocksDbWeight; + type Version = (); + + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type BaseCallFilter = frame_support::traits::Everything; + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; + } + + parameter_types! { + pub const ExistentialDeposit: Balance = 500; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; + } + + impl pallet_balances::Config for Test { + type RuntimeFreezeReason = (); + type FreezeIdentifier = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxFreezes = (); + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + } + + parameter_types! { + pub const StringLimit: u32 = 50; + + } + + impl pallet_assets::Config for Test { + type RuntimeEvent = (); + type Balance = Balance; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = EnsureSigned; + type ForceOrigin = EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = StringLimit; + type Freezer = (); + type WeightInfo = (); + type CallbackHandle = (); + type Extra = (); + type RemoveItemsLimit = ConstU32<5>; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); + } + + parameter_types! { + pub const CurrencyDeposit: Balance = 500; + pub const MaxCurrencies: u32 = 50; + pub const CollateralAssetId: u32 = 0; + } + + impl Config for Test { + type DepositCurrency = Balances; + type CollateralAssetId = CollateralAssetId; + type CollateralCurrency = Assets; + type DepositPerCurrency = CurrencyDeposit; + type Fungibles = Assets; + type MaxCurrencies = MaxCurrencies; + type MaxStringLength = StringLimit; + type PoolCreateOrigin = EnsureSigned; + type PoolId = AccountId; + type RuntimeEvent = (); + type RuntimeHoldReason = RuntimeHoldReason; + } + + #[derive(Clone, Default)] + pub(crate) struct ExtBuilder { + balances: Vec<(AccountId, DepositCurrencyBalanceOf)>, + // id, owner, balance amount, Name, Decimals + bonded_currency: Vec<(u32, AccountId, Balance, [u8; 4], u8)>, + } + + impl ExtBuilder { + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, DepositCurrencyBalanceOf)>) -> Self { + self.balances = balances; + self + } + + pub(crate) fn with_currencies(mut self, bonded_currency: Vec<(u32, AccountId, Balance, [u8; 4], u8)>) -> Self { + self.bonded_currency = bonded_currency; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: self.balances.clone(), + } + .assimilate_storage(&mut storage) + .expect("assimilate should not fail"); + + pallet_assets::GenesisConfig:: { + + assets: self + .bonded_currency + .clone() + .into_iter() + // id, admin, is_sufficient, min_balance + .map(|(id, acc, _, _, _)| (id, acc, false, 1)) + .collect(), + + metadata: self + .bonded_currency + .clone() + .into_iter() + // id, name, symbol, decimals + .map(|(id, _, _, name, denomination)| (id, name.clone().into(), name.into(), denomination)) + .collect(), + + accounts: self + .bonded_currency + .into_iter() + // id, owner, balance + .map(|(id, acc, balance, _, _)| (id, acc, balance)) + .collect(), + } + .assimilate_storage(&mut storage) + .expect("assimilate should not fail"); + + let mut ext = sp_io::TestExternalities::new(storage); + + ext.execute_with(|| {}); + + ext + } + + #[cfg(feature = "runtime-benchmarks")] + pub(crate) fn build_with_keystore(self) -> sp_io::TestExternalities { + use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; + use sp_std::sync::Arc; + + let mut ext = self.build(); + + let keystore = MemoryKeystore::new(); + ext.register_extension(KeystoreExt(Arc::new(keystore))); + + ext + } + } +} diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index e2e07bc94..83294919e 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -45,8 +45,7 @@ pub struct PoolDetails { pub transferable: bool, } -impl - PoolDetails +impl PoolDetails where AccountId: PartialEq, { diff --git a/runtimes/peregrine/src/lib.rs b/runtimes/peregrine/src/lib.rs index d9d728fc2..8e7b12657 100644 --- a/runtimes/peregrine/src/lib.rs +++ b/runtimes/peregrine/src/lib.rs @@ -734,9 +734,8 @@ parameter_types! { pub MaxNameLength: u32 = 100; pub MaxSymbolLength: u32 = 100; pub AssetLocation: Location = xcm::v4::Junctions::Here.into(); - -} +} impl pallet_bonded_coins::Config for Runtime { type CollateralCurrency = Fungibles; @@ -749,12 +748,9 @@ impl pallet_bonded_coins::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeHoldReason = RuntimeHoldReason; type CollateralAssetId = AssetLocation; - type MaxNameLength = MaxNameLength; - type MaxSymbolLength = MaxSymbolLength; + type MaxStringLength = runtime_common::constants::assets::StringLimit; } - - impl pallet_inflation::Config for Runtime { type Currency = Balances; type InitialPeriodLength = constants::treasury::InitialPeriodLength; From dba92663b70619ab5036096d153d1c4422636b98 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Thu, 19 Sep 2024 18:16:58 +0200 Subject: [PATCH 33/46] test + fixes for create pool --- pallets/pallet-bonded-coins/Cargo.toml | 1 + pallets/pallet-bonded-coins/src/lib.rs | 78 +++++++++++---- pallets/pallet-bonded-coins/src/mock.rs | 80 +++++++++------- .../src/tests/create_pool.rs | 95 +++++++++++++++++++ pallets/pallet-bonded-coins/src/tests/mod.rs | 2 + pallets/pallet-bonded-coins/src/types.rs | 3 +- runtimes/peregrine/src/lib.rs | 1 + 7 files changed, 206 insertions(+), 54 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/tests/create_pool.rs diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index 87ceec160..d8b38549c 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -40,6 +40,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", ] std = [ "frame-benchmarking/std", diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index ac4dc5d04..67299a5ad 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -23,7 +23,7 @@ pub mod pallet { use frame_support::{ dispatch::DispatchResult, ensure, - pallet_prelude::*, + pallet_prelude::{OptionQuery, *}, traits::{ fungible::{Inspect as InspectFungible, MutateHold}, fungibles::{ @@ -36,12 +36,13 @@ pub mod pallet { Hashable, }; use frame_system::{ensure_root, pallet_prelude::*}; + use parity_scale_codec::FullCodec; use sp_arithmetic::{traits::CheckedDiv, FixedU128}; use sp_runtime::{ - traits::{CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, + traits::{CheckedAdd, CheckedSub, One, Saturating, StaticLookup, Zero}, ArithmeticError, BoundedVec, SaturatedConversion, }; - use sp_std::vec::Vec; + use sp_std::{iter::Iterator, vec::Vec}; use crate::{ curves_parameters::transform_denomination_currency_amount, @@ -72,12 +73,11 @@ pub mod pallet { type BoundedCurrencyVec = BoundedVec, ::MaxCurrencies>; - type CurrencyNameOf = BoundedVec::MaxStringLength>; + pub(crate) type CurrencyNameOf = BoundedVec::MaxStringLength>; - type CurrencySymbolOf = BoundedVec::MaxStringLength>; + pub(crate) type CurrencySymbolOf = BoundedVec::MaxStringLength>; - type TokenMetaOf = - TokenMeta, FungiblesAssetIdOf, CurrencySymbolOf, CurrencyNameOf>; + type TokenMetaOf = TokenMeta, CurrencySymbolOf, CurrencyNameOf>; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -89,7 +89,7 @@ pub mod pallet { /// The currency used as collateral for minting bonded tokens. type CollateralCurrency: MutateFungibles; /// Implementation of creating and managing new fungibles - type Fungibles: CreateFungibles + type Fungibles: CreateFungibles + DestroyFungibles + FungiblesMetadata + FungiblesInspect @@ -106,6 +106,10 @@ pub mod pallet { #[pallet::constant] type DepositPerCurrency: Get>; + /// The base deposit required to create a new pool, primarily to cover the ED of the pool account. + #[pallet::constant] + type BaseDeposit: Get>; + /// The asset id of the collateral currency. type CollateralAssetId: Get>; /// Who can create new bonded currency pools. @@ -113,6 +117,17 @@ pub mod pallet { /// The type used for pool ids type PoolId: Parameter + MaxEncodedLen + From<[u8; 32]> + Into; + type AssetId: FullCodec + + Clone + + Eq + + PartialEq + + sp_std::fmt::Debug + + scale_info::TypeInfo + + MaxEncodedLen + + Default + + Saturating + + One; + type RuntimeHoldReason: From; } @@ -126,6 +141,10 @@ pub mod pallet { #[pallet::getter(fn pools)] pub(crate) type Pools = StorageMap<_, Twox64Concat, T::PoolId, PoolDetailsOf, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn nex_asset_id)] + pub(crate) type NextAssetId = StorageValue<_, FungiblesAssetIdOf, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -189,38 +208,50 @@ pub mod pallet { // ensure origin is PoolCreateOrigin let who = T::PoolCreateOrigin::ensure_origin(origin)?; + let currency_length = currencies.len(); + ensure!( - (1..=T::MaxCurrencies::get().saturated_into()).contains(¤cies.len()), + (1..=T::MaxCurrencies::get().saturated_into()).contains(¤cy_length), Error::::CurrenciesNumber ); - let currency_ids = BoundedVec::truncate_from(currencies.iter().map(|c| c.id.clone()).collect()); + let current_asset_id = NextAssetId::::get(); + + let (currency_ids_vec, next_asset_id) = + Self::generate_sequential_asset_ids(current_asset_id, currency_length); + + // update the storage for the next tx. + NextAssetId::::set(next_asset_id); + + // Should never fail. + let currency_ids = + BoundedVec::, T::MaxCurrencies>::try_from(currency_ids_vec.clone()) + .map_err(|_| Error::::CurrenciesNumber)?; let pool_id = T::PoolId::from(currency_ids.blake2_256()); T::DepositCurrency::hold( &T::RuntimeHoldReason::from(HoldReason::Deposit), &who, - T::DepositPerCurrency::get() - .saturating_mul(currencies.len().saturated_into()) - .saturated_into(), + T::BaseDeposit::get().saturating_add( + T::DepositPerCurrency::get() + .saturating_mul(currency_length.saturated_into()) + .saturated_into(), + ), )?; - for entry in currencies { - let asset_id = entry.id.clone(); - - // create new asset class; fail if it already exists + for (idx, entry) in currencies.iter().enumerate() { + let asset_id = currency_ids_vec.get(idx).ok_or(Error::::CurrenciesNumber)?; T::Fungibles::create(asset_id.clone(), pool_id.clone().into(), false, entry.min_balance)?; // set metadata for new asset class T::Fungibles::set( - asset_id, + asset_id.clone(), &pool_id.clone().into(), entry.name.clone().into(), entry.symbol.clone().into(), entry.decimals, )?; - // TODO: use fungibles::roles::ResetTeam to update currency admin } @@ -731,5 +762,14 @@ pub mod pallet { } Ok(()) } + + fn generate_sequential_asset_ids(mut start_id: T::AssetId, count: usize) -> (Vec, T::AssetId) { + let mut currency_ids_vec = Vec::new(); + for _ in 0..count { + currency_ids_vec.push(start_id.clone()); + start_id = start_id.saturating_plus_one(); + } + (currency_ids_vec, start_id) + } } } diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 7424e9534..63fc6f0a9 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1,27 +1,39 @@ +use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU32}, + weights::constants::RocksDbWeight, + Hashable, +}; +use frame_system::{EnsureRoot, EnsureSigned}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, +}; + +pub type Hash = sp_core::H256; +pub type Balance = u128; +pub type AssetId = u32; +pub type Signature = MultiSignature; +pub type AccountPublic = ::Signer; +pub type AccountId = ::AccountId; + #[cfg(test)] pub mod runtime { + + use super::*; + use crate::{Config, DepositCurrencyBalanceOf}; - use frame_support::{ - parameter_types, - traits::{ConstU128, ConstU32}, - weights::constants::RocksDbWeight, - }; - use frame_system::{EnsureRoot, EnsureSigned}; - use sp_runtime::{ - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, - BuildStorage, MultiSignature, - }; pub type Block = frame_system::mocking::MockBlock; - pub type Hash = sp_core::H256; - pub type Balance = u128; - pub type Signature = MultiSignature; - pub type AccountPublic = ::Signer; - pub type AccountId = ::AccountId; - pub(crate) const ACCOUNT_00: AccountId = AccountId::new([1u8; 32]); - pub(super) const DEFAULT_COLLATERAL_CURRENCY: (u32, AccountId, Balance, [u8; 4], u8) = - (0, ACCOUNT_00, 1_000_000_000_000, [85, 83, 68, 84], 10); + pub(crate) const DEFAULT_COLLATERAL_CURRENCY: (u32, AccountId, Balance, [u8; 4], u8) = + (u32::MAX, ACCOUNT_00, 1_000_000_000_000, [85, 83, 68, 84], 10); + + pub const UNIT: Balance = 10u128.pow(15); + + pub(crate) fn calculate_pool_id(currencies: Vec) -> AccountId { + AccountId::from(currencies.blake2_256()) + } frame_support::construct_runtime!( pub enum Test @@ -97,30 +109,28 @@ pub mod runtime { impl pallet_assets::Config for Test { type RuntimeEvent = (); type Balance = Balance; - type AssetId = u32; - type AssetIdParameter = u32; + type AssetId = AssetId; + type AssetIdParameter = AssetId; type Currency = Balances; type CreateOrigin = EnsureSigned; type ForceOrigin = EnsureRoot; - type AssetDeposit = ConstU128<1>; + type AssetDeposit = ConstU128<0>; type AssetAccountDeposit = ConstU128<10>; - type MetadataDepositBase = ConstU128<1>; - type MetadataDepositPerByte = ConstU128<1>; - type ApprovalDeposit = ConstU128<1>; + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type ApprovalDeposit = ConstU128<0>; type StringLimit = StringLimit; type Freezer = (); type WeightInfo = (); type CallbackHandle = (); type Extra = (); type RemoveItemsLimit = ConstU32<5>; - #[cfg(feature = "runtime-benchmarks")] - type BenchmarkHelper = (); } parameter_types! { pub const CurrencyDeposit: Balance = 500; pub const MaxCurrencies: u32 = 50; - pub const CollateralAssetId: u32 = 0; + pub const CollateralAssetId: u32 = u32::MAX; } impl Config for Test { @@ -135,13 +145,15 @@ pub mod runtime { type PoolId = AccountId; type RuntimeEvent = (); type RuntimeHoldReason = RuntimeHoldReason; + type AssetId = AssetId; + type BaseDeposit = ExistentialDeposit; } #[derive(Clone, Default)] pub(crate) struct ExtBuilder { balances: Vec<(AccountId, DepositCurrencyBalanceOf)>, // id, owner, balance amount, Name, Decimals - bonded_currency: Vec<(u32, AccountId, Balance, [u8; 4], u8)>, + bonded_currency: Vec<(AssetId, AccountId, Balance, [u8; 4], u8)>, } impl ExtBuilder { @@ -150,7 +162,10 @@ pub mod runtime { self } - pub(crate) fn with_currencies(mut self, bonded_currency: Vec<(u32, AccountId, Balance, [u8; 4], u8)>) -> Self { + pub(crate) fn with_currencies( + mut self, + bonded_currency: Vec<(AssetId, AccountId, Balance, [u8; 4], u8)>, + ) -> Self { self.bonded_currency = bonded_currency; self } @@ -164,15 +179,14 @@ pub mod runtime { .expect("assimilate should not fail"); pallet_assets::GenesisConfig:: { - assets: self .bonded_currency .clone() .into_iter() - // id, admin, is_sufficient, min_balance + // id, admin, is_sufficient, min_balance .map(|(id, acc, _, _, _)| (id, acc, false, 1)) .collect(), - + metadata: self .bonded_currency .clone() @@ -180,7 +194,7 @@ pub mod runtime { // id, name, symbol, decimals .map(|(id, _, _, name, denomination)| (id, name.clone().into(), name.into(), denomination)) .collect(), - + accounts: self .bonded_currency .into_iter() diff --git a/pallets/pallet-bonded-coins/src/tests/create_pool.rs b/pallets/pallet-bonded-coins/src/tests/create_pool.rs new file mode 100644 index 000000000..a8c59f77f --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/create_pool.rs @@ -0,0 +1,95 @@ +use frame_support::{ + assert_ok, + traits::fungibles::{metadata::Inspect as InspectMetaData, roles::Inspect as InspectRoles}, +}; +use sp_arithmetic::FixedU128; +use sp_runtime::BoundedVec; + +use crate::{ + curves_parameters::LinearBondingFunctionParameters, + mock::{runtime::*, AccountId}, + types::{Curve, PoolStatus, TokenMeta}, + NextAssetId, Pools, +}; + +#[test] +fn test_create_pool_linear_bonding_curve() { + // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x + let m = FixedU128::from_u32(2); + let n = FixedU128::from_u32(3); + let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + let state = PoolStatus::Active; + + let token_meta = TokenMeta { + decimals: 10, + name: BoundedVec::try_from("BTC".as_bytes().to_vec()).expect("creating name should not fail"), + symbol: BoundedVec::try_from("BTC".as_bytes().to_vec()).expect("creating symbol should not fail"), + min_balance: 1, + }; + + let currencies = BoundedVec::try_from(vec![token_meta.clone()]).expect("creating currencies should not fail"); + + ExtBuilder::default() + .with_balances(vec![(ACCOUNT_00, UNIT * 10)]) + .with_currencies(vec![DEFAULT_COLLATERAL_CURRENCY]) + .build() + .execute_with(|| { + let current_asset_id = NextAssetId::::get(); + // Create a pool with the linear bonding curve + assert_ok!(BondingPallet::create_pool( + RuntimeOrigin::signed(ACCOUNT_00), + curve.clone(), + currencies, + false, + state.clone(), + ACCOUNT_00 + )); + + let count_pools = Pools::::iter().count(); + + // we should have one additional pool + assert_eq!(count_pools, 1); + + let pool_id = calculate_pool_id(vec![current_asset_id]); + + let details = Pools::::get(&pool_id).expect("Pool should exist"); + + // Do some basic checks on the [PoolDetails] struct. + assert_eq!(details.manager, ACCOUNT_00); + assert_eq!(details.curve, curve); + assert_eq!(details.state, state); + // we have created only one currency + assert_eq!(details.bonded_currencies.len(), 1); + assert_eq!(details.bonded_currencies[0], 0); + assert_eq!(details.transferable, false); + + // The next possible asset id should be 1 + assert_eq!(NextAssetId::::get(), 1); + + let currency_id = details.bonded_currencies[0]; + + // created metadata should match + let decimals = >::decimals(currency_id); + let name = >::name(currency_id); + let symbol = >::symbol(currency_id); + + assert_eq!(decimals, token_meta.decimals); + assert_eq!(name, token_meta.name.into_inner()); + assert_eq!(symbol, token_meta.symbol.into_inner()); + + // check roles of created assets + let owner = >::owner(currency_id).expect("Owner should be set"); + let admin = >::admin(currency_id).expect("Admin should be set"); + let issuer = >::issuer(currency_id).expect("Issuer should be set"); + let freezer = >::freezer(currency_id).expect("Freezer should be set"); + + assert_eq!(owner, pool_id); + assert_eq!(admin, pool_id); + assert_eq!(issuer, pool_id); + assert_eq!(freezer, pool_id); + + // Supply should be zero + let total_supply = Assets::total_supply(currency_id); + assert_eq!(total_supply, 0); + }); +} diff --git a/pallets/pallet-bonded-coins/src/tests/mod.rs b/pallets/pallet-bonded-coins/src/tests/mod.rs index 0451a780e..b5e0c4b15 100644 --- a/pallets/pallet-bonded-coins/src/tests/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/mod.rs @@ -1,2 +1,4 @@ +mod create_pool; mod curves_parameters; + mod types; diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 83294919e..56db5cf4f 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -95,8 +95,7 @@ where } #[derive(Debug, Encode, Decode, Clone, PartialEq, TypeInfo)] -pub struct TokenMeta { - pub id: AssetId, +pub struct TokenMeta { pub name: Name, pub symbol: Symbol, pub decimals: u8, diff --git a/runtimes/peregrine/src/lib.rs b/runtimes/peregrine/src/lib.rs index 8e7b12657..05e614474 100644 --- a/runtimes/peregrine/src/lib.rs +++ b/runtimes/peregrine/src/lib.rs @@ -749,6 +749,7 @@ impl pallet_bonded_coins::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type CollateralAssetId = AssetLocation; type MaxStringLength = runtime_common::constants::assets::StringLimit; + type AssetId = u32; } impl pallet_inflation::Config for Runtime { From a44a90425d117bfc1520bb5a5cb67ed561ceb719 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Fri, 20 Sep 2024 11:17:57 +0200 Subject: [PATCH 34/46] test + fixes for create pool --- pallets/pallet-bonded-coins/src/lib.rs | 19 ++++++++----------- pallets/pallet-bonded-coins/src/tests/mod.rs | 4 +--- .../tests/{ => transactions}/create_pool.rs | 6 +++++- .../src/tests/transactions/mint_into.rs | 0 .../src/tests/transactions/mod.rs | 2 ++ .../tests/{ => types}/curves_parameters.rs | 0 .../src/tests/types/mod.rs | 2 ++ .../src/tests/{ => types}/types.rs | 0 8 files changed, 18 insertions(+), 15 deletions(-) rename pallets/pallet-bonded-coins/src/tests/{ => transactions}/create_pool.rs (93%) create mode 100644 pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/transactions/mod.rs rename pallets/pallet-bonded-coins/src/tests/{ => types}/curves_parameters.rs (100%) create mode 100644 pallets/pallet-bonded-coins/src/tests/types/mod.rs rename pallets/pallet-bonded-coins/src/tests/{ => types}/types.rs (100%) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 67299a5ad..1f113febc 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -32,6 +32,7 @@ pub mod pallet { Mutate as MutateFungibles, }, tokens::{Fortitude, Precision, Preservation}, + AccountTouch, }, Hashable, }; @@ -87,13 +88,14 @@ pub mod pallet { /// The currency used for storage deposits. type DepositCurrency: MutateHold; /// The currency used as collateral for minting bonded tokens. - type CollateralCurrency: MutateFungibles; + type CollateralCurrency: MutateFungibles + + AccountTouch, Self::AccountId>; /// Implementation of creating and managing new fungibles type Fungibles: CreateFungibles + DestroyFungibles + FungiblesMetadata + FungiblesInspect - + MutateFungibles; + + MutateFungibles>; /// The maximum number of currencies allowed for a single pool. #[pallet::constant] type MaxCurrencies: Get; @@ -190,10 +192,7 @@ pub mod pallet { impl Hooks> for Pallet {} #[pallet::call] - impl Pallet - where - FungiblesBalanceOf: TryInto>, - { + impl Pallet { #[pallet::call_index(0)] #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn create_pool( @@ -203,7 +202,6 @@ pub mod pallet { tradable: bool, // Todo: why do we need that? state: PoolStatus, pool_manager: AccountIdOf, // Todo: maybe change that back to owner. - // currency_admin: Option> TODO: use this to set currency admin ) -> DispatchResult { // ensure origin is PoolCreateOrigin let who = T::PoolCreateOrigin::ensure_origin(origin)?; @@ -255,6 +253,8 @@ pub mod pallet { // TODO: use fungibles::roles::ResetTeam to update currency admin } + T::CollateralCurrency::touch(T::CollateralAssetId::get(), &pool_id.clone().into(), &who)?; + Pools::::set( &pool_id, Some(PoolDetails::new(pool_manager, curve, currency_ids, tradable, state)), @@ -591,10 +591,7 @@ pub mod pallet { } } - impl Pallet - where - FungiblesBalanceOf: TryInto>, - CollateralCurrencyBalanceOf: TryInto, + impl Pallet { /// save usage of currency_ids. pub fn get_collateral_diff( diff --git a/pallets/pallet-bonded-coins/src/tests/mod.rs b/pallets/pallet-bonded-coins/src/tests/mod.rs index b5e0c4b15..5daa154b5 100644 --- a/pallets/pallet-bonded-coins/src/tests/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/mod.rs @@ -1,4 +1,2 @@ -mod create_pool; -mod curves_parameters; - mod types; +mod transactions; diff --git a/pallets/pallet-bonded-coins/src/tests/create_pool.rs b/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs similarity index 93% rename from pallets/pallet-bonded-coins/src/tests/create_pool.rs rename to pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs index a8c59f77f..465c40cb3 100644 --- a/pallets/pallet-bonded-coins/src/tests/create_pool.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs @@ -1,6 +1,6 @@ use frame_support::{ assert_ok, - traits::fungibles::{metadata::Inspect as InspectMetaData, roles::Inspect as InspectRoles}, + traits::{fungibles::{metadata::Inspect as InspectMetaData, roles::Inspect as InspectRoles}, ContainsPair}, }; use sp_arithmetic::FixedU128; use sp_runtime::BoundedVec; @@ -91,5 +91,9 @@ fn test_create_pool_linear_bonding_curve() { // Supply should be zero let total_supply = Assets::total_supply(currency_id); assert_eq!(total_supply, 0); + + // check if pool_account is created. + assert!(Assets::contains(&DEFAULT_COLLATERAL_CURRENCY.0, &pool_id)); + }); } diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs new file mode 100644 index 000000000..e69de29bb diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs new file mode 100644 index 000000000..21cdf2d84 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs @@ -0,0 +1,2 @@ +mod create_pool; +mod mint_into; diff --git a/pallets/pallet-bonded-coins/src/tests/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs similarity index 100% rename from pallets/pallet-bonded-coins/src/tests/curves_parameters.rs rename to pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs diff --git a/pallets/pallet-bonded-coins/src/tests/types/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/mod.rs new file mode 100644 index 000000000..0451a780e --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types/mod.rs @@ -0,0 +1,2 @@ +mod curves_parameters; +mod types; diff --git a/pallets/pallet-bonded-coins/src/tests/types.rs b/pallets/pallet-bonded-coins/src/tests/types/types.rs similarity index 100% rename from pallets/pallet-bonded-coins/src/tests/types.rs rename to pallets/pallet-bonded-coins/src/tests/types/types.rs From a7f2cec8bf105296cfe43ad8b393f95b64fde9dc Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Fri, 20 Sep 2024 15:46:08 +0200 Subject: [PATCH 35/46] test for mint_into. Refactor mocking --- .../src/curves_parameters.rs | 7 +- pallets/pallet-bonded-coins/src/lib.rs | 5 +- pallets/pallet-bonded-coins/src/mock.rs | 153 +++++++++++++----- pallets/pallet-bonded-coins/src/tests/mod.rs | 2 +- .../src/tests/transactions/create_pool.rs | 30 ++-- .../src/tests/transactions/mint_into.rs | 65 ++++++++ .../src/tests/types/curves_parameters.rs | 4 +- 7 files changed, 202 insertions(+), 64 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index 0c0278ab3..a58cf57e1 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -59,9 +59,12 @@ where { /// F(x) = m * x + n fn get_value(&self, x: F) -> Result { - let mx = self.m.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; + let x2 = Self::get_power_2(x)?; + + let mx2 = self.m.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; + let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; - let result = mx.checked_add(&self.n).ok_or(ArithmeticError::Overflow)?; + let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; Ok(result) } } diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 1f113febc..282e2690e 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -66,7 +66,7 @@ pub mod pallet { type FungiblesAssetIdOf = <::Fungibles as InspectFungibles<::AccountId>>::AssetId; - type PoolDetailsOf = + pub(crate) type PoolDetailsOf = PoolDetails<::AccountId, Curve, BoundedCurrencyVec>; type CollateralAssetIdOf = @@ -591,8 +591,7 @@ pub mod pallet { } } - impl Pallet - { + impl Pallet { /// save usage of currency_ids. pub fn get_collateral_diff( kind: DiffKind, diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 63fc6f0a9..fc097f507 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1,13 +1,20 @@ use frame_support::{ parameter_types, - traits::{ConstU128, ConstU32}, + traits::{fungible::Mutate, AccountTouch, ConstU128, ConstU32}, weights::constants::RocksDbWeight, Hashable, }; use frame_system::{EnsureRoot, EnsureSigned}; +use sp_arithmetic::FixedU128; use sp_runtime::{ traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, - BuildStorage, MultiSignature, + BoundedVec, BuildStorage, MultiSignature, +}; + +use crate::{ + curves_parameters::LinearBondingFunctionParameters, + types::{Curve, Locks, PoolStatus}, + Config, DepositCurrencyBalanceOf, PoolDetailsOf, }; pub type Hash = sp_core::H256; @@ -17,22 +24,57 @@ pub type Signature = MultiSignature; pub type AccountPublic = ::Signer; pub type AccountId = ::AccountId; +// accounts +// should not be used for testing +const ACCOUNT_99: AccountId = AccountId::new([99u8; 32]); +pub(crate) const ACCOUNT_00: AccountId = AccountId::new([0u8; 32]); +pub(crate) const ACCOUNT_01: AccountId = AccountId::new([1u8; 32]); + +// assets +pub(crate) const DEFAULT_BONDED_CURRENCY_ID: AssetId = 0; +pub(crate) const DEFAULT_COLLATERAL_CURRENCY_ID: AssetId = AssetId::MAX; +pub(crate) const DEFAULT_COLLATERAL_DENOMINATION: u8 = 10; +pub(crate) const DEFAULT_BONDED_DENOMINATION: u8 = 10; +pub const UNIT_NATIVE: Balance = 10u128.pow(15); + +// helper functions + +pub(crate) fn get_linear_bonding_curve() -> Curve { + let m = FixedU128::from_u32(2); + let n = FixedU128::from_u32(3); + Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }) +} + +pub(crate) fn calculate_pool_id(currencies: Vec) -> AccountId { + AccountId::from(currencies.blake2_256()) +} + +pub(crate) fn get_currency_unit(denomination: u8) -> Balance { + 10u128.pow(denomination as u32) +} + #[cfg(test)] pub mod runtime { use super::*; - use crate::{Config, DepositCurrencyBalanceOf}; - pub type Block = frame_system::mocking::MockBlock; - pub(crate) const ACCOUNT_00: AccountId = AccountId::new([1u8; 32]); - pub(crate) const DEFAULT_COLLATERAL_CURRENCY: (u32, AccountId, Balance, [u8; 4], u8) = - (u32::MAX, ACCOUNT_00, 1_000_000_000_000, [85, 83, 68, 84], 10); - pub const UNIT: Balance = 10u128.pow(15); - - pub(crate) fn calculate_pool_id(currencies: Vec) -> AccountId { - AccountId::from(currencies.blake2_256()) + pub fn calculate_pool_details( + currencies: Vec, + manager: AccountId, + transferable: bool, + curve: Curve, + state: PoolStatus, + ) -> PoolDetailsOf { + let bonded_currencies = BoundedVec::truncate_from(currencies); + PoolDetailsOf:: { + curve, + manager, + transferable, + bonded_currencies, + state, + } } frame_support::construct_runtime!( @@ -115,7 +157,7 @@ pub mod runtime { type CreateOrigin = EnsureSigned; type ForceOrigin = EnsureRoot; type AssetDeposit = ConstU128<0>; - type AssetAccountDeposit = ConstU128<10>; + type AssetAccountDeposit = ConstU128<0>; type MetadataDepositBase = ConstU128<0>; type MetadataDepositPerByte = ConstU128<0>; type ApprovalDeposit = ConstU128<0>; @@ -151,55 +193,76 @@ pub mod runtime { #[derive(Clone, Default)] pub(crate) struct ExtBuilder { - balances: Vec<(AccountId, DepositCurrencyBalanceOf)>, - // id, owner, balance amount, Name, Decimals - bonded_currency: Vec<(AssetId, AccountId, Balance, [u8; 4], u8)>, + native_assets: Vec<(AccountId, DepositCurrencyBalanceOf)>, + currencies: Vec>, + bonded_balance: Vec<(AssetId, AccountId, Balance)>, + meta_data: Vec<(AssetId, u8)>, + pools: Vec<(AccountId, PoolDetailsOf)>, + collateral_asset_id: AssetId, } impl ExtBuilder { - pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, DepositCurrencyBalanceOf)>) -> Self { - self.balances = balances; + pub(crate) fn with_native_balances( + mut self, + native_assets: Vec<(AccountId, DepositCurrencyBalanceOf)>, + ) -> Self { + self.native_assets = native_assets; self } - pub(crate) fn with_currencies( - mut self, - bonded_currency: Vec<(AssetId, AccountId, Balance, [u8; 4], u8)>, - ) -> Self { - self.bonded_currency = bonded_currency; + pub(crate) fn with_collateral_asset_id(mut self, collateral_asset_id: AssetId) -> Self { + self.collateral_asset_id = collateral_asset_id; self } - pub(crate) fn build(self) -> sp_io::TestExternalities { + pub(crate) fn with_currencies(mut self, currencies: Vec>) -> Self { + self.currencies = currencies; + self + } + + pub(crate) fn with_metadata(mut self, meta_data: Vec<(AssetId, u8)>) -> Self { + self.meta_data = meta_data; + self + } + + pub(crate) fn with_pools(mut self, pools: Vec<(AccountId, PoolDetailsOf)>) -> Self { + self.pools = pools; + self + } + + pub(crate) fn with_bonded_balance(mut self, bonded_balance: Vec<(AssetId, AccountId, Balance)>) -> Self { + self.bonded_balance = bonded_balance; + self + } + + pub(crate) fn build(mut self) -> sp_io::TestExternalities { let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { - balances: self.balances.clone(), + balances: self.native_assets.clone(), } .assimilate_storage(&mut storage) .expect("assimilate should not fail"); + self.currencies.push(vec![self.collateral_asset_id]); + pallet_assets::GenesisConfig:: { assets: self - .bonded_currency - .clone() + .currencies .into_iter() - // id, admin, is_sufficient, min_balance - .map(|(id, acc, _, _, _)| (id, acc, false, 1)) + .map(|x| { + let admin = calculate_pool_id(x.clone()); + x.into_iter() + .map(|id| (id, admin.clone(), false, 1u128)) + .collect::>() + }) + .flatten() .collect(), + accounts: self.bonded_balance, metadata: self - .bonded_currency - .clone() - .into_iter() - // id, name, symbol, decimals - .map(|(id, _, _, name, denomination)| (id, name.clone().into(), name.into(), denomination)) - .collect(), - - accounts: self - .bonded_currency + .meta_data .into_iter() - // id, owner, balance - .map(|(id, acc, balance, _, _)| (id, acc, balance)) + .map(|(id, denomination)| (id, vec![], vec![], denomination)) .collect(), } .assimilate_storage(&mut storage) @@ -207,7 +270,17 @@ pub mod runtime { let mut ext = sp_io::TestExternalities::new(storage); - ext.execute_with(|| {}); + ext.execute_with(|| { + self.pools.iter().for_each(|(pool_id, pool)| { + crate::Pools::::insert(pool_id.clone(), pool.clone()); + + >::mint_into(&ACCOUNT_99, UNIT_NATIVE * 100) + .expect("Minting should not fail."); + + >::touch(self.collateral_asset_id, pool_id, &ACCOUNT_99) + .expect("Touching pool_id should not fail."); + }); + }); ext } diff --git a/pallets/pallet-bonded-coins/src/tests/mod.rs b/pallets/pallet-bonded-coins/src/tests/mod.rs index 5daa154b5..712e9395e 100644 --- a/pallets/pallet-bonded-coins/src/tests/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/mod.rs @@ -1,2 +1,2 @@ -mod types; mod transactions; +mod types; diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs b/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs index 465c40cb3..75d0e9bb9 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs @@ -1,23 +1,21 @@ use frame_support::{ assert_ok, - traits::{fungibles::{metadata::Inspect as InspectMetaData, roles::Inspect as InspectRoles}, ContainsPair}, + traits::{ + fungibles::{metadata::Inspect as InspectMetaData, roles::Inspect as InspectRoles}, + ContainsPair, + }, }; -use sp_arithmetic::FixedU128; use sp_runtime::BoundedVec; use crate::{ - curves_parameters::LinearBondingFunctionParameters, - mock::{runtime::*, AccountId}, - types::{Curve, PoolStatus, TokenMeta}, + mock::{runtime::*, *}, + types::{PoolStatus, TokenMeta}, NextAssetId, Pools, }; #[test] -fn test_create_pool_linear_bonding_curve() { - // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x - let m = FixedU128::from_u32(2); - let n = FixedU128::from_u32(3); - let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); +fn test_create_pool() { + let curve = get_linear_bonding_curve(); let state = PoolStatus::Active; let token_meta = TokenMeta { @@ -30,8 +28,9 @@ fn test_create_pool_linear_bonding_curve() { let currencies = BoundedVec::try_from(vec![token_meta.clone()]).expect("creating currencies should not fail"); ExtBuilder::default() - .with_balances(vec![(ACCOUNT_00, UNIT * 10)]) - .with_currencies(vec![DEFAULT_COLLATERAL_CURRENCY]) + .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10)]) + .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) + .with_metadata(vec![(DEFAULT_COLLATERAL_CURRENCY_ID, DEFAULT_COLLATERAL_DENOMINATION)]) .build() .execute_with(|| { let current_asset_id = NextAssetId::::get(); @@ -77,7 +76,7 @@ fn test_create_pool_linear_bonding_curve() { assert_eq!(name, token_meta.name.into_inner()); assert_eq!(symbol, token_meta.symbol.into_inner()); - // check roles of created assets + // check roles of created assets TODO needs to be changed later. let owner = >::owner(currency_id).expect("Owner should be set"); let admin = >::admin(currency_id).expect("Admin should be set"); let issuer = >::issuer(currency_id).expect("Issuer should be set"); @@ -92,8 +91,7 @@ fn test_create_pool_linear_bonding_curve() { let total_supply = Assets::total_supply(currency_id); assert_eq!(total_supply, 0); - // check if pool_account is created. - assert!(Assets::contains(&DEFAULT_COLLATERAL_CURRENCY.0, &pool_id)); - + // check if pool_account is created. + assert!(Assets::contains(&DEFAULT_COLLATERAL_CURRENCY_ID, &pool_id)); }); } diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs index e69de29bb..070c86df5 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs @@ -0,0 +1,65 @@ +use frame_support::assert_ok; +use sp_runtime::FixedU128; + +use crate::{ + curves_parameters::transform_denomination_currency_amount, + mock::{runtime::*, *}, + types::PoolStatus, +}; + +#[test] +fn test_mint_into_account() { + let currencies = vec![DEFAULT_BONDED_CURRENCY_ID]; + let pool_id = calculate_pool_id(currencies.clone()); + let one_bonded_currency = get_currency_unit(DEFAULT_BONDED_DENOMINATION); + let one_collateral_currency = get_currency_unit(DEFAULT_COLLATERAL_DENOMINATION); + + let curve = get_linear_bonding_curve(); + + let pool = calculate_pool_details(currencies, ACCOUNT_01, false, curve.clone(), PoolStatus::Active); + + let active_issuance_pre = FixedU128::from_inner(0); + let passive_issuance = FixedU128::from_inner(0); + let active_issuance_post = FixedU128::from_u32(1); + + let expected_costs_normalized = curve + .calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance) + .expect("Cost calculation should not fail"); + + let expected_raw_costs = + transform_denomination_currency_amount(expected_costs_normalized.into_inner(), 18, DEFAULT_BONDED_DENOMINATION) + .expect("Transforming costs should not fail") + .into_inner(); + + ExtBuilder::default() + .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10)]) + .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) + .with_currencies(vec![vec![DEFAULT_BONDED_CURRENCY_ID]]) + .with_metadata(vec![ + (DEFAULT_COLLATERAL_CURRENCY_ID, DEFAULT_COLLATERAL_DENOMINATION), + (DEFAULT_BONDED_CURRENCY_ID, DEFAULT_BONDED_DENOMINATION), + ]) + .with_pools(vec![(pool_id.clone(), pool)]) + .with_bonded_balance(vec![( + DEFAULT_COLLATERAL_CURRENCY_ID, + ACCOUNT_00, + one_collateral_currency * 10, + )]) + .build() + .execute_with(|| { + assert_ok!(BondingPallet::mint_into( + RuntimeOrigin::signed(ACCOUNT_00), + pool_id.clone(), + 0, + one_bonded_currency, + one_collateral_currency * 5, + ACCOUNT_00 + )); + + let supply_minted_coins = Assets::balance(DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00); + assert_eq!(supply_minted_coins, one_bonded_currency); + + let collateral_balance = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, pool_id); + assert_eq!(collateral_balance, expected_raw_costs); + }); +} diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs index e91bddb68..8efa08ce0 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs @@ -27,9 +27,9 @@ fn test_linear_bonding_function_fraction() { let curve = LinearBondingFunctionParameters { m, n }; - // 0.5*1^2 + 2*1 = 2 + // 0.5*1^2 + 2*1 = 2.5 let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_u32(2)); + assert_eq!(result, FixedU128::from_rational(5, 2)); } #[test] From cd28d4200ced5c03f537e867f0ee79094451f73e Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Fri, 20 Sep 2024 15:59:56 +0200 Subject: [PATCH 36/46] test for mint_into. Refactor mocking --- pallets/pallet-bonded-coins/src/mock.rs | 24 +++++++++++++++---- .../src/tests/transactions/burn_into.rs | 0 .../src/tests/transactions/create_pool.rs | 4 ++++ .../src/tests/transactions/mod.rs | 2 ++ 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index fc097f507..b127e1f78 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -77,6 +77,20 @@ pub mod runtime { } } + pub(crate) fn events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let RuntimeEvent::BondingPallet(e) = e { + Some(e) + } else { + None + } + }) + .collect::>() + } + frame_support::construct_runtime!( pub enum Test { @@ -103,7 +117,7 @@ pub mod runtime { type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type DbWeight = RocksDbWeight; type Version = (); @@ -134,7 +148,7 @@ pub mod runtime { type MaxFreezes = (); type Balance = Balance; type DustRemoval = (); - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); @@ -149,7 +163,7 @@ pub mod runtime { } impl pallet_assets::Config for Test { - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type Balance = Balance; type AssetId = AssetId; type AssetIdParameter = AssetId; @@ -185,7 +199,7 @@ pub mod runtime { type MaxStringLength = StringLimit; type PoolCreateOrigin = EnsureSigned; type PoolId = AccountId; - type RuntimeEvent = (); + type RuntimeEvent = RuntimeEvent; type RuntimeHoldReason = RuntimeHoldReason; type AssetId = AssetId; type BaseDeposit = ExistentialDeposit; @@ -271,6 +285,8 @@ pub mod runtime { let mut ext = sp_io::TestExternalities::new(storage); ext.execute_with(|| { + System::set_block_number(System::block_number() + 1); + self.pools.iter().for_each(|(pool_id, pool)| { crate::Pools::::insert(pool_id.clone(), pool.clone()); diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs new file mode 100644 index 000000000..e69de29bb diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs b/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs index 75d0e9bb9..c43624daf 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/create_pool.rs @@ -93,5 +93,9 @@ fn test_create_pool() { // check if pool_account is created. assert!(Assets::contains(&DEFAULT_COLLATERAL_CURRENCY_ID, &pool_id)); + + // check events + + assert_eq!(events(), vec![crate::Event::::PoolCreated(pool_id)]) }); } diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs index 21cdf2d84..0b9f70ea3 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs @@ -1,2 +1,4 @@ mod create_pool; mod mint_into; + +mod burn_into; From fe1ee46275261a6c2f48ed833e7a9cb87dd6fb76 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Mon, 23 Sep 2024 12:35:57 +0200 Subject: [PATCH 37/46] test for burn_into --- pallets/pallet-bonded-coins/src/lib.rs | 31 ++++--- pallets/pallet-bonded-coins/src/mock.rs | 10 +-- .../src/tests/transactions/burn_into.rs | 83 +++++++++++++++++++ .../src/tests/transactions/mint_into.rs | 28 ++++++- .../src/tests/types/types.rs | 7 +- pallets/pallet-bonded-coins/src/types.rs | 25 +++--- 6 files changed, 142 insertions(+), 42 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 282e2690e..5b77a7b68 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -617,18 +617,14 @@ pub mod pallet { .collect::, ArithmeticError>>()?; // normalize the amount to mint - let normalized_amount_to_mint = transform_denomination_currency_amount( + let normalized_amount = transform_denomination_currency_amount( amount.clone().saturated_into(), total_issuances[currency_idx].1, target_denomination_normalization, )?; - let (active_issuance_pre, active_issuance_post) = Self::calculate_pre_post_issuances( - kind, - &normalized_amount_to_mint, - &normalized_issuances, - currency_idx, - )?; + let (active_issuance_pre, active_issuance_post) = + Self::calculate_pre_post_issuances(&kind, &normalized_amount, &normalized_issuances, currency_idx)?; let passive_issuance = normalized_issuances .iter() @@ -636,7 +632,8 @@ pub mod pallet { .filter(|&(idx, _)| idx != currency_idx) .fold(FixedU128::zero(), |sum, (_, x)| sum.saturating_add(*x)); - let normalize_cost = curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance)?; + let normalize_cost = + curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance, kind)?; // transform the cost back to the target denomination of the collateral currency let real_costs = transform_denomination_currency_amount( @@ -652,7 +649,7 @@ pub mod pallet { } fn calculate_pre_post_issuances( - kind: DiffKind, + kind: &DiffKind, amount: &FixedU128, total_issuances: &[FixedU128], currency_idx: usize, @@ -680,14 +677,6 @@ pub mod pallet { .get(currency_idx) .ok_or(Error::::IndexOutOfBounds)?; - let real_amount = T::Fungibles::burn_from( - burn_currency_id.clone(), - &payer, - amount, - Precision::Exact, - Fortitude::Polite, - )?; - let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details .bonded_currencies .iter() @@ -699,6 +688,14 @@ pub mod pallet { }) .collect(); + let real_amount = T::Fungibles::burn_from( + burn_currency_id.clone(), + &payer, + amount, + Precision::Exact, + Fortitude::Polite, + )?; + // let returns = Self::get_collateral_diff( DiffKind::Burn, diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index b127e1f78..f587f7bb6 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1,6 +1,6 @@ use frame_support::{ parameter_types, - traits::{fungible::Mutate, AccountTouch, ConstU128, ConstU32}, + traits::{ConstU128, ConstU32}, weights::constants::RocksDbWeight, Hashable, }; @@ -25,8 +25,6 @@ pub type AccountPublic = ::Signer; pub type AccountId = ::AccountId; // accounts -// should not be used for testing -const ACCOUNT_99: AccountId = AccountId::new([99u8; 32]); pub(crate) const ACCOUNT_00: AccountId = AccountId::new([0u8; 32]); pub(crate) const ACCOUNT_01: AccountId = AccountId::new([1u8; 32]); @@ -289,12 +287,6 @@ pub mod runtime { self.pools.iter().for_each(|(pool_id, pool)| { crate::Pools::::insert(pool_id.clone(), pool.clone()); - - >::mint_into(&ACCOUNT_99, UNIT_NATIVE * 100) - .expect("Minting should not fail."); - - >::touch(self.collateral_asset_id, pool_id, &ACCOUNT_99) - .expect("Touching pool_id should not fail."); }); }); diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs index e69de29bb..54cd76baf 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs @@ -0,0 +1,83 @@ +use frame_support::assert_ok; +use sp_runtime::{traits::Zero, FixedU128}; + +use crate::{ + curves_parameters::transform_denomination_currency_amount, + mock::{runtime::*, *}, + types::{DiffKind, PoolStatus}, +}; + +#[test] +fn test_burn_into_account() { + let currencies = vec![DEFAULT_BONDED_CURRENCY_ID]; + let pool_id = calculate_pool_id(currencies.clone()); + let one_bonded_currency = get_currency_unit(DEFAULT_BONDED_DENOMINATION); + let one_collateral_currency = get_currency_unit(DEFAULT_COLLATERAL_DENOMINATION); + + let curve = get_linear_bonding_curve(); + + let pool = calculate_pool_details(currencies, ACCOUNT_01, false, curve.clone(), PoolStatus::Active); + + let active_issuance_pre = FixedU128::from_u32(1); + let passive_issuance = FixedU128::from_inner(0); + let active_issuance_post = FixedU128::from_u32(0); + + let expected_costs_normalized = curve + .calculate_cost( + active_issuance_pre, + active_issuance_post, + passive_issuance, + DiffKind::Burn, + ) + .expect("Cost calculation should not fail"); + + let expected_raw_return = + transform_denomination_currency_amount(expected_costs_normalized.into_inner(), 18, DEFAULT_BONDED_DENOMINATION) + .expect("Transforming costs should not fail") + .into_inner(); + + let collateral_balance_supply = one_collateral_currency * 10; + + ExtBuilder::default() + .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) + .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) + .with_currencies(vec![vec![DEFAULT_BONDED_CURRENCY_ID]]) + .with_metadata(vec![ + (DEFAULT_COLLATERAL_CURRENCY_ID, DEFAULT_COLLATERAL_DENOMINATION), + (DEFAULT_BONDED_CURRENCY_ID, DEFAULT_BONDED_DENOMINATION), + ]) + .with_pools(vec![(pool_id.clone(), pool)]) + .with_bonded_balance(vec![ + ( + DEFAULT_COLLATERAL_CURRENCY_ID, + pool_id.clone(), + collateral_balance_supply, + ), + (DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00, one_bonded_currency), + ]) + .build() + .execute_with(|| { + assert_ok!(BondingPallet::burn_into( + RuntimeOrigin::signed(ACCOUNT_00), + pool_id.clone(), + 0, + one_bonded_currency, + 0, + ACCOUNT_00 + )); + + // User should have no bonded coins + let supply_bonded_coins_user = Assets::balance(DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00); + assert_eq!(supply_bonded_coins_user, Zero::zero()); + + // user should have some collateral + let collateral_balance_submitter = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, ACCOUNT_00); + assert_eq!(collateral_balance_submitter, expected_raw_return); + + let collateral_balance_pool = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, pool_id); + assert_eq!(collateral_balance_pool, collateral_balance_supply - expected_raw_return); + + // The total supply should be zero + assert_eq!(Assets::total_supply(DEFAULT_BONDED_CURRENCY_ID), Zero::zero()); + }); +} diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs index 070c86df5..90c1a30a5 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs @@ -4,7 +4,7 @@ use sp_runtime::FixedU128; use crate::{ curves_parameters::transform_denomination_currency_amount, mock::{runtime::*, *}, - types::PoolStatus, + types::{DiffKind, PoolStatus}, }; #[test] @@ -23,7 +23,12 @@ fn test_mint_into_account() { let active_issuance_post = FixedU128::from_u32(1); let expected_costs_normalized = curve - .calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance) + .calculate_cost( + active_issuance_pre, + active_issuance_post, + passive_issuance, + DiffKind::Mint, + ) .expect("Cost calculation should not fail"); let expected_raw_costs = @@ -31,8 +36,10 @@ fn test_mint_into_account() { .expect("Transforming costs should not fail") .into_inner(); + let collateral_balance_supply = one_collateral_currency * 10; + ExtBuilder::default() - .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10)]) + .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) .with_currencies(vec![vec![DEFAULT_BONDED_CURRENCY_ID]]) .with_metadata(vec![ @@ -43,7 +50,7 @@ fn test_mint_into_account() { .with_bonded_balance(vec![( DEFAULT_COLLATERAL_CURRENCY_ID, ACCOUNT_00, - one_collateral_currency * 10, + collateral_balance_supply, )]) .build() .execute_with(|| { @@ -56,10 +63,23 @@ fn test_mint_into_account() { ACCOUNT_00 )); + // user should have the requested bonded coin let supply_minted_coins = Assets::balance(DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00); assert_eq!(supply_minted_coins, one_bonded_currency); + // pool should have the required collateral for minting the coin let collateral_balance = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, pool_id); assert_eq!(collateral_balance, expected_raw_costs); + + // the total supply should be one + let bonded_currency_total_supply = Assets::total_supply(DEFAULT_BONDED_CURRENCY_ID); + assert_eq!(bonded_currency_total_supply, one_bonded_currency); + + // the submitter should have the collateral balance reduced by the minting cost + let collateral_balance_submitter = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, ACCOUNT_00); + assert_eq!( + collateral_balance_submitter, + collateral_balance_supply - expected_raw_costs + ); }); } diff --git a/pallets/pallet-bonded-coins/src/tests/types/types.rs b/pallets/pallet-bonded-coins/src/tests/types/types.rs index 245a3e03a..076c7e4e9 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/types.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/types.rs @@ -3,7 +3,7 @@ use sp_runtime::traits::Zero; use crate::{ curves_parameters::{transform_denomination_currency_amount, LinearBondingFunctionParameters}, - types::Curve, + types::{Curve, DiffKind}, }; // target denomination for collateral currency @@ -38,6 +38,7 @@ fn test_mint_first_coin() { normalized_active_issuance_pre, normalized_active_issuance_post, passive_issuance, + DiffKind::Mint, ) .unwrap(); @@ -73,6 +74,7 @@ fn test_mint_coin_with_existing_supply() { normalized_active_issuance_pre, normalized_active_issuance_post, passive_issuance, + DiffKind::Mint, ) .unwrap(); @@ -112,6 +114,7 @@ fn test_mint_coin_with_existing_passive_supply() { normalized_active_issuance_pre, normalized_active_issuance_post, normalized_passive_issuance, + DiffKind::Mint, ) .unwrap(); @@ -151,6 +154,7 @@ fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { normalized_active_issuance_pre, normalized_active_issuance_post, normalized_passive_issuance, + DiffKind::Mint, ) .unwrap(); @@ -186,6 +190,7 @@ fn test_mint_first_coin_frac_bonding_curve() { normalized_active_issuance_pre, normalized_active_issuance_post, passive_issuance, + DiffKind::Mint, ) .unwrap(); diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 56db5cf4f..6b7c8eb87 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -124,20 +124,23 @@ where active_issuance_pre: F, active_issuance_post: F, passive_issuance: F, + kind: DiffKind, ) -> Result { - let active_issuance_pre_with_passive = active_issuance_pre.saturating_add(passive_issuance); - let active_issuance_post_with_passive = active_issuance_post.saturating_add(passive_issuance); + let (low, high) = match kind { + DiffKind::Burn => ( + active_issuance_post.saturating_add(passive_issuance), + active_issuance_pre.saturating_add(passive_issuance), + ), + DiffKind::Mint => ( + active_issuance_pre.saturating_add(passive_issuance), + active_issuance_post.saturating_add(passive_issuance), + ), + }; match self { - Curve::LinearRatioCurve(params) => { - params.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) - } - Curve::QuadraticRatioCurve(params) => { - params.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) - } - Curve::SquareRootBondingFunction(params) => { - params.calculate_costs(active_issuance_pre_with_passive, active_issuance_post_with_passive) - } + Curve::LinearRatioCurve(params) => params.calculate_costs(low, high), + Curve::QuadraticRatioCurve(params) => params.calculate_costs(low, high), + Curve::SquareRootBondingFunction(params) => params.calculate_costs(low, high), Curve::RationalBondingFunction(params) => params.calculate_costs( (active_issuance_pre, passive_issuance), (active_issuance_post, passive_issuance), From b8d3898cfe7e5091c36f2868116bd6c6f36f20e6 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Mon, 23 Sep 2024 16:10:08 +0200 Subject: [PATCH 38/46] test for switch and locks --- pallets/pallet-bonded-coins/src/lib.rs | 3 +- .../src/tests/transactions/mod.rs | 5 +- .../src/tests/transactions/set_lock.rs | 44 +++++++++++++ .../src/tests/transactions/swap_into.rs | 65 +++++++++++++++++++ .../src/tests/transactions/unlock.rs | 49 ++++++++++++++ 5 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/transactions/swap_into.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 5b77a7b68..9ca2b3715 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -365,7 +365,6 @@ pub mod pallet { to_idx: u32, amount_to_swap: FungiblesBalanceOf, beneficiary: AccountIdLookupOf, - min_return: FungiblesBalanceOf, ) -> DispatchResult { let who = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; @@ -459,7 +458,7 @@ pub mod pallet { &pool_details, to_idx_usize, beneficiary, - min_return, + amount_to_swap, )?; } }; diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs index 0b9f70ea3..1e1336198 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mod.rs @@ -1,4 +1,7 @@ +mod burn_into; mod create_pool; mod mint_into; +mod set_lock; +mod swap_into; -mod burn_into; +mod unlock; diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs b/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs new file mode 100644 index 000000000..430072e9d --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs @@ -0,0 +1,44 @@ +use frame_support::assert_ok; + +use crate::{ + mock::{runtime::*, *}, + types::{Locks, PoolStatus}, + Pools, +}; + +#[test] +fn test_set_lock() { + let currencies = vec![DEFAULT_BONDED_CURRENCY_ID]; + let pool_id = calculate_pool_id(currencies.clone()); + + let curve = get_linear_bonding_curve(); + + let pool = calculate_pool_details(currencies, ACCOUNT_01, false, curve.clone(), PoolStatus::Active); + + let target_lock: Locks = Default::default(); + + ExtBuilder::default() + .with_native_balances(vec![(ACCOUNT_01, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) + .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) + .with_currencies(vec![vec![DEFAULT_BONDED_CURRENCY_ID]]) + .with_metadata(vec![ + (DEFAULT_COLLATERAL_CURRENCY_ID, DEFAULT_COLLATERAL_DENOMINATION), + (DEFAULT_BONDED_CURRENCY_ID, DEFAULT_BONDED_DENOMINATION), + ]) + .with_pools(vec![(pool_id.clone(), pool)]) + .build() + .execute_with(|| { + let pool_details = Pools::::get(&pool_id).expect("Pool should exist"); + + assert_eq!(pool_details.state, PoolStatus::Active); + assert_ok!(BondingPallet::set_lock( + RuntimeOrigin::signed(ACCOUNT_01), + pool_id.clone(), + target_lock.clone() + )); + + let pool_details_after_tx = Pools::::get(&pool_id).expect("Pool should exist"); + + assert_eq!(pool_details_after_tx.state, PoolStatus::Locked(target_lock)); + }); +} diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/swap_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/swap_into.rs new file mode 100644 index 000000000..fae0d7eb1 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/transactions/swap_into.rs @@ -0,0 +1,65 @@ +use frame_support::assert_ok; +use sp_runtime::traits::Zero; + +use crate::{ + mock::{runtime::*, *}, + types::PoolStatus, +}; + +#[test] +fn test_swap_into_non_ratio_function() { + let second_currency_id = 1; + let currencies = vec![DEFAULT_BONDED_CURRENCY_ID, second_currency_id]; + let pool_id = calculate_pool_id(currencies.clone()); + + let one_bonded_currency = get_currency_unit(DEFAULT_BONDED_DENOMINATION); + let one_collateral_currency = get_currency_unit(DEFAULT_COLLATERAL_DENOMINATION); + + let curve = get_linear_bonding_curve(); + + let pool_details = calculate_pool_details(currencies, ACCOUNT_01, false, curve.clone(), PoolStatus::Active); + + let collateral_balance_supply = one_collateral_currency * 10; + + ExtBuilder::default() + .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) + .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) + .with_currencies(vec![vec![DEFAULT_BONDED_CURRENCY_ID, second_currency_id]]) + .with_metadata(vec![ + (DEFAULT_COLLATERAL_CURRENCY_ID, DEFAULT_COLLATERAL_DENOMINATION), + (DEFAULT_BONDED_CURRENCY_ID, DEFAULT_BONDED_DENOMINATION), + (second_currency_id, 10), + ]) + .with_pools(vec![(pool_id.clone(), pool_details)]) + .with_bonded_balance(vec![ + (DEFAULT_COLLATERAL_CURRENCY_ID, ACCOUNT_00, collateral_balance_supply), + (DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00, one_bonded_currency), + ]) + .build() + .execute_with(|| { + let funds_bonded_coin_before_tx = Assets::balance(DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00); + assert_eq!(funds_bonded_coin_before_tx, one_bonded_currency); + + let collateral_asset_pool_before_tx = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, pool_id.clone()); + + assert_ok!(BondingPallet::swap_into( + RuntimeOrigin::signed(ACCOUNT_00), + pool_id.clone(), + 0, + 1, + one_bonded_currency, + ACCOUNT_00, + )); + + // Collateral should have not change. + let collateral_asset_pool_after_tx = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, pool_id.clone()); + assert_eq!(collateral_asset_pool_after_tx, collateral_asset_pool_before_tx); + + // Bonded should have not change. + let funds_bonded_coin_after_tx = Assets::balance(DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00); + assert_eq!(funds_bonded_coin_after_tx, Zero::zero()); + + let funds_target_bonded_coin_after_tx = Assets::balance(second_currency_id, ACCOUNT_00); + assert_eq!(funds_target_bonded_coin_after_tx, one_bonded_currency); + }); +} diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs b/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs new file mode 100644 index 000000000..4c05fe75c --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs @@ -0,0 +1,49 @@ +use frame_support::assert_ok; + +use crate::{ + mock::{runtime::*, *}, + types::{Locks, PoolStatus}, + Pools, +}; + +#[test] +fn test_unlock() { + let currencies = vec![DEFAULT_BONDED_CURRENCY_ID]; + let pool_id = calculate_pool_id(currencies.clone()); + + let curve = get_linear_bonding_curve(); + + let target_lock: Locks = Default::default(); + + let pool = calculate_pool_details( + currencies, + ACCOUNT_01, + false, + curve.clone(), + PoolStatus::Locked(target_lock.clone()), + ); + + ExtBuilder::default() + .with_native_balances(vec![(ACCOUNT_01, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) + .with_collateral_asset_id(DEFAULT_COLLATERAL_CURRENCY_ID) + .with_currencies(vec![vec![DEFAULT_BONDED_CURRENCY_ID]]) + .with_metadata(vec![ + (DEFAULT_COLLATERAL_CURRENCY_ID, DEFAULT_COLLATERAL_DENOMINATION), + (DEFAULT_BONDED_CURRENCY_ID, DEFAULT_BONDED_DENOMINATION), + ]) + .with_pools(vec![(pool_id.clone(), pool)]) + .build() + .execute_with(|| { + let pool_details = Pools::::get(&pool_id).expect("Pool should exist"); + + assert_eq!(pool_details.state, PoolStatus::Locked(target_lock.clone())); + assert_ok!(BondingPallet::unlock( + RuntimeOrigin::signed(ACCOUNT_01), + pool_id.clone(), + )); + + let pool_details_after_tx = Pools::::get(&pool_id).expect("Pool should exist"); + + assert_eq!(pool_details_after_tx.state, PoolStatus::Active); + }); +} From 12f8de8e46ed9f782c15c6d5a01dd5272ef18931 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Tue, 24 Sep 2024 10:38:47 +0200 Subject: [PATCH 39/46] curve parameter generic --- .../src/curves_parameters.rs | 8 +- pallets/pallet-bonded-coins/src/lib.rs | 85 +++++++++-------- pallets/pallet-bonded-coins/src/mock.rs | 3 + .../src/tests/transactions/burn_into.rs | 11 ++- .../src/tests/transactions/mint_into.rs | 11 ++- .../src/tests/transactions/set_lock.rs | 3 + .../src/tests/transactions/unlock.rs | 3 + .../src/tests/types/curves_parameters.rs | 16 ++-- .../src/tests/types/types.rs | 95 ++++++++++++------- 9 files changed, 148 insertions(+), 87 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index a58cf57e1..f4586c46a 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -3,6 +3,8 @@ use scale_info::TypeInfo; use sp_arithmetic::ArithmeticError; use sp_runtime::{FixedPointNumber, FixedU128}; use sp_std::marker::PhantomData; + +use crate::{Config, CurveParameterTypeOf}; /// Little helper trait to calculate the square root of Fixed, in order to maintain the generic. pub trait SquareRoot: Sized { fn try_sqrt(self) -> Option; @@ -176,11 +178,11 @@ where } } -pub fn transform_denomination_currency_amount( +pub fn transform_denomination_currency_amount( amount: u128, current_denomination: u8, target_denomination: u8, -) -> Result { +) -> Result, ArithmeticError> { let diff = target_denomination as i8 - current_denomination as i8; let value = { if diff > 0 { @@ -193,5 +195,5 @@ pub fn transform_denomination_currency_amount( } }?; - Ok(FixedU128::from_inner(value)) + Ok(CurveParameterTypeOf::::from_inner(value)) } diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 9ca2b3715..f7dab2afe 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -34,19 +34,20 @@ pub mod pallet { tokens::{Fortitude, Precision, Preservation}, AccountTouch, }, - Hashable, + Hashable, Parameter, }; use frame_system::{ensure_root, pallet_prelude::*}; use parity_scale_codec::FullCodec; - use sp_arithmetic::{traits::CheckedDiv, FixedU128}; + use scale_info::TypeInfo; + use sp_arithmetic::traits::CheckedDiv; use sp_runtime::{ traits::{CheckedAdd, CheckedSub, One, Saturating, StaticLookup, Zero}, - ArithmeticError, BoundedVec, SaturatedConversion, + ArithmeticError, BoundedVec, FixedPointNumber, SaturatedConversion, }; - use sp_std::{iter::Iterator, vec::Vec}; + use sp_std::{default::Default, iter::Iterator, vec::Vec}; use crate::{ - curves_parameters::transform_denomination_currency_amount, + curves_parameters::{transform_denomination_currency_amount, SquareRoot}, types::{Curve, DiffKind, Locks, PoolDetails, PoolStatus, TokenMeta}, }; @@ -67,7 +68,7 @@ pub mod pallet { <::Fungibles as InspectFungibles<::AccountId>>::AssetId; pub(crate) type PoolDetailsOf = - PoolDetails<::AccountId, Curve, BoundedCurrencyVec>; + PoolDetails<::AccountId, Curve>, BoundedCurrencyVec>; type CollateralAssetIdOf = <::CollateralCurrency as InspectFungibles<::AccountId>>::AssetId; @@ -80,6 +81,8 @@ pub mod pallet { type TokenMetaOf = TokenMeta, CurrencySymbolOf, CurrencyNameOf>; + pub(crate) type CurveParameterTypeOf = ::CurveParameterType; + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -89,7 +92,8 @@ pub mod pallet { type DepositCurrency: MutateHold; /// The currency used as collateral for minting bonded tokens. type CollateralCurrency: MutateFungibles - + AccountTouch, Self::AccountId>; + + AccountTouch, Self::AccountId> + + FungiblesMetadata; /// Implementation of creating and managing new fungibles type Fungibles: CreateFungibles + DestroyFungibles @@ -119,21 +123,22 @@ pub mod pallet { /// The type used for pool ids type PoolId: Parameter + MaxEncodedLen + From<[u8; 32]> + Into; - type AssetId: FullCodec - + Clone - + Eq - + PartialEq - + sp_std::fmt::Debug - + scale_info::TypeInfo - + MaxEncodedLen - + Default - + Saturating - + One; + type AssetId: Parameter + Member + FullCodec + TypeInfo + MaxEncodedLen + Saturating + One + Default; type RuntimeHoldReason: From; - } - type CurveParameterType = FixedU128; + type CurveParameterType: Parameter + + Member + + FixedPointNumber + + TypeInfo + + MaxEncodedLen + + SquareRoot + + Zero + + CheckedAdd + + CheckedSub; + + type NormalizationFactor: Get; + } #[pallet::pallet] pub struct Pallet(_); @@ -197,11 +202,11 @@ pub mod pallet { #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn create_pool( origin: OriginFor, - curve: Curve, + curve: Curve>, currencies: BoundedVec, T::MaxCurrencies>, tradable: bool, // Todo: why do we need that? state: PoolStatus, - pool_manager: AccountIdOf, // Todo: maybe change that back to owner. + pool_manager: AccountIdOf, ) -> DispatchResult { // ensure origin is PoolCreateOrigin let who = T::PoolCreateOrigin::ensure_origin(origin)?; @@ -228,6 +233,7 @@ pub mod pallet { let pool_id = T::PoolId::from(currency_ids.blake2_256()); + // Todo: change that. T::DepositCurrency::hold( &T::RuntimeHoldReason::from(HoldReason::Deposit), &who, @@ -253,6 +259,7 @@ pub mod pallet { // TODO: use fungibles::roles::ResetTeam to update currency admin } + // Touch the pool account in order to able to transfer the collateral currency to it T::CollateralCurrency::touch(T::CollateralAssetId::get(), &pool_id.clone().into(), &who)?; Pools::::set( @@ -379,7 +386,7 @@ pub mod pallet { match &pool_details.curve { Curve::RationalBondingFunction(_) => { - let target_denomination_normalization = 18; + let target_denomination_normalization = T::NormalizationFactor::get(); let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details .bonded_currencies @@ -399,13 +406,13 @@ pub mod pallet { amount_to_swap, )?; - let normalized_collateral = transform_denomination_currency_amount( + let normalized_collateral = transform_denomination_currency_amount::( collateral.saturated_into::(), currencies_metadata[from_idx_usize].1, target_denomination_normalization, )?; - let normalized_target_issuance = transform_denomination_currency_amount( + let normalized_target_issuance = transform_denomination_currency_amount::( amount_to_swap.clone().saturated_into(), currencies_metadata[to_idx_usize].1, target_denomination_normalization, @@ -417,13 +424,15 @@ pub mod pallet { .enumerate() .filter(|(idx, _)| *idx != to_idx_usize) .map(|(_, (x, d))| { - transform_denomination_currency_amount( + transform_denomination_currency_amount::( x.saturated_into::(), d, target_denomination_normalization, ) }) - .try_fold(FixedU128::zero(), |sum, result| result.map(|x| sum.saturating_add(x)))?; + .try_fold(CurveParameterTypeOf::::zero(), |sum, result| { + result.map(|x| sum.saturating_add(x)) + })?; let ratio = normalized_target_issuance .checked_div(&normalized_passive_issuances) @@ -433,7 +442,7 @@ pub mod pallet { .checked_div(&ratio) .ok_or(ArithmeticError::DivisionByZero)?; - let raw_supply = transform_denomination_currency_amount( + let raw_supply = transform_denomination_currency_amount::( supply_to_mint.into_inner(), target_denomination_normalization, currencies_metadata[to_idx_usize].1, @@ -594,29 +603,29 @@ pub mod pallet { /// save usage of currency_ids. pub fn get_collateral_diff( kind: DiffKind, - curve: &Curve, + curve: &Curve>, amount: &FungiblesBalanceOf, total_issuances: Vec<(FungiblesBalanceOf, u8)>, currency_idx: usize, ) -> Result, ArithmeticError> { // todo: change that. We have also to restrict the denomination of the pool currencies maybe? - let target_denomination_normalization = 18; - let target_denomination_costs = 10; + let target_denomination_normalization = T::NormalizationFactor::get(); + let target_denomination_costs = T::CollateralCurrency::decimals(T::CollateralAssetId::get()); let normalized_issuances = total_issuances .clone() .into_iter() .map(|(x, d)| { - transform_denomination_currency_amount( + transform_denomination_currency_amount::( x.saturated_into::(), d, target_denomination_normalization, ) }) - .collect::, ArithmeticError>>()?; + .collect::>, ArithmeticError>>()?; // normalize the amount to mint - let normalized_amount = transform_denomination_currency_amount( + let normalized_amount = transform_denomination_currency_amount::( amount.clone().saturated_into(), total_issuances[currency_idx].1, target_denomination_normalization, @@ -629,13 +638,13 @@ pub mod pallet { .iter() .enumerate() .filter(|&(idx, _)| idx != currency_idx) - .fold(FixedU128::zero(), |sum, (_, x)| sum.saturating_add(*x)); + .fold(CurveParameterTypeOf::::zero(), |sum, (_, x)| sum.saturating_add(*x)); let normalize_cost = curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance, kind)?; // transform the cost back to the target denomination of the collateral currency - let real_costs = transform_denomination_currency_amount( + let real_costs = transform_denomination_currency_amount::( normalize_cost.into_inner(), target_denomination_normalization, target_denomination_costs, @@ -649,10 +658,10 @@ pub mod pallet { fn calculate_pre_post_issuances( kind: &DiffKind, - amount: &FixedU128, - total_issuances: &[FixedU128], + amount: &CurveParameterTypeOf, + total_issuances: &[CurveParameterTypeOf], currency_idx: usize, - ) -> Result<(FixedU128, FixedU128), ArithmeticError> { + ) -> Result<(CurveParameterTypeOf, CurveParameterTypeOf), ArithmeticError> { let active_issuance_pre = total_issuances[currency_idx]; let active_issuance_post = match kind { DiffKind::Mint => active_issuance_pre diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index f587f7bb6..2c4d7b742 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -185,6 +185,7 @@ pub mod runtime { pub const CurrencyDeposit: Balance = 500; pub const MaxCurrencies: u32 = 50; pub const CollateralAssetId: u32 = u32::MAX; + pub const NormalizationFactor: u8 = 18; } impl Config for Test { @@ -201,6 +202,8 @@ pub mod runtime { type RuntimeHoldReason = RuntimeHoldReason; type AssetId = AssetId; type BaseDeposit = ExistentialDeposit; + type CurveParameterType = FixedU128; + type NormalizationFactor = NormalizationFactor; } #[derive(Clone, Default)] diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs index 54cd76baf..64cf9777f 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs @@ -31,10 +31,13 @@ fn test_burn_into_account() { ) .expect("Cost calculation should not fail"); - let expected_raw_return = - transform_denomination_currency_amount(expected_costs_normalized.into_inner(), 18, DEFAULT_BONDED_DENOMINATION) - .expect("Transforming costs should not fail") - .into_inner(); + let expected_raw_return = transform_denomination_currency_amount::( + expected_costs_normalized.into_inner(), + 18, + DEFAULT_BONDED_DENOMINATION, + ) + .expect("Transforming costs should not fail") + .into_inner(); let collateral_balance_supply = one_collateral_currency * 10; diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs index 90c1a30a5..4ceec429f 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs @@ -31,10 +31,13 @@ fn test_mint_into_account() { ) .expect("Cost calculation should not fail"); - let expected_raw_costs = - transform_denomination_currency_amount(expected_costs_normalized.into_inner(), 18, DEFAULT_BONDED_DENOMINATION) - .expect("Transforming costs should not fail") - .into_inner(); + let expected_raw_costs = transform_denomination_currency_amount::( + expected_costs_normalized.into_inner(), + 18, + DEFAULT_BONDED_DENOMINATION, + ) + .expect("Transforming costs should not fail") + .into_inner(); let collateral_balance_supply = one_collateral_currency * 10; diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs b/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs index 430072e9d..8e0a9dabc 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/set_lock.rs @@ -40,5 +40,8 @@ fn test_set_lock() { let pool_details_after_tx = Pools::::get(&pool_id).expect("Pool should exist"); assert_eq!(pool_details_after_tx.state, PoolStatus::Locked(target_lock)); + + // check events + assert_eq!(events(), vec![crate::Event::::LockSet(pool_id)]) }); } diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs b/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs index 4c05fe75c..c7ab676bd 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/unlock.rs @@ -45,5 +45,8 @@ fn test_unlock() { let pool_details_after_tx = Pools::::get(&pool_id).expect("Pool should exist"); assert_eq!(pool_details_after_tx.state, PoolStatus::Active); + + // check events + assert_eq!(events(), vec![crate::Event::::Unlocked(pool_id)]) }); } diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs index 8efa08ce0..86b1dc62b 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs @@ -2,8 +2,9 @@ use frame_support::assert_err; use sp_arithmetic::{ArithmeticError, FixedU128}; use sp_runtime::traits::Zero; -use crate::curves_parameters::{ - transform_denomination_currency_amount, BondingFunction, LinearBondingFunctionParameters, +use crate::{ + curves_parameters::{transform_denomination_currency_amount, BondingFunction, LinearBondingFunctionParameters}, + mock::runtime::*, }; #[test] @@ -62,7 +63,8 @@ fn test_increase_denomination_currency_amount() { let current_denomination = 2; let target_denomination = 3; - let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination).unwrap(); + let result = + transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::from_inner(1000)); } @@ -72,7 +74,8 @@ fn test_decrease_denomination_currency_amount() { let current_denomination = 3; let target_denomination = 2; - let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination).unwrap(); + let result = + transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::from_inner(100)); } @@ -84,7 +87,7 @@ fn test_increase_denomination_overflow() { // just increase denomination by one. This should overflow let target_denomination = 11; - let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination); + let result = transform_denomination_currency_amount::(amount, current_denomination, target_denomination); assert_err!(result, ArithmeticError::Overflow); } @@ -97,6 +100,7 @@ fn test_decrease_denomination_underflow() { let target_denomination = 4; // we should have dropped all relevant bits. This should gives use an Ok with zero - let result = transform_denomination_currency_amount(amount, current_denomination, target_denomination).unwrap(); + let result = + transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::zero()) } diff --git a/pallets/pallet-bonded-coins/src/tests/types/types.rs b/pallets/pallet-bonded-coins/src/tests/types/types.rs index 076c7e4e9..4c37e512c 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/types.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/types.rs @@ -3,6 +3,7 @@ use sp_runtime::traits::Zero; use crate::{ curves_parameters::{transform_denomination_currency_amount, LinearBondingFunctionParameters}, + mock::runtime::*, types::{Curve, DiffKind}, }; @@ -25,12 +26,18 @@ fn test_mint_first_coin() { // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); - let normalized_active_issuance_pre = - transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); - let normalized_active_issuance_post = - transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + let normalized_active_issuance_pre = transform_denomination_currency_amount::( + active_issuance_pre, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); + let normalized_active_issuance_post = transform_denomination_currency_amount::( + active_issuance_post, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); // The cost to mint the first coin should be 4. let costs = curve @@ -59,12 +66,18 @@ fn test_mint_coin_with_existing_supply() { // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); - let normalized_active_issuance_pre = - transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); - let normalized_active_issuance_post = - transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + let normalized_active_issuance_pre = transform_denomination_currency_amount::( + active_issuance_pre, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); + let normalized_active_issuance_post = transform_denomination_currency_amount::( + active_issuance_post, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); // Existing supply: 100^2 + 3*100 = 10300 // New supply: 110^2 + 3*110 = 12130 @@ -95,14 +108,20 @@ fn test_mint_coin_with_existing_passive_supply() { // Multiple coins in pool. Passive issuance is 10. let passive_issuance = ONE_COIN * 10; - let normalized_active_issuance_pre = - transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); - let normalized_active_issuance_post = - transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + let normalized_active_issuance_pre = transform_denomination_currency_amount::( + active_issuance_pre, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); + let normalized_active_issuance_post = transform_denomination_currency_amount::( + active_issuance_post, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); let normalized_passive_issuance = - transform_denomination_currency_amount(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + transform_denomination_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) .unwrap(); // The passive issuance should influence the price of the new selected currency. @@ -135,14 +154,20 @@ fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { // Multiple coins in pool. Passive issuance is 10. let passive_issuance = ONE_COIN * 10; - let normalized_active_issuance_pre = - transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); - let normalized_active_issuance_post = - transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + let normalized_active_issuance_pre = transform_denomination_currency_amount::( + active_issuance_pre, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); + let normalized_active_issuance_post = transform_denomination_currency_amount::( + active_issuance_post, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); let normalized_passive_issuance = - transform_denomination_currency_amount(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) + transform_denomination_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) .unwrap(); // The passive issuance should influence the price of the new selected currency. @@ -175,12 +200,18 @@ fn test_mint_first_coin_frac_bonding_curve() { // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); - let normalized_active_issuance_pre = - transform_denomination_currency_amount(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); - let normalized_active_issuance_post = - transform_denomination_currency_amount(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + let normalized_active_issuance_pre = transform_denomination_currency_amount::( + active_issuance_pre, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); + let normalized_active_issuance_post = transform_denomination_currency_amount::( + active_issuance_post, + CURRENT_DENOMINATION, + NORMALIZED_DENOMINATION, + ) + .unwrap(); // Existing supply: 1/2*(0)^2 + (0)*3 = 0 // New supply: 1/2*(1)^2 + (1)*3 = 3.5 From 87a948ab26e9bfa495e69e1bbe49292cc84c8558 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Tue, 24 Sep 2024 14:52:06 +0200 Subject: [PATCH 40/46] get rid of narmalization factor --- .../src/curves_parameters.rs | 16 +++-- pallets/pallet-bonded-coins/src/lib.rs | 65 ++++++++++--------- pallets/pallet-bonded-coins/src/mock.rs | 4 +- .../src/tests/transactions/burn_into.rs | 14 ++-- .../src/tests/transactions/mint_into.rs | 18 +++-- .../src/tests/types/curves_parameters.rs | 16 ++--- .../src/tests/types/types.rs | 9 ++- 7 files changed, 72 insertions(+), 70 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index f4586c46a..d0d1136a9 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -180,17 +180,19 @@ where pub fn transform_denomination_currency_amount( amount: u128, - current_denomination: u8, - target_denomination: u8, + current_denomination: u128, + target_denomination: u128, ) -> Result, ArithmeticError> { - let diff = target_denomination as i8 - current_denomination as i8; let value = { - if diff > 0 { - let factor = 10u128.pow(diff as u32); + if target_denomination > current_denomination { + let factor = target_denomination + .checked_div(current_denomination) + .ok_or(ArithmeticError::DivisionByZero)?; amount.checked_mul(factor).ok_or(ArithmeticError::Overflow) } else { - let factor = 10u128.pow(diff.abs() as u32); - // Dividing by zero can never happen 10^0 = 1. Lets be save. + let factor = current_denomination + .checked_div(target_denomination) + .ok_or(ArithmeticError::DivisionByZero)?; amount.checked_div(factor).ok_or(ArithmeticError::DivisionByZero) } }?; diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index f7dab2afe..869a5462b 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -123,10 +123,12 @@ pub mod pallet { /// The type used for pool ids type PoolId: Parameter + MaxEncodedLen + From<[u8; 32]> + Into; + /// The type used for asset ids. This is the type of the bonded currencies. type AssetId: Parameter + Member + FullCodec + TypeInfo + MaxEncodedLen + Saturating + One + Default; type RuntimeHoldReason: From; + /// The type used for the curve parameters. type CurveParameterType: Parameter + Member + FixedPointNumber @@ -136,8 +138,6 @@ pub mod pallet { + Zero + CheckedAdd + CheckedSub; - - type NormalizationFactor: Get; } #[pallet::pallet] @@ -204,7 +204,7 @@ pub mod pallet { origin: OriginFor, curve: Curve>, currencies: BoundedVec, T::MaxCurrencies>, - tradable: bool, // Todo: why do we need that? + tradable: bool, // Todo: make it useful. state: PoolStatus, pool_manager: AccountIdOf, ) -> DispatchResult { @@ -256,7 +256,8 @@ pub mod pallet { entry.symbol.clone().into(), entry.decimals, )?; - // TODO: use fungibles::roles::ResetTeam to update currency admin + + // TODO: reset team account } // Touch the pool account in order to able to transfer the collateral currency to it @@ -285,7 +286,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; - let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + let pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; ensure!(pool_details.is_minting_authorized(&who), Error::::Locked); @@ -301,7 +302,7 @@ pub mod pallet { )?; // withdraw the collateral and put it in the deposit account - let real_costs = T::CollateralCurrency::transfer( + T::CollateralCurrency::transfer( T::CollateralAssetId::get(), &who, &pool_id.into(), @@ -310,7 +311,7 @@ pub mod pallet { )?; // fail if cost > max_cost - ensure!(real_costs <= max_cost, Error::::Slippage); + ensure!(cost <= max_cost, Error::::Slippage); // TODO: apply lock if pool_details.transferable != true @@ -386,18 +387,19 @@ pub mod pallet { match &pool_details.curve { Curve::RationalBondingFunction(_) => { - let target_denomination_normalization = T::NormalizationFactor::get(); + let target_denomination_normalization = CurveParameterTypeOf::::DIV; - let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details + let currencies_metadata = pool_details .bonded_currencies .iter() .map(|id| { - ( - T::Fungibles::total_issuance(id.clone()), - T::Fungibles::decimals(id.clone()), - ) + let total_issuance = T::Fungibles::total_issuance(id.clone()); + let decimals = 10u128 + .checked_pow(T::Fungibles::decimals(id.clone()).into()) + .ok_or(ArithmeticError::Overflow)?; + Ok((total_issuance, decimals)) }) - .collect(); + .collect::, u128)>, ArithmeticError>>()?; let collateral = Self::burn_pool_currency_and_calculate_collateral( &pool_details, @@ -605,12 +607,13 @@ pub mod pallet { kind: DiffKind, curve: &Curve>, amount: &FungiblesBalanceOf, - total_issuances: Vec<(FungiblesBalanceOf, u8)>, + total_issuances: Vec<(FungiblesBalanceOf, u128)>, currency_idx: usize, ) -> Result, ArithmeticError> { - // todo: change that. We have also to restrict the denomination of the pool currencies maybe? - let target_denomination_normalization = T::NormalizationFactor::get(); - let target_denomination_costs = T::CollateralCurrency::decimals(T::CollateralAssetId::get()); + let target_denomination_normalization = CurveParameterTypeOf::::DIV; + let target_denomination_costs = 10u128 + .checked_pow(T::CollateralCurrency::decimals(T::CollateralAssetId::get()).into()) + .ok_or(ArithmeticError::Overflow)?; let normalized_issuances = total_issuances .clone() @@ -685,16 +688,17 @@ pub mod pallet { .get(currency_idx) .ok_or(Error::::IndexOutOfBounds)?; - let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details + let currencies_metadata = pool_details .bonded_currencies .iter() .map(|id| { - ( - T::Fungibles::total_issuance(id.clone()), - T::Fungibles::decimals(id.clone()), - ) + let total_issuance = T::Fungibles::total_issuance(id.clone()); + let decimals = 10u128 + .checked_pow(T::Fungibles::decimals(id.clone()).into()) + .ok_or(ArithmeticError::Overflow)?; + Ok((total_issuance, decimals)) }) - .collect(); + .collect::, u128)>, ArithmeticError>>()?; let real_amount = T::Fungibles::burn_from( burn_currency_id.clone(), @@ -729,16 +733,17 @@ pub mod pallet { .get(currency_idx) .ok_or(Error::::IndexOutOfBounds)?; - let currencies_metadata: Vec<(FungiblesBalanceOf, u8)> = pool_details + let currencies_metadata = pool_details .bonded_currencies .iter() .map(|id| { - ( - T::Fungibles::total_issuance(id.clone()), - T::Fungibles::decimals(id.clone()), - ) + let total_issuance = T::Fungibles::total_issuance(id.clone()); + let decimals = 10u128 + .checked_pow(T::Fungibles::decimals(id.clone()).into()) + .ok_or(ArithmeticError::Overflow)?; + Ok((total_issuance, decimals)) }) - .collect(); + .collect::, u128)>, ArithmeticError>>()?; let cost = Self::get_collateral_diff( DiffKind::Mint, diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 2c4d7b742..e6c4211f0 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -33,6 +33,8 @@ pub(crate) const DEFAULT_BONDED_CURRENCY_ID: AssetId = 0; pub(crate) const DEFAULT_COLLATERAL_CURRENCY_ID: AssetId = AssetId::MAX; pub(crate) const DEFAULT_COLLATERAL_DENOMINATION: u8 = 10; pub(crate) const DEFAULT_BONDED_DENOMINATION: u8 = 10; +pub(crate) const DEFAULT_COLLATERAL_UNIT: Balance = 10u128.pow(10); +pub(crate) const DEFAULT_BONDED_UNIT: Balance = 10u128.pow(10); pub const UNIT_NATIVE: Balance = 10u128.pow(15); // helper functions @@ -185,7 +187,6 @@ pub mod runtime { pub const CurrencyDeposit: Balance = 500; pub const MaxCurrencies: u32 = 50; pub const CollateralAssetId: u32 = u32::MAX; - pub const NormalizationFactor: u8 = 18; } impl Config for Test { @@ -203,7 +204,6 @@ pub mod runtime { type AssetId = AssetId; type BaseDeposit = ExistentialDeposit; type CurveParameterType = FixedU128; - type NormalizationFactor = NormalizationFactor; } #[derive(Clone, Default)] diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs index 64cf9777f..288ebe7e8 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs @@ -1,5 +1,5 @@ use frame_support::assert_ok; -use sp_runtime::{traits::Zero, FixedU128}; +use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128}; use crate::{ curves_parameters::transform_denomination_currency_amount, @@ -11,8 +11,6 @@ use crate::{ fn test_burn_into_account() { let currencies = vec![DEFAULT_BONDED_CURRENCY_ID]; let pool_id = calculate_pool_id(currencies.clone()); - let one_bonded_currency = get_currency_unit(DEFAULT_BONDED_DENOMINATION); - let one_collateral_currency = get_currency_unit(DEFAULT_COLLATERAL_DENOMINATION); let curve = get_linear_bonding_curve(); @@ -33,13 +31,13 @@ fn test_burn_into_account() { let expected_raw_return = transform_denomination_currency_amount::( expected_costs_normalized.into_inner(), - 18, - DEFAULT_BONDED_DENOMINATION, + FixedU128::DIV, + 10u128.pow(DEFAULT_BONDED_DENOMINATION.into()), ) .expect("Transforming costs should not fail") .into_inner(); - let collateral_balance_supply = one_collateral_currency * 10; + let collateral_balance_supply = DEFAULT_COLLATERAL_UNIT * 10; ExtBuilder::default() .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) @@ -56,7 +54,7 @@ fn test_burn_into_account() { pool_id.clone(), collateral_balance_supply, ), - (DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00, one_bonded_currency), + (DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00, DEFAULT_BONDED_UNIT), ]) .build() .execute_with(|| { @@ -64,7 +62,7 @@ fn test_burn_into_account() { RuntimeOrigin::signed(ACCOUNT_00), pool_id.clone(), 0, - one_bonded_currency, + DEFAULT_BONDED_UNIT, 0, ACCOUNT_00 )); diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs index 4ceec429f..6ec5a42cf 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs @@ -1,5 +1,5 @@ use frame_support::assert_ok; -use sp_runtime::FixedU128; +use sp_runtime::{FixedPointNumber, FixedU128}; use crate::{ curves_parameters::transform_denomination_currency_amount, @@ -11,8 +11,6 @@ use crate::{ fn test_mint_into_account() { let currencies = vec![DEFAULT_BONDED_CURRENCY_ID]; let pool_id = calculate_pool_id(currencies.clone()); - let one_bonded_currency = get_currency_unit(DEFAULT_BONDED_DENOMINATION); - let one_collateral_currency = get_currency_unit(DEFAULT_COLLATERAL_DENOMINATION); let curve = get_linear_bonding_curve(); @@ -33,13 +31,13 @@ fn test_mint_into_account() { let expected_raw_costs = transform_denomination_currency_amount::( expected_costs_normalized.into_inner(), - 18, - DEFAULT_BONDED_DENOMINATION, + FixedU128::DIV, + 10u128.pow(DEFAULT_BONDED_DENOMINATION.into()), ) .expect("Transforming costs should not fail") .into_inner(); - let collateral_balance_supply = one_collateral_currency * 10; + let collateral_balance_supply = DEFAULT_COLLATERAL_UNIT * 10; ExtBuilder::default() .with_native_balances(vec![(ACCOUNT_00, UNIT_NATIVE * 10), (pool_id.clone(), UNIT_NATIVE)]) @@ -61,14 +59,14 @@ fn test_mint_into_account() { RuntimeOrigin::signed(ACCOUNT_00), pool_id.clone(), 0, - one_bonded_currency, - one_collateral_currency * 5, + DEFAULT_BONDED_UNIT, + DEFAULT_COLLATERAL_UNIT * 5, ACCOUNT_00 )); // user should have the requested bonded coin let supply_minted_coins = Assets::balance(DEFAULT_BONDED_CURRENCY_ID, ACCOUNT_00); - assert_eq!(supply_minted_coins, one_bonded_currency); + assert_eq!(supply_minted_coins, DEFAULT_BONDED_UNIT); // pool should have the required collateral for minting the coin let collateral_balance = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, pool_id); @@ -76,7 +74,7 @@ fn test_mint_into_account() { // the total supply should be one let bonded_currency_total_supply = Assets::total_supply(DEFAULT_BONDED_CURRENCY_ID); - assert_eq!(bonded_currency_total_supply, one_bonded_currency); + assert_eq!(bonded_currency_total_supply, DEFAULT_BONDED_UNIT); // the submitter should have the collateral balance reduced by the minting cost let collateral_balance_submitter = Assets::balance(DEFAULT_COLLATERAL_CURRENCY_ID, ACCOUNT_00); diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs index 86b1dc62b..36ba73342 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs @@ -60,8 +60,8 @@ fn test_linear_bonding_overflow_m() { #[test] fn test_increase_denomination_currency_amount() { let amount = 100; - let current_denomination = 2; - let target_denomination = 3; + let current_denomination = 100; + let target_denomination = 1000; let result = transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); @@ -71,8 +71,8 @@ fn test_increase_denomination_currency_amount() { #[test] fn test_decrease_denomination_currency_amount() { let amount = 1000; - let current_denomination = 3; - let target_denomination = 2; + let current_denomination = 1000; + let target_denomination = 100; let result = transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); @@ -82,10 +82,10 @@ fn test_decrease_denomination_currency_amount() { #[test] fn test_increase_denomination_overflow() { let amount = u128::MAX; - let current_denomination = 10; + let current_denomination = 10000000000; // just increase denomination by one. This should overflow - let target_denomination = 11; + let target_denomination = 100000000000; let result = transform_denomination_currency_amount::(amount, current_denomination, target_denomination); assert_err!(result, ArithmeticError::Overflow); @@ -94,10 +94,10 @@ fn test_increase_denomination_overflow() { #[test] fn test_decrease_denomination_underflow() { let amount = 1; - let current_denomination = 5; + let current_denomination = 100000; // just increase - let target_denomination = 4; + let target_denomination = 10000; // we should have dropped all relevant bits. This should gives use an Ok with zero let result = diff --git a/pallets/pallet-bonded-coins/src/tests/types/types.rs b/pallets/pallet-bonded-coins/src/tests/types/types.rs index 4c37e512c..1c83de740 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/types.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/types.rs @@ -1,16 +1,15 @@ use sp_arithmetic::FixedU128; -use sp_runtime::traits::Zero; +use sp_runtime::{traits::Zero, FixedPointNumber}; use crate::{ curves_parameters::{transform_denomination_currency_amount, LinearBondingFunctionParameters}, mock::runtime::*, types::{Curve, DiffKind}, }; - // target denomination for collateral currency -const CURRENT_DENOMINATION: u8 = 15; -const NORMALIZED_DENOMINATION: u8 = 18; -const ONE_COIN: u128 = 10u128.pow(CURRENT_DENOMINATION as u32); +const CURRENT_DENOMINATION: u128 = 10u128.pow(15); +const NORMALIZED_DENOMINATION: u128 = FixedU128::DIV; +const ONE_COIN: u128 = CURRENT_DENOMINATION; #[test] fn test_mint_first_coin() { From 92f4bacce480a02a736c5811626f92701200f1c2 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 25 Sep 2024 10:59:14 +0200 Subject: [PATCH 41/46] minor refactoring --- .../src/curves_parameters.rs | 68 ++++++- pallets/pallet-bonded-coins/src/lib.rs | 179 +++++++----------- .../src/tests/transactions/burn_into.rs | 4 +- .../src/tests/transactions/mint_into.rs | 4 +- .../src/tests/types/curves_parameters.rs | 13 +- .../src/tests/types/types.rs | 88 +++------ pallets/pallet-bonded-coins/src/types.rs | 1 + 7 files changed, 163 insertions(+), 194 deletions(-) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index d0d1136a9..bd6070ac3 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -1,10 +1,14 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::ArithmeticError; -use sp_runtime::{FixedPointNumber, FixedU128}; +use sp_runtime::{ + traits::{CheckedDiv, Zero}, + DispatchError, FixedPointNumber, FixedU128, SaturatedConversion, Saturating, +}; use sp_std::marker::PhantomData; -use crate::{Config, CurveParameterTypeOf}; +use crate::{CollateralCurrencyBalanceOf, Config, CurveParameterTypeOf, Error, FungiblesBalanceOf}; + /// Little helper trait to calculate the square root of Fixed, in order to maintain the generic. pub trait SquareRoot: Sized { fn try_sqrt(self) -> Option; @@ -176,9 +180,67 @@ where .checked_add(&b_divided_sum_squared_multiplied_multiplied) .ok_or(ArithmeticError::Overflow) } + + pub fn process_swap( + &self, + currencies_metadata: Vec<(FungiblesBalanceOf, u128)>, + collateral_meta: (CollateralCurrencyBalanceOf, u128), + to_idx_usize: usize, + ) -> Result, DispatchError> { + let normalized_denomination = CurveParameterTypeOf::::DIV; + + let (collateral, denomination) = collateral_meta; + + let normalized_collateral = convert_currency_amount::( + collateral.saturated_into::(), + denomination, + normalized_denomination, + )?; + + let target_issuance = currencies_metadata + .get(to_idx_usize) + .ok_or(Error::::IndexOutOfBounds)? + .0; + + let normalized_target_issuance = convert_currency_amount::( + target_issuance.clone().saturated_into(), + currencies_metadata + .get(to_idx_usize) + .ok_or(Error::::IndexOutOfBounds)? + .1, + normalized_denomination, + )?; + + let normalized_total_issuances = currencies_metadata + .clone() + .into_iter() + .map(|(x, d)| convert_currency_amount::(x.saturated_into::(), d, normalized_denomination)) + .try_fold(CurveParameterTypeOf::::zero(), |sum, result| { + result.map(|x| sum.saturating_add(x)) + })?; + + let ratio = normalized_target_issuance + .checked_div(&normalized_total_issuances) + .ok_or(ArithmeticError::DivisionByZero)?; + + let supply_to_mint = normalized_collateral + .checked_div(&ratio) + .ok_or(ArithmeticError::DivisionByZero)?; + + let raw_supply = convert_currency_amount::( + supply_to_mint.into_inner(), + normalized_denomination, + currencies_metadata + .get(to_idx_usize) + .ok_or(Error::::IndexOutOfBounds)? + .1, + )?; + + Ok(raw_supply.into_inner().saturated_into()) + } } -pub fn transform_denomination_currency_amount( +pub fn convert_currency_amount( amount: u128, current_denomination: u128, target_denomination: u128, diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 869a5462b..87b665290 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -39,7 +39,6 @@ pub mod pallet { use frame_system::{ensure_root, pallet_prelude::*}; use parity_scale_codec::FullCodec; use scale_info::TypeInfo; - use sp_arithmetic::traits::CheckedDiv; use sp_runtime::{ traits::{CheckedAdd, CheckedSub, One, Saturating, StaticLookup, Zero}, ArithmeticError, BoundedVec, FixedPointNumber, SaturatedConversion, @@ -47,7 +46,7 @@ pub mod pallet { use sp_std::{default::Default, iter::Iterator, vec::Vec}; use crate::{ - curves_parameters::{transform_denomination_currency_amount, SquareRoot}, + curves_parameters::{convert_currency_amount, SquareRoot}, types::{Curve, DiffKind, Locks, PoolDetails, PoolStatus, TokenMeta}, }; @@ -58,10 +57,10 @@ pub mod pallet { pub(crate) type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; - type CollateralCurrencyBalanceOf = + pub(crate) type CollateralCurrencyBalanceOf = <::CollateralCurrency as InspectFungibles<::AccountId>>::Balance; - type FungiblesBalanceOf = + pub(crate) type FungiblesBalanceOf = <::Fungibles as InspectFungibles<::AccountId>>::Balance; type FungiblesAssetIdOf = @@ -331,7 +330,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; - let pool_details = >::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; + let pool_details = Pools::::get(pool_id.clone()).ok_or(Error::::PoolUnknown)?; ensure!(pool_details.is_burning_authorized(&who), Error::::Locked); @@ -347,7 +346,7 @@ pub mod pallet { )?; // withdraw collateral from deposit and transfer to beneficiary account; deposit account may be drained - let returns = T::CollateralCurrency::transfer( + T::CollateralCurrency::transfer( T::CollateralAssetId::get(), &pool_id.into(), &beneficiary, @@ -359,7 +358,7 @@ pub mod pallet { // this also serves as a validation of the currency_idx parameter // fail if returns < min_return - ensure!(returns >= min_return, Error::::Slippage); + ensure!(collateral_return >= min_return, Error::::Slippage); Ok(()) } @@ -386,21 +385,7 @@ pub mod pallet { let to_idx_usize: usize = to_idx.saturated_into(); match &pool_details.curve { - Curve::RationalBondingFunction(_) => { - let target_denomination_normalization = CurveParameterTypeOf::::DIV; - - let currencies_metadata = pool_details - .bonded_currencies - .iter() - .map(|id| { - let total_issuance = T::Fungibles::total_issuance(id.clone()); - let decimals = 10u128 - .checked_pow(T::Fungibles::decimals(id.clone()).into()) - .ok_or(ArithmeticError::Overflow)?; - Ok((total_issuance, decimals)) - }) - .collect::, u128)>, ArithmeticError>>()?; - + Curve::RationalBondingFunction(para) => { let collateral = Self::burn_pool_currency_and_calculate_collateral( &pool_details, from_idx_usize, @@ -408,53 +393,21 @@ pub mod pallet { amount_to_swap, )?; - let normalized_collateral = transform_denomination_currency_amount::( - collateral.saturated_into::(), - currencies_metadata[from_idx_usize].1, - target_denomination_normalization, - )?; + let collateral_denomination = Self::get_collateral_denomination()?; - let normalized_target_issuance = transform_denomination_currency_amount::( - amount_to_swap.clone().saturated_into(), - currencies_metadata[to_idx_usize].1, - target_denomination_normalization, - )?; + let currencies_metadata = Self::get_currencies_metadata(&pool_details)?; - let normalized_passive_issuances = currencies_metadata - .clone() - .into_iter() - .enumerate() - .filter(|(idx, _)| *idx != to_idx_usize) - .map(|(_, (x, d))| { - transform_denomination_currency_amount::( - x.saturated_into::(), - d, - target_denomination_normalization, - ) - }) - .try_fold(CurveParameterTypeOf::::zero(), |sum, result| { - result.map(|x| sum.saturating_add(x)) - })?; - - let ratio = normalized_target_issuance - .checked_div(&normalized_passive_issuances) - .ok_or(ArithmeticError::DivisionByZero)?; - - let supply_to_mint = normalized_collateral - .checked_div(&ratio) - .ok_or(ArithmeticError::DivisionByZero)?; - - let raw_supply = transform_denomination_currency_amount::( - supply_to_mint.into_inner(), - target_denomination_normalization, - currencies_metadata[to_idx_usize].1, + let raw_supply = para.process_swap::( + currencies_metadata, + (collateral, collateral_denomination), + to_idx_usize, )?; Self::mint_pool_currency_and_calculate_collateral( &pool_details, to_idx_usize, beneficiary, - raw_supply.into_inner().saturated_into(), + raw_supply, )?; } // The price for burning and minting in the pool is the same, if the bonding curve is not [RationalBondingFunction]. @@ -517,6 +470,7 @@ pub mod pallet { Ok(()) } + // TODO: not sure if we really need that. Check that out with Raphael. #[pallet::call_index(6)] #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))] pub fn start_destroy(origin: OriginFor, pool_id: T::PoolId) -> DispatchResult { @@ -609,35 +563,33 @@ pub mod pallet { amount: &FungiblesBalanceOf, total_issuances: Vec<(FungiblesBalanceOf, u128)>, currency_idx: usize, - ) -> Result, ArithmeticError> { - let target_denomination_normalization = CurveParameterTypeOf::::DIV; - let target_denomination_costs = 10u128 - .checked_pow(T::CollateralCurrency::decimals(T::CollateralAssetId::get()).into()) - .ok_or(ArithmeticError::Overflow)?; + ) -> Result, DispatchError> { + let denomination_normalization = CurveParameterTypeOf::::DIV; + let denomination_collateral_currency = Self::get_collateral_denomination()?; - let normalized_issuances = total_issuances + let denomination_bonded_currency = total_issuances.get(currency_idx).ok_or(Error::::IndexOutOfBounds)?.1; + + let normalized_total_issuances = total_issuances .clone() .into_iter() - .map(|(x, d)| { - transform_denomination_currency_amount::( - x.saturated_into::(), - d, - target_denomination_normalization, - ) - }) + .map(|(x, d)| convert_currency_amount::(x.saturated_into::(), d, denomination_normalization)) .collect::>, ArithmeticError>>()?; // normalize the amount to mint - let normalized_amount = transform_denomination_currency_amount::( + let normalized_amount = convert_currency_amount::( amount.clone().saturated_into(), - total_issuances[currency_idx].1, - target_denomination_normalization, + denomination_bonded_currency, + denomination_normalization, )?; - let (active_issuance_pre, active_issuance_post) = - Self::calculate_pre_post_issuances(&kind, &normalized_amount, &normalized_issuances, currency_idx)?; + let (active_issuance_pre, active_issuance_post) = Self::calculate_pre_post_issuances( + &kind, + &normalized_amount, + &normalized_total_issuances, + currency_idx, + )?; - let passive_issuance = normalized_issuances + let passive_issuance = normalized_total_issuances .iter() .enumerate() .filter(|&(idx, _)| idx != currency_idx) @@ -647,16 +599,13 @@ pub mod pallet { curve.calculate_cost(active_issuance_pre, active_issuance_post, passive_issuance, kind)?; // transform the cost back to the target denomination of the collateral currency - let real_costs = transform_denomination_currency_amount::( + let collateral = convert_currency_amount::( normalize_cost.into_inner(), - target_denomination_normalization, - target_denomination_costs, + denomination_normalization, + denomination_collateral_currency, )?; - real_costs - .into_inner() - .try_into() - .map_err(|_| ArithmeticError::Overflow) + Ok(collateral.into_inner().saturated_into()) } fn calculate_pre_post_issuances( @@ -664,8 +613,8 @@ pub mod pallet { amount: &CurveParameterTypeOf, total_issuances: &[CurveParameterTypeOf], currency_idx: usize, - ) -> Result<(CurveParameterTypeOf, CurveParameterTypeOf), ArithmeticError> { - let active_issuance_pre = total_issuances[currency_idx]; + ) -> Result<(CurveParameterTypeOf, CurveParameterTypeOf), DispatchError> { + let active_issuance_pre = total_issuances.get(currency_idx).ok_or(Error::::IndexOutOfBounds)?; let active_issuance_post = match kind { DiffKind::Mint => active_issuance_pre .checked_add(amount) @@ -674,7 +623,7 @@ pub mod pallet { .checked_sub(amount) .ok_or(ArithmeticError::Underflow)?, }; - Ok((active_issuance_pre, active_issuance_post)) + Ok((*active_issuance_pre, active_issuance_post)) } fn burn_pool_currency_and_calculate_collateral( @@ -688,19 +637,9 @@ pub mod pallet { .get(currency_idx) .ok_or(Error::::IndexOutOfBounds)?; - let currencies_metadata = pool_details - .bonded_currencies - .iter() - .map(|id| { - let total_issuance = T::Fungibles::total_issuance(id.clone()); - let decimals = 10u128 - .checked_pow(T::Fungibles::decimals(id.clone()).into()) - .ok_or(ArithmeticError::Overflow)?; - Ok((total_issuance, decimals)) - }) - .collect::, u128)>, ArithmeticError>>()?; + let currencies_metadata = Self::get_currencies_metadata(pool_details)?; - let real_amount = T::Fungibles::burn_from( + T::Fungibles::burn_from( burn_currency_id.clone(), &payer, amount, @@ -712,7 +651,7 @@ pub mod pallet { let returns = Self::get_collateral_diff( DiffKind::Burn, &pool_details.curve, - &real_amount, + &amount, currencies_metadata, currency_idx, )?; @@ -733,17 +672,7 @@ pub mod pallet { .get(currency_idx) .ok_or(Error::::IndexOutOfBounds)?; - let currencies_metadata = pool_details - .bonded_currencies - .iter() - .map(|id| { - let total_issuance = T::Fungibles::total_issuance(id.clone()); - let decimals = 10u128 - .checked_pow(T::Fungibles::decimals(id.clone()).into()) - .ok_or(ArithmeticError::Overflow)?; - Ok((total_issuance, decimals)) - }) - .collect::, u128)>, ArithmeticError>>()?; + let currencies_metadata = Self::get_currencies_metadata(pool_details)?; let cost = Self::get_collateral_diff( DiffKind::Mint, @@ -777,5 +706,27 @@ pub mod pallet { } (currency_ids_vec, start_id) } + + fn get_currencies_metadata( + pool_details: &PoolDetailsOf, + ) -> Result, u128)>, ArithmeticError> { + pool_details + .bonded_currencies + .iter() + .map(|id| { + let total_issuance = T::Fungibles::total_issuance(id.clone()); + let decimals = 10u128 + .checked_pow(T::Fungibles::decimals(id.clone()).into()) + .ok_or(ArithmeticError::Overflow)?; + Ok((total_issuance, decimals)) + }) + .collect() + } + + fn get_collateral_denomination() -> Result { + 10u128 + .checked_pow(T::CollateralCurrency::decimals(T::CollateralAssetId::get()).into()) + .ok_or(ArithmeticError::Overflow) + } } } diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs index 288ebe7e8..eddbcf293 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/burn_into.rs @@ -2,7 +2,7 @@ use frame_support::assert_ok; use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128}; use crate::{ - curves_parameters::transform_denomination_currency_amount, + curves_parameters::convert_currency_amount, mock::{runtime::*, *}, types::{DiffKind, PoolStatus}, }; @@ -29,7 +29,7 @@ fn test_burn_into_account() { ) .expect("Cost calculation should not fail"); - let expected_raw_return = transform_denomination_currency_amount::( + let expected_raw_return = convert_currency_amount::( expected_costs_normalized.into_inner(), FixedU128::DIV, 10u128.pow(DEFAULT_BONDED_DENOMINATION.into()), diff --git a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs index 6ec5a42cf..96baff0c5 100644 --- a/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs +++ b/pallets/pallet-bonded-coins/src/tests/transactions/mint_into.rs @@ -2,7 +2,7 @@ use frame_support::assert_ok; use sp_runtime::{FixedPointNumber, FixedU128}; use crate::{ - curves_parameters::transform_denomination_currency_amount, + curves_parameters::convert_currency_amount, mock::{runtime::*, *}, types::{DiffKind, PoolStatus}, }; @@ -29,7 +29,7 @@ fn test_mint_into_account() { ) .expect("Cost calculation should not fail"); - let expected_raw_costs = transform_denomination_currency_amount::( + let expected_raw_costs = convert_currency_amount::( expected_costs_normalized.into_inner(), FixedU128::DIV, 10u128.pow(DEFAULT_BONDED_DENOMINATION.into()), diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs index 36ba73342..1594e4cad 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs @@ -3,7 +3,7 @@ use sp_arithmetic::{ArithmeticError, FixedU128}; use sp_runtime::traits::Zero; use crate::{ - curves_parameters::{transform_denomination_currency_amount, BondingFunction, LinearBondingFunctionParameters}, + curves_parameters::{convert_currency_amount, BondingFunction, LinearBondingFunctionParameters}, mock::runtime::*, }; @@ -63,8 +63,7 @@ fn test_increase_denomination_currency_amount() { let current_denomination = 100; let target_denomination = 1000; - let result = - transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); + let result = convert_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::from_inner(1000)); } @@ -74,8 +73,7 @@ fn test_decrease_denomination_currency_amount() { let current_denomination = 1000; let target_denomination = 100; - let result = - transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); + let result = convert_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::from_inner(100)); } @@ -87,7 +85,7 @@ fn test_increase_denomination_overflow() { // just increase denomination by one. This should overflow let target_denomination = 100000000000; - let result = transform_denomination_currency_amount::(amount, current_denomination, target_denomination); + let result = convert_currency_amount::(amount, current_denomination, target_denomination); assert_err!(result, ArithmeticError::Overflow); } @@ -100,7 +98,6 @@ fn test_decrease_denomination_underflow() { let target_denomination = 10000; // we should have dropped all relevant bits. This should gives use an Ok with zero - let result = - transform_denomination_currency_amount::(amount, current_denomination, target_denomination).unwrap(); + let result = convert_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::zero()) } diff --git a/pallets/pallet-bonded-coins/src/tests/types/types.rs b/pallets/pallet-bonded-coins/src/tests/types/types.rs index 1c83de740..e53ad417f 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/types.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/types.rs @@ -2,7 +2,7 @@ use sp_arithmetic::FixedU128; use sp_runtime::{traits::Zero, FixedPointNumber}; use crate::{ - curves_parameters::{transform_denomination_currency_amount, LinearBondingFunctionParameters}, + curves_parameters::{convert_currency_amount, LinearBondingFunctionParameters}, mock::runtime::*, types::{Curve, DiffKind}, }; @@ -25,18 +25,10 @@ fn test_mint_first_coin() { // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); - let normalized_active_issuance_pre = transform_denomination_currency_amount::( - active_issuance_pre, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); - let normalized_active_issuance_post = transform_denomination_currency_amount::( - active_issuance_post, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); // The cost to mint the first coin should be 4. let costs = curve @@ -65,18 +57,10 @@ fn test_mint_coin_with_existing_supply() { // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); - let normalized_active_issuance_pre = transform_denomination_currency_amount::( - active_issuance_pre, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); - let normalized_active_issuance_post = transform_denomination_currency_amount::( - active_issuance_post, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); // Existing supply: 100^2 + 3*100 = 10300 // New supply: 110^2 + 3*110 = 12130 @@ -107,21 +91,12 @@ fn test_mint_coin_with_existing_passive_supply() { // Multiple coins in pool. Passive issuance is 10. let passive_issuance = ONE_COIN * 10; - let normalized_active_issuance_pre = transform_denomination_currency_amount::( - active_issuance_pre, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); - let normalized_active_issuance_post = transform_denomination_currency_amount::( - active_issuance_post, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); let normalized_passive_issuance = - transform_denomination_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + convert_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); // The passive issuance should influence the price of the new selected currency. // Existing supply: (10)^2 + (10 )*3 = 130 @@ -153,21 +128,12 @@ fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { // Multiple coins in pool. Passive issuance is 10. let passive_issuance = ONE_COIN * 10; - let normalized_active_issuance_pre = transform_denomination_currency_amount::( - active_issuance_pre, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); - let normalized_active_issuance_post = transform_denomination_currency_amount::( - active_issuance_post, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); let normalized_passive_issuance = - transform_denomination_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION) - .unwrap(); + convert_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); // The passive issuance should influence the price of the new selected currency. // Existing supply: (20)^2 + (20)*3 = 460 @@ -199,18 +165,10 @@ fn test_mint_first_coin_frac_bonding_curve() { // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); - let normalized_active_issuance_pre = transform_denomination_currency_amount::( - active_issuance_pre, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); - let normalized_active_issuance_post = transform_denomination_currency_amount::( - active_issuance_post, - CURRENT_DENOMINATION, - NORMALIZED_DENOMINATION, - ) - .unwrap(); + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); // Existing supply: 1/2*(0)^2 + (0)*3 = 0 // New supply: 1/2*(1)^2 + (1)*3 = 3.5 diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 6b7c8eb87..53a6527ec 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -141,6 +141,7 @@ where Curve::LinearRatioCurve(params) => params.calculate_costs(low, high), Curve::QuadraticRatioCurve(params) => params.calculate_costs(low, high), Curve::SquareRootBondingFunction(params) => params.calculate_costs(low, high), + // TODO: This is probably a bug. Curve::RationalBondingFunction(params) => params.calculate_costs( (active_issuance_pre, passive_issuance), (active_issuance_post, passive_issuance), From 86b2d48cb20809560d43140f8275c56b618193f6 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 25 Sep 2024 15:13:11 +0200 Subject: [PATCH 42/46] more tests for bonding function --- .../src/curves_parameters.rs | 16 +- pallets/pallet-bonded-coins/src/mock.rs | 16 +- .../types/curve_parameters/linear_function.rs | 190 ++++++++++++++ .../mod.rs} | 59 +---- .../curve_parameters/quadratic_function.rs | 234 ++++++++++++++++++ .../curve_parameters/square_root_function.rs | 185 ++++++++++++++ .../src/tests/types/mod.rs | 2 +- 7 files changed, 641 insertions(+), 61 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs rename pallets/pallet-bonded-coins/src/tests/types/{curves_parameters.rs => curve_parameters/mod.rs} (51%) create mode 100644 pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index bd6070ac3..9af48a676 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -1,3 +1,4 @@ +use frame_support::ensure; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_arithmetic::ArithmeticError; @@ -63,7 +64,7 @@ impl BondingFunction for LinearBondingFunctionParameters where F: FixedPointNumber, { - /// F(x) = m * x + n + /// F(x) = m * x^2 + xn fn get_value(&self, x: F) -> Result { let x2 = Self::get_power_2(x)?; @@ -71,6 +72,8 @@ where let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; + + ensure!(result >= F::zero(), ArithmeticError::Underflow); Ok(result) } } @@ -100,6 +103,8 @@ where .ok_or(ArithmeticError::Overflow)? .checked_add(&ox) .ok_or(ArithmeticError::Overflow)?; + + ensure!(result >= F::zero(), ArithmeticError::Underflow); Ok(result) } } @@ -114,14 +119,17 @@ impl BondingFunction for SquareRootBondingFunctionParameters where F: FixedPointNumber + SquareRoot, { - /// F(x) = m * sqrt(x^2) + n * x + /// F(x) = m * sqrt(x^3) + n * x fn get_value(&self, x: F) -> Result { let x3 = Self::get_power_3(x)?; - let sqrt_x3 = x3.try_sqrt().ok_or(ArithmeticError::Overflow)?; + let sqrt_x3 = x3.try_sqrt().ok_or(ArithmeticError::Underflow)?; let mx3 = self.m.clone().checked_mul(&sqrt_x3).ok_or(ArithmeticError::Overflow)?; let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; - mx3.checked_add(&nx).ok_or(ArithmeticError::Overflow) + let result = mx3.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; + + ensure!(result >= F::zero(), ArithmeticError::Underflow); + Ok(result) } } diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index e6c4211f0..8c261114c 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -8,11 +8,11 @@ use frame_system::{EnsureRoot, EnsureSigned}; use sp_arithmetic::FixedU128; use sp_runtime::{ traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, - BoundedVec, BuildStorage, MultiSignature, + BoundedVec, BuildStorage, FixedI128, MultiSignature, }; use crate::{ - curves_parameters::LinearBondingFunctionParameters, + curves_parameters::{LinearBondingFunctionParameters, SquareRoot}, types::{Curve, Locks, PoolStatus}, Config, DepositCurrencyBalanceOf, PoolDetailsOf, }; @@ -53,6 +53,18 @@ pub(crate) fn get_currency_unit(denomination: u8) -> Balance { 10u128.pow(denomination as u32) } +// trait implementations + +impl SquareRoot for FixedI128 { + fn sqrt(self) -> Self { + self.sqrt() + } + + fn try_sqrt(self) -> Option { + self.try_sqrt() + } +} + #[cfg(test)] pub mod runtime { diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs new file mode 100644 index 000000000..884b555b4 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs @@ -0,0 +1,190 @@ +use frame_support::assert_err; +use sp_arithmetic::{ArithmeticError, FixedI128, FixedU128}; +use sp_runtime::FixedPointNumber; + +use crate::curves_parameters::{BondingFunction, LinearBondingFunctionParameters}; + +#[test] +fn test_all_zero() { + let params = LinearBondingFunctionParameters { + m: FixedU128::from(0), + n: FixedU128::from(0), + }; + let x = FixedU128::from(0); + assert_eq!(params.get_value(x), Ok(FixedU128::from(0))); +} + +#[test] +fn test_basic_test() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = LinearBondingFunctionParameters { m, n }; + + // 1*1^2 + 2*1 = 3 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(3)); +} + +#[test] +fn test_fraction() { + let m = FixedU128::from_rational(1, 2); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = LinearBondingFunctionParameters { m, n }; + + // 0.5*1^2 + 2*1 = 2.5 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(5, 2)); +} + +#[test] +fn test_large_values() { + let params = LinearBondingFunctionParameters { + m: FixedU128::from_u32(1000000), + n: FixedU128::from_u32(1000000), + }; + let x = FixedU128::from_u32(1); + // 1000000 * 1^2 + 1000000 * 1 = 2000000 + assert_eq!(params.get_value(x), Ok(FixedU128::from(2000000))); +} + +#[test] +fn test_large_x() { + let params = LinearBondingFunctionParameters { + m: FixedU128::from(2), + n: FixedU128::from(3), + }; + let x = FixedU128::from_u32(1000000000); + + // 2*1000000000^2 + 3*1000000000 = 2000000003000000000 + assert_eq!(params.get_value(x), Ok(FixedU128::from(2000000003000000000))); +} + +#[test] +fn test_negative() { + let params = LinearBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(3), + }; + let x = FixedI128::from(-4); + // 2*(-4)^2 + 3*(-4) = 32 - 12 = 20 + assert_eq!(params.get_value(x), Ok(FixedI128::from(20))); +} + +#[test] +fn test_negative_m() { + let params = LinearBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(3), + }; + let x = FixedI128::from(4); + // -2*4^2 + 3*4 = -32 + 12 = -20 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_m_and_n() { + let params = LinearBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(-3), + }; + let x = FixedI128::from(4); + // -2*4^2 - 3*4 = -32 - 12 = -44 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_n() { + let params = LinearBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(-3), + }; + let x = FixedI128::from(4); + // 2*4^2 - 3*4 = 32 - 12 = 20 + assert_eq!(params.get_value(x), Ok(FixedI128::from(20))); +} + +#[test] +fn test_overflow_m() { + let m = FixedU128::from_inner(u128::MAX); + let n = FixedU128::from_inner(1); + let x = FixedU128::from_u32(2); + + let curve = LinearBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_overflow_n() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_inner(u128::MAX); + let x = FixedU128::from_u32(2); + + let curve = LinearBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_precision_large_fraction() { + let m = FixedU128::from_rational(999999, 1000000); // 0.999999 + let n = FixedU128::from_rational(999999, 1000000); // 0.999999 + let x = FixedU128::from_rational(999999, 1000000); // 0.999999 + + let curve = LinearBondingFunctionParameters { m, n }; + + // 0.999999*(0.999999^2) + 0.999999*0.999999 + // = 1.999995000003999999 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(1999995000003999999, FixedU128::DIV)); +} + +#[test] +fn test_precision_mixed_fraction() { + // 0.3 + let m = FixedU128::from_rational(3, 10); + // 0.75 + let n = FixedU128::from_rational(3, 4); + let x = FixedU128::from_u32(1); // 1 + + let curve = LinearBondingFunctionParameters { m, n }; + + // 0.3*(1) + 0.75*1 + // = 1.05 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(105, 100)); +} + +#[test] +fn test_precision_small_fraction() { + // 0.001 + let m = FixedU128::from_rational(1, 1000); + let n = FixedU128::from_rational(1, 1000); + // 1 + let x = FixedU128::from_u32(1); + + let curve = LinearBondingFunctionParameters { m, n }; + + // 0.001*(1^2) + 0.001*1 + // = 0.002 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(2, 1000)); +} + +#[test] +fn test_zero_x() { + let params = LinearBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(3), + }; + let x = FixedI128::from(0); + + // 2*0^2 + 3*0 = 0 + assert_eq!(params.get_value(x), Ok(FixedI128::from(0))); +} diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs similarity index 51% rename from pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs rename to pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs index 1594e4cad..4a794ee3a 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs @@ -1,61 +1,12 @@ +mod linear_function; +mod quadratic_function; +mod square_root_function; + use frame_support::assert_err; use sp_arithmetic::{ArithmeticError, FixedU128}; use sp_runtime::traits::Zero; -use crate::{ - curves_parameters::{convert_currency_amount, BondingFunction, LinearBondingFunctionParameters}, - mock::runtime::*, -}; - -#[test] -fn test_linear_bonding_function_basic_test() { - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(2); - let x = FixedU128::from_u32(1); - - let curve = LinearBondingFunctionParameters { m, n }; - - // 1*1^2 + 2*1 = 3 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_u32(3)); -} - -#[test] -fn test_linear_bonding_function_fraction() { - let m = FixedU128::from_rational(1, 2); - let n = FixedU128::from_u32(2); - let x = FixedU128::from_u32(1); - - let curve = LinearBondingFunctionParameters { m, n }; - - // 0.5*1^2 + 2*1 = 2.5 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_rational(5, 2)); -} - -#[test] -fn test_linear_bonding_overflow_n() { - let m = FixedU128::from_u32(1); - let n = FixedU128::from_inner(u128::MAX); - let x = FixedU128::from_u32(2); - - let curve = LinearBondingFunctionParameters { m, n }; - - let result = curve.get_value(x); - assert_err!(result, ArithmeticError::Overflow); -} - -#[test] -fn test_linear_bonding_overflow_m() { - let m = FixedU128::from_inner(u128::MAX); - let n = FixedU128::from_inner(1); - let x = FixedU128::from_u32(2); - - let curve = LinearBondingFunctionParameters { m, n }; - - let result = curve.get_value(x); - assert_err!(result, ArithmeticError::Overflow); -} +use crate::{curves_parameters::convert_currency_amount, mock::runtime::*}; #[test] fn test_increase_denomination_currency_amount() { diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs new file mode 100644 index 000000000..313b9ead3 --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs @@ -0,0 +1,234 @@ +use frame_support::assert_err; +use sp_arithmetic::{ArithmeticError, FixedI128, FixedU128}; +use sp_runtime::FixedPointNumber; + +use crate::curves_parameters::{BondingFunction, QuadraticBondingFunctionParameters}; + +#[test] +fn test_all_zero() { + let params = QuadraticBondingFunctionParameters { + m: FixedU128::from(0), + n: FixedU128::from(0), + o: FixedU128::from(0), + }; + let x = FixedU128::from(0); + assert_eq!(params.get_value(x), Ok(FixedU128::from(0))); +} + +#[test] +fn test_basic_test() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let o = FixedU128::from_u32(3); + let x = FixedU128::from_u32(1); + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + // 1*1^3 + 2*1^2 + 3* 1 = 6 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(6)); +} + +#[test] +fn test_fraction() { + let m = FixedU128::from_rational(1, 2); + let n = FixedU128::from_u32(2); + let o = FixedU128::from_u32(3); + let x = FixedU128::from_u32(1); + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + // 0.5*1^3 + 2*1^2 + 3* 1 = 5.5 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(11, 2)); +} + +#[test] +fn test_large_values() { + let params = QuadraticBondingFunctionParameters { + m: FixedU128::from_u32(1000000), + n: FixedU128::from_u32(1000000), + o: FixedU128::from_u32(1000000), + }; + let x = FixedU128::from_u32(1); + // 1000000 * 1^3 + 1000000 * 1^2 + 1000000 * 1 = 3000000 + assert_eq!(params.get_value(x), Ok(FixedU128::from(3000000))); +} + +#[test] +fn test_large_x() { + let params = QuadraticBondingFunctionParameters { + m: FixedU128::from(2), + n: FixedU128::from(3), + o: FixedU128::from(4), + }; + let x = FixedU128::from(1000000); + + // 2*1000000^3 + 3*1000000^2 + 4*1000000 = 2000003000004000000 + assert_eq!(params.get_value(x), Ok(FixedU128::from(2000003000004000000))); +} + +#[test] +fn test_negative() { + let params = QuadraticBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(3), + o: FixedI128::from(4), + }; + let x = FixedI128::from(-4); + + // 2*(-4)^3 + 3*(-4)^2 + 4 * -4 = -96 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_m() { + let params = QuadraticBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(3), + o: FixedI128::from(4), + }; + let x = FixedI128::from(4); + // -2*4^3 + 3*4^2 + 4 * 4 = -128 + 48 + 16 = -64 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_m_and_n() { + let params = QuadraticBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(-3), + o: FixedI128::from(4), + }; + let x = FixedI128::from(4); + + // -2*4^3 - 3*4^2 + 4 *4 = -128 - 48 + 16 = -160 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_m_n_and_o() { + let params = QuadraticBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(-3), + o: FixedI128::from(-4), + }; + let x = FixedI128::from(4); + + // -2*4^3 - 3*4^2 - 4*4 = -128 - 48 - 16 = -192 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_n() { + let params = QuadraticBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(-3), + o: FixedI128::from(4), + }; + let x = FixedI128::from(4); + // 2*4^3 - 3*4^2 + 4 * 6 = 128 - 48 + 16 = 96 + assert_eq!(params.get_value(x), Ok(FixedI128::from(96))); +} + +#[test] +fn test_overflow_m() { + let m = FixedU128::from_inner(u128::MAX); + let n = FixedU128::from_inner(1); + let o = FixedU128::from_inner(1); + let x = FixedU128::from_u32(2); + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_overflow_n() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_inner(u128::MAX); + let o = FixedU128::from_u32(1); + let x = FixedU128::from_u32(2); + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_overflow_o() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(1); + let o = FixedU128::from_inner(u128::MAX); + let x = FixedU128::from_u32(2); + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_precision_large_fraction() { + let m = FixedU128::from_rational(999999, 1000000); // 0.999999 + let n = FixedU128::from_rational(999999, 1000000); // 0.999999 + let o = FixedU128::from_rational(999999, 1000000); // 0.999999 + let x = FixedU128::from_rational(999999, 1000000); // 0.999999 + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + // 0.999999*(0.999999^3) + 0.999999*0.999999^2 + 0.999999*0.999999 + // = 2.999991000009999995000001 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(2999991000009999995, FixedU128::DIV)); +} + +#[test] +fn test_precision_mixed_fraction() { + // 0.3 + let m = FixedU128::from_rational(3, 10); + // 0.75 + let n = FixedU128::from_rational(3, 4); + // 0.5 + let o = FixedU128::from_rational(1, 2); + let x = FixedU128::from_u32(1); // 1 + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + // 0.3*(1^3) + 0.75*1^2 + 0.5*1 + // = 1.55 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(155, 100)); +} + +#[test] +fn test_precision_small_fraction() { + // 0.001 + let m = FixedU128::from_rational(1, 1000); + let n = FixedU128::from_rational(1, 1000); + let o = FixedU128::from_rational(1, 1000); + // 1 + let x = FixedU128::from_u32(1); + + let curve = QuadraticBondingFunctionParameters { m, n, o }; + + // 0.001*(1^3) + 0.001*1^2 + 0.001*1 + // = 0.003 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(3, 1000)); +} + +#[test] +fn test_zero_x() { + let params = QuadraticBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(3), + o: FixedI128::from(4), + }; + let x = FixedI128::from(0); + + // 2*0^3 + 3*0^2 + 4*0 = 0 + assert_eq!(params.get_value(x), Ok(FixedI128::from(0))); +} diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs new file mode 100644 index 000000000..21c28f0fb --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs @@ -0,0 +1,185 @@ +use frame_support::assert_err; +use sp_arithmetic::{ArithmeticError, FixedI128, FixedU128}; +use sp_runtime::FixedPointNumber; + +use crate::curves_parameters::{BondingFunction, SquareRootBondingFunctionParameters}; + +#[test] +fn test_all_zero() { + let params = SquareRootBondingFunctionParameters { + m: FixedU128::from(0), + n: FixedU128::from(0), + }; + let x = FixedU128::from(0); + assert_eq!(params.get_value(x), Ok(FixedU128::from(0))); +} + +#[test] +fn test_basic_test() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = SquareRootBondingFunctionParameters { m, n }; + + // 1*sqrt(1^3) + 2*1 = 1 + 2 = 3 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(3)); +} + +#[test] +fn test_fraction() { + let m = FixedU128::from_rational(1, 2); + let n = FixedU128::from_u32(2); + let x = FixedU128::from_u32(1); + + let curve = SquareRootBondingFunctionParameters { m, n }; + + // 0.5*sqrt(1^3) + 2*1 = 0.5*1 + 2 = 2.5 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(5, 2)); +} + +#[test] +fn test_large_values() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from_u32(1000000), + n: FixedI128::from_u32(1000000), + }; + let x = FixedI128::from_u32(1000000); + // 1000000*sqrt(1000000^3) + 1000000*1000000 = 1001000000000000 + assert_eq!(params.get_value(x), Ok(FixedI128::from(1001000000000000))); +} + +#[test] +fn test_negative() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(3), + }; + let x = FixedI128::from(-4); + // 2*sqrt((-4)^3) + 3*(-4) = 2*sqrt(-64) - 12 = 2*8i - 12 (complex number) + // Since sqrt of negative number is not defined in real numbers, it should return an error + let result = params.get_value(x); + assert_err!(result, ArithmeticError::Underflow); +} + +#[test] +fn test_negative_m() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(3), + }; + let x = FixedI128::from(4); + // -2*sqrt(4^3) + 3*4 = -2*sqrt(64) + 12 = -2*8 + 12 = -16 + 12 = -4 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_m_and_n() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from(-2), + n: FixedI128::from(-3), + }; + let x = FixedI128::from(4); + // -2*sqrt(4^3) - 3*4 = -2*sqrt(64) - 12 = -2*8 - 12 = -16 - 12 = -28 + assert_err!(params.get_value(x), ArithmeticError::Underflow); +} + +#[test] +fn test_negative_n() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(-3), + }; + let x = FixedI128::from(4); + // 2*sqrt(4^3) - 3*4 = 2*sqrt(64) - 12 = 2*8 - 12 = 16 - 12 = 4 + assert_eq!(params.get_value(x), Ok(FixedI128::from(4))); +} + +#[test] +fn test_overflow_m() { + let m = FixedU128::from_inner(u128::MAX); + let n = FixedU128::from_inner(1); + let x = FixedU128::from_u32(2); + + let curve = SquareRootBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_overflow_n() { + let m = FixedU128::from_u32(1); + let n = FixedU128::from_inner(u128::MAX); + let x = FixedU128::from_u32(2); + + let curve = SquareRootBondingFunctionParameters { m, n }; + + let result = curve.get_value(x); + assert_err!(result, ArithmeticError::Overflow); +} + +#[test] +fn test_precision_large_fraction() { + let m = FixedU128::from_rational(999999, 1000000); // 0.999999 + let n = FixedU128::from_rational(999999, 1000000); // 0.999999 + let x = FixedU128::from_rational(999999, 1000000); // 0.999999 + + let curve = SquareRootBondingFunctionParameters { m, n }; + + // 0.999999*sqrt(0.999999^3) + 0.999999*0.999999 + // = 1.999995500002874999 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(1999995500002874999, FixedU128::DIV)); +} +#[test] +fn test_precision_mixed_fraction() { + let m = FixedU128::from_rational(3, 10); // 0.3 + let n = FixedU128::from_rational(3, 4); // 0.75 + let x = FixedU128::from_rational(1, 2); // 0.5 + + let curve = SquareRootBondingFunctionParameters { m, n }; + + // 0.3*sqrt(0.5^3) + 0.75*0.5 + // = 0.481066017177982128 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(481066017177982128, FixedU128::DIV)); +} + +#[test] +fn test_precision_small_fraction() { + let m = FixedU128::from_rational(1, 1000); // 0.001 + let n = FixedU128::from_rational(1, 1000); // 0.001 + let x = FixedU128::from_rational(1, 1000); // 0.001 + + let curve = SquareRootBondingFunctionParameters { m, n }; + + // 0.001*sqrt(0.001^3) + 0.001*0.001 + // = 0.000001031622776601 + let result = curve.get_value(x).unwrap(); + assert_eq!(result, FixedU128::from_rational(1031622776601, FixedU128::DIV)); +} + +#[test] +fn test_zero_m_and_n() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from(0), + n: FixedI128::from(0), + }; + let x = FixedI128::from(4); + // 0*sqrt(4^3) + 0*4 = 0 + assert_eq!(params.get_value(x), Ok(FixedI128::from(0))); +} + +#[test] +fn test_zero_x() { + let params = SquareRootBondingFunctionParameters { + m: FixedI128::from(2), + n: FixedI128::from(3), + }; + let x = FixedI128::from(0); + // 2*sqrt(0^3) + 3*0 = 0 + assert_eq!(params.get_value(x), Ok(FixedI128::from(0))); +} diff --git a/pallets/pallet-bonded-coins/src/tests/types/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/mod.rs index 0451a780e..b4be483ee 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/mod.rs @@ -1,2 +1,2 @@ -mod curves_parameters; +mod curve_parameters; mod types; From b23e8df2f10fdac4ac1b06a6c37cc8c413cfa69d Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Wed, 25 Sep 2024 15:27:30 +0200 Subject: [PATCH 43/46] get rid of linear function --- .../src/curves_parameters.rs | 31 +-- pallets/pallet-bonded-coins/src/mock.rs | 9 +- .../types/curve_parameters/linear_function.rs | 190 ------------------ .../src/tests/types/curve_parameters/mod.rs | 3 +- .../curve_parameters/quadratic_function.rs | 36 ++-- .../types/curve_parameters/ratio_function.rs | 0 .../src/tests/types/types.rs | 37 ++-- pallets/pallet-bonded-coins/src/types.rs | 6 +- 8 files changed, 50 insertions(+), 262 deletions(-) delete mode 100644 pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs create mode 100644 pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index 9af48a676..ba3b7ef84 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -51,41 +51,14 @@ pub trait BondingFunction { } } -/// A linear bonding function with the shape of f(x) = mx + n, -/// which results in the primitive integral F(x) = m' * x^2 + n * x. -/// It is expected that the user provides the correct parameters for the curve. #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct LinearBondingFunctionParameters { - pub m: F, - pub n: F, -} - -impl BondingFunction for LinearBondingFunctionParameters -where - F: FixedPointNumber, -{ - /// F(x) = m * x^2 + xn - fn get_value(&self, x: F) -> Result { - let x2 = Self::get_power_2(x)?; - - let mx2 = self.m.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; - let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; - - let result = mx2.checked_add(&nx).ok_or(ArithmeticError::Overflow)?; - - ensure!(result >= F::zero(), ArithmeticError::Underflow); - Ok(result) - } -} - -#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct QuadraticBondingFunctionParameters { +pub struct PolynomialFunctionParameters { pub m: F, pub n: F, pub o: F, } -impl BondingFunction for QuadraticBondingFunctionParameters +impl BondingFunction for PolynomialFunctionParameters where F: FixedPointNumber, { diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 8c261114c..fce2323eb 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -12,7 +12,7 @@ use sp_runtime::{ }; use crate::{ - curves_parameters::{LinearBondingFunctionParameters, SquareRoot}, + curves_parameters::{PolynomialFunctionParameters, SquareRoot}, types::{Curve, Locks, PoolStatus}, Config, DepositCurrencyBalanceOf, PoolDetailsOf, }; @@ -40,9 +40,10 @@ pub const UNIT_NATIVE: Balance = 10u128.pow(15); // helper functions pub(crate) fn get_linear_bonding_curve() -> Curve { - let m = FixedU128::from_u32(2); - let n = FixedU128::from_u32(3); - Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }) + let m = FixedU128::from_u32(0); + let n = FixedU128::from_u32(2); + let o = FixedU128::from_u32(3); + Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }) } pub(crate) fn calculate_pool_id(currencies: Vec) -> AccountId { diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs deleted file mode 100644 index 884b555b4..000000000 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/linear_function.rs +++ /dev/null @@ -1,190 +0,0 @@ -use frame_support::assert_err; -use sp_arithmetic::{ArithmeticError, FixedI128, FixedU128}; -use sp_runtime::FixedPointNumber; - -use crate::curves_parameters::{BondingFunction, LinearBondingFunctionParameters}; - -#[test] -fn test_all_zero() { - let params = LinearBondingFunctionParameters { - m: FixedU128::from(0), - n: FixedU128::from(0), - }; - let x = FixedU128::from(0); - assert_eq!(params.get_value(x), Ok(FixedU128::from(0))); -} - -#[test] -fn test_basic_test() { - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(2); - let x = FixedU128::from_u32(1); - - let curve = LinearBondingFunctionParameters { m, n }; - - // 1*1^2 + 2*1 = 3 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_u32(3)); -} - -#[test] -fn test_fraction() { - let m = FixedU128::from_rational(1, 2); - let n = FixedU128::from_u32(2); - let x = FixedU128::from_u32(1); - - let curve = LinearBondingFunctionParameters { m, n }; - - // 0.5*1^2 + 2*1 = 2.5 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_rational(5, 2)); -} - -#[test] -fn test_large_values() { - let params = LinearBondingFunctionParameters { - m: FixedU128::from_u32(1000000), - n: FixedU128::from_u32(1000000), - }; - let x = FixedU128::from_u32(1); - // 1000000 * 1^2 + 1000000 * 1 = 2000000 - assert_eq!(params.get_value(x), Ok(FixedU128::from(2000000))); -} - -#[test] -fn test_large_x() { - let params = LinearBondingFunctionParameters { - m: FixedU128::from(2), - n: FixedU128::from(3), - }; - let x = FixedU128::from_u32(1000000000); - - // 2*1000000000^2 + 3*1000000000 = 2000000003000000000 - assert_eq!(params.get_value(x), Ok(FixedU128::from(2000000003000000000))); -} - -#[test] -fn test_negative() { - let params = LinearBondingFunctionParameters { - m: FixedI128::from(2), - n: FixedI128::from(3), - }; - let x = FixedI128::from(-4); - // 2*(-4)^2 + 3*(-4) = 32 - 12 = 20 - assert_eq!(params.get_value(x), Ok(FixedI128::from(20))); -} - -#[test] -fn test_negative_m() { - let params = LinearBondingFunctionParameters { - m: FixedI128::from(-2), - n: FixedI128::from(3), - }; - let x = FixedI128::from(4); - // -2*4^2 + 3*4 = -32 + 12 = -20 - assert_err!(params.get_value(x), ArithmeticError::Underflow); -} - -#[test] -fn test_negative_m_and_n() { - let params = LinearBondingFunctionParameters { - m: FixedI128::from(-2), - n: FixedI128::from(-3), - }; - let x = FixedI128::from(4); - // -2*4^2 - 3*4 = -32 - 12 = -44 - assert_err!(params.get_value(x), ArithmeticError::Underflow); -} - -#[test] -fn test_negative_n() { - let params = LinearBondingFunctionParameters { - m: FixedI128::from(2), - n: FixedI128::from(-3), - }; - let x = FixedI128::from(4); - // 2*4^2 - 3*4 = 32 - 12 = 20 - assert_eq!(params.get_value(x), Ok(FixedI128::from(20))); -} - -#[test] -fn test_overflow_m() { - let m = FixedU128::from_inner(u128::MAX); - let n = FixedU128::from_inner(1); - let x = FixedU128::from_u32(2); - - let curve = LinearBondingFunctionParameters { m, n }; - - let result = curve.get_value(x); - assert_err!(result, ArithmeticError::Overflow); -} - -#[test] -fn test_overflow_n() { - let m = FixedU128::from_u32(1); - let n = FixedU128::from_inner(u128::MAX); - let x = FixedU128::from_u32(2); - - let curve = LinearBondingFunctionParameters { m, n }; - - let result = curve.get_value(x); - assert_err!(result, ArithmeticError::Overflow); -} - -#[test] -fn test_precision_large_fraction() { - let m = FixedU128::from_rational(999999, 1000000); // 0.999999 - let n = FixedU128::from_rational(999999, 1000000); // 0.999999 - let x = FixedU128::from_rational(999999, 1000000); // 0.999999 - - let curve = LinearBondingFunctionParameters { m, n }; - - // 0.999999*(0.999999^2) + 0.999999*0.999999 - // = 1.999995000003999999 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_rational(1999995000003999999, FixedU128::DIV)); -} - -#[test] -fn test_precision_mixed_fraction() { - // 0.3 - let m = FixedU128::from_rational(3, 10); - // 0.75 - let n = FixedU128::from_rational(3, 4); - let x = FixedU128::from_u32(1); // 1 - - let curve = LinearBondingFunctionParameters { m, n }; - - // 0.3*(1) + 0.75*1 - // = 1.05 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_rational(105, 100)); -} - -#[test] -fn test_precision_small_fraction() { - // 0.001 - let m = FixedU128::from_rational(1, 1000); - let n = FixedU128::from_rational(1, 1000); - // 1 - let x = FixedU128::from_u32(1); - - let curve = LinearBondingFunctionParameters { m, n }; - - // 0.001*(1^2) + 0.001*1 - // = 0.002 - let result = curve.get_value(x).unwrap(); - assert_eq!(result, FixedU128::from_rational(2, 1000)); -} - -#[test] -fn test_zero_x() { - let params = LinearBondingFunctionParameters { - m: FixedI128::from(2), - n: FixedI128::from(3), - }; - let x = FixedI128::from(0); - - // 2*0^2 + 3*0 = 0 - assert_eq!(params.get_value(x), Ok(FixedI128::from(0))); -} diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs index 4a794ee3a..1e088f287 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs @@ -1,7 +1,8 @@ -mod linear_function; mod quadratic_function; mod square_root_function; +mod ratio_function; + use frame_support::assert_err; use sp_arithmetic::{ArithmeticError, FixedU128}; use sp_runtime::traits::Zero; diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs index 313b9ead3..d8dbc2205 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs @@ -2,11 +2,11 @@ use frame_support::assert_err; use sp_arithmetic::{ArithmeticError, FixedI128, FixedU128}; use sp_runtime::FixedPointNumber; -use crate::curves_parameters::{BondingFunction, QuadraticBondingFunctionParameters}; +use crate::curves_parameters::{BondingFunction, PolynomialFunctionParameters}; #[test] fn test_all_zero() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedU128::from(0), n: FixedU128::from(0), o: FixedU128::from(0), @@ -22,7 +22,7 @@ fn test_basic_test() { let o = FixedU128::from_u32(3); let x = FixedU128::from_u32(1); - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; // 1*1^3 + 2*1^2 + 3* 1 = 6 let result = curve.get_value(x).unwrap(); @@ -36,7 +36,7 @@ fn test_fraction() { let o = FixedU128::from_u32(3); let x = FixedU128::from_u32(1); - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; // 0.5*1^3 + 2*1^2 + 3* 1 = 5.5 let result = curve.get_value(x).unwrap(); @@ -45,7 +45,7 @@ fn test_fraction() { #[test] fn test_large_values() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedU128::from_u32(1000000), n: FixedU128::from_u32(1000000), o: FixedU128::from_u32(1000000), @@ -57,7 +57,7 @@ fn test_large_values() { #[test] fn test_large_x() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedU128::from(2), n: FixedU128::from(3), o: FixedU128::from(4), @@ -70,7 +70,7 @@ fn test_large_x() { #[test] fn test_negative() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedI128::from(2), n: FixedI128::from(3), o: FixedI128::from(4), @@ -83,7 +83,7 @@ fn test_negative() { #[test] fn test_negative_m() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedI128::from(-2), n: FixedI128::from(3), o: FixedI128::from(4), @@ -95,7 +95,7 @@ fn test_negative_m() { #[test] fn test_negative_m_and_n() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedI128::from(-2), n: FixedI128::from(-3), o: FixedI128::from(4), @@ -108,7 +108,7 @@ fn test_negative_m_and_n() { #[test] fn test_negative_m_n_and_o() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedI128::from(-2), n: FixedI128::from(-3), o: FixedI128::from(-4), @@ -121,7 +121,7 @@ fn test_negative_m_n_and_o() { #[test] fn test_negative_n() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedI128::from(2), n: FixedI128::from(-3), o: FixedI128::from(4), @@ -138,7 +138,7 @@ fn test_overflow_m() { let o = FixedU128::from_inner(1); let x = FixedU128::from_u32(2); - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; let result = curve.get_value(x); assert_err!(result, ArithmeticError::Overflow); @@ -151,7 +151,7 @@ fn test_overflow_n() { let o = FixedU128::from_u32(1); let x = FixedU128::from_u32(2); - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; let result = curve.get_value(x); assert_err!(result, ArithmeticError::Overflow); @@ -164,7 +164,7 @@ fn test_overflow_o() { let o = FixedU128::from_inner(u128::MAX); let x = FixedU128::from_u32(2); - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; let result = curve.get_value(x); assert_err!(result, ArithmeticError::Overflow); @@ -177,7 +177,7 @@ fn test_precision_large_fraction() { let o = FixedU128::from_rational(999999, 1000000); // 0.999999 let x = FixedU128::from_rational(999999, 1000000); // 0.999999 - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; // 0.999999*(0.999999^3) + 0.999999*0.999999^2 + 0.999999*0.999999 // = 2.999991000009999995000001 @@ -195,7 +195,7 @@ fn test_precision_mixed_fraction() { let o = FixedU128::from_rational(1, 2); let x = FixedU128::from_u32(1); // 1 - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; // 0.3*(1^3) + 0.75*1^2 + 0.5*1 // = 1.55 @@ -212,7 +212,7 @@ fn test_precision_small_fraction() { // 1 let x = FixedU128::from_u32(1); - let curve = QuadraticBondingFunctionParameters { m, n, o }; + let curve = PolynomialFunctionParameters { m, n, o }; // 0.001*(1^3) + 0.001*1^2 + 0.001*1 // = 0.003 @@ -222,7 +222,7 @@ fn test_precision_small_fraction() { #[test] fn test_zero_x() { - let params = QuadraticBondingFunctionParameters { + let params = PolynomialFunctionParameters { m: FixedI128::from(2), n: FixedI128::from(3), o: FixedI128::from(4), diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs new file mode 100644 index 000000000..e69de29bb diff --git a/pallets/pallet-bonded-coins/src/tests/types/types.rs b/pallets/pallet-bonded-coins/src/tests/types/types.rs index e53ad417f..c3b8687d1 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/types.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/types.rs @@ -2,7 +2,7 @@ use sp_arithmetic::FixedU128; use sp_runtime::{traits::Zero, FixedPointNumber}; use crate::{ - curves_parameters::{convert_currency_amount, LinearBondingFunctionParameters}, + curves_parameters::{convert_currency_amount, PolynomialFunctionParameters}, mock::runtime::*, types::{Curve, DiffKind}, }; @@ -14,9 +14,10 @@ const ONE_COIN: u128 = CURRENT_DENOMINATION; #[test] fn test_mint_first_coin() { // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(3); - let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + let m = FixedU128::from_u32(0); + let n = FixedU128::from_u32(1); + let o = FixedU128::from_u32(3); + let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply, where denomination is 15. Active issuance is zero. let active_issuance_pre: u128 = 0; @@ -46,9 +47,10 @@ fn test_mint_first_coin() { #[test] fn test_mint_coin_with_existing_supply() { // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(3); - let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + let m = FixedU128::from_u32(0); + let n = FixedU128::from_u32(1); + let o = FixedU128::from_u32(3); + let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply. Active issuance is 100. We want to mint 10 additional coins. let active_issuance_pre: u128 = ONE_COIN * 100; @@ -80,9 +82,10 @@ fn test_mint_coin_with_existing_supply() { #[test] fn test_mint_coin_with_existing_passive_supply() { // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(3); - let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + let m = FixedU128::from_u32(0); + let n = FixedU128::from_u32(1); + let o = FixedU128::from_u32(3); + let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply. Active issuance is Zero. We only mint a single coin. let active_issuance_pre: u128 = 0; @@ -117,9 +120,10 @@ fn test_mint_coin_with_existing_passive_supply() { #[test] fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { // Create curve with shape f(x) = 2x + 3, resulting into integral function F(x) = x^2 + 3x - let m = FixedU128::from_u32(1); - let n = FixedU128::from_u32(3); - let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + let m = FixedU128::from_u32(0); + let n = FixedU128::from_u32(1); + let o = FixedU128::from_u32(3); + let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply. Active issuance is 10. We mint 10 additional coins. let active_issuance_pre: u128 = ONE_COIN * 10; @@ -154,9 +158,10 @@ fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { #[test] fn test_mint_first_coin_frac_bonding_curve() { // Create curve with shape f(x) = x + 3, resulting into integral function F(x) = 1/2*x^2 + 3x - let m = FixedU128::from_rational(1, 2); - let n = FixedU128::from_u32(3); - let curve = Curve::LinearRatioCurve(LinearBondingFunctionParameters { m, n }); + let m = FixedU128::from_u32(0); + let n = FixedU128::from_rational(1, 2); + let o = FixedU128::from_u32(3); + let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply, where denomination is 15. Active issuance is zero. let active_issuance_pre: u128 = 0; diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 53a6527ec..5f3836e4f 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -104,8 +104,7 @@ pub struct TokenMeta { #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub enum Curve { - LinearRatioCurve(curves_parameters::LinearBondingFunctionParameters), - QuadraticRatioCurve(curves_parameters::QuadraticBondingFunctionParameters), + PolynomialFunction(curves_parameters::PolynomialFunctionParameters), SquareRootBondingFunction(curves_parameters::SquareRootBondingFunctionParameters), RationalBondingFunction(curves_parameters::RationalBondingFunctionParameters), } @@ -138,8 +137,7 @@ where }; match self { - Curve::LinearRatioCurve(params) => params.calculate_costs(low, high), - Curve::QuadraticRatioCurve(params) => params.calculate_costs(low, high), + Curve::PolynomialFunction(params) => params.calculate_costs(low, high), Curve::SquareRootBondingFunction(params) => params.calculate_costs(low, high), // TODO: This is probably a bug. Curve::RationalBondingFunction(params) => params.calculate_costs( From aaf188f55277fa5ee006b8c47892aafdb9e07ffb Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Thu, 26 Sep 2024 10:19:36 +0200 Subject: [PATCH 44/46] tests for ratio curves --- .../src/curves_parameters.rs | 101 +++++++++--------- pallets/pallet-bonded-coins/src/lib.rs | 6 +- .../src/tests/types/curve_parameters/mod.rs | 2 +- ...tic_function.rs => polynomial_function.rs} | 0 .../types/curve_parameters/ratio_function.rs | 65 +++++++++++ .../curve_parameters/square_root_function.rs | 2 +- pallets/pallet-bonded-coins/src/types.rs | 6 +- 7 files changed, 122 insertions(+), 60 deletions(-) rename pallets/pallet-bonded-coins/src/tests/types/curve_parameters/{quadratic_function.rs => polynomial_function.rs} (100%) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index ba3b7ef84..8b3fe41d6 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -1,3 +1,4 @@ +// Imports use frame_support::ensure; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -10,7 +11,20 @@ use sp_std::marker::PhantomData; use crate::{CollateralCurrencyBalanceOf, Config, CurveParameterTypeOf, Error, FungiblesBalanceOf}; -/// Little helper trait to calculate the square root of Fixed, in order to maintain the generic. +// Utility functions +mod utils { + use super::*; + + pub fn get_power_2(x: F) -> Result { + Ok(x.saturating_mul(x)) + } + + pub fn get_power_3(x: F) -> Result { + Ok(get_power_2(x)?.saturating_mul(x)) + } +} + +// SquareRoot trait and implementation pub trait SquareRoot: Sized { fn try_sqrt(self) -> Option; fn sqrt(self) -> Self; @@ -26,31 +40,23 @@ impl SquareRoot for FixedU128 { } } -/// A trait to define the bonding curve functions +// BondingFunction trait pub trait BondingFunction { /// returns the value of the curve at x. /// The bonding curve is already the primitive integral of f(x). /// Therefore the costs can be calculated by the difference of the values of the curve at two points. fn get_value(&self, x: F) -> Result; - /// static function to calculate the power of 2 of x - fn get_power_2(x: F) -> Result { - Ok(x.saturating_mul(x)) - } - - /// static function to calculate the power of 3 of x - fn get_power_3(x: F) -> Result { - Ok(Self::get_power_2(x)?.saturating_mul(x)) - } - /// calculates the cost of the curve between low and high fn calculate_costs(&self, low: F, high: F) -> Result { let high_val = self.get_value(high)?; let low_val = self.get_value(low)?; - Ok(high_val.saturating_sub(low_val)) + let result = high_val.checked_sub(&low_val).ok_or(ArithmeticError::Underflow)?; + Ok(result) } } +// PolynomialFunctionParameters struct and implementation #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct PolynomialFunctionParameters { pub m: F, @@ -64,8 +70,8 @@ where { /// F(x) = m * x^3 + n * x^2 + o * x fn get_value(&self, x: F) -> Result { - let x2 = Self::get_power_2(x)?; - let x3 = Self::get_power_3(x)?; + let x2 = utils::get_power_2(x)?; + let x3 = utils::get_power_3(x)?; let mx3 = self.m.clone().checked_mul(&x3).ok_or(ArithmeticError::Overflow)?; let nx2 = self.n.clone().checked_mul(&x2).ok_or(ArithmeticError::Overflow)?; @@ -82,6 +88,7 @@ where } } +// SquareRootBondingFunctionParameters struct and implementation #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct SquareRootBondingFunctionParameters { pub m: F, @@ -94,7 +101,7 @@ where { /// F(x) = m * sqrt(x^3) + n * x fn get_value(&self, x: F) -> Result { - let x3 = Self::get_power_3(x)?; + let x3 = utils::get_power_3(x)?; let sqrt_x3 = x3.try_sqrt().ok_or(ArithmeticError::Underflow)?; let mx3 = self.m.clone().checked_mul(&sqrt_x3).ok_or(ArithmeticError::Overflow)?; let nx = self.n.clone().checked_mul(&x).ok_or(ArithmeticError::Overflow)?; @@ -106,6 +113,7 @@ where } } +// RationalBondingFunctionParameters struct and implementation #[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct RationalBondingFunctionParameters(PhantomData); @@ -113,57 +121,45 @@ impl RationalBondingFunctionParameters where F: FixedPointNumber, { - fn get_power_2(x: F) -> Result { - Ok(x.saturating_mul(x)) - } - - pub fn calculate_costs(&self, low: (F, F), high: (F, F)) -> Result { - let high_val = self.calculate_ration(high.0, high.1)?; - let low_val = self.calculate_ration(low.0, high.1)?; + pub fn calculate_costs(low: (F, F), high: (F, F)) -> Result { + let high_val = Self::calculate_ration(high.0, high.1)?; + let low_val = Self::calculate_ration(low.0, high.1)?; Ok(high_val.saturating_sub(low_val)) } - /// F(a) = 0.5 * (a / (a + b))**2 + 0.5 * (b / (a + b))**2 * (a+b) , where b is the supply of the other assets. - fn calculate_ration(&self, a: F, b: F) -> Result { - // for the case, that a and b has no supply, we return 0. + /// F(a) = (1/2 * (a^2 + ab^2 + b^3)) / ((a+b)^2) , where b is the supply of the other assets. + pub fn calculate_ration(a: F, b: F) -> Result { if a.is_zero() && b.is_zero() { return Ok(F::zero()); } - // Should never happen, but lets be save. If 0.5 can not be represented as a fixed point number, we have an underflow. - let constant = F::checked_from_rational(1, 2).ok_or(ArithmeticError::Underflow)?; + let half_constant = F::checked_from_rational(1, 2).ok_or(ArithmeticError::Underflow)?; let sum_a_b = a.checked_add(&b).ok_or(ArithmeticError::Overflow)?; - - // Should never happen. - let a_divided_sum = a.checked_div(&sum_a_b).ok_or(ArithmeticError::DivisionByZero)?; - - // Should never happen. - let b_divided_sum = a.checked_div(&sum_a_b).ok_or(ArithmeticError::DivisionByZero)?; - - let a_divided_sum_squared = Self::get_power_2(a_divided_sum)?; - - let b_divided_sum_squared = Self::get_power_2(b_divided_sum)?; - - let a_divided_sum_squared_multiplied = a_divided_sum_squared - .checked_mul(&constant) - .ok_or(ArithmeticError::Overflow)?; - - let b_divided_sum_squared_multiplied = b_divided_sum_squared - .checked_mul(&constant) + let denominator = utils::get_power_2(sum_a_b)?; + + let a2 = utils::get_power_2(a)?; + let b2 = utils::get_power_2(b)?; + let b3 = utils::get_power_3(b)?; + let ab2 = a.checked_mul(&b2).ok_or(ArithmeticError::Overflow)?; + + let numerator = half_constant + .checked_mul( + &a2.checked_add(&ab2) + .ok_or(ArithmeticError::Overflow)? + .checked_add(&b3) + .ok_or(ArithmeticError::Overflow)?, + ) .ok_or(ArithmeticError::Overflow)?; - let b_divided_sum_squared_multiplied_multiplied = b_divided_sum_squared_multiplied - .checked_mul(&sum_a_b) - .ok_or(ArithmeticError::Overflow)?; + let result = numerator + .checked_div(&denominator) + .ok_or(ArithmeticError::DivisionByZero)?; - a_divided_sum_squared_multiplied - .checked_add(&b_divided_sum_squared_multiplied_multiplied) - .ok_or(ArithmeticError::Overflow) + Ok(result) } pub fn process_swap( - &self, currencies_metadata: Vec<(FungiblesBalanceOf, u128)>, collateral_meta: (CollateralCurrencyBalanceOf, u128), to_idx_usize: usize, @@ -221,6 +217,7 @@ where } } +// Convert currency amount function pub fn convert_currency_amount( amount: u128, current_denomination: u128, diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 87b665290..508942ba8 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -46,7 +46,7 @@ pub mod pallet { use sp_std::{default::Default, iter::Iterator, vec::Vec}; use crate::{ - curves_parameters::{convert_currency_amount, SquareRoot}, + curves_parameters::{convert_currency_amount, RationalBondingFunctionParameters, SquareRoot}, types::{Curve, DiffKind, Locks, PoolDetails, PoolStatus, TokenMeta}, }; @@ -385,7 +385,7 @@ pub mod pallet { let to_idx_usize: usize = to_idx.saturated_into(); match &pool_details.curve { - Curve::RationalBondingFunction(para) => { + Curve::RationalBondingFunction => { let collateral = Self::burn_pool_currency_and_calculate_collateral( &pool_details, from_idx_usize, @@ -397,7 +397,7 @@ pub mod pallet { let currencies_metadata = Self::get_currencies_metadata(&pool_details)?; - let raw_supply = para.process_swap::( + let raw_supply = RationalBondingFunctionParameters::::process_swap::( currencies_metadata, (collateral, collateral_denomination), to_idx_usize, diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs index 1e088f287..9ebb2a788 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs @@ -1,4 +1,4 @@ -mod quadratic_function; +mod polynomial_function; mod square_root_function; mod ratio_function; diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/polynomial_function.rs similarity index 100% rename from pallets/pallet-bonded-coins/src/tests/types/curve_parameters/quadratic_function.rs rename to pallets/pallet-bonded-coins/src/tests/types/curve_parameters/polynomial_function.rs diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs index e69de29bb..6dfe4934c 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs @@ -0,0 +1,65 @@ +use frame_support::assert_err; +use sp_runtime::{ArithmeticError, FixedPointNumber, FixedU128}; + +use crate::curves_parameters::RationalBondingFunctionParameters; + +#[test] +fn test_all_zero() { + let first_coin_supply = FixedU128::from_u32(0); + let second_coin_supply = FixedU128::from_u32(0); + + let ratio = RationalBondingFunctionParameters::::calculate_ration(first_coin_supply, second_coin_supply); + + assert_eq!(ratio, Ok(FixedU128::from_inner(0))); +} + +#[test] +fn test_basic() { + // A total supply of 30. 10 A Coins and 20 B Coins + let first_coin_supply = FixedU128::from_u32(10); + let second_coin_supply = FixedU128::from_u32(20); + + //(1/2 * (10^2 + 10* 20^2 + 20^3)) / ((10+30)^2) = 6.7222.. + let ratio = RationalBondingFunctionParameters::::calculate_ration(first_coin_supply, second_coin_supply); + + assert_eq!(ratio, Ok(FixedU128::from_inner(6722222222222222222))); +} + +#[test] +fn test_coin_supply_0() { + // A total supply of 10. 10 A Coins and 0 B Coins + let first_coin_supply = FixedU128::from_u32(10); + let second_coin_supply = FixedU128::from_u32(0); + + //(1/2 * (10^2 + 10* 0^2 + 0^3)) / ((10+0)^2) = 0.5 + let ratio = RationalBondingFunctionParameters::::calculate_ration(first_coin_supply, second_coin_supply); + + assert_eq!(ratio, Ok(FixedU128::from_rational(1, 2))); +} + +#[test] +fn test_large_values() { + // A total supply of 30. 10 A Coins and 20 B Coins + let first_coin_supply = FixedU128::from_u32(100000); + let second_coin_supply = FixedU128::from_u32(200000); + + //(1/2 * (100000^2 + 10* 200000^2 + 200000^3)) / ((100000+200000)^2) = 66666.722222222222222222.. + let ratio = RationalBondingFunctionParameters::::calculate_ration(first_coin_supply, second_coin_supply); + + assert_eq!( + ratio, + Ok(FixedU128::from_rational(66666722222222222222222, FixedU128::DIV)) + ); +} + +#[test] +fn test_overflow() { + // A total supply of 30. 10 A Coins and 20 B Coins + let first_coin_supply = FixedU128::from_inner(u128::MAX); + let second_coin_supply = FixedU128::from_u32(200000); + + //(1/2 * (100000^2 + 10* 200000^2 + 200000^3)) / ((100000+200000)^2) = 66666.722222222222222222.. + let ratio = RationalBondingFunctionParameters::::calculate_ration(first_coin_supply, second_coin_supply); + + assert_err!(ratio, ArithmeticError::Overflow); +} diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs index 21c28f0fb..052884882 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs @@ -15,7 +15,7 @@ fn test_all_zero() { } #[test] -fn test_basic_test() { +fn test_basic() { let m = FixedU128::from_u32(1); let n = FixedU128::from_u32(2); let x = FixedU128::from_u32(1); diff --git a/pallets/pallet-bonded-coins/src/types.rs b/pallets/pallet-bonded-coins/src/types.rs index 5f3836e4f..e428d7bf0 100644 --- a/pallets/pallet-bonded-coins/src/types.rs +++ b/pallets/pallet-bonded-coins/src/types.rs @@ -2,7 +2,7 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ArithmeticError, FixedPointNumber}; -use crate::curves_parameters::{self, BondingFunction, SquareRoot}; +use crate::curves_parameters::{self, BondingFunction, RationalBondingFunctionParameters, SquareRoot}; #[derive(Default, Clone, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen, Debug)] pub struct Locks { @@ -106,7 +106,7 @@ pub struct TokenMeta { pub enum Curve { PolynomialFunction(curves_parameters::PolynomialFunctionParameters), SquareRootBondingFunction(curves_parameters::SquareRootBondingFunctionParameters), - RationalBondingFunction(curves_parameters::RationalBondingFunctionParameters), + RationalBondingFunction, } pub enum DiffKind { @@ -140,7 +140,7 @@ where Curve::PolynomialFunction(params) => params.calculate_costs(low, high), Curve::SquareRootBondingFunction(params) => params.calculate_costs(low, high), // TODO: This is probably a bug. - Curve::RationalBondingFunction(params) => params.calculate_costs( + Curve::RationalBondingFunction => RationalBondingFunctionParameters::::calculate_costs( (active_issuance_pre, passive_issuance), (active_issuance_post, passive_issuance), ), From eb9900aff320ad7f641bfd1218cda081127cf655 Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Thu, 26 Sep 2024 10:41:07 +0200 Subject: [PATCH 45/46] util tests --- .../src/curves_parameters.rs | 6 +- .../src/tests/types/curve_parameters/mod.rs | 69 +++++++++++++++++-- ...l_function.rs => polynomial_parameters.rs} | 0 ...{ratio_function.rs => ratio_parameters.rs} | 0 ..._function.rs => square_root_parameters.rs} | 0 .../src/tests/types/curves/mod.rs | 1 + .../{types.rs => curves/polynomial_curve.rs} | 5 +- .../src/tests/types/mod.rs | 3 +- 8 files changed, 72 insertions(+), 12 deletions(-) rename pallets/pallet-bonded-coins/src/tests/types/curve_parameters/{polynomial_function.rs => polynomial_parameters.rs} (100%) rename pallets/pallet-bonded-coins/src/tests/types/curve_parameters/{ratio_function.rs => ratio_parameters.rs} (100%) rename pallets/pallet-bonded-coins/src/tests/types/curve_parameters/{square_root_function.rs => square_root_parameters.rs} (100%) create mode 100644 pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs rename pallets/pallet-bonded-coins/src/tests/types/{types.rs => curves/polynomial_curve.rs} (98%) diff --git a/pallets/pallet-bonded-coins/src/curves_parameters.rs b/pallets/pallet-bonded-coins/src/curves_parameters.rs index 8b3fe41d6..2b83a78e5 100644 --- a/pallets/pallet-bonded-coins/src/curves_parameters.rs +++ b/pallets/pallet-bonded-coins/src/curves_parameters.rs @@ -12,15 +12,15 @@ use sp_std::marker::PhantomData; use crate::{CollateralCurrencyBalanceOf, Config, CurveParameterTypeOf, Error, FungiblesBalanceOf}; // Utility functions -mod utils { +pub(crate) mod utils { use super::*; pub fn get_power_2(x: F) -> Result { - Ok(x.saturating_mul(x)) + x.checked_mul(&x).ok_or(ArithmeticError::Overflow) } pub fn get_power_3(x: F) -> Result { - Ok(get_power_2(x)?.saturating_mul(x)) + get_power_2(x)?.checked_mul(&x).ok_or(ArithmeticError::Overflow) } } diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs index 9ebb2a788..5bce91f1e 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/mod.rs @@ -1,13 +1,16 @@ -mod polynomial_function; -mod square_root_function; +mod polynomial_parameters; +mod square_root_parameters; -mod ratio_function; +mod ratio_parameters; use frame_support::assert_err; -use sp_arithmetic::{ArithmeticError, FixedU128}; +use sp_arithmetic::{ArithmeticError, FixedI128, FixedU128}; use sp_runtime::traits::Zero; -use crate::{curves_parameters::convert_currency_amount, mock::runtime::*}; +use crate::{ + curves_parameters::{convert_currency_amount, utils::*}, + mock::runtime::*, +}; #[test] fn test_increase_denomination_currency_amount() { @@ -53,3 +56,59 @@ fn test_decrease_denomination_underflow() { let result = convert_currency_amount::(amount, current_denomination, target_denomination).unwrap(); assert_eq!(result, FixedU128::zero()) } + +#[test] +fn test_get_power_2_positive() { + let x = FixedU128::from_u32(2); + let result = get_power_2(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(4)); +} + +#[test] +fn test_get_power_2_zero() { + let x = FixedU128::from_u32(0); + let result = get_power_2(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(0)); +} + +#[test] +fn test_get_power_2_negative() { + let x = FixedI128::from(-2); + let result = get_power_2(x).unwrap(); + assert_eq!(result, FixedI128::from_u32(4)); +} + +#[test] +fn test_get_power_2_overflow() { + let x = FixedU128::from_inner(u128::MAX); + let result = get_power_2(x); + assert!(result.is_err()); +} + +#[test] +fn test_get_power_3_positive() { + let x = FixedU128::from_u32(2); + let result = get_power_3(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(8)); +} + +#[test] +fn test_get_power_3_zero() { + let x = FixedU128::from_u32(0); + let result = get_power_3(x).unwrap(); + assert_eq!(result, FixedU128::from_u32(0)); +} + +#[test] +fn test_get_power_3_negative() { + let x = FixedI128::from(-2); + let result = get_power_3(x).unwrap(); + assert_eq!(result, FixedI128::from(-8)); +} + +#[test] +fn test_get_power_3_overflow() { + let x = FixedU128::from_inner(u128::MAX); + let result = get_power_3(x); + assert!(result.is_err()); +} diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/polynomial_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/polynomial_parameters.rs similarity index 100% rename from pallets/pallet-bonded-coins/src/tests/types/curve_parameters/polynomial_function.rs rename to pallets/pallet-bonded-coins/src/tests/types/curve_parameters/polynomial_parameters.rs diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_parameters.rs similarity index 100% rename from pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_function.rs rename to pallets/pallet-bonded-coins/src/tests/types/curve_parameters/ratio_parameters.rs diff --git a/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs b/pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_parameters.rs similarity index 100% rename from pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_function.rs rename to pallets/pallet-bonded-coins/src/tests/types/curve_parameters/square_root_parameters.rs diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs new file mode 100644 index 000000000..2edee769a --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs @@ -0,0 +1 @@ +mod polynomial_curve; diff --git a/pallets/pallet-bonded-coins/src/tests/types/types.rs b/pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs similarity index 98% rename from pallets/pallet-bonded-coins/src/tests/types/types.rs rename to pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs index c3b8687d1..d1fbb20ae 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/types.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs @@ -5,10 +5,11 @@ use crate::{ curves_parameters::{convert_currency_amount, PolynomialFunctionParameters}, mock::runtime::*, types::{Curve, DiffKind}, + CurveParameterTypeOf, }; // target denomination for collateral currency const CURRENT_DENOMINATION: u128 = 10u128.pow(15); -const NORMALIZED_DENOMINATION: u128 = FixedU128::DIV; +const NORMALIZED_DENOMINATION: u128 = CurveParameterTypeOf::::DIV; const ONE_COIN: u128 = CURRENT_DENOMINATION; #[test] @@ -189,5 +190,3 @@ fn test_mint_first_coin_frac_bonding_curve() { assert_eq!(costs, FixedU128::from_rational(7, 2)); } - -// TODO add more tests for passive and existing active supply. diff --git a/pallets/pallet-bonded-coins/src/tests/types/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/mod.rs index b4be483ee..0b01e3766 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/mod.rs @@ -1,2 +1,3 @@ mod curve_parameters; -mod types; + +mod curves; From d0f6ea9d987cdad7b4c2eec34685de02ac18b8dd Mon Sep 17 00:00:00 2001 From: Adel Golghalyani Date: Thu, 26 Sep 2024 13:19:59 +0200 Subject: [PATCH 46/46] tests for square root function --- .../src/tests/types/curves/mod.rs | 8 + .../tests/types/curves/polynomial_curve.rs | 32 +-- .../src/tests/types/curves/square_root.rs | 187 ++++++++++++++++++ 3 files changed, 211 insertions(+), 16 deletions(-) create mode 100644 pallets/pallet-bonded-coins/src/tests/types/curves/square_root.rs diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs b/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs index 2edee769a..6a3bca382 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves/mod.rs @@ -1 +1,9 @@ mod polynomial_curve; +mod square_root; + +use sp_runtime::FixedPointNumber; + +use crate::{mock::runtime::*, CurveParameterTypeOf}; + +const NORMALIZED_DENOMINATION: u128 = CurveParameterTypeOf::::DIV; +const CURRENT_DENOMINATION: u128 = 10u128.pow(15); diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs b/pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs index d1fbb20ae..8bd64a4ec 100644 --- a/pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs +++ b/pallets/pallet-bonded-coins/src/tests/types/curves/polynomial_curve.rs @@ -1,16 +1,12 @@ use sp_arithmetic::FixedU128; -use sp_runtime::{traits::Zero, FixedPointNumber}; +use sp_runtime::traits::Zero; use crate::{ curves_parameters::{convert_currency_amount, PolynomialFunctionParameters}, mock::runtime::*, + tests::types::curves::{CURRENT_DENOMINATION, NORMALIZED_DENOMINATION}, types::{Curve, DiffKind}, - CurveParameterTypeOf, }; -// target denomination for collateral currency -const CURRENT_DENOMINATION: u128 = 10u128.pow(15); -const NORMALIZED_DENOMINATION: u128 = CurveParameterTypeOf::::DIV; -const ONE_COIN: u128 = CURRENT_DENOMINATION; #[test] fn test_mint_first_coin() { @@ -22,7 +18,7 @@ fn test_mint_first_coin() { // Create supply, where denomination is 15. Active issuance is zero. let active_issuance_pre: u128 = 0; - let active_issuance_post: u128 = ONE_COIN; + let active_issuance_post: u128 = CURRENT_DENOMINATION; // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); @@ -32,7 +28,9 @@ fn test_mint_first_coin() { let normalized_active_issuance_post = convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); - // The cost to mint the first coin should be 4. + // Existing supply: 0^2 + 3*0 = 0 + // New Supply: 1^2 + 3*1 = 4 + // Cost to mint the first coin: 4 - 0 = 4 let costs = curve .calculate_cost( normalized_active_issuance_pre, @@ -54,8 +52,8 @@ fn test_mint_coin_with_existing_supply() { let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply. Active issuance is 100. We want to mint 10 additional coins. - let active_issuance_pre: u128 = ONE_COIN * 100; - let active_issuance_post: u128 = ONE_COIN * 110; + let active_issuance_pre: u128 = CURRENT_DENOMINATION * 100; + let active_issuance_post: u128 = CURRENT_DENOMINATION * 110; // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); @@ -90,10 +88,10 @@ fn test_mint_coin_with_existing_passive_supply() { // Create supply. Active issuance is Zero. We only mint a single coin. let active_issuance_pre: u128 = 0; - let active_issuance_post: u128 = ONE_COIN; + let active_issuance_post: u128 = CURRENT_DENOMINATION; // Multiple coins in pool. Passive issuance is 10. - let passive_issuance = ONE_COIN * 10; + let passive_issuance = CURRENT_DENOMINATION * 10; let normalized_active_issuance_pre = convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); @@ -127,11 +125,11 @@ fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { let curve = Curve::PolynomialFunction(PolynomialFunctionParameters { m, n, o }); // Create supply. Active issuance is 10. We mint 10 additional coins. - let active_issuance_pre: u128 = ONE_COIN * 10; - let active_issuance_post: u128 = ONE_COIN * 20; + let active_issuance_pre: u128 = CURRENT_DENOMINATION * 10; + let active_issuance_post: u128 = CURRENT_DENOMINATION * 20; // Multiple coins in pool. Passive issuance is 10. - let passive_issuance = ONE_COIN * 10; + let passive_issuance = CURRENT_DENOMINATION * 10; let normalized_active_issuance_pre = convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); @@ -166,7 +164,7 @@ fn test_mint_first_coin_frac_bonding_curve() { // Create supply, where denomination is 15. Active issuance is zero. let active_issuance_pre: u128 = 0; - let active_issuance_post: u128 = ONE_COIN; + let active_issuance_post: u128 = CURRENT_DENOMINATION; // single coin in pool. Passive issuance is zero. let passive_issuance = FixedU128::zero(); @@ -190,3 +188,5 @@ fn test_mint_first_coin_frac_bonding_curve() { assert_eq!(costs, FixedU128::from_rational(7, 2)); } + +// TODO: tests for burning diff --git a/pallets/pallet-bonded-coins/src/tests/types/curves/square_root.rs b/pallets/pallet-bonded-coins/src/tests/types/curves/square_root.rs new file mode 100644 index 000000000..41668c95c --- /dev/null +++ b/pallets/pallet-bonded-coins/src/tests/types/curves/square_root.rs @@ -0,0 +1,187 @@ +use sp_arithmetic::FixedU128; +use sp_runtime::{traits::Zero, FixedPointNumber}; + +use crate::{ + curves_parameters::{convert_currency_amount, SquareRootBondingFunctionParameters}, + mock::runtime::*, + tests::types::curves::{CURRENT_DENOMINATION, NORMALIZED_DENOMINATION}, + types::{Curve, DiffKind}, +}; + +#[test] +fn test_mint_first_coin() { + // Create curve with shape f(x) = 2x^1/2 + 2, resulting into integral function F(x) = x^3/2 + 2x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let curve = Curve::SquareRootBondingFunction(SquareRootBondingFunctionParameters { m, n }); + + // Create supply, where denomination is 15. Active issuance is zero. + let active_issuance_pre: u128 = 0; + let active_issuance_post: u128 = CURRENT_DENOMINATION; + + // single coin in pool. Passive issuance is zero. + let passive_issuance = FixedU128::zero(); + + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + + // Existing supply: 0^3/2 + 3*0 = 0 + // New Supply: 1^3/2 + 2*1 = 3 + // Cost to mint the first coin: 3 - 0 = 3 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + passive_issuance, + DiffKind::Mint, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_u32(3)); +} + +#[test] +fn test_mint_coin_with_existing_supply() { + // Create curve with shape f(x) = 2x^1/2 + 2, resulting into integral function F(x) = x^3/2 + 2x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let curve = Curve::SquareRootBondingFunction(SquareRootBondingFunctionParameters { m, n }); + + // Create supply. Active issuance is 100. We want to mint 10 additional coins. + let active_issuance_pre: u128 = CURRENT_DENOMINATION * 100; + let active_issuance_post: u128 = CURRENT_DENOMINATION * 110; + + // single coin in pool. Passive issuance is zero. + let passive_issuance = FixedU128::zero(); + + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + + // Existing supply: 100^3/2 + 2*100 = 1200 + // New supply: 110^2 + 3*110 = 1373.689732987 + // Cost to mint 10 coins: 1373.689732987 - 1200 = 173.689732987 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + passive_issuance, + DiffKind::Mint, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_rational(173689732987, 1_000_000_000)); +} + +#[test] +fn test_mint_coin_with_existing_passive_supply() { + // Create curve with shape f(x) = 2x^1/2 + 2, resulting into integral function F(x) = x^3/2 + 2x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let curve = Curve::SquareRootBondingFunction(SquareRootBondingFunctionParameters { m, n }); + + // Create supply. Active issuance is Zero. We only mint a single coin. + let active_issuance_pre: u128 = 0; + let active_issuance_post: u128 = CURRENT_DENOMINATION; + + // Multiple coins in pool. Passive issuance is 10. + let passive_issuance = CURRENT_DENOMINATION * 10; + + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_passive_issuance = + convert_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + + // The passive issuance should influence the price of the new selected currency. + // Existing supply: (10)^3/2 + (10)*2 = 51.6227766016837933199 + // New supply: (10 + 1)^3/2 + (10 + 1)*2 = 58.4828726939093983402642601 + // Cost to mint 1 coin: 58.4828726939093983402642601 - 51.6227766016837933199 = 6.860096092 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + normalized_passive_issuance, + DiffKind::Mint, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_rational(6860096092, 1_000_000_000)); +} + +#[test] +fn test_mint_coin_with_existing_passive_supply_and_existing_active_supply() { + // Create curve with shape f(x) = 2x^1/2 + 2, resulting into integral function F(x) = x^3/2 + 2x + let m = FixedU128::from_u32(1); + let n = FixedU128::from_u32(2); + let curve = Curve::SquareRootBondingFunction(SquareRootBondingFunctionParameters { m, n }); + + // Create supply. Active issuance is 10. We mint 10 additional coins. + let active_issuance_pre: u128 = CURRENT_DENOMINATION * 10; + let active_issuance_post: u128 = CURRENT_DENOMINATION * 20; + + // Multiple coins in pool. Passive issuance is 10. + let passive_issuance = CURRENT_DENOMINATION * 10; + + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_passive_issuance = + convert_currency_amount::(passive_issuance, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + + // The passive issuance should influence the price of the new selected currency. + // Existing supply: (20)^(3/2) + (20)*2 = 129.442719099991 + // New supply: (30)^(3/2) + (30)*2 = 224.3167672515498 + // Cost to mint 10 coin: 224.3167672515498 - 129.442719099991 = 94.874048152 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + normalized_passive_issuance, + DiffKind::Mint, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_rational(94874048152, 1_000_000_000)); +} + +#[test] +fn test_mint_first_coin_frac_bonding_curve() { + // Create curve with shape f(x) = x^1/2 + 2, resulting into integral function F(x) = 2/3 x^3/2 + 2x + let m = FixedU128::from_rational(2, 3); + let n = FixedU128::from_u32(2); + let curve = Curve::SquareRootBondingFunction(SquareRootBondingFunctionParameters { m, n }); + + // Create supply, where denomination is 15. Active issuance is zero. + let active_issuance_pre: u128 = 0; + let active_issuance_post: u128 = CURRENT_DENOMINATION; + + // single coin in pool. Passive issuance is zero. + let passive_issuance = FixedU128::zero(); + + let normalized_active_issuance_pre = + convert_currency_amount::(active_issuance_pre, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + let normalized_active_issuance_post = + convert_currency_amount::(active_issuance_post, CURRENT_DENOMINATION, NORMALIZED_DENOMINATION).unwrap(); + + // Existing supply: 2/3*(0)^(3/2) + 2/3*(0)*2 = 0 + // New supply: 2/3*(1)^(3/2) + (1)*2 = 2.666666666.. + // Cost to mint 10 coin: 2 - 0 = 0 + let costs = curve + .calculate_cost( + normalized_active_issuance_pre, + normalized_active_issuance_post, + passive_issuance, + DiffKind::Mint, + ) + .unwrap(); + + assert_eq!(costs, FixedU128::from_rational(2666666666666666667, FixedU128::DIV)); +} + +// TODO: more tests for burning and frac.