diff --git a/Cargo.lock b/Cargo.lock index 74e1913efd..663175b873 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11128,6 +11128,7 @@ dependencies = [ "polkadot-runtime-parachains", "rococo-runtime", "runtime-common", + "sc-block-builder", "sc-client-api", "sc-executor", "sc-service", @@ -11144,6 +11145,7 @@ dependencies = [ "sp-std", "sp-timestamp", "sp-tracing", + "sp-transaction-pool", "tokio", "tracing-subscriber", "xcm", diff --git a/Cargo.toml b/Cargo.toml index 279c13c1b0..18d4795c22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -169,8 +169,8 @@ fc-mapping-sync = { git = "https://github.com/PureStake/frontier", default-featu fc-rpc = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } fc-rpc-core = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } fp-consensus = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } -fp-rpc = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } fp-evm = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } +fp-rpc = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } fp-storage = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } pallet-evm = { git = "https://github.com/PureStake/frontier", default-features = false, branch = "moonbeam-polkadot-v0.9.38" } diff --git a/pallets/liquidity-pools/src/lib.rs b/pallets/liquidity-pools/src/lib.rs index 2c77fa54eb..f745f815de 100644 --- a/pallets/liquidity-pools/src/lib.rs +++ b/pallets/liquidity-pools/src/lib.rs @@ -193,6 +193,7 @@ pub mod pallet { BalanceRatio = Self::BalanceRatio, PoolId = Self::PoolId, TrancheId = Self::TrancheId, + Moment = Seconds, >; /// The source of truth for investment permissions. @@ -452,9 +453,8 @@ pub mod pallet { // TODO(future): Once we diverge from 1-to-1 conversions for foreign and pool // currencies, this price must be first converted into the currency_id and then // re-denominated to 18 decimals (i.e. `Ratio` precision) - let price = T::TrancheTokenPrice::get(pool_id, tranche_id) - .ok_or(Error::::MissingTranchePrice)? - .price; + let price_value = T::TrancheTokenPrice::get(pool_id, tranche_id) + .ok_or(Error::::MissingTranchePrice)?; // Check that the registered asset location matches the destination match Self::try_get_wrapped_token(¤cy_id)? { @@ -474,7 +474,8 @@ pub mod pallet { pool_id, tranche_id, currency, - price, + price: price_value.price, + computed_at: price_value.last_updated, }, )?; diff --git a/pallets/liquidity-pools/src/message.rs b/pallets/liquidity-pools/src/message.rs index 977dcbbac2..4df15d4bec 100644 --- a/pallets/liquidity-pools/src/message.rs +++ b/pallets/liquidity-pools/src/message.rs @@ -80,6 +80,8 @@ where tranche_id: TrancheId, currency: u128, price: Ratio, + /// The timestamp at which the price was computed + computed_at: Seconds, }, /// Whitelist an address for the specified pair of pool and tranche token on /// the target domain. @@ -449,6 +451,7 @@ impl< tranche_id, currency, price, + computed_at, } => encoded_message( self.call_type(), vec![ @@ -456,6 +459,7 @@ impl< tranche_id.encode(), encode_be(currency), encode_be(price), + computed_at.to_be_bytes().to_vec(), ], ), Message::UpdateMember { @@ -751,6 +755,7 @@ impl< tranche_id: decode::<16, _, _>(input)?, currency: decode_be_bytes::<16, _, _>(input)?, price: decode_be_bytes::<16, _, _>(input)?, + computed_at: decode_be_bytes::<8, _, _>(input)?, }), 6 => Ok(Self::UpdateMember { pool_id: decode_be_bytes::<8, _, _>(input)?, @@ -1025,8 +1030,9 @@ mod tests { tranche_id: default_tranche_id(), currency: TOKEN_ID, price: Ratio::one(), + computed_at: 1698131924, }, - "050000000000000001811acd5b3f17c06841c7e41e9e04cb1b0000000000000000000000000eb5ec7b00000000000000000de0b6b3a7640000", + "050000000000000001811acd5b3f17c06841c7e41e9e04cb1b0000000000000000000000000eb5ec7b00000000000000000de0b6b3a76400000000000065376fd4", ) } diff --git a/runtime/centrifuge/src/lib.rs b/runtime/centrifuge/src/lib.rs index 597807ba17..a2501bb907 100644 --- a/runtime/centrifuge/src/lib.rs +++ b/runtime/centrifuge/src/lib.rs @@ -134,7 +134,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("centrifuge"), impl_name: create_runtime_str!("centrifuge"), authoring_version: 1, - spec_version: 1023, + spec_version: 1024, impl_version: 1, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, @@ -1946,7 +1946,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, - migrations::UpgradeCentrifuge1022, + migrations::UpgradeCentrifuge1024, >; pub struct TransactionConverter; diff --git a/runtime/centrifuge/src/migrations.rs b/runtime/centrifuge/src/migrations.rs index 7aef42b688..708b9fe508 100644 --- a/runtime/centrifuge/src/migrations.rs +++ b/runtime/centrifuge/src/migrations.rs @@ -9,339 +9,5 @@ // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use crate::{Runtime, Weight}; -pub type UpgradeCentrifuge1022 = ( - anemoy_pool::Migration, - add_wrapped_usdc_variants::Migration, - // Sets account codes for all precompiles - runtime_common::migrations::precompile_account_codes::Migration, -); - -/// Migrate the Anemoy Pool's currency from LpEthUSC to Circle's USDC, -/// native on Polkadot's AssetHub. -mod anemoy_pool { - use cfg_primitives::PoolId; - use cfg_traits::PoolInspect; - use cfg_types::tokens::usdc::{CURRENCY_ID_DOT_NATIVE, CURRENCY_ID_LP_ETH}; - #[cfg(feature = "try-runtime")] - use codec::{Decode, Encode}; - #[cfg(feature = "try-runtime")] - use frame_support::ensure; - use frame_support::traits::{fungibles::Inspect, OnRuntimeUpgrade}; - #[cfg(feature = "try-runtime")] - use pallet_pool_system::PoolDetailsOf; - use sp_std::vec; - #[cfg(feature = "try-runtime")] - use sp_std::vec::Vec; - - use super::*; - use crate::PoolSystem; - - const ANEMOY_POOL_ID: PoolId = 4_139_607_887; - - pub struct Migration(sp_std::marker::PhantomData); - - impl OnRuntimeUpgrade for Migration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - let pool_details: PoolDetailsOf = - PoolSystem::pool(ANEMOY_POOL_ID).ok_or("Could not find Anemoy Pool")?; - - ensure!( - pool_details.currency == CURRENCY_ID_LP_ETH, - "anemoy_pool::Migration: pre_upgrade failing as Anemoy's currency should be LpEthUSDC" - ); - - Ok(pool_details.encode()) - } - - fn on_runtime_upgrade() -> Weight { - let (sanity_checks, weight) = verify_sanity_checks(); - if !sanity_checks { - log::error!("anemoy_pool::Migration: Sanity checks FAILED"); - return weight; - } - - pallet_pool_system::Pool::::mutate(ANEMOY_POOL_ID, |maybe_details| { - if let Some(details) = maybe_details { - details.currency = CURRENCY_ID_DOT_NATIVE; - log::info!("anemoy_pool::Migration: currency set to USDC ✓"); - } else { - log::warn!("anemoy_pool::Migration: Pool details empty, skipping migration"); - } - }); - - weight.saturating_add( - ::DbWeight::get().reads_writes(1, 1), - ) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(old_state: Vec) -> Result<(), &'static str> { - let mut old_pool_details = PoolDetailsOf::::decode(&mut old_state.as_ref()) - .map_err(|_| "Error decoding pre-upgrade state")?; - - let pool_details: PoolDetailsOf = - PoolSystem::pool(ANEMOY_POOL_ID).ok_or("Could not find Anemoy Pool")?; - - // Ensure the currency set to USDC is the only mutation performed - old_pool_details.currency = CURRENCY_ID_DOT_NATIVE; - ensure!( - old_pool_details == pool_details, - "Corrupted migration: Only the currency of the Anemoy pool should have changed" - ); - - log::info!("anemoy_pool::Migration: post_upgrade succeeded ✓"); - Ok(()) - } - } - - fn verify_sanity_checks() -> (bool, Weight) { - let res = - crate::Tokens::balance( - CURRENCY_ID_LP_ETH, - &>::account_for(ANEMOY_POOL_ID), - ) == 0 && pallet_investments::ActiveInvestOrders::::iter_keys() - .filter(|investment| investment.pool_id == ANEMOY_POOL_ID) - .count() == 0 && pallet_investments::ActiveRedeemOrders::::iter_keys() - .filter(|investment| investment.pool_id == ANEMOY_POOL_ID) - .count() == 0 && pallet_investments::InvestOrders::::iter_keys() - .filter(|(_, investment)| investment.pool_id == ANEMOY_POOL_ID) - .count() == 0 && pallet_investments::RedeemOrders::::iter_keys() - .filter(|(_, investment)| investment.pool_id == ANEMOY_POOL_ID) - .count() == 0; - - let weight = ::DbWeight::get().reads( - vec![ - 1, // pool account balance read - pallet_investments::ActiveInvestOrders::::iter_keys().count(), - pallet_investments::ActiveRedeemOrders::::iter_keys().count(), - pallet_investments::InvestOrders::::iter_keys().count(), - pallet_investments::RedeemOrders::::iter_keys().count(), - ] - .iter() - .fold(0u64, |acc, x| acc.saturating_add(*x as u64)), - ); - - (res, weight) - } -} - -/// Add more LP wrapped USDC variants to the asset registry as well as -/// bidirectional trading pairs to and from DOT native USDC for these. -pub mod add_wrapped_usdc_variants { - #[cfg(feature = "try-runtime")] - use cfg_traits::TokenSwaps; - use cfg_types::tokens::{ - usdc::{ - lp_wrapped_usdc_metadata, CHAIN_ID_ARBITRUM_MAINNET, CHAIN_ID_BASE_MAINNET, - CHAIN_ID_CELO_MAINNET, CONTRACT_ARBITRUM, CONTRACT_BASE, CONTRACT_CELO, - CURRENCY_ID_DOT_NATIVE, CURRENCY_ID_LP_ARB, CURRENCY_ID_LP_BASE, CURRENCY_ID_LP_CELO, - CURRENCY_ID_LP_ETH, MIN_SWAP_ORDER_AMOUNT, - }, - CurrencyId, CustomMetadata, - }; - use frame_support::traits::OnRuntimeUpgrade; - use orml_asset_registry::AssetMetadata; - use sp_runtime::SaturatedConversion; - use sp_std::{vec, vec::Vec}; - - use super::*; - #[cfg(feature = "try-runtime")] - use crate::OrderBook; - use crate::{liquidity_pools::LiquidityPoolsPalletIndex, Balance, OrmlAssetRegistry, Runtime}; - - pub struct Migration(sp_std::marker::PhantomData); - - impl OnRuntimeUpgrade for Migration { - fn on_runtime_upgrade() -> Weight { - let mut writes = 0u64; - // Register assets - for (currency_id, metadata) in Self::get_unregistered_metadata().into_iter() { - log::info!("Registering asset {:?}", currency_id); - if OrmlAssetRegistry::metadata(currency_id).is_none() { - OrmlAssetRegistry::do_register_asset_without_asset_processor( - metadata, - currency_id, - ) - .map_err(|e| { - log::error!( - "Failed to register asset {:?} due to error {:?}", - currency_id, - e - ); - }) - // Add trading pairs if asset was registered successfully - .map(|_| { - log::info!( - "Adding bidirectional USDC trading pair for asset {:?}", - currency_id - ); - pallet_order_book::TradingPair::::insert( - CURRENCY_ID_DOT_NATIVE, - currency_id, - MIN_SWAP_ORDER_AMOUNT, - ); - pallet_order_book::TradingPair::::insert( - currency_id, - CURRENCY_ID_DOT_NATIVE, - MIN_SWAP_ORDER_AMOUNT, - ); - // 2 from updating metadata and location, 2 from trading pairs - writes = writes.saturating_add(4); - }) - .ok(); - } else { - log::warn!("Skipping registration of asset {:?}", currency_id); - } - } - // Add trading pair for already registered LpEthUsdc if it does not exist yet - if !pallet_order_book::TradingPair::::contains_key( - CURRENCY_ID_DOT_NATIVE, - CURRENCY_ID_LP_ETH, - ) { - log::info!( - "Adding trading pair from asset {:?} to USDC", - CURRENCY_ID_LP_ETH - ); - pallet_order_book::TradingPair::::insert( - CURRENCY_ID_DOT_NATIVE, - CURRENCY_ID_LP_ETH, - MIN_SWAP_ORDER_AMOUNT, - ); - writes = writes.saturating_add(1); - } else { - log::warn!( - "Skipping adding trading pair from asset {:?} to USDC", - CURRENCY_ID_LP_ETH - ); - } - if !pallet_order_book::TradingPair::::contains_key( - CURRENCY_ID_LP_ETH, - CURRENCY_ID_DOT_NATIVE, - ) { - log::info!( - "Adding trading pair from USDC to asset {:?}", - CURRENCY_ID_LP_ETH - ); - pallet_order_book::TradingPair::::insert( - CURRENCY_ID_LP_ETH, - CURRENCY_ID_DOT_NATIVE, - MIN_SWAP_ORDER_AMOUNT, - ); - writes = writes.saturating_add(1); - } else { - log::warn!( - "Skipping adding trading pair from USDC to asset {:?}", - CURRENCY_ID_LP_ETH - ); - } - - log::info!("add_wrapped_usdc_variants::Migration: on_runtime_upgrade succeeded ✓"); - - let new_assets: u64 = Self::get_unregistered_ids().len().saturated_into(); - ::DbWeight::get() - .reads_writes(new_assets.saturating_add(2), writes) - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - assert!( - Self::get_unregistered_ids() - .into_iter() - .all(|currency_id| OrmlAssetRegistry::metadata(currency_id).is_none()), - "At least one of new the wrapped USDC variants is already registered" - ); - - log::info!("add_wrapped_usdc_variants::Migration: pre_upgrade succeeded ✓"); - Ok(Vec::new()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_: Vec) -> Result<(), &'static str> { - assert!( - Self::get_unregistered_ids() - .into_iter() - .all(|currency_id| OrmlAssetRegistry::metadata(currency_id).is_some()), - "At least one of new the wrapped USDC variants was not registered" - ); - - assert!( - Self::get_tradeable_ids() - .into_iter() - .all(|wrapped_usdc_id| { - OrderBook::valid_pair(CURRENCY_ID_DOT_NATIVE, wrapped_usdc_id) - }), - "At least one of the wrapped USDC variants is not enabled as trading pair into DOT native USDC" - ); - - assert!( - Self::get_tradeable_ids() - .into_iter() - .all(|wrapped_usdc_id| { - OrderBook::valid_pair(wrapped_usdc_id, CURRENCY_ID_DOT_NATIVE) - }), - "At least one of the wrapped USDC variants is not enabled as trading pair from DOT native USDC" - ); - - log::info!("add_wrapped_usdc_variants::Migration: post_upgrade succeeded ✓"); - Ok(()) - } - } - - impl Migration { - fn get_unregistered_ids() -> Vec { - vec![CURRENCY_ID_LP_BASE, CURRENCY_ID_LP_ARB, CURRENCY_ID_LP_CELO] - } - - #[cfg(feature = "try-runtime")] - fn get_tradeable_ids() -> Vec { - vec![ - CURRENCY_ID_LP_ETH, - CURRENCY_ID_LP_BASE, - CURRENCY_ID_LP_ARB, - CURRENCY_ID_LP_CELO, - ] - } - - fn get_unregistered_metadata() -> Vec<(CurrencyId, AssetMetadata)> - { - vec![ - ( - CURRENCY_ID_LP_BASE, - lp_wrapped_usdc_metadata( - "LP Base Wrapped USDC".as_bytes().to_vec(), - "LpBaseUSDC".as_bytes().to_vec(), - LiquidityPoolsPalletIndex::get(), - CHAIN_ID_BASE_MAINNET, - CONTRACT_BASE, - true, - ), - ), - ( - CURRENCY_ID_LP_ARB, - lp_wrapped_usdc_metadata( - "LP Arbitrum Wrapped USDC".as_bytes().to_vec(), - "LpArbUSDC".as_bytes().to_vec(), - LiquidityPoolsPalletIndex::get(), - CHAIN_ID_ARBITRUM_MAINNET, - CONTRACT_ARBITRUM, - true, - ), - ), - ( - CURRENCY_ID_LP_CELO, - lp_wrapped_usdc_metadata( - "LP Celo Wrapped USDC".as_bytes().to_vec(), - "LpCeloUSDC".as_bytes().to_vec(), - LiquidityPoolsPalletIndex::get(), - CHAIN_ID_CELO_MAINNET, - CONTRACT_CELO, - true, - ), - ), - ] - } - } -} +pub type UpgradeCentrifuge1024 = (); diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index d4f8a29a8f..af63fd23c7 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -8,9 +8,9 @@ homepage = "https://centrifuge.io/" repository = "https://github.com/centrifuge/centrifuge-chain" [dependencies] +hex-literal = { version = "0.3.4", default-features = false } serde = { version = "1.0.119" } smallvec = "1.6.1" -hex-literal = { version = "0.3.4", default-features = false } # Substrate dependencies codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index 9f87e145b8..9f7e6db94e 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -141,7 +141,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("centrifuge-devel"), impl_name: create_runtime_str!("centrifuge-devel"), authoring_version: 1, - spec_version: 1032, + spec_version: 1033, impl_version: 1, #[cfg(not(feature = "disable-runtime-api"))] apis: RUNTIME_API_VERSIONS, @@ -1976,7 +1976,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, - crate::migrations::UpgradeDevelopment1031, + crate::migrations::UpgradeDevelopment1033, >; impl fp_self_contained::SelfContainedCall for RuntimeCall { diff --git a/runtime/development/src/migrations.rs b/runtime/development/src/migrations.rs index 475565ae78..d9623c0606 100644 --- a/runtime/development/src/migrations.rs +++ b/runtime/development/src/migrations.rs @@ -10,7 +10,4 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -pub type UpgradeDevelopment1031 = ( - // Sets account codes for all precompiles - runtime_common::migrations::precompile_account_codes::Migration, -); +pub type UpgradeDevelopment1033 = (); diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index 6db66868ee..e1857235bc 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -21,34 +21,35 @@ frame-benchmarking = { git = "https://github.com/paritytech/substrate", optional frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } pallet-aura = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } +pallet-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } pallet-collective = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } pallet-democracy = { git = "https://github.com/paritytech//substrate", rev = "bcff60a227d455d95b4712b6cb356ce56b1ff672" } pallet-preimage = { git = "https://github.com/paritytech//substrate", rev = "bcff60a227d455d95b4712b6cb356ce56b1ff672" } -pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } -pallet-uniques = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } +pallet-transaction-payment = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } pallet-treasury = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -pallet-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } - +pallet-uniques = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } ## Substrate-Primitives sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } #sp-authorship = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } +fp-self-contained = { git = "https://github.com/PureStake/frontier", branch = "moonbeam-polkadot-v0.9.38" } sp-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-inherents = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.38" } +sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } -fp-self-contained = { git = "https://github.com/PureStake/frontier", branch = "moonbeam-polkadot-v0.9.38" } +sp-transaction-pool = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } ## Substrate-Client node-primitives = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } +sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } #sc-consensus-uncles = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } sc-executor = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } @@ -67,8 +68,8 @@ rococo-runtime = { git = "https://github.com/paritytech/polkadot", branch = "rel xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.38" } # Cumulus -cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.38" } cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.38" } +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.38" } cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.38" } cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.38" } @@ -119,8 +120,8 @@ pallet-order-book = { path = "../../pallets/order-book" } pallet-permissions = { path = "../../pallets/permissions" } pallet-pool-registry = { path = "../../pallets/pool-registry" } pallet-pool-system = { path = "../../pallets/pool-system" } -pallet-rewards = { path = "../../pallets/rewards" } pallet-restricted-tokens = { path = "../../pallets/restricted-tokens" } +pallet-rewards = { path = "../../pallets/rewards" } pallet-session = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.38" } pallet-xcm-transactor = { git = "https://github.com/PureStake/moonbeam", default-features = false, rev = "00b3e3d97806e889b02e1bcb4b69e65433dd805d" } diff --git a/runtime/integration-tests/src/generic/cases/example.rs b/runtime/integration-tests/src/generic/cases/example.rs index 28ccf9d1bc..61e32ef89d 100644 --- a/runtime/integration-tests/src/generic/cases/example.rs +++ b/runtime/integration-tests/src/generic/cases/example.rs @@ -1,10 +1,14 @@ -use cfg_primitives::{AuraId, Balance, CFG}; +use cfg_primitives::{Balance, CFG}; use frame_support::traits::Get; +use sp_api::runtime_decl_for_Core::CoreV4; use crate::{ generic::{ environment::{Blocks, Env}, - envs::runtime_env::RuntimeEnv, + envs::{ + fudge_env::{FudgeEnv, FudgeSupport}, + runtime_env::RuntimeEnv, + }, runtime::Runtime, utils::genesis::Genesis, }, @@ -16,11 +20,10 @@ fn transfer_balance() { const FOR_FEES: Balance = 1 * CFG; // Set up all GenesisConfig for your initial state + // You can choose `RuntimeEnv` by `FudgeEnv` to make it working with fudge + // environment. let mut env = RuntimeEnv::::from_storage( Genesis::default() - .add(pallet_aura::GenesisConfig:: { - authorities: vec![AuraId::from(Keyring::Charlie.public())], - }) .add(pallet_balances::GenesisConfig:: { balances: vec![( Keyring::Alice.to_account_id(), @@ -31,14 +34,15 @@ fn transfer_balance() { ); // Call an extrinsic that would be processed immediately - env.submit( - Keyring::Alice, - pallet_balances::Call::transfer { - dest: Keyring::Bob.into(), - value: TRANSFER, - }, - ) - .unwrap(); + let fee = env + .submit_now( + Keyring::Alice, + pallet_balances::Call::transfer { + dest: Keyring::Bob.into(), + value: TRANSFER, + }, + ) + .unwrap(); // Check for an even occurred in this block env.check_event(pallet_balances::Event::Transfer { @@ -48,71 +52,111 @@ fn transfer_balance() { }) .unwrap(); - // Pass blocks to evolve the system - env.pass(Blocks::ByNumber(1)); - // Check the state env.state(|| { + assert_eq!( + pallet_balances::Pallet::::free_balance(Keyring::Alice.to_account_id()), + T::ExistentialDeposit::get() + FOR_FEES - fee, + ); assert_eq!( pallet_balances::Pallet::::free_balance(Keyring::Bob.to_account_id()), TRANSFER ); }); -} - -fn call_api() { - let env = RuntimeEnv::::from_storage( - Genesis::default() - .add(pallet_aura::GenesisConfig:: { - authorities: vec![AuraId::from(Keyring::Charlie.public())], - }) - .storage(), - ); - env.state(|| { - // Call to Core::version() API. - // It's automatically implemented by the runtime T, so you can easily do: - // T::version() - assert_eq!(T::version(), ::Version::get()); - }) + // Pass blocks to evolve the system + env.pass(Blocks::ByNumber(1)); } -fn check_fee() { - let mut env = RuntimeEnv::::from_storage( +// Identical to `transfer_balance()` test but using fudge. +fn fudge_transfer_balance() { + const TRANSFER: Balance = 1000 * CFG; + const FOR_FEES: Balance = 1 * CFG; + + let mut env = FudgeEnv::::from_storage( Genesis::default() - .add(pallet_aura::GenesisConfig:: { - authorities: vec![AuraId::from(Keyring::Charlie.public())], - }) .add(pallet_balances::GenesisConfig:: { - balances: vec![(Keyring::Alice.to_account_id(), 1 * CFG)], + balances: vec![( + Keyring::Alice.to_account_id(), + T::ExistentialDeposit::get() + FOR_FEES + TRANSFER, + )], }) .storage(), ); - env.submit( + env.submit_later( Keyring::Alice, - frame_system::Call::remark { remark: vec![] }, + pallet_balances::Call::transfer { + dest: Keyring::Bob.into(), + value: TRANSFER, + }, ) .unwrap(); - // Get the fee of the last submitted extrinsic - let fee = env.last_fee(); + // submit-later will only take effect if a block has passed + env.pass(Blocks::ByNumber(1)); + // Check for an even occurred in this block + env.check_event(pallet_balances::Event::Transfer { + from: Keyring::Alice.to_account_id(), + to: Keyring::Bob.to_account_id(), + amount: TRANSFER, + }) + .unwrap(); + + // Look for the fee for the last transaction + let fee = env + .find_event(|e| match e { + pallet_transaction_payment::Event::TransactionFeePaid { actual_fee, .. } => { + Some(actual_fee) + } + _ => None, + }) + .unwrap(); + + // Check the state env.state(|| { assert_eq!( pallet_balances::Pallet::::free_balance(Keyring::Alice.to_account_id()), - 1 * CFG - fee + T::ExistentialDeposit::get() + FOR_FEES - fee, + ); + assert_eq!( + pallet_balances::Pallet::::free_balance(Keyring::Bob.to_account_id()), + TRANSFER ); }); } -// Generate tests for all runtimes -crate::test_for_runtimes!((development, altair, centrifuge), transfer_balance); -crate::test_for_all_runtimes!(call_api); -crate::test_for_all_runtimes!(check_fee); +fn call_api() { + let env = RuntimeEnv::::from_storage(Default::default()); + + env.state(|| { + // If imported the trait: sp_api::runtime_decl_for_Core::CoreV4, + // you can easily do: T::Api::version() + assert_eq!( + T::Api::version(), + ::Version::get() + ); + }) +} + +fn fudge_call_api() { + let env = FudgeEnv::::from_storage(Default::default()); + + // Exclusive from fudge environment. + // It uses a client to access the runtime api. + env.with_api(|api, latest| { + // We include the API we want to use + use sp_api::Core; + + let result = api.version(&latest).unwrap(); + + assert_eq!(result, T::Api::version()); + assert_eq!(result, ::Version::get()); + }) +} -// Output: for `cargo test -p runtime-integration-tests transfer_balance` -// running 6 tests -// test generic::cases::example::transfer_balance::altair ... ok -// test generic::cases::example::transfer_balance::development ... ok -// test generic::cases::example::transfer_balance::centrifuge ... ok +crate::test_for_runtimes!([development, altair, centrifuge], transfer_balance); +crate::test_for_runtimes!(all, call_api); +crate::test_for_runtimes!(all, fudge_transfer_balance); +crate::test_for_runtimes!(all, fudge_call_api); diff --git a/runtime/integration-tests/src/generic/cases/loans.rs b/runtime/integration-tests/src/generic/cases/loans.rs new file mode 100644 index 0000000000..82fcf4b749 --- /dev/null +++ b/runtime/integration-tests/src/generic/cases/loans.rs @@ -0,0 +1,153 @@ +use cfg_primitives::{Balance, CollectionId, ItemId, PoolId, SECONDS_PER_YEAR}; +use cfg_traits::{ + interest::{CompoundingSchedule, InterestRate}, + Seconds, TimeAsSecs, +}; +use cfg_types::permissions::PoolRole; +use frame_support::traits::Get; +use pallet_loans::{ + entities::{ + input::PrincipalInput, + loans::LoanInfo, + pricing::{ + internal::{InternalPricing, MaxBorrowAmount as IntMaxBorrowAmount}, + Pricing, + }, + }, + types::{ + valuation::ValuationMethod, BorrowRestrictions, InterestPayments, LoanRestrictions, + Maturity, PayDownSchedule, RepayRestrictions, RepaymentSchedule, + }, +}; +use runtime_common::apis::runtime_decl_for_PoolsApi::PoolsApiV1; + +use crate::{ + generic::{ + environment::{Blocks, Env}, + envs::runtime_env::RuntimeEnv, + runtime::Runtime, + utils::{ + self, + genesis::{ + self, + currency::{cfg, usd6, CurrencyInfo, Usd6}, + Genesis, + }, + POOL_MIN_EPOCH_TIME, + }, + }, + utils::{accounts::Keyring, tokens::rate_from_percent}, +}; + +const POOL_ADMIN: Keyring = Keyring::Admin; +const INVESTOR: Keyring = Keyring::Alice; +const BORROWER: Keyring = Keyring::Bob; + +const POOL_A: PoolId = 23; +const NFT_A: (CollectionId, ItemId) = (1, ItemId(10)); + +const FOR_FEES: Balance = cfg(1); +const EXPECTED_POOL_BALANCE: Balance = usd6(1_000_000); +const COLLATERAL_VALUE: Balance = usd6(100_000); + +fn initialize_state_for_loans, T: Runtime>() -> Environment { + let mut env = Environment::from_storage( + Genesis::::default() + .add(genesis::balances(T::ExistentialDeposit::get() + FOR_FEES)) + .add(genesis::assets(vec![Usd6::ID])) + .add(genesis::tokens(vec![(Usd6::ID, Usd6::ED)])) + .storage(), + ); + + env.state_mut(|| { + // Creating a pool + utils::give_balance::(POOL_ADMIN.id(), T::PoolDeposit::get()); + utils::create_empty_pool::(POOL_ADMIN.id(), POOL_A, Usd6::ID); + + // Funding a pool + let tranche_id = T::Api::tranche_id(POOL_A, 0).unwrap(); + let tranche_investor = PoolRole::TrancheInvestor(tranche_id, Seconds::MAX); + utils::give_pool_role::(INVESTOR.id(), POOL_A, tranche_investor); + utils::give_tokens::(INVESTOR.id(), Usd6::ID, EXPECTED_POOL_BALANCE); + utils::invest::(INVESTOR.id(), POOL_A, tranche_id, EXPECTED_POOL_BALANCE); + }); + + env.pass(Blocks::BySeconds(POOL_MIN_EPOCH_TIME)); + + env.state_mut(|| { + // New epoch with the investor funds available + utils::close_pool_epoch::(POOL_ADMIN.id(), POOL_A); + + // Preparing borrower + utils::give_pool_role::(BORROWER.id(), POOL_A, PoolRole::Borrower); + utils::give_nft::(BORROWER.id(), NFT_A); + }); + + env +} + +fn internal_priced_loan(now: Seconds) -> LoanInfo { + LoanInfo { + schedule: RepaymentSchedule { + maturity: Maturity::Fixed { + date: now + SECONDS_PER_YEAR, + extension: SECONDS_PER_YEAR / 2, + }, + interest_payments: InterestPayments::None, + pay_down_schedule: PayDownSchedule::None, + }, + interest_rate: InterestRate::Fixed { + rate_per_year: rate_from_percent(20), + compounding: CompoundingSchedule::Secondly, + }, + collateral: NFT_A, + pricing: Pricing::Internal(InternalPricing { + collateral_value: COLLATERAL_VALUE, + max_borrow_amount: IntMaxBorrowAmount::UpToTotalBorrowed { + advance_rate: rate_from_percent(100), + }, + valuation_method: ValuationMethod::OutstandingDebt, + }), + restrictions: LoanRestrictions { + borrows: BorrowRestrictions::NotWrittenOff, + repayments: RepayRestrictions::None, + }, + } +} + +fn borrow() { + let mut env = initialize_state_for_loans::, T>(); + + let info = env.state(|| { + let now = as TimeAsSecs>::now(); + internal_priced_loan::(now) + }); + + env.submit_now( + BORROWER, + pallet_loans::Call::create { + pool_id: POOL_A, + info, + }, + ) + .unwrap(); + + let loan_id = env + .find_event(|e| match e { + pallet_loans::Event::::Created { loan_id, .. } => Some(loan_id), + _ => None, + }) + .unwrap(); + + env.submit_now( + BORROWER, + pallet_loans::Call::borrow { + pool_id: POOL_A, + loan_id, + amount: PrincipalInput::Internal(COLLATERAL_VALUE / 2), + }, + ) + .unwrap(); +} + +crate::test_for_runtimes!(all, borrow); diff --git a/runtime/integration-tests/src/generic/environment.rs b/runtime/integration-tests/src/generic/environment.rs index e7cae2bb5e..77ee636071 100644 --- a/runtime/integration-tests/src/generic/environment.rs +++ b/runtime/integration-tests/src/generic/environment.rs @@ -1,6 +1,11 @@ -use cfg_primitives::{Balance, BlockNumber}; -use cfg_traits::Seconds; -use sp_runtime::{DispatchResult, Storage}; +use cfg_primitives::{Address, Balance, BlockNumber, Index}; +use cfg_traits::{IntoSeconds, Seconds}; +use codec::Encode; +use sp_runtime::{ + generic::{Era, SignedPayload}, + traits::{Block, Extrinsic}, + DispatchError, DispatchResult, MultiSignature, Storage, +}; use crate::{generic::runtime::Runtime, utils::accounts::Keyring}; @@ -27,11 +32,49 @@ pub trait Env { /// Load the environment from a storage fn from_storage(storage: Storage) -> Self; - /// Submit an extrinsic mutating the state - fn submit(&mut self, who: Keyring, call: impl Into) -> DispatchResult; + /// Submit an extrinsic mutating the state instantly and returning the + /// consumed fee + fn submit_now( + &mut self, + who: Keyring, + call: impl Into, + ) -> Result; + + /// Submit an extrinsic mutating the state when the block is finalized + fn submit_later(&mut self, who: Keyring, call: impl Into) -> DispatchResult; /// Pass any number of blocks - fn pass(&mut self, blocks: Blocks); + fn pass(&mut self, blocks: Blocks) { + let (next, end_block) = self.state(|| { + let next = frame_system::Pallet::::block_number() + 1; + + let end_block = next + + match blocks { + Blocks::ByNumber(n) => n, + Blocks::BySeconds(secs) => { + let n = secs / pallet_aura::Pallet::::slot_duration().into_seconds(); + if n % pallet_aura::Pallet::::slot_duration() != 0 { + n as BlockNumber + 1 + } else { + n as BlockNumber + } + } + Blocks::UntilEvent { limit, .. } => limit, + }; + + (next, end_block) + }); + + for i in next..end_block { + self.__priv_build_block(i); + + if let Blocks::UntilEvent { event, .. } = blocks.clone() { + if self.check_event(event).is_some() { + break; + } + } + } + } /// Allows to mutate the storage state through the closure fn state_mut(&mut self, f: impl FnOnce() -> R) -> R; @@ -67,20 +110,42 @@ pub trait Env { frame_system::Pallet::::events() .into_iter() .rev() - .map(|record| record.event.try_into().ok()) - .find_map(|event| event.map(|e| f(e))) + .find_map(|record| record.event.try_into().map(|e| f(e)).ok()) .flatten() }) } - /// Retrieve the fees used in the last submit call - fn last_fee(&self) -> Balance { - self.find_event(|e| match e { - pallet_transaction_payment::Event::TransactionFeePaid { actual_fee, .. } => { - Some(actual_fee) - } - _ => None, - }) - .expect("Expected transaction") + fn __priv_build_block(&mut self, i: BlockNumber); +} + +pub mod utils { + use super::*; + + /// Creates an extrinsic, used mainly by the environment implementations. + /// To create and submit an extrinsic, see `submit()` + pub fn create_extrinsic( + who: Keyring, + call: impl Into, + nonce: Index, + ) -> ::Extrinsic { + let runtime_call = call.into(); + let signed_extra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(Era::mortal(256, 0)), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ); + + let raw_payload = SignedPayload::new(runtime_call.clone(), signed_extra.clone()).unwrap(); + let signature = + MultiSignature::Sr25519(raw_payload.using_encoded(|payload| who.sign(payload))); + + let multi_address = (Address::Id(who.to_account_id()), signature, signed_extra); + + ::Extrinsic::new(runtime_call, Some(multi_address)).unwrap() } } diff --git a/runtime/integration-tests/src/generic/envs/fudge_env.rs b/runtime/integration-tests/src/generic/envs/fudge_env.rs index 882aa285f2..abb5e2e997 100644 --- a/runtime/integration-tests/src/generic/envs/fudge_env.rs +++ b/runtime/integration-tests/src/generic/envs/fudge_env.rs @@ -1 +1,152 @@ -// TODO: implement generic::env::Env for an environment using fudge +pub mod handle; + +use std::collections::HashMap; + +use cfg_primitives::{Balance, BlockNumber, Index}; +use fudge::primitives::Chain; +use handle::{FudgeHandle, ParachainClient}; +use sc_client_api::HeaderBackend; +use sp_api::{ApiRef, ProvideRuntimeApi}; +use sp_runtime::{generic::BlockId, DispatchError, DispatchResult, Storage}; + +use crate::{ + generic::{ + environment::{utils, Env}, + runtime::Runtime, + }, + utils::accounts::Keyring, +}; + +/// Trait that represent a runtime with Fudge support +pub trait FudgeSupport: Runtime { + /// Type to interact with fudge + type FudgeHandle: FudgeHandle; +} + +/// Evironment that uses fudge to interact with the runtime +pub struct FudgeEnv { + handle: T::FudgeHandle, + nonce_storage: HashMap, +} + +impl Env for FudgeEnv { + fn from_storage(storage: Storage) -> Self { + let mut handle = T::FudgeHandle::new(Storage::default(), storage); + + handle.evolve(); + + Self { + handle, + nonce_storage: HashMap::default(), + } + } + + fn submit_now( + &mut self, + _who: Keyring, + _call: impl Into, + ) -> Result { + unimplemented!("FudgeEnv does not support submit_now() try submit_later()") + } + + fn submit_later(&mut self, who: Keyring, call: impl Into) -> DispatchResult { + let nonce = *self.nonce_storage.entry(who).or_default(); + + let extrinsic = self.state(|| utils::create_extrinsic::(who, call, nonce)); + + self.handle + .parachain_mut() + .append_extrinsic(extrinsic) + .map(|_| ()) + .map_err(|_| { + DispatchError::Other("Specific kind of DispatchError not supported by fudge now") + // More information, issue: https://github.com/centrifuge/fudge/issues/67 + })?; + + self.nonce_storage.insert(who, nonce + 1); + + Ok(()) + } + + fn state_mut(&mut self, f: impl FnOnce() -> R) -> R { + self.handle.parachain_mut().with_mut_state(f).unwrap() + } + + fn state(&self, f: impl FnOnce() -> R) -> R { + self.handle.parachain().with_state(f).unwrap() + } + + fn __priv_build_block(&mut self, _i: BlockNumber) { + self.handle.evolve(); + } +} + +type ApiRefOf<'a, T> = ApiRef< + 'a, + ::Block, + <::FudgeHandle as FudgeHandle>::ParachainConstructApi, + > as sp_api::ProvideRuntimeApi<::Block>>::Api, +>; + +/// Specialized fudge methods +impl FudgeEnv { + pub fn chain_state_mut(&mut self, chain: Chain, f: impl FnOnce() -> R) -> R { + self.handle.with_mut_state(chain, f) + } + + pub fn chain_state(&self, chain: Chain, f: impl FnOnce() -> R) -> R { + self.handle.with_state(chain, f) + } + + pub fn with_api(&self, exec: F) + where + F: FnOnce(ApiRefOf, BlockId), + { + let client = self.handle.parachain().client(); + let best_hash = client.info().best_hash; + let api = client.runtime_api(); + let best_hash = BlockId::hash(best_hash); + + exec(api, best_hash); + } +} + +mod tests { + use cfg_primitives::CFG; + + use super::*; + use crate::generic::{environment::Blocks, utils::genesis::Genesis}; + + fn correct_nonce_for_submit_later() { + let mut env = FudgeEnv::::from_storage( + Genesis::default() + .add(pallet_balances::GenesisConfig:: { + balances: vec![(Keyring::Alice.to_account_id(), 1 * CFG)], + }) + .storage(), + ); + + env.submit_later( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + + env.submit_later( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + + env.pass(Blocks::ByNumber(1)); + + env.submit_later( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + } + + crate::test_for_runtimes!(all, correct_nonce_for_submit_later); +} diff --git a/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs b/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs new file mode 100644 index 0000000000..59b0d4fa1d --- /dev/null +++ b/runtime/integration-tests/src/generic/envs/fudge_env/handle.rs @@ -0,0 +1,263 @@ +use std::sync::Arc; + +use cfg_primitives::{AuraId, BlockNumber}; +use frame_support::traits::GenesisBuild; +use fudge::{ + digest::{DigestCreator as DigestCreatorT, DigestProvider, FudgeAuraDigest, FudgeBabeDigest}, + inherent::{ + CreateInherentDataProviders, FudgeDummyInherentRelayParachain, FudgeInherentParaParachain, + FudgeInherentTimestamp, + }, + primitives::Chain, + state::StateProvider, + TWasmExecutor, +}; +use polkadot_core_primitives::{Block as RelayBlock, Header as RelayHeader}; +use polkadot_parachain::primitives::Id as ParaId; +use polkadot_primitives::runtime_api::ParachainHost; +use sc_block_builder::BlockBuilderApi; +use sc_client_api::Backend; +use sc_service::{TFullBackend, TFullClient}; +use sp_api::{ApiExt, ConstructRuntimeApi}; +use sp_consensus_aura::{sr25519::AuthorityId, AuraApi}; +use sp_consensus_babe::BabeApi; +use sp_consensus_slots::SlotDuration; +use sp_core::H256; +use sp_runtime::Storage; +use sp_transaction_pool::runtime_api::TaggedTransactionQueue; +use tokio::runtime::Handle; + +use crate::{generic::runtime::Runtime, utils::time::START_DATE}; + +type InherentCreator = Box< + dyn CreateInherentDataProviders< + Block, + (), + InherentDataProviders = ( + FudgeInherentTimestamp, + InherentDataProvider, + InherentParachain, + ), + >, +>; + +pub type RelayInherentCreator = InherentCreator< + RelayBlock, + FudgeDummyInherentRelayParachain, + sp_consensus_babe::inherents::InherentDataProvider, +>; + +pub type ParachainInherentCreator = InherentCreator< + Block, + FudgeInherentParaParachain, + sp_consensus_aura::inherents::InherentDataProvider, +>; + +pub type DigestCreator = Box + Send + Sync>; + +pub type RelaychainBuilder = fudge::RelaychainBuilder< + RelayBlock, + RuntimeApi, + Runtime, + RelayInherentCreator, + DigestCreator, +>; + +pub type ParachainBuilder = fudge::ParachainBuilder< + Block, + RuntimeApi, + ParachainInherentCreator, + DigestCreator, +>; + +pub type RelayClient = TFullClient; +pub type ParachainClient = TFullClient; + +pub trait FudgeHandle { + type RelayRuntime: frame_system::Config + + polkadot_runtime_parachains::paras::Config + + polkadot_runtime_parachains::session_info::Config + + polkadot_runtime_parachains::initializer::Config; + + type RelayConstructApi: ConstructRuntimeApi< + RelayBlock, + RelayClient, + RuntimeApi = Self::RelayApi, + > + Send + + Sync + + 'static; + + type RelayApi: BlockBuilderApi + + BabeApi + + ParachainHost + + ApiExt as Backend>::State> + + TaggedTransactionQueue; + + type ParachainConstructApi: ConstructRuntimeApi< + T::Block, + ParachainClient, + RuntimeApi = Self::ParachainApi, + > + Send + + Sync + + 'static; + + type ParachainApi: BlockBuilderApi + + ApiExt as Backend>::State> + + AuraApi + + TaggedTransactionQueue; + + const RELAY_CODE: Option<&'static [u8]>; + const PARACHAIN_CODE: Option<&'static [u8]>; + const PARA_ID: u32; + + fn relay(&self) -> &RelaychainBuilder; + fn relay_mut(&mut self) -> &mut RelaychainBuilder; + + fn parachain(&self) -> &ParachainBuilder; + fn parachain_mut(&mut self) -> &mut ParachainBuilder; + + fn append_extrinsic(&mut self, chain: Chain, extrinsic: Vec) -> Result<(), ()>; + + fn with_state(&self, chain: Chain, f: impl FnOnce() -> R) -> R; + fn with_mut_state(&mut self, chain: Chain, f: impl FnOnce() -> R) -> R; + fn evolve(&mut self); + + fn new(relay_storage: Storage, parachain_storage: Storage) -> Self; + + fn new_relay_builder( + storage: Storage, + ) -> RelaychainBuilder { + sp_tracing::enter_span!(sp_tracing::Level::INFO, "Relay - StartUp"); + + let code = Self::RELAY_CODE.expect("ESSENTIAL: WASM is built."); + let mut state = StateProvider::new(code); + + state.insert_storage( + polkadot_runtime_parachains::configuration::GenesisConfig::::default() + .build_storage() + .expect("ESSENTIAL: GenesisBuild must not fail at this stage.") + ); + + state.insert_storage( + frame_system::GenesisConfig { + code: code.to_vec(), + } + .build_storage::() + .expect("ESSENTIAL: GenesisBuild must not fail at this stage."), + ); + + state.insert_storage(storage); + + let mut init = fudge::initiator::default(Handle::current()); + init.with_genesis(Box::new(state)); + + let cidp = |client: Arc>| -> RelayInherentCreator { + let instance_id = FudgeInherentTimestamp::create_instance( + std::time::Duration::from_secs(6), + Some(std::time::Duration::from_millis(START_DATE)), + ); + + Box::new(move |parent: H256, ()| { + let client = client.clone(); + let parent_header = client + .header(parent.clone()) + .expect("ESSENTIAL: Relay CIDP must not fail.") + .expect("ESSENTIAL: Relay CIDP must not fail."); + + async move { + let timestamp = FudgeInherentTimestamp::get_instance(instance_id) + .expect("Instances is initialized"); + + let slot = + sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + timestamp.current_time(), + SlotDuration::from_millis(std::time::Duration::from_secs(6).as_millis() as u64), + ); + + let relay_para_inherent = FudgeDummyInherentRelayParachain::new(parent_header); + Ok((timestamp, slot, relay_para_inherent)) + } + }) + }; + + let dp: DigestCreator = Box::new(move |parent, inherents| async move { + let babe = FudgeBabeDigest::::new(); + let digest = babe.build_digest(&parent, &inherents).await?; + Ok(digest) + }); + + RelaychainBuilder::new(init, |client| (cidp(client), dp)) + } + + fn new_parachain_builder( + relay: &RelaychainBuilder, + storage: Storage, + ) -> ParachainBuilder { + sp_tracing::enter_span!(sp_tracing::Level::INFO, "Centrifuge - StartUp"); + + let code = Self::PARACHAIN_CODE.expect("ESSENTIAL: WASM is built."); + let mut state = StateProvider::new(code); + + state.insert_storage( + frame_system::GenesisConfig { + code: code.to_vec(), + } + .build_storage::() + .expect("ESSENTIAL: GenesisBuild must not fail at this stage."), + ); + state.insert_storage( + pallet_aura::GenesisConfig:: { + authorities: vec![AuraId::from(sp_core::sr25519::Public([0u8; 32]))], + } + .build_storage() + .expect("ESSENTIAL: GenesisBuild must not fail at this stage."), + ); + + state.insert_storage(storage); + + let mut init = fudge::initiator::default(Handle::current()); + init.with_genesis(Box::new(state)); + + let para_id = ParaId::from(Self::PARA_ID); + let inherent_builder = relay.inherent_builder(para_id.clone()); + let instance_id = FudgeInherentTimestamp::create_instance( + std::time::Duration::from_secs(12), + Some(std::time::Duration::from_millis(START_DATE)), + ); + + let cidp = Box::new(move |_parent: H256, ()| { + let inherent_builder_clone = inherent_builder.clone(); + async move { + let timestamp = FudgeInherentTimestamp::get_instance(instance_id) + .expect("Instances is initialized"); + + let slot = + sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( + timestamp.current_time(), + SlotDuration::from_millis(std::time::Duration::from_secs(12).as_millis() as u64), + ); + let inherent = inherent_builder_clone.parachain_inherent().await.unwrap(); + let relay_para_inherent = FudgeInherentParaParachain::new(inherent); + Ok((timestamp, slot, relay_para_inherent)) + } + }); + + let dp = |clone_client: Arc>| { + Box::new(move |parent, inherents| { + let client = clone_client.clone(); + + async move { + let aura = FudgeAuraDigest::< + T::Block, + ParachainClient, + >::new(&*client); + + let digest = aura.build_digest(&parent, &inherents).await?; + Ok(digest) + } + }) + }; + + ParachainBuilder::new(init, |client| (cidp, dp(client))) + } +} diff --git a/runtime/integration-tests/src/generic/envs/runtime_env.rs b/runtime/integration-tests/src/generic/envs/runtime_env.rs index 9f6b6c0c02..fca028b81c 100644 --- a/runtime/integration-tests/src/generic/envs/runtime_env.rs +++ b/runtime/integration-tests/src/generic/envs/runtime_env.rs @@ -1,113 +1,84 @@ -use std::{cell::RefCell, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc}; -use cfg_primitives::{Address, BlockNumber, Header, Index}; +use cfg_primitives::{AuraId, Balance, BlockNumber, Header}; use codec::Encode; use cumulus_primitives_core::PersistedValidationData; use cumulus_primitives_parachain_inherent::ParachainInherentData; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::{ inherent::{InherentData, ProvideInherent}, - storage::transactional, + traits::GenesisBuild, }; +use sp_api::runtime_decl_for_Core::CoreV4; +use sp_block_builder::runtime_decl_for_BlockBuilder::BlockBuilderV6; use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; -use sp_core::H256; -use sp_runtime::{ - generic::{Era, SignedPayload}, - traits::{Block, Extrinsic}, - Digest, DigestItem, DispatchError, DispatchResult, MultiSignature, Storage, TransactionOutcome, -}; +use sp_core::{sr25519::Public, H256}; +use sp_runtime::{traits::Extrinsic, Digest, DigestItem, DispatchError, DispatchResult, Storage}; use sp_timestamp::Timestamp; use crate::{ generic::{ - environment::{Blocks, Env}, + environment::{utils, Env}, runtime::Runtime, }, utils::accounts::Keyring, }; +/// Evironment that interact directly with the runtime, +/// without the usage of a client. pub struct RuntimeEnv { - nonce: Index, ext: Rc>, + pending_extrinsics: Vec<(Keyring, T::RuntimeCallExt)>, _config: PhantomData, } impl Env for RuntimeEnv { - fn from_storage(storage: Storage) -> Self { + fn from_storage(mut storage: Storage) -> Self { + // Needed for the aura usage + pallet_aura::GenesisConfig:: { + authorities: vec![AuraId::from(Public([0; 32]))], + } + .assimilate_storage(&mut storage) + .unwrap(); + let mut ext = sp_io::TestExternalities::new(storage); ext.execute_with(|| Self::prepare_block(1)); Self { - nonce: 0, ext: Rc::new(RefCell::new(ext)), + pending_extrinsics: Vec::default(), _config: PhantomData, } } - fn submit(&mut self, who: Keyring, call: impl Into) -> DispatchResult { - self.ext.borrow_mut().execute_with(|| { - let runtime_call = call.into(); - let signed_extra = ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(Era::mortal(256, 0)), - frame_system::CheckNonce::::from(self.nonce), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0), - ); - - let raw_payload = - SignedPayload::new(runtime_call.clone(), signed_extra.clone()).unwrap(); - let signature = - MultiSignature::Sr25519(raw_payload.using_encoded(|payload| who.sign(payload))); - - let multi_address = (Address::Id(who.to_account_id()), signature, signed_extra); - - let extrinsic = - ::Extrinsic::new(runtime_call, Some(multi_address)).unwrap(); - - self.nonce += 1; - - T::apply_extrinsic(extrinsic).unwrap() - }) + fn submit_now( + &mut self, + who: Keyring, + call: impl Into, + ) -> Result { + let extrinsic = self.state(|| { + let nonce = frame_system::Pallet::::account(who.to_account_id()).nonce; + utils::create_extrinsic::(who, call, nonce) + }); + + self.state_mut(|| T::Api::apply_extrinsic(extrinsic).unwrap())?; + + let fee = self + .find_event(|e| match e { + pallet_transaction_payment::Event::TransactionFeePaid { actual_fee, .. } => { + Some(actual_fee) + } + _ => None, + }) + .unwrap(); + + Ok(fee) } - fn pass(&mut self, blocks: Blocks) { - self.ext.borrow_mut().execute_with(|| { - let next = frame_system::Pallet::::block_number() + 1; - - let end_block = match blocks { - Blocks::ByNumber(n) => next + n, - Blocks::BySeconds(secs) => { - let blocks = secs / pallet_aura::Pallet::::slot_duration(); - if blocks % pallet_aura::Pallet::::slot_duration() != 0 { - blocks as BlockNumber + 1 - } else { - blocks as BlockNumber - } - } - Blocks::UntilEvent { limit, .. } => limit, - }; - - for i in next..end_block { - T::finalize_block(); - Self::prepare_block(i); - - if let Blocks::UntilEvent { event, .. } = blocks.clone() { - let event: T::RuntimeEventExt = event.into(); - if frame_system::Pallet::::events() - .into_iter() - .find(|record| record.event == event) - .is_some() - { - break; - } - } - } - }) + fn submit_later(&mut self, who: Keyring, call: impl Into) -> DispatchResult { + self.pending_extrinsics.push((who, call.into())); + Ok(()) } fn state_mut(&mut self, f: impl FnOnce() -> R) -> R { @@ -116,16 +87,39 @@ impl Env for RuntimeEnv { fn state(&self, f: impl FnOnce() -> R) -> R { self.ext.borrow_mut().execute_with(|| { - transactional::with_transaction(|| { - // We revert all changes done by the closure to offer an inmutable state method - TransactionOutcome::Rollback::>(Ok(f())) - }) - .unwrap() + let version = frame_support::StateVersion::V1; + let hash = frame_support::storage_root(version); + + let result = f(); + + assert_eq!(hash, frame_support::storage_root(version)); + result }) } + + fn __priv_build_block(&mut self, i: BlockNumber) { + self.process_pending_extrinsics(); + self.state_mut(|| { + T::Api::finalize_block(); + Self::prepare_block(i); + }); + } } impl RuntimeEnv { + fn process_pending_extrinsics(&mut self) { + let pending_extrinsics = mem::replace(&mut self.pending_extrinsics, Vec::default()); + + for (who, call) in pending_extrinsics { + let extrinsic = self.state(|| { + let nonce = frame_system::Pallet::::account(who.to_account_id()).nonce; + utils::create_extrinsic::(who, call, nonce) + }); + + self.state_mut(|| T::Api::apply_extrinsic(extrinsic).unwrap().unwrap()); + } + } + fn prepare_block(i: BlockNumber) { let slot = Slot::from(i as u64); let digest = Digest { @@ -140,7 +134,7 @@ impl RuntimeEnv { parent_hash: H256::default(), }; - T::initialize_block(&header); + T::Api::initialize_block(&header); let timestamp = i as u64 * pallet_aura::Pallet::::slot_duration(); let inherent_extrinsics = vec![ @@ -149,11 +143,11 @@ impl RuntimeEnv { ]; for extrinsic in inherent_extrinsics { - T::apply_extrinsic(extrinsic).unwrap().unwrap(); + T::Api::apply_extrinsic(extrinsic).unwrap().unwrap(); } } - fn cumulus_inherent(i: BlockNumber) -> T::RuntimeCall { + fn cumulus_inherent(i: BlockNumber) -> T::RuntimeCallExt { let mut inherent_data = InherentData::default(); let sproof_builder = RelayStateSproofBuilder::default(); @@ -184,7 +178,7 @@ impl RuntimeEnv { .into() } - fn timestamp_inherent(timestamp: u64) -> T::RuntimeCall { + fn timestamp_inherent(timestamp: u64) -> T::RuntimeCallExt { let mut inherent_data = InherentData::default(); let timestamp_inherent = Timestamp::new(timestamp); @@ -198,3 +192,65 @@ impl RuntimeEnv { .into() } } + +mod tests { + use cfg_primitives::CFG; + + use super::*; + use crate::generic::{environment::Blocks, utils::genesis::Genesis}; + + fn correct_nonce_for_submit_now() { + let mut env = RuntimeEnv::::from_storage( + Genesis::default() + .add(pallet_balances::GenesisConfig:: { + balances: vec![(Keyring::Alice.to_account_id(), 1 * CFG)], + }) + .storage(), + ); + + env.submit_now( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + + env.submit_now( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + } + + fn correct_nonce_for_submit_later() { + let mut env = RuntimeEnv::::from_storage( + Genesis::default() + .add(pallet_balances::GenesisConfig:: { + balances: vec![(Keyring::Alice.to_account_id(), 1 * CFG)], + }) + .storage(), + ); + + env.submit_later( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + + env.submit_later( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + + env.pass(Blocks::ByNumber(1)); + + env.submit_later( + Keyring::Alice, + frame_system::Call::remark { remark: vec![] }, + ) + .unwrap(); + } + + crate::test_for_runtimes!(all, correct_nonce_for_submit_now); + crate::test_for_runtimes!(all, correct_nonce_for_submit_later); +} diff --git a/runtime/integration-tests/src/generic/mod.rs b/runtime/integration-tests/src/generic/mod.rs index f87b09a241..8d62d5ee0a 100644 --- a/runtime/integration-tests/src/generic/mod.rs +++ b/runtime/integration-tests/src/generic/mod.rs @@ -9,55 +9,190 @@ pub mod envs { pub mod runtime_env; } pub mod runtime; -pub mod utils { - pub mod genesis; -} +pub mod utils; // Test cases mod cases { mod example; + mod loans; } use runtime::{Runtime, RuntimeKind}; -macro_rules! impl_config { - ($runtime:ident, $kind:ident) => { - impl Runtime for $runtime::Runtime { - type Block = $runtime::Block; - type RuntimeCallExt = $runtime::RuntimeCall; - type RuntimeEventExt = $runtime::RuntimeEvent; - - const KIND: RuntimeKind = RuntimeKind::$kind; - } - }; -} - -impl_config!(development_runtime, Development); -impl_config!(altair_runtime, Altair); -impl_config!(centrifuge_runtime, Centrifuge); - +/// Generate tests for the specified runtimes or all runtimes. +/// Usage +/// +/// ```rust +/// use crate::generic::runtime::Runtime; +/// +/// fn foo { +/// /// Your test here... +/// } +/// +/// crate::test_for_runtimes!([development, altair, centrifuge], foo); +/// ``` +/// For the following command: `cargo test -p runtime-integration-tests foo`, +/// it will generate the following output: +/// +/// ```text +/// test generic::foo::altair ... ok +/// test generic::foo::development ... ok +/// test generic::foo::centrifuge ... ok +/// ``` +/// +/// Available input for the first argument is: +/// - Any combination of `development`, `altair`, `centrifuge` inside `[]`. +/// - The world `all`. #[macro_export] macro_rules! test_for_runtimes { - ( ( $($runtime:ident),* ), $name:ident ) => { - mod $name { + ( [ $($runtime_name:ident),* ], $test_name:ident ) => { + mod $test_name { use super::*; + + #[allow(unused)] use development_runtime as development; + + #[allow(unused)] use altair_runtime as altair; + + #[allow(unused)] use centrifuge_runtime as centrifuge; $( - #[test] - fn $runtime() { - $name::<$runtime::Runtime>() + #[tokio::test] + async fn $runtime_name() { + $test_name::<$runtime_name::Runtime>() } )* } }; + ( all , $test_name:ident ) => { + $crate::test_for_runtimes!([development, altair, centrifuge], $test_name); + }; } -#[macro_export] -macro_rules! test_for_all_runtimes { - ( $name:ident ) => { - $crate::test_for_runtimes!((development, altair, centrifuge), $name); +/// Implements the `Runtime` trait for a runtime +macro_rules! impl_runtime { + ($runtime_path:ident, $kind:ident) => { + impl Runtime for $runtime_path::Runtime { + type Api = Self; + type Block = $runtime_path::Block; + type MaxTranchesExt = $runtime_path::MaxTranches; + type RuntimeCallExt = $runtime_path::RuntimeCall; + type RuntimeEventExt = $runtime_path::RuntimeEvent; + + const KIND: RuntimeKind = RuntimeKind::$kind; + } + }; +} + +impl_runtime!(development_runtime, Development); +impl_runtime!(altair_runtime, Altair); +impl_runtime!(centrifuge_runtime, Centrifuge); + +/// Implements fudge support for a runtime +macro_rules! impl_fudge_support { + ( + $fudge_companion_type:ident, + $relay_path:ident, + $parachain_path:ident, + $parachain_id:literal + ) => { + const _: () = { + use fudge::primitives::Chain; + use polkadot_core_primitives::Block as RelayBlock; + use sp_api::ConstructRuntimeApi; + use sp_runtime::Storage; + + use crate::generic::envs::fudge_env::{ + handle::{ + FudgeHandle, ParachainBuilder, ParachainClient, RelayClient, RelaychainBuilder, + }, + FudgeSupport, + }; + + #[fudge::companion] + pub struct $fudge_companion_type { + #[fudge::relaychain] + pub relay: RelaychainBuilder<$relay_path::RuntimeApi, $relay_path::Runtime>, + + #[fudge::parachain($parachain_id)] + pub parachain: + ParachainBuilder<$parachain_path::Block, $parachain_path::RuntimeApi>, + } + + // Implement for T only one time when fudge::companion + // supports generic in the struct signature. + impl FudgeHandle<$parachain_path::Runtime> for $fudge_companion_type { + type ParachainApi = <$parachain_path::RuntimeApi as ConstructRuntimeApi< + $parachain_path::Block, + ParachainClient<$parachain_path::Block, Self::ParachainConstructApi>, + >>::RuntimeApi; + type ParachainConstructApi = $parachain_path::RuntimeApi; + type RelayApi = <$relay_path::RuntimeApi as ConstructRuntimeApi< + RelayBlock, + RelayClient, + >>::RuntimeApi; + type RelayConstructApi = $relay_path::RuntimeApi; + type RelayRuntime = $relay_path::Runtime; + + const PARACHAIN_CODE: Option<&'static [u8]> = $parachain_path::WASM_BINARY; + const PARA_ID: u32 = $parachain_id; + const RELAY_CODE: Option<&'static [u8]> = $relay_path::WASM_BINARY; + + fn new(relay_storage: Storage, parachain_storage: Storage) -> Self { + let relay = Self::new_relay_builder(relay_storage); + let parachain = Self::new_parachain_builder(&relay, parachain_storage); + + Self::new(relay, parachain).unwrap() + } + + fn relay(&self) -> &RelaychainBuilder { + &self.relay + } + + fn relay_mut( + &mut self, + ) -> &mut RelaychainBuilder { + &mut self.relay + } + + fn parachain( + &self, + ) -> &ParachainBuilder<$parachain_path::Block, Self::ParachainConstructApi> { + &self.parachain + } + + fn parachain_mut( + &mut self, + ) -> &mut ParachainBuilder<$parachain_path::Block, Self::ParachainConstructApi> { + &mut self.parachain + } + + fn append_extrinsic(&mut self, chain: Chain, extrinsic: Vec) -> Result<(), ()> { + self.append_extrinsic(chain, extrinsic) + } + + fn with_state(&self, chain: Chain, f: impl FnOnce() -> R) -> R { + self.with_state(chain, f).unwrap() + } + + fn with_mut_state(&mut self, chain: Chain, f: impl FnOnce() -> R) -> R { + self.with_mut_state(chain, f).unwrap() + } + + fn evolve(&mut self) { + self.evolve().unwrap() + } + } + + impl FudgeSupport for $parachain_path::Runtime { + type FudgeHandle = $fudge_companion_type; + } + }; }; } + +impl_fudge_support!(FudgeDevelopment, rococo_runtime, development_runtime, 2000); +impl_fudge_support!(FudgeAltair, kusama_runtime, altair_runtime, 2088); +impl_fudge_support!(CentrifugeAltair, polkadot_runtime, centrifuge_runtime, 2031); diff --git a/runtime/integration-tests/src/generic/runtime.rs b/runtime/integration-tests/src/generic/runtime.rs index 2d255354e3..1ef6afbd35 100644 --- a/runtime/integration-tests/src/generic/runtime.rs +++ b/runtime/integration-tests/src/generic/runtime.rs @@ -6,9 +6,11 @@ use cfg_primitives::{ }; use cfg_traits::Millis; use cfg_types::{ + fixed_point::{Quantity, Rate}, permissions::{PermissionScope, Role}, tokens::{CurrencyId, CustomMetadata, TrancheCurrency}, }; +use codec::Codec; use fp_self_contained::{SelfContainedCall, UncheckedExtrinsic}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}, @@ -20,7 +22,11 @@ use runtime_common::{ apis, fees::{DealWithFees, WeightToFee}, }; -use sp_runtime::traits::{AccountIdLookup, Block, Dispatchable, Member}; +use sp_core::H256; +use sp_runtime::{ + scale_info::TypeInfo, + traits::{AccountIdLookup, Block, Dispatchable, Get, Member}, +}; /// Kind of runtime to check in runtime time pub enum RuntimeKind { @@ -45,20 +51,25 @@ pub trait Runtime: Balance = Balance, PoolId = PoolId, TrancheId = TrancheId, + BalanceRatio = Quantity, + MaxTranches = Self::MaxTranchesExt, > + pallet_balances::Config - + pallet_investments::Config + pallet_pool_registry::Config< CurrencyId = CurrencyId, PoolId = PoolId, Balance = Balance, + MaxTranches = Self::MaxTranchesExt, ModifyPool = pallet_pool_system::Pallet, ModifyWriteOffPolicy = pallet_loans::Pallet, > + pallet_permissions::Config> + + pallet_investments::Config + pallet_loans::Config< Balance = Balance, PoolId = PoolId, + LoanId = LoanId, CollectionId = CollectionId, ItemId = ItemId, + Rate = Rate, > + orml_tokens::Config + orml_asset_registry::Config< AssetId = CurrencyId, @@ -70,23 +81,13 @@ pub trait Runtime: + pallet_authorship::Config + pallet_treasury::Config> + pallet_transaction_payment::Config< - AccountId = AccountId, + AccountId = AccountId, WeightToFee = WeightToFee, OnChargeTransaction = CurrencyAdapter, DealWithFees>, > + pallet_restricted_tokens::Config< Balance = Balance, NativeFungible = pallet_balances::Pallet, > + cumulus_pallet_parachain_system::Config - - // APIS: - + sp_api::runtime_decl_for_Core::CoreV4 - + sp_block_builder::runtime_decl_for_BlockBuilder::BlockBuilderV6 - + apis::runtime_decl_for_LoansApi::LoansApiV1< - Self::Block, - PoolId, - LoanId, - pallet_loans::entities::loans::ActiveLoanInfo, - > { /// Just the RuntimeCall type, but redefined with extra bounds. /// You can add `From` bounds in order to convert pallet calls to @@ -95,9 +96,13 @@ pub trait Runtime: + Dispatchable + GetDispatchInfo + SelfContainedCall + + Sync + + Send + From> + From> + From> + + From> + + From> + From>; /// Just the RuntimeEvent type, but redefined with extra bounds. @@ -111,12 +116,15 @@ pub trait Runtime: + TryInto> + TryInto> + TryInto> + + TryInto> + From> + From> - + From>; + + From> + + From>; /// Block used by the runtime type Block: Block< + Hash = H256, Header = Header, Extrinsic = UncheckedExtrinsic< Address, @@ -135,6 +143,26 @@ pub trait Runtime: >, >; + /// You can extend this bounds to give extra API support + type Api: sp_api::runtime_decl_for_Core::CoreV4 + + sp_block_builder::runtime_decl_for_BlockBuilder::BlockBuilderV6 + + apis::runtime_decl_for_LoansApi::LoansApiV1< + Self::Block, + PoolId, + LoanId, + pallet_loans::entities::loans::ActiveLoanInfo, + > + apis::runtime_decl_for_PoolsApi::PoolsApiV1< + Self::Block, + PoolId, + TrancheId, + Balance, + CurrencyId, + Quantity, + Self::MaxTranchesExt, + >; + + type MaxTranchesExt: Codec + Get + Member + PartialOrd + TypeInfo; + /// Value to differentiate the runtime in tests. const KIND: RuntimeKind; } diff --git a/runtime/integration-tests/src/generic/utils/genesis.rs b/runtime/integration-tests/src/generic/utils/genesis.rs index f6b64d3ecf..e37fe3df17 100644 --- a/runtime/integration-tests/src/generic/utils/genesis.rs +++ b/runtime/integration-tests/src/generic/utils/genesis.rs @@ -1,15 +1,29 @@ +use std::marker::PhantomData; + +use cfg_primitives::{Balance, CFG}; +use cfg_types::tokens::{AssetMetadata, CrossChainTransferability, CurrencyId, CustomMetadata}; +use codec::Encode; use frame_support::traits::GenesisBuild; use sp_runtime::Storage; -use crate::generic::runtime::Runtime; +use crate::{generic::runtime::Runtime, utils::accounts::default_accounts}; -#[derive(Default)] -pub struct Genesis { +pub struct Genesis { storage: Storage, + _config: PhantomData, +} + +impl Default for Genesis { + fn default() -> Self { + Self { + storage: Default::default(), + _config: Default::default(), + } + } } -impl Genesis { - pub fn add(mut self, builder: impl GenesisBuild) -> Genesis { +impl Genesis { + pub fn add(mut self, builder: impl GenesisBuild) -> Self { builder.assimilate_storage(&mut self.storage).unwrap(); self } @@ -18,3 +32,117 @@ impl Genesis { self.storage } } + +pub fn balances(balance: Balance) -> impl GenesisBuild { + pallet_balances::GenesisConfig:: { + balances: default_accounts() + .into_iter() + .map(|keyring| (keyring.id(), balance)) + .collect(), + } +} + +pub fn tokens(values: Vec<(CurrencyId, Balance)>) -> impl GenesisBuild { + orml_tokens::GenesisConfig:: { + balances: default_accounts() + .into_iter() + .map(|keyring| { + values + .clone() + .into_iter() + .map(|(curency_id, balance)| (keyring.id(), curency_id, balance)) + .collect::>() + }) + .flatten() + .collect(), + } +} + +pub fn assets(currency_ids: Vec) -> impl GenesisBuild { + orml_asset_registry::GenesisConfig:: { + assets: currency_ids + .into_iter() + .map(|currency_id| (currency_id, currency::find_metadata(currency_id).encode())) + .collect(), + last_asset_id: Default::default(), // It seems deprecated + } +} + +pub mod currency { + use super::*; + + pub const fn cfg(amount: Balance) -> Balance { + amount * CFG + } + + pub trait CurrencyInfo { + const ID: CurrencyId; + const DECIMALS: u32; + const UNIT: Balance = 10u128.pow(Self::DECIMALS); + const SYMBOL: &'static str; + const NAME: &'static str = Self::SYMBOL; + const LOCATION: Option = None; + const CUSTOM: CustomMetadata; + const ED: Balance = 0; + + fn metadata() -> AssetMetadata { + AssetMetadata { + decimals: Self::DECIMALS, + name: Self::NAME.as_bytes().to_vec(), + symbol: Self::SYMBOL.as_bytes().to_vec(), + existential_deposit: Self::ED, + location: None, + additional: CustomMetadata { + pool_currency: true, + ..Default::default() + }, + } + } + } + + pub struct Usd6; + impl CurrencyInfo for Usd6 { + const CUSTOM: CustomMetadata = CustomMetadata { + pool_currency: true, + ..CONST_DEFAULT_CUSTOM + }; + const DECIMALS: u32 = 6; + const ID: CurrencyId = CurrencyId::ForeignAsset(1); + const SYMBOL: &'static str = "USD6"; + } + + pub const fn usd6(amount: Balance) -> Balance { + amount * Usd6::UNIT + } + + pub struct Usd12; + impl CurrencyInfo for Usd12 { + const CUSTOM: CustomMetadata = CustomMetadata { + pool_currency: true, + ..CONST_DEFAULT_CUSTOM + }; + const DECIMALS: u32 = 12; + const ID: CurrencyId = CurrencyId::ForeignAsset(2); + const SYMBOL: &'static str = "USD12"; + } + + pub const fn usd12(amount: Balance) -> Balance { + amount * Usd12::UNIT + } + + /// Matches default() but for const support + const CONST_DEFAULT_CUSTOM: CustomMetadata = CustomMetadata { + transferability: CrossChainTransferability::None, + mintable: false, + permissioned: false, + pool_currency: false, + }; + + pub fn find_metadata(currency: CurrencyId) -> AssetMetadata { + match currency { + Usd6::ID => Usd6::metadata(), + Usd12::ID => Usd12::metadata(), + _ => panic!("Unsupported currency {currency:?}"), + } + } +} diff --git a/runtime/integration-tests/src/generic/utils/mod.rs b/runtime/integration-tests/src/generic/utils/mod.rs new file mode 100644 index 0000000000..8bbd9c6920 --- /dev/null +++ b/runtime/integration-tests/src/generic/utils/mod.rs @@ -0,0 +1,136 @@ +// Divide this utilties into files when it grows + +use cfg_primitives::{AccountId, Balance, CollectionId, ItemId, PoolId, TrancheId}; +use cfg_traits::{investments::TrancheCurrency as _, Seconds}; +use cfg_types::{ + permissions::{PermissionScope, PoolRole, Role}, + tokens::{CurrencyId, TrancheCurrency}, +}; +use frame_system::RawOrigin; +use sp_runtime::traits::StaticLookup; +pub mod genesis; + +use cfg_types::pools::TrancheMetadata; +use frame_support::BoundedVec; +use pallet_pool_system::tranches::{TrancheInput, TrancheType}; +use sp_runtime::{traits::One, Perquintill}; + +use crate::generic::runtime::{Runtime, RuntimeKind}; + +pub const POOL_MIN_EPOCH_TIME: Seconds = 24; + +pub fn give_nft(dest: AccountId, (collection_id, item_id): (CollectionId, ItemId)) { + pallet_uniques::Pallet::::force_create( + RawOrigin::Root.into(), + collection_id, + T::Lookup::unlookup(dest.clone()), + true, + ) + .unwrap(); + + pallet_uniques::Pallet::::mint( + RawOrigin::Signed(dest.clone()).into(), + collection_id, + item_id, + T::Lookup::unlookup(dest), + ) + .unwrap() +} + +pub fn give_balance(dest: AccountId, amount: Balance) { + let data = pallet_balances::Account::::get(dest.clone()); + pallet_balances::Pallet::::set_balance( + RawOrigin::Root.into(), + T::Lookup::unlookup(dest), + data.free + amount, + data.reserved, + ) + .unwrap(); +} + +pub fn give_tokens(dest: AccountId, currency_id: CurrencyId, amount: Balance) { + let data = orml_tokens::Accounts::::get(dest.clone(), currency_id); + orml_tokens::Pallet::::set_balance( + RawOrigin::Root.into(), + T::Lookup::unlookup(dest), + currency_id, + data.free + amount, + data.reserved, + ) + .unwrap(); +} + +pub fn give_pool_role(dest: AccountId, pool_id: PoolId, role: PoolRole) { + pallet_permissions::Pallet::::add( + RawOrigin::Root.into(), + Role::PoolRole(role), + dest, + PermissionScope::Pool(pool_id), + Role::PoolRole(role), + ) + .unwrap(); +} + +pub fn create_empty_pool(admin: AccountId, pool_id: PoolId, currency_id: CurrencyId) { + pallet_pool_registry::Pallet::::register( + match T::KIND { + RuntimeKind::Development => RawOrigin::Signed(admin.clone()).into(), + _ => RawOrigin::Root.into(), + }, + admin, + pool_id, + vec![ + TrancheInput { + tranche_type: TrancheType::Residual, + seniority: None, + metadata: TrancheMetadata { + token_name: BoundedVec::default(), + token_symbol: BoundedVec::default(), + }, + }, + TrancheInput { + tranche_type: TrancheType::NonResidual { + interest_rate_per_sec: One::one(), + min_risk_buffer: Perquintill::from_percent(0), + }, + seniority: None, + metadata: TrancheMetadata { + token_name: BoundedVec::default(), + token_symbol: BoundedVec::default(), + }, + }, + ], + currency_id, + Balance::MAX, + None, + BoundedVec::default(), + ) + .unwrap(); + + // In order to later close the epoch fastly, + // we mofify here that requirement to significalty reduce the testing time. + // The only way to do it is breaking the integration tests rules mutating + // this state directly. + pallet_pool_system::Pool::::mutate(pool_id, |pool| { + pool.as_mut().unwrap().parameters.min_epoch_time = POOL_MIN_EPOCH_TIME; + }); +} + +pub fn close_pool_epoch(admin: AccountId, pool_id: PoolId) { + pallet_pool_system::Pallet::::close_epoch(RawOrigin::Signed(admin.clone()).into(), pool_id) + .unwrap(); +} + +pub fn invest( + investor: AccountId, + pool_id: PoolId, + tranche_id: TrancheId, + amount: Balance, +) { + pallet_investments::Pallet::::update_invest_order( + RawOrigin::Signed(investor).into(), + TrancheCurrency::generate(pool_id, tranche_id), + amount, + ) + .unwrap(); +} diff --git a/runtime/integration-tests/src/utils/accounts.rs b/runtime/integration-tests/src/utils/accounts.rs index ae61b304d6..d4cc842c72 100644 --- a/runtime/integration-tests/src/utils/accounts.rs +++ b/runtime/integration-tests/src/utils/accounts.rs @@ -154,6 +154,11 @@ impl Keyring { self.public().0.into() } + /// Shorter alias for `to_account_id()` + pub fn id(self) -> AccountId32 { + self.to_account_id() + } + pub fn sign(self, msg: &[u8]) -> Signature { Pair::from(self).sign(msg) }