diff --git a/pallets/pallet-bonded-coins/Cargo.toml b/pallets/pallet-bonded-coins/Cargo.toml index ca7aa6e714..36e3d0e150 100644 --- a/pallets/pallet-bonded-coins/Cargo.toml +++ b/pallets/pallet-bonded-coins/Cargo.toml @@ -20,6 +20,7 @@ scale-info = { workspace = true, features = ["derive"] } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +pallet-assets = { workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } @@ -30,7 +31,6 @@ log = { workspace = true } substrate-fixed = { workspace = true } [dev-dependencies] -pallet-assets = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } serde = { workspace = true } sp-keystore = { workspace = true, features = ["std"] } @@ -44,12 +44,14 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", ] std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "pallet-assets/std", "parity-scale-codec/std", "scale-info/std", "substrate-fixed/std", diff --git a/pallets/pallet-bonded-coins/src/lib.rs b/pallets/pallet-bonded-coins/src/lib.rs index 65c761f608..11489bc7ee 100644 --- a/pallets/pallet-bonded-coins/src/lib.rs +++ b/pallets/pallet-bonded-coins/src/lib.rs @@ -7,7 +7,7 @@ pub use pallet::*; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -#[cfg(test)] +#[cfg(any(test, feature = "runtime-benchmarks"))] mod mock; #[cfg(test)] mod tests; @@ -62,7 +62,7 @@ pub mod pallet { type AccountIdLookupOf = <::Lookup as sp_runtime::traits::StaticLookup>::Source; - type AccountIdOf = ::AccountId; + pub(crate) type AccountIdOf = ::AccountId; pub(crate) type DepositCurrencyBalanceOf = <::DepositCurrency as InspectFungible<::AccountId>>::Balance; diff --git a/pallets/pallet-bonded-coins/src/mock.rs b/pallets/pallet-bonded-coins/src/mock.rs index 92cae110d3..d1fcebd5ec 100644 --- a/pallets/pallet-bonded-coins/src/mock.rs +++ b/pallets/pallet-bonded-coins/src/mock.rs @@ -1,6 +1,47 @@ -use substrate_fixed::types::I75F53; +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}, + BoundedVec, BuildStorage, MultiSignature, +}; +use substrate_fixed::{ + traits::{FixedSigned, FixedUnsigned}, + types::{I75F53, U75F53}, +}; + +use crate::{ + self as pallet_bonded_coins, + curves::{ + polynomial::{PolynomialParameters, PolynomialParametersInput}, + Curve, CurveInput, + }, + types::{Locks, PoolStatus}, + DepositCurrencyBalanceOf, PoolDetailsOf, +}; pub type Float = I75F53; +pub(crate) type FloatInput = U75F53; +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; + +// accounts +pub(crate) const ACCOUNT_00: AccountId = AccountId::new([0u8; 32]); +pub(crate) const ACCOUNT_01: AccountId = AccountId::new([1u8; 32]); +const ACCOUNT_99: AccountId = AccountId::new([99u8; 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; // helper functions pub fn assert_relative_eq(target: Float, expected: Float, epsilon: Float) { @@ -11,3 +52,283 @@ pub fn assert_relative_eq(target: Float, expected: Float, epsilon: Float) { target ); } + +pub(crate) fn get_linear_bonding_curve() -> Curve { + let m = Float::from_num(0); + let n = Float::from_num(2); + let o = Float::from_num(3); + Curve::Polynomial(PolynomialParameters { m, n, o }) +} + +pub(crate) fn get_linear_bonding_curve_input() -> CurveInput { + let m = Float::from_num(0); + let n = Float::from_num(2); + let o = Float::from_num(3); + CurveInput::Polynomial(PolynomialParametersInput { m, n, o }) +} + +pub(crate) fn calculate_pool_id(currencies: &[AssetId]) -> AccountId { + AccountId::from(currencies.to_vec().blake2_256()) +} + +#[cfg(test)] +pub mod runtime { + + use super::*; + + pub type Block = frame_system::mocking::MockBlock; + + pub fn generate_pool_details( + currencies: Vec, + curve: Curve, + transferable: bool, + state: Option>, + manager: Option, + collateral_id: Option, + owner: Option, + ) -> PoolDetailsOf { + let bonded_currencies = BoundedVec::truncate_from(currencies); + let state = state.unwrap_or(PoolStatus::Active); + let owner = owner.unwrap_or(ACCOUNT_99); + let collateral_id = collateral_id.unwrap_or(DEFAULT_COLLATERAL_CURRENCY_ID); + PoolDetailsOf:: { + curve, + manager, + transferable, + bonded_currencies, + state, + collateral_id, + denomination: 10, + owner, + } + } + + 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 + { + 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 AccountData = pallet_balances::AccountData; + type AccountId = AccountId; + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = BlockHashCount; + type BlockLength = (); + type BlockWeights = (); + type DbWeight = RocksDbWeight; + type Hash = Hash; + type Hashing = BlakeTwo256; + type Lookup = IdentityLookup; + type MaxConsumers = ConstU32<16>; + type Nonce = u64; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type PalletInfo = PalletInfo; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeTask = (); + type SS58Prefix = SS58Prefix; + type SystemWeightInfo = (); + type Version = (); + } + + parameter_types! { + pub const ExistentialDeposit: Balance = 500; + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; + } + + impl pallet_balances::Config for Test { + type AccountStore = System; + type Balance = Balance; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type FreezeIdentifier = (); + type MaxFreezes = (); + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type RuntimeEvent = RuntimeEvent; + type RuntimeFreezeReason = (); + type RuntimeHoldReason = RuntimeHoldReason; + type WeightInfo = (); + } + + parameter_types! { + pub const StringLimit: u32 = 50; + + } + impl pallet_assets::Config for Test { + type ApprovalDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type AssetDeposit = ConstU128<0>; + type AssetId = AssetId; + type AssetIdParameter = AssetId; + type Balance = Balance; + type CallbackHandle = (); + type CreateOrigin = EnsureSigned; + type Currency = Balances; + type Extra = (); + type ForceOrigin = EnsureRoot; + type Freezer = (); + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type RemoveItemsLimit = ConstU32<5>; + type RuntimeEvent = RuntimeEvent; + type StringLimit = StringLimit; + type WeightInfo = (); + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); + } + parameter_types! { + pub const CurrencyDeposit: Balance = 500; + pub const MaxCurrencies: u32 = 50; + pub const CollateralAssetId: u32 = u32::MAX; + } + + impl pallet_bonded_coins::Config for Test { + type AssetId = AssetId; + type BaseDeposit = ExistentialDeposit; + type CollateralCurrencies = Assets; + type CurveParameterInput = FloatInput; + type CurveParameterType = Float; + type DefaultOrigin = EnsureSigned; + type DepositCurrency = Balances; + type DepositPerCurrency = CurrencyDeposit; + type ForceOrigin = EnsureRoot; + type Fungibles = Assets; + type MaxCurrencies = MaxCurrencies; + type MaxStringLength = StringLimit; + type PoolCreateOrigin = EnsureSigned; + type PoolId = AccountId; + type RuntimeEvent = RuntimeEvent; + type RuntimeHoldReason = RuntimeHoldReason; + } + + #[derive(Clone, Default)] + pub(crate) struct ExtBuilder { + native_assets: Vec<(AccountId, DepositCurrencyBalanceOf)>, + bonded_balance: Vec<(AssetId, AccountId, Balance)>, + // pool_id, PoolDetails + pools: Vec<(AccountId, PoolDetailsOf)>, + collaterals: Vec, + } + + impl ExtBuilder { + pub(crate) fn with_native_balances( + mut self, + native_assets: Vec<(AccountId, DepositCurrencyBalanceOf)>, + ) -> Self { + self.native_assets = native_assets; + self + } + + pub(crate) fn with_collaterals(mut self, collaterals: Vec) -> Self { + self.collaterals = collaterals; + 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(self) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: self.native_assets.clone(), + } + .assimilate_storage(&mut storage) + .expect("assimilate should not fail"); + + let collateral_assets = self.collaterals.into_iter().map(|id| (id, ACCOUNT_99, false, 1)); + + pallet_assets::GenesisConfig:: { + assets: self + .pools + .iter() + .flat_map(|(owner, pool)| { + pool.bonded_currencies + .iter() + .map(|id| (*id, owner.to_owned(), false, 1u128)) + .collect::>() + }) + .chain(collateral_assets) + .collect(), + + accounts: self.bonded_balance, + metadata: self + .pools + .iter() + .flat_map(|(_, pool_details)| { + pool_details + .bonded_currencies + .iter() + .map(|id| (*id, vec![], vec![], pool_details.denomination)) + .collect::, Vec, u8)>>() + }) + .collect(), + } + .assimilate_storage(&mut storage) + .expect("assimilate should not fail"); + + let mut ext = sp_io::TestExternalities::new(storage); + + ext.execute_with(|| { + System::set_block_number(System::block_number() + 1); + + self.pools.into_iter().for_each(|(pool_id, pool)| { + crate::Pools::::insert(pool_id, pool); + }); + }); + + 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/traits.rs b/pallets/pallet-bonded-coins/src/traits.rs index 41afa9b0a7..8f35477ff9 100644 --- a/pallets/pallet-bonded-coins/src/traits.rs +++ b/pallets/pallet-bonded-coins/src/traits.rs @@ -1,5 +1,9 @@ -use frame_support::{dispatch::DispatchResult, traits::fungibles::Inspect}; -use sp_runtime::DispatchError; +use frame_support::{dispatch::DispatchResult, traits::fungibles::roles::Inspect}; +use frame_system::RawOrigin; +use pallet_assets::{Config as AssetConfig, Pallet as AssetsPallet}; +use sp_runtime::{traits::StaticLookup, DispatchError}; + +use crate::AccountIdOf; pub trait FreezeAccounts { type Error: Into; @@ -9,8 +13,35 @@ pub trait FreezeAccounts { fn thaw(asset_id: &AssetId, who: &AccountId) -> Result<(), Self::Error>; } + +type AssetIdOf = ::AssetId; + +impl FreezeAccounts, ::AssetId> for AssetsPallet +where + T: AssetConfig, + AccountIdOf: Clone, + <::Lookup as StaticLookup>::Source: From>, +{ + type Error = DispatchError; + + fn freeze(asset_id: &AssetIdOf, who: &AccountIdOf) -> Result<(), Self::Error> { + let asset_id: ::AssetId = asset_id.to_owned(); + let freezer = AssetsPallet::::freezer(asset_id.clone()).ok_or(Self::Error::Unavailable)?; + let origin = RawOrigin::Signed(freezer); + AssetsPallet::::freeze(origin.into(), asset_id.into(), who.to_owned().into()) + } + + fn thaw(asset_id: &AssetIdOf, who: &AccountIdOf) -> Result<(), Self::Error> { + let asset_id: ::AssetId = asset_id.to_owned(); + let admin = AssetsPallet::::admin(asset_id.clone()).ok_or(Self::Error::Unavailable)?; + let origin = RawOrigin::Signed(admin); + AssetsPallet::::thaw(origin.into(), asset_id.into(), who.to_owned().into()) + } +} + + /// Copy from the Polkadot SDK. once we are at version 1.13.0, we can remove -/// this. +/// this. pub trait ResetTeam: Inspect { /// Reset the team for the asset with the given `id`. /// @@ -28,3 +59,21 @@ pub trait ResetTeam: Inspect { freezer: AccountId, ) -> DispatchResult; } + +impl ResetTeam> for AssetsPallet +where + T: AssetConfig, + <::Lookup as StaticLookup>::Source: From>, +{ + fn reset_team( + id: Self::AssetId, + _owner: AccountIdOf, + admin: AccountIdOf, + issuer: AccountIdOf, + freezer: AccountIdOf, + ) -> DispatchResult { + let owner = AssetsPallet::::owner(id.clone()).ok_or(DispatchError::Unavailable)?; + let origin = RawOrigin::Signed(owner); + AssetsPallet::::set_team(origin.into(), id.into(), issuer.into(), admin.into(), freezer.into()) + } +}