From f41e83c6917c3bcf6946791fa0fd5d21012c3db5 Mon Sep 17 00:00:00 2001 From: nuno Date: Wed, 29 Jun 2022 16:18:15 +0200 Subject: [PATCH] connectors: MVP v0.0 Squashed all commits so far. Here we miss the XCM integration with xcm-transactor that builds and sends the XCM message to Moonbeam. --- Cargo.lock | 186 ++++++++ Cargo.toml | 1 + libs/common-traits/src/lib.rs | 2 +- pallets/connectors/Cargo.toml | 81 ++++ pallets/connectors/src/lib.rs | 417 ++++++++++++++++++ pallets/connectors/src/message.rs | 277 ++++++++++++ pallets/connectors/src/routers.rs | 18 + pallets/connectors/src/weights.rs | 96 ++++ runtime/development/Cargo.toml | 7 + runtime/development/src/lib.rs | 57 +++ runtime/integration-tests/Cargo.toml | 1 + .../src/xcm/development/mod.rs | 3 + .../src/xcm/development/setup.rs | 121 +++++ .../src/xcm/development/test_net.rs | 159 +++++++ .../src/xcm/development/tests/connectors.rs | 112 +++++ .../src/xcm/development/tests/mod.rs | 1 + runtime/integration-tests/src/xcm/mod.rs | 1 + 17 files changed, 1539 insertions(+), 1 deletion(-) create mode 100644 pallets/connectors/Cargo.toml create mode 100644 pallets/connectors/src/lib.rs create mode 100644 pallets/connectors/src/message.rs create mode 100644 pallets/connectors/src/routers.rs create mode 100644 pallets/connectors/src/weights.rs create mode 100644 runtime/integration-tests/src/xcm/development/mod.rs create mode 100644 runtime/integration-tests/src/xcm/development/setup.rs create mode 100644 runtime/integration-tests/src/xcm/development/test_net.rs create mode 100644 runtime/integration-tests/src/xcm/development/tests/connectors.rs create mode 100644 runtime/integration-tests/src/xcm/development/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7d05091725..40d5685c75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2322,6 +2322,7 @@ dependencies = [ "pallet-collator-allowlist", "pallet-collator-selection", "pallet-collective", + "pallet-connectors", "pallet-crowdloan-claim", "pallet-crowdloan-reward", "pallet-democracy", @@ -2360,6 +2361,7 @@ dependencies = [ "pallet-utility", "pallet-vesting", "pallet-xcm", + "pallet-xcm-transactor", "parachain-info", "parity-scale-codec", "polkadot-parachain", @@ -2391,6 +2393,7 @@ dependencies = [ "xcm", "xcm-builder", "xcm-executor", + "xcm-primitives", ] [[package]] @@ -2654,6 +2657,53 @@ dependencies = [ "libc", ] +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23750149fe8834c0e24bb9adcbacbe06c45b9861f15df53e09f26cb7c4ab91ef" +dependencies = [ + "bytes", + "ethereum-types", + "hash-db", + "hash256-std-hasher", + "parity-scale-codec", + "rlp", + "rlp-derive", + "scale-info", + "sha3 0.10.1", + "triehash", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "primitive-types", + "scale-info", + "uint", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -3652,6 +3702,15 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + [[package]] name = "impl-serde" version = "0.3.2" @@ -5948,6 +6007,31 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-connectors" +version = "0.0.1" +dependencies = [ + "common-traits", + "common-types", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "orml-tokens", + "orml-traits", + "pallet-balances", + "pallet-uniques", + "pallet-xcm-transactor", + "parity-scale-codec", + "runtime-common", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", +] + [[package]] name = "pallet-crowdloan-claim" version = "0.1.0" @@ -6820,6 +6904,29 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "pallet-xcm-transactor" +version = "0.1.0" +source = "git+https://github.com/NunoAlexandre/moonbeam?rev=d288eb94a136b92566d73e7d546e64f57145539e#d288eb94a136b92566d73e7d546e64f57145539e" +dependencies = [ + "cumulus-primitives-core", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "orml-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", + "xcm-primitives", +] + [[package]] name = "parachain-info" version = "0.1.0" @@ -8286,6 +8393,7 @@ checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" dependencies = [ "fixed-hash", "impl-codec", + "impl-rlp", "impl-serde", "scale-info", "uint", @@ -8870,6 +8978,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rlp" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rocksdb" version = "0.18.0" @@ -9046,6 +9175,7 @@ dependencies = [ "pallet-asset-tx-payment", "pallet-babe", "pallet-balances", + "pallet-connectors", "pallet-loans", "pallet-permissions", "pallet-pools", @@ -10556,6 +10686,19 @@ dependencies = [ "digest 0.10.3", ] +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" +dependencies = [ + "block-buffer 0.7.3", + "byte-tools", + "digest 0.8.1", + "keccak", + "opaque-debug 0.2.3", +] + [[package]] name = "sha3" version = "0.9.1" @@ -11933,6 +12076,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -12154,6 +12306,16 @@ dependencies = [ "hash-db", ] +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp", +] + [[package]] name = "trust-dns-proto" version = "0.21.2" @@ -13031,6 +13193,30 @@ dependencies = [ "xcm", ] +[[package]] +name = "xcm-primitives" +version = "0.1.0" +source = "git+https://github.com/NunoAlexandre/moonbeam?rev=d288eb94a136b92566d73e7d546e64f57145539e#d288eb94a136b92566d73e7d546e64f57145539e" +dependencies = [ + "ethereum", + "ethereum-types", + "frame-support", + "frame-system", + "hex", + "log", + "orml-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sha3 0.8.2", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "xcm-procedural" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4849750e48..b42a0b79be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ members = [ "pallets/anchors", "pallets/claims", "pallets/collator-allowlist", + "pallets/connectors", "pallets/crowdloan-claim", "pallets/crowdloan-reward", "pallets/migration", diff --git a/libs/common-traits/src/lib.rs b/libs/common-traits/src/lib.rs index e2db70ffe6..0967b219f2 100644 --- a/libs/common-traits/src/lib.rs +++ b/libs/common-traits/src/lib.rs @@ -110,7 +110,7 @@ pub trait PoolNAV { /// A trait that support pool inspection operations such as pool existence checks and pool admin of permission set. pub trait PoolInspect { - type PoolId: Parameter + Member + Debug + Copy + Default + TypeInfo; + type PoolId: Parameter + Member + Debug + Copy + Default + TypeInfo + Encode + Decode; type TrancheId: Parameter + Member + Debug + Copy + Default + TypeInfo; type Rate; type Moment; diff --git a/pallets/connectors/Cargo.toml b/pallets/connectors/Cargo.toml new file mode 100644 index 0000000000..a9f55f280f --- /dev/null +++ b/pallets/connectors/Cargo.toml @@ -0,0 +1,81 @@ +[package] +authors = ["Centrifuge "] +description = 'Centrifuge Connectors Pallet' +edition = '2018' +license = "LGPL-3.0" +name = 'pallet-connectors' +repository = "https://github.com/centrifuge/centrifuge-chain/pallets/connectors" +version = '0.0.1' + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] + +[dependencies] +codec = { package = 'parity-scale-codec', version = '3.0.0', features = ['derive'] , default-features = false } +scale-info = { version = "2.0", default-features = false, features = ["derive"] } +sp-std = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } +frame-support = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } +frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } + +# Substrate crates +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } + +# Optional dependencies for benchmarking +frame-benchmarking = { git = "https://github.com/paritytech/substrate", default-features = false , optional = true , branch = "polkadot-v0.9.24" } +pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false , optional = true, branch = "polkadot-v0.9.24" } +pallet-uniques = { git = "https://github.com/paritytech/substrate", default-features = false , optional = true, branch = "polkadot-v0.9.24" } +orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, optional = true, branch = "polkadot-v0.9.24" } +orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, optional = true, branch = "polkadot-v0.9.24" } + +# Our custom pallets +common-types = { path = "../../libs/common-types", default-features = false } +common-traits = { path = "../../libs/common-traits", default-features = false } +runtime-common = { path = "../../runtime/common", default-features = false } + +# Polkadot +xcm = { git = "https://github.com/paritytech/polkadot", default-features = false , branch = "release-v0.9.24" } + +pallet-xcm-transactor = { git = "https://github.com/NunoAlexandre/moonbeam", default-features = false, rev = "d288eb94a136b92566d73e7d546e64f57145539e" } + +[dev-dependencies] +hex = "0.4.3" + +# Substrate crates & pallets +sp-core = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } +sp-io = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } +pallet-balances = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "polkadot-v0.9.24" } + +# Orml crates +orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = true, branch = "polkadot-v0.9.24" } +orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", default-features = false, branch = "polkadot-v0.9.24" } + +# Local crates +runtime-common = { path = "../../runtime/common", default-features = true } + +[features] +default = ['std'] +runtime-benchmarks = [ + "frame-benchmarking", + "pallet-balances", +# "common-types", +# "common-traits", +# "runtime-common", + "orml-tokens", + "orml-traits", +] +std = [ + 'codec/std', + 'common-types/std', + 'common-traits/std', + 'frame-support/std', + 'frame-system/std', + 'sp-std/std', + 'sp-runtime/std', + 'orml-tokens/std', + 'orml-traits/std', + 'pallet-balances/std', + 'runtime-common/std', + 'xcm/std', + 'pallet-xcm-transactor/std', +] diff --git a/pallets/connectors/src/lib.rs b/pallets/connectors/src/lib.rs new file mode 100644 index 0000000000..8c80282ca8 --- /dev/null +++ b/pallets/connectors/src/lib.rs @@ -0,0 +1,417 @@ +//! Centrifuge Connectors pallet +//! +//! TODO(nuno): add description +//! +//! +#![cfg_attr(not(feature = "std"), no_std)] +use codec::{Decode, Encode, HasCompact}; +use common_traits::PoolInspect; +use frame_support::dispatch::DispatchResult; +use frame_support::traits::fungibles::{Inspect, Mutate, Transfer}; +use frame_system::ensure_signed; +use scale_info::TypeInfo; +use sp_core::TypeId; +use sp_runtime::traits::AtLeast32BitUnsigned; +use sp_runtime::FixedPointNumber; +use sp_std::convert::TryInto; + +pub use pallet::*; + +pub mod weights; + +mod message; +pub use message::*; + +mod routers; +pub use routers::*; + +#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Domain { + Centrifuge, + Moonbeam, + Ethereum, + Avalanche, + Gnosis, +} + +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +pub struct DomainLocator { + pub domain: Domain, +} + +impl TypeId for DomainLocator { + const TYPE_ID: [u8; 4] = *b"domn"; +} + +#[derive(Encode, Decode, Default, Clone, PartialEq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct DomainAddress { + pub domain: Domain, + pub address: [u8; 32], +} + +impl TypeId for DomainAddress { + const TYPE_ID: [u8; 4] = *b"dadr"; +} + +// Type aliases +pub type PoolIdOf = <::PoolInspect as PoolInspect< + ::AccountId, + ::CurrencyId, +>>::PoolId; + +pub type TrancheIdOf = <::PoolInspect as PoolInspect< + ::AccountId, + ::CurrencyId, +>>::TrancheId; + +pub type MessageOf = + Message, TrancheIdOf, ::Balance, ::Rate>; + +pub type CurrencyIdOf = ::CurrencyId; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::weights::WeightInfo; + use common_traits::{Moment, Permissions, PoolInspect}; + use common_types::{CurrencyId, PermissionScope, PoolRole, Role}; + use frame_support::{error::BadOrigin, pallet_prelude::*, traits::UnixTime}; + use frame_system::pallet_prelude::*; + use sp_runtime::traits::AccountIdConversion; + use sp_runtime::traits::Zero; + use xcm::v0::MultiLocation; + use xcm::v2::OriginKind; + use xcm::VersionedMultiLocation; + + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_xcm_transactor::Config { + type Event: From> + IsType<::Event>; + + type WeightInfo: WeightInfo; + + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen; + + type Rate: Parameter + Member + MaybeSerializeDeserialize + FixedPointNumber + TypeInfo; + + type CurrencyId: Parameter + + Copy + // TODO(nuno): remove Default is not needed after MVP tests + + Default + + IsType<::CurrencyId>; + + type AdminOrigin: EnsureOrigin; + + type PoolInspect: PoolInspect< + Self::AccountId, + ::CurrencyId, + Rate = Self::Rate, + >; + + type Permission: Permissions< + Self::AccountId, + Scope = PermissionScope, ::CurrencyId>, + Role = Role, Moment>, + Error = DispatchError, + >; + + type Time: UnixTime; + + type Tokens: Mutate + + Inspect< + Self::AccountId, + AssetId = ::CurrencyId, + Balance = ::Balance, + > + Transfer; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A message was sent to a domain + MessageSent { + domain: Domain, + message: MessageOf, + }, + } + + #[pallet::storage] + pub(crate) type DomainRouter = StorageMap<_, Blake2_128Concat, Domain, Router>; + + #[pallet::error] + pub enum Error { + /// A pool could not be found + PoolNotFound, + /// A tranche could not be found + TrancheNotFound, + /// The specified domain has no associated router + InvalidDomain, + /// Failed to send a message + SendFailure, + /// Token price is not set + MissingPrice, + /// The router does not exist + MissingRouter, + /// The selected domain is not yet supported + UnsupportedDomain, + /// Transfer amount must be non-zero + InvalidTransferAmount, + } + + #[pallet::call] + impl Pallet { + /// Add a pool to a given domain + #[pallet::weight(::WeightInfo::add_pool())] + pub fn add_pool( + origin: OriginFor, + pool_id: PoolIdOf, + domain: Domain, + ) -> DispatchResult { + ensure_signed(origin.clone())?; + + // Check the pool exists + ensure!( + T::PoolInspect::pool_exists(pool_id), + Error::::PoolNotFound + ); + + // Send the message through the router + Self::do_send_message(Message::AddPool { pool_id }, domain)?; + + Ok(()) + } + + /// Add a tranche to a given domain + #[pallet::weight(::WeightInfo::add_tranche())] + pub fn add_tranche( + origin: OriginFor, + pool_id: PoolIdOf, + tranche_id: TrancheIdOf, + domain: Domain, + ) -> DispatchResult { + ensure_signed(origin.clone())?; + + // Check the tranche exists + ensure!( + T::PoolInspect::tranche_exists(pool_id, tranche_id), + Error::::TrancheNotFound + ); + + // Send the message through the router + // + // TODO: retrieve token name and symbol from asset-registry + // Depends on https://github.com/centrifuge/centrifuge-chain/issues/842 + // + // TODO: only allow calling add_tranche when + // both the name and symbol are non-zero values. + Self::do_send_message( + Message::AddTranche { + pool_id, + tranche_id, + token_name: [0; 32], + token_symbol: [0; 32], + }, + domain, + )?; + + Ok(()) + } + + /// Update a token price + #[pallet::weight(::WeightInfo::update_token_price())] + pub fn update_token_price( + origin: OriginFor, + pool_id: PoolIdOf, + tranche_id: TrancheIdOf, + domain: Domain, + ) -> DispatchResult { + ensure_signed(origin.clone())?; + + // Check the tranche exists + ensure!( + T::PoolInspect::tranche_exists(pool_id, tranche_id), + Error::::TrancheNotFound + ); + + // Get the current price + let latest_price = T::PoolInspect::get_tranche_token_price(pool_id, tranche_id) + .ok_or(Error::::MissingPrice)?; + + // Send the message through the router + Self::do_send_message( + Message::UpdateTokenPrice { + pool_id, + tranche_id, + price: latest_price.price, + }, + domain, + )?; + + Ok(()) + } + + /// Update a member + #[pallet::weight(::WeightInfo::update_member())] + pub fn update_member( + origin: OriginFor, + address: DomainAddress, + pool_id: PoolIdOf, + tranche_id: TrancheIdOf, + valid_until: Moment, + ) -> DispatchResult { + let who = ensure_signed(origin.clone())?; + + // Check that the origin is a member of this tranche token or is a memberlist admin and thus allowed to add other members. + ensure!( + T::Permission::has( + PermissionScope::Pool(pool_id), + who.clone(), + Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, Self::now())) + ) || T::Permission::has( + PermissionScope::Pool(pool_id), + who.clone(), + Role::PoolRole(PoolRole::MemberListAdmin) + ), + BadOrigin + ); + + T::Permission::add( + PermissionScope::Pool(pool_id), + address.into_account_truncating(), + Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, valid_until)), + )?; + + // Send the message through the router + Self::do_send_message( + Message::UpdateMember { + pool_id, + tranche_id, + valid_until, + address: address.address, + }, + address.domain, + )?; + + Ok(()) + } + + /// Update a member + #[pallet::weight(::WeightInfo::transfer())] + pub fn transfer( + origin: OriginFor, + pool_id: PoolIdOf, + tranche_id: TrancheIdOf, + address: DomainAddress, + amount: ::Balance, + ) -> DispatchResult { + let who = ensure_signed(origin.clone())?; + + // Check that the destination is a member of this tranche token + ensure!( + T::Permission::has( + PermissionScope::Pool(pool_id), + address.into_account_truncating(), + Role::PoolRole(PoolRole::TrancheInvestor(tranche_id, Self::now())) + ), + BadOrigin + ); + + // Check whether amount > 0 + ensure!(!amount.is_zero(), Error::::InvalidTransferAmount); + + // TODO: Transfer to the domain account for bookkeeping + // T::Tokens::transfer( + // T::CurrencyId::Tranche(pool_id, tranche_id), + // &who, + // &DomainLocator { + // domain: address.domain, + // } + // .into_account_truncating(), + // amount, + // false, + // )?; + + // Send the message through the router + Self::do_send_message( + Message::Transfer { + pool_id, + tranche_id, + amount, + domain: address.clone().domain, + destination: address.clone().address, + }, + address.domain, + )?; + + Ok(()) + } + } + + impl Pallet { + pub(crate) fn now() -> Moment { + T::Time::now().as_secs() + } + + pub fn do_set_router(domain: Domain, router: Router) -> DispatchResult { + >::insert(domain.clone(), router); + Ok(()) + } + + pub fn do_send_message(message: MessageOf, domain: Domain) -> Result<(), Error> { + let router = >::get(domain.clone()).ok_or(Error::::MissingRouter)?; + + match router { + Router::Xcm { location } => Self::send_through_xcm(&message, location), + _ => Err(Error::::UnsupportedDomain.into()), + }?; + + Self::deposit_event(Event::MessageSent { message, domain }); + Ok(()) + } + + fn send_through_xcm( + message: &MessageOf, + dest_location: VersionedMultiLocation, + ) -> Result<(), Error> { + use codec::Encode; + use frame_support::traits::OriginTrait; + use Default; + + todo!() + } + + //TODO(nuno): this is just for testing purposes for now. + pub fn send_through_xcm_tests( + message: &MessageOf, + dest_location: VersionedMultiLocation, + fees_location: VersionedMultiLocation, + fee_payer: T::AccountId, + ) -> DispatchResult { + use codec::Encode; + use frame_support::traits::OriginTrait; + use sp_std::boxed::Box; + + pallet_xcm_transactor::Pallet::::transact_through_sovereign( + T::Origin::root(), + Box::new(dest_location), + fee_payer, + Box::new(fees_location), + 8_000_000_000_000, + (*message).encode().to_vec(), + OriginKind::SovereignAccount, + ) + } + } +} diff --git a/pallets/connectors/src/message.rs b/pallets/connectors/src/message.rs new file mode 100644 index 0000000000..ba97bc5ab9 --- /dev/null +++ b/pallets/connectors/src/message.rs @@ -0,0 +1,277 @@ +use common_traits::Moment; +use frame_support::BoundedVec; +use sp_std::vec; +use sp_std::vec::Vec; + +use crate::*; + +#[derive(Decode, Clone, PartialEq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Message +where + Domain: Encode + Decode, + PoolId: Encode + Decode, + TrancheId: Encode + Decode, + Balance: Encode + Decode, + Rate: Encode + Decode, +{ + Invalid, + AddPool { + pool_id: PoolId, + }, + AddTranche { + pool_id: PoolId, + tranche_id: TrancheId, + token_name: [u8; 32], + token_symbol: [u8; 32], + }, + UpdateTokenPrice { + pool_id: PoolId, + tranche_id: TrancheId, + price: Rate, + }, + UpdateMember { + pool_id: PoolId, + tranche_id: TrancheId, + address: [u8; 32], + valid_until: Moment, + }, + Transfer { + pool_id: PoolId, + tranche_id: TrancheId, + domain: Domain, + destination: [u8; 32], + amount: Balance, + }, +} + +impl< + Domain: Encode + Decode, + PoolId: Encode + Decode, + TrancheId: Encode + Decode, + Balance: Encode + Decode, + Rate: Encode + Decode, + > Message +{ + fn call_type(&self) -> u8 { + match self { + Self::Invalid => 0, + Self::AddPool { .. } => 1, + Self::AddTranche { .. } => 2, + Self::UpdateTokenPrice { .. } => 3, + Self::UpdateMember { .. } => 4, + Self::Transfer { .. } => 5, + } + } +} + +impl< + Domain: Encode + Decode, + PoolId: Encode + Decode, + TrancheId: Encode + Decode, + Balance: Encode + Decode, + Rate: Encode + Decode, + > Encode for Message +{ + fn encode(&self) -> Vec { + match self { + Message::Invalid => vec![self.call_type()], + Message::AddPool { pool_id } => { + let mut message: Vec = vec![]; + message.push(self.call_type()); + + let mut encoded_pool_id = pool_id.encode(); + encoded_pool_id.reverse(); + message.append(&mut encoded_pool_id); + + message + } + Message::AddTranche { + pool_id, + tranche_id, + token_name, + token_symbol, + } => { + let mut message: Vec = vec![]; + message.push(self.call_type()); + + let mut encoded_pool_id = pool_id.encode(); + encoded_pool_id.reverse(); + message.append(&mut encoded_pool_id); + + message.append(&mut tranche_id.encode()); + message.append(&mut token_name.encode()); + message.append(&mut token_symbol.encode()); + + message + } + Message::UpdateTokenPrice { + pool_id, + tranche_id, + price, + } => { + let mut message: Vec = vec![]; + message.push(self.call_type()); + + let mut encoded_pool_id = pool_id.encode(); + encoded_pool_id.reverse(); + message.append(&mut encoded_pool_id); + + message.append(&mut tranche_id.encode()); + message.append(&mut price.encode()); + + message + } + Message::UpdateMember { + pool_id, + tranche_id, + address, + valid_until, + } => { + let mut message: Vec = vec![]; + message.push(self.call_type()); + + let mut encoded_pool_id = pool_id.encode(); + encoded_pool_id.reverse(); + message.append(&mut encoded_pool_id); + + message.append(&mut tranche_id.encode()); + message.append(&mut address.encode()); + message.append(&mut valid_until.encode()); + + message + } + Message::Transfer { + pool_id, + tranche_id, + domain, + destination, + amount, + } => { + let mut message: Vec = vec![]; + message.push(self.call_type()); + + let mut encoded_pool_id = pool_id.encode(); + encoded_pool_id.reverse(); + message.append(&mut encoded_pool_id); + + message.append(&mut tranche_id.encode()); + message.append(&mut domain.encode()); + message.append(&mut destination.encode()); + message.append(&mut amount.encode()); + + message + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::Message; + use codec::{Decode, Encode}; + use hex::FromHex; + use sp_runtime::traits::One; + + type PoolId = u64; + type TrancheId = [u8; 16]; + type Balance = runtime_common::Balance; + type Rate = runtime_common::Rate; + + const CURRENCY: Balance = 1_000_000_000_000_000_000; + + pub mod encode { + use crate::Domain; + + use super::*; + + #[test] + fn invalid() { + let msg = Message::::Invalid; + assert_eq!(msg.encode(), vec![msg.call_type()]); + assert_eq!(msg.encode(), vec![0]); + } + + #[test] + fn add_pool_zero() { + let msg = Message::::AddPool { pool_id: 0 }; + let encoded = msg.encode(); + + let expected_hex = "010000000000000000"; + let expected = <[u8; 9]>::from_hex(expected_hex).expect("Decoding failed"); + assert_eq!(encoded, expected); + } + + #[test] + fn add_pool_long() { + let msg = + Message::::AddPool { pool_id: 12378532 }; + let encoded = msg.encode(); + + let expected_hex = "010000000000bce1a4"; + let expected = <[u8; 9]>::from_hex(expected_hex).expect("Decoding failed"); + assert_eq!(encoded, expected); + } + + #[test] + fn add_tranche() { + let msg = Message::::AddTranche { + pool_id: 1, + tranche_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + token_name: [0; 32], + token_symbol: [0; 32], + }; + let encoded = msg.encode(); + + let expected_hex = "0200000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + let expected = <[u8; 89]>::from_hex(expected_hex).expect("Decoding failed"); + assert_eq!(encoded, expected); + } + + #[test] + fn update_token_price() { + let msg = Message::::UpdateTokenPrice { + pool_id: 1, + tranche_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + price: Rate::one(), + }; + let encoded = msg.encode(); + + let input = "03000000000000000100000000000000000000000000000001000000e83c80d09f3c2e3b0300000000"; + let expected = <[u8; 41]>::from_hex(input).expect("Decoding failed"); + assert_eq!(encoded, expected); + } + + #[test] + fn update_member() { + let msg = Message::::UpdateMember { + pool_id: 1, + tranche_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + address: [1; 32], + valid_until: 100, + }; + let encoded = msg.encode(); + + let input = "0400000000000000010000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101016400000000000000"; + let expected = <[u8; 65]>::from_hex(input).expect("Decoding failed"); + assert_eq!(encoded, expected); + } + + #[test] + fn transfer() { + let msg = Message::::Transfer { + pool_id: 1, + tranche_id: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], + domain: Domain::Avalanche, + destination: [1; 32], + amount: 100 * CURRENCY, + }; + let encoded = msg.encode(); + println!("{}", hex::encode(encoded.clone())); + + let input = "0400000000000000010000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101016400000000000000"; + let expected = <[u8; 65]>::from_hex(input).expect("Decoding failed"); + assert_eq!(encoded, expected); + } + } +} diff --git a/pallets/connectors/src/routers.rs b/pallets/connectors/src/routers.rs new file mode 100644 index 0000000000..842900a707 --- /dev/null +++ b/pallets/connectors/src/routers.rs @@ -0,0 +1,18 @@ +use crate::{Domain, Message}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::DispatchResult; +use xcm::VersionedMultiLocation; + +#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Router { + // The router for a domain that is to be routed through Nomad + Nomad { + forwarding_contract: VersionedMultiLocation, + }, + // The router for a domain that is to be routed through XCM + Xcm { + location: VersionedMultiLocation, + }, +} diff --git a/pallets/connectors/src/weights.rs b/pallets/connectors/src/weights.rs new file mode 100644 index 0000000000..995a0e1928 --- /dev/null +++ b/pallets/connectors/src/weights.rs @@ -0,0 +1,96 @@ +//! Autogenerated weights for pallet_connectors +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-03-02, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("development-local"), DB CACHE: 1024 + +// Executed Command: +// target/release/centrifuge-chain +// benchmark +// --chain=development-local +// --steps=50 +// --repeat=20 +// --pallet=pallet-connectors +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./pallets/connectors/src/weights.rs +// --template=./scripts/frame-weight-template.hbs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_connectors. +pub trait WeightInfo { + fn add_pool() -> Weight; + fn add_tranche() -> Weight; + fn update_token_price() -> Weight; + fn update_member() -> Weight; + fn transfer() -> Weight; +} + +/// Weights for pallet_connectors using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn add_pool() -> Weight { + (32_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + fn add_tranche() -> Weight { + (32_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + fn update_token_price() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn update_member() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn transfer() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn add_pool() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn add_tranche() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn update_token_price() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn update_member() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + fn transfer() -> Weight { + (32_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } +} diff --git a/runtime/development/Cargo.toml b/runtime/development/Cargo.toml index 584305bf52..fa1f7bd6c2 100644 --- a/runtime/development/Cargo.toml +++ b/runtime/development/Cargo.toml @@ -129,6 +129,10 @@ pallet-bridge = { path = "../../pallets/bridge", default-features = false } pallet-nft = { path = "../../pallets/nft", default-features = false } pallet-interest-accrual = { path = "../../pallets/interest-accrual", default-features = false } pallet-keystore = { path = "../../pallets/keystore", default-features = false } +pallet-connectors = { path = "../../pallets/connectors", default-features = false } + +xcm-primitives = { git = "https://github.com/NunoAlexandre/moonbeam", default-features = false, rev = "d288eb94a136b92566d73e7d546e64f57145539e" } +pallet-xcm-transactor = { git = "https://github.com/NunoAlexandre/moonbeam", default-features = false, rev = "d288eb94a136b92566d73e7d546e64f57145539e" } [build-dependencies] substrate-wasm-builder = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.24" } @@ -224,6 +228,9 @@ std = [ "pallet-bridge/std", "pallet-keystore/std", "pallet-interest-accrual/std", + "pallet-connectors/std", + "pallet-xcm-transactor/std", + "xcm-primitives/std", ] runtime-benchmarks = [ diff --git a/runtime/development/src/lib.rs b/runtime/development/src/lib.rs index 79f4248449..4224fdaea0 100644 --- a/runtime/development/src/lib.rs +++ b/runtime/development/src/lib.rs @@ -4,6 +4,9 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +use ::xcm::prelude::X1; +use ::xcm::v1::prelude::Parachain; +use ::xcm::v2::MultiLocation; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::sp_std::marker::PhantomData; use frame_support::{ @@ -66,6 +69,7 @@ use pallet_restricted_tokens::{ pub use runtime_common::{Index, *}; use chainbridge::constants::DEFAULT_RELAYER_VOTE_THRESHOLD; +use xcm_primitives::{UtilityAvailableCalls, UtilityEncodeCall}; pub mod xcm; pub use crate::xcm::*; @@ -1066,6 +1070,44 @@ impl pallet_collator_selection::Config for Runtime { type WeightInfo = pallet_collator_selection::weights::SubstrateWeight; } +#[derive(Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)] +pub enum DestTransactors { + Moonbeam, +} + +impl UtilityEncodeCall for DestTransactors { + fn encode_call(self, call: UtilityAvailableCalls) -> Vec { + todo!("hello darkness / my old friend") + } +} + +impl xcm_primitives::XcmTransact for DestTransactors { + fn destination(self) -> MultiLocation { + match self { + DestTransactors::Moonbeam => MultiLocation::new(1, X1(Parachain(2023))), + } + } +} + +impl pallet_xcm_transactor::Config for Runtime { + type Event = Event; + type Balance = Balance; + type Transactor = DestTransactors; + type DerivativeAddressRegistrationOrigin = EnsureRoot; + type SovereignAccountDispatcherOrigin = EnsureRoot; + type CurrencyId = CurrencyId; + type AccountIdToMultiLocation = xcm::AccountIdToMultiLocation; + type CurrencyIdToMultiLocation = xcm::CurrencyIdConvert; + type SelfLocation = SelfLocation; + type Weigher = xcm_builder::FixedWeightBounds; + type LocationInverter = xcm_builder::LocationInverter; + type XcmSender = XcmRouter; + type BaseXcmWeight = BaseXcmWeight; + type AssetTransactor = xcm::FungiblesTransactor; + type ReserveProvider = xcm_primitives::AbsoluteAndRelativeReserve; + type WeightInfo = (); +} + parameter_types! { pub const LoansPalletId: PalletId = common_types::ids::LOANS_PALLET_ID; pub const MaxActiveLoansPerPool: u32 = 50; @@ -1261,6 +1303,19 @@ impl pallet_interest_accrual::Config for Runtime { type Weights = (); } +impl pallet_connectors::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Balance = Balance; + type Rate = Rate; + type CurrencyId = CurrencyId; + type AdminOrigin = EnsureRoot; + type Permission = Permissions; + type PoolInspect = Pools; + type Time = Timestamp; + type Tokens = Tokens; +} + parameter_types! { pub const BridgePalletId: PalletId = common_types::ids::BRIDGE_PALLET_ID; pub HashId: chainbridge::ResourceId = chainbridge::derive_resource_id(1, &sp_io::hashing::blake2_128(&common_types::ids::CHAIN_BRIDGE_HASH_ID)); @@ -1395,6 +1450,7 @@ construct_runtime!( Bridge: pallet_bridge::{Pallet, Call, Storage, Config, Event} = 101, InterestAccrual: pallet_interest_accrual::{Pallet, Storage, Event, Config} = 102, Keystore: pallet_keystore::{Pallet, Call, Storage, Event} = 104, + Connectors: pallet_connectors::{Pallet, Call, Storage, Event} = 105, // XCM XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 120, @@ -1402,6 +1458,7 @@ construct_runtime!( CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 122, DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event} = 123, XTokens: orml_xtokens::{Pallet, Storage, Call, Event} = 124, + XcmTransactor: pallet_xcm_transactor::{Pallet, Call, Storage, Event} = 125, // 3rd party pallets OrmlTokens: orml_tokens::{Pallet, Storage, Event, Config} = 150, diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index c97264d9b3..6d03158a5c 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -84,6 +84,7 @@ common-types = { path = "../../libs/common-types" } pallet-pools = { path = "../../pallets/pools" } pallet-loans = { path = "../../pallets/loans" } pallet-permissions = { path = "../../pallets/permissions" } +pallet-connectors = { path = "../../pallets/connectors" } [features] default = [ "runtime-development" ] diff --git a/runtime/integration-tests/src/xcm/development/mod.rs b/runtime/integration-tests/src/xcm/development/mod.rs new file mode 100644 index 0000000000..34f13591a7 --- /dev/null +++ b/runtime/integration-tests/src/xcm/development/mod.rs @@ -0,0 +1,3 @@ +mod setup; +mod test_net; +mod tests; diff --git a/runtime/integration-tests/src/xcm/development/setup.rs b/runtime/integration-tests/src/xcm/development/setup.rs new file mode 100644 index 0000000000..ae1b202801 --- /dev/null +++ b/runtime/integration-tests/src/xcm/development/setup.rs @@ -0,0 +1,121 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// +// This file is part of the Centrifuge chain project. +// Centrifuge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version (see http://www.gnu.org/licenses). +// Centrifuge is distributed in the hope that it will be useful, +// 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 altair_runtime::CustomMetadata; +pub use altair_runtime::{AccountId, CurrencyId, Origin, Runtime, System}; +use frame_support::traits::GenesisBuild; +use orml_traits::asset_registry::AssetMetadata; +use runtime_common::{decimals, parachains, Balance}; + +/// Accounts +pub const ALICE: [u8; 32] = [4u8; 32]; +pub const BOB: [u8; 32] = [5u8; 32]; + +/// A PARA ID used for a sibling parachain emulating Moonbeam. +/// It must be one that doesn't collide with any other in use. +pub const PARA_ID_MOONBEAM: u32 = 2023; + +pub struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, + parachain_id: u32, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balances: vec![], + parachain_id: parachains::polkadot::centrifuge::ID, + } + } +} + +impl ExtBuilder { + pub fn balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn parachain_id(mut self, parachain_id: u32) -> Self { + self.parachain_id = parachain_id; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + let native_currency_id = development_runtime::NativeToken::get(); + pallet_balances::GenesisConfig:: { + balances: self + .balances + .clone() + .into_iter() + .filter(|(_, currency_id, _)| *currency_id == native_currency_id) + .map(|(account_id, _, initial_balance)| (account_id, initial_balance)) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + orml_tokens::GenesisConfig:: { + balances: self + .balances + .into_iter() + .filter(|(_, currency_id, _)| *currency_id != native_currency_id) + .collect::>(), + } + .assimilate_storage(&mut t) + .unwrap(); + + >::assimilate_storage( + ¶chain_info::GenesisConfig { + parachain_id: self.parachain_id.into(), + }, + &mut t, + ) + .unwrap(); + + >::assimilate_storage( + &pallet_xcm::GenesisConfig { + safe_xcm_version: Some(2), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +pub fn cfg(amount: Balance) -> Balance { + amount * dollar(decimals::NATIVE) +} + +pub fn dollar(decimals: u32) -> Balance { + 10u128.saturating_pow(decimals.into()) +} + +pub fn moonbeam_account() -> AccountId { + parachain_account(PARA_ID_MOONBEAM.into()) +} + +pub fn centrifuge_account() -> AccountId { + parachain_account(parachains::polkadot::centrifuge::ID.into()) +} + +fn parachain_account(id: u32) -> AccountId { + use sp_runtime::traits::AccountIdConversion; + + polkadot_parachain::primitives::Sibling::from(id).into_account_truncating() +} diff --git a/runtime/integration-tests/src/xcm/development/test_net.rs b/runtime/integration-tests/src/xcm/development/test_net.rs new file mode 100644 index 0000000000..b433335f18 --- /dev/null +++ b/runtime/integration-tests/src/xcm/development/test_net.rs @@ -0,0 +1,159 @@ +// Copyright 2021 Centrifuge GmbH (centrifuge.io). +// This file is part of Centrifuge chain project. + +// Centrifuge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version (see http://www.gnu.org/licenses). + +// Centrifuge is distributed in the hope that it will be useful, +// 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. + +//! Relay chain and parachains emulation. + +use cumulus_primitives_core::ParaId; +use frame_support::traits::GenesisBuild; +use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; +use polkadot_runtime_parachains::configuration::HostConfiguration; +use sp_runtime::traits::AccountIdConversion; +use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +use development_runtime::CurrencyId; +use runtime_common::{parachains, AccountId}; + +use super::setup::{cfg, ExtBuilder, ALICE, BOB, PARA_ID_MOONBEAM}; + +decl_test_relay_chain! { + pub struct RelayChain { + Runtime = polkadot_runtime::Runtime, + XcmConfig = polkadot_runtime::xcm_config::XcmConfig, + new_ext = relay_ext(), + } +} + +decl_test_parachain! { + pub struct Development { + Runtime = development_runtime::Runtime, + Origin = development_runtime::Origin, + XcmpMessageHandler = development_runtime::XcmpQueue, + DmpMessageHandler = development_runtime::DmpQueue, + new_ext = para_ext(parachains::polkadot::centrifuge::ID), + } +} + +decl_test_parachain! { + pub struct Moonbeam { + Runtime = development_runtime::Runtime, + Origin = development_runtime::Origin, + XcmpMessageHandler = development_runtime::XcmpQueue, + DmpMessageHandler = development_runtime::DmpQueue, + new_ext = para_ext(PARA_ID_MOONBEAM), + } +} + +decl_test_network! { + pub struct TestNet { + relay_chain = RelayChain, + parachains = vec![ + // N.B: Ideally, we could use the defined para id constants but doing so + // fails with: "error: arbitrary expressions aren't allowed in patterns" + + // Be sure to use `parachains::polkadot::centrifuge::ID` + (2031, Development), + // Be sure to use `PARA_ID_MOONBEAM` + (2023, Moonbeam), + ], + } +} + +pub fn relay_ext() -> sp_io::TestExternalities { + use polkadot_runtime::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (AccountId::from(ALICE), cfg(2002)), + ( + ParaId::from(parachains::polkadot::centrifuge::ID).into_account_truncating(), + cfg(7), + ), + ( + ParaId::from(PARA_ID_MOONBEAM).into_account_truncating(), + cfg(7), + ), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + polkadot_runtime_parachains::configuration::GenesisConfig:: { + config: default_parachains_host_configuration(), + } + .assimilate_storage(&mut t) + .unwrap(); + + >::assimilate_storage( + &pallet_xcm::GenesisConfig { + safe_xcm_version: Some(2), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { + ExtBuilder::default() + .balances(vec![ + (AccountId::from(ALICE), CurrencyId::Native, cfg(10)), + (AccountId::from(BOB), CurrencyId::Native, cfg(10)), + ]) + .parachain_id(parachain_id) + .build() +} + +fn default_parachains_host_configuration() -> HostConfiguration { + HostConfiguration { + minimum_validation_upgrade_delay: 5, + validation_upgrade_cooldown: 5u32, + validation_upgrade_delay: 5, + code_retention_period: 1200, + max_code_size: MAX_CODE_SIZE, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 32 * 1024, + group_rotation_frequency: 20, + chain_availability_period: 4, + thread_availability_period: 4, + max_upward_queue_count: 8, + max_upward_queue_size: 1024 * 1024, + max_downward_message_size: 1024, + ump_service_total_weight: 4 * 1_000_000_000, + max_upward_message_size: 50 * 1024, + max_upward_message_num_per_candidate: 5, + hrmp_sender_deposit: 0, + hrmp_recipient_deposit: 0, + hrmp_channel_max_capacity: 8, + hrmp_channel_max_total_size: 8 * 1024, + hrmp_max_parachain_inbound_channels: 4, + hrmp_max_parathread_inbound_channels: 4, + hrmp_channel_max_message_size: 1024 * 1024, + hrmp_max_parachain_outbound_channels: 4, + hrmp_max_parathread_outbound_channels: 4, + hrmp_max_message_num_per_candidate: 5, + dispute_period: 6, + no_show_slots: 2, + n_delay_tranches: 25, + needed_approvals: 2, + relay_vrf_modulo_samples: 2, + zeroth_delay_tranche_width: 0, + ..Default::default() + } +} diff --git a/runtime/integration-tests/src/xcm/development/tests/connectors.rs b/runtime/integration-tests/src/xcm/development/tests/connectors.rs new file mode 100644 index 0000000000..f396439a59 --- /dev/null +++ b/runtime/integration-tests/src/xcm/development/tests/connectors.rs @@ -0,0 +1,112 @@ +// Copyright 2021 Centrifuge GmbH (centrifuge.io). +// This file is part of Centrifuge chain project. +// +// Centrifuge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version (see http://www.gnu.org/licenses). +// Centrifuge is distributed in the hope that it will be useful, +// 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. + +// Copyright 2021 Centrifuge GmbH (centrifuge.io). +// This file is part of Centrifuge chain project. +// +// Centrifuge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version (see http://www.gnu.org/licenses). +// Centrifuge is distributed in the hope that it will be useful, +// 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::xcm::development::setup::{ + centrifuge_account, cfg, moonbeam_account, ALICE, BOB, PARA_ID_MOONBEAM, +}; +use crate::xcm::development::test_net::{Development, Moonbeam, RelayChain, TestNet}; +use development_runtime::{ + Balances, Connectors, CurrencyId, CustomMetadata, Origin, OrmlAssetRegistry, OrmlTokens, + XTokens, XcmTransactor, +}; +use pallet_connectors::{Domain, Message, Router}; + +use frame_support::assert_noop; +use frame_support::assert_ok; +use frame_support::dispatch::Weight; +use orml_traits::{asset_registry::AssetMetadata, FixedConversionRateProvider, MultiCurrency}; +use runtime_common::xcm_fees::default_per_second; +use runtime_common::{decimals, parachains, Balance, XcmMetadata}; +use sp_runtime::traits::BadOrigin; +use xcm::latest::{Junction, Junction::*, Junctions::*, MultiLocation, NetworkId}; +use xcm::prelude::{Parachain, X1, X2}; +use xcm::VersionedMultiLocation; +use xcm_emulator::TestExt; + +#[test] +fn add_pool_works() { + TestNet::reset(); + + // Verify that we can successfully call Connectors::add_pool on "Centrifuge" and + // have the targeted pool added on Moonbeam. + // For this to work, we would need to deploy the Connectors Solidy contract on + // Moonbeam and verify that it works but that's probably not feasible here. + // We can start by first checking that we are able to send a transact message + // to Moonbeam through XcmTransactor. + let moonbeam_location = MultiLocation { + parents: 1, + interior: X1(Parachain(PARA_ID_MOONBEAM)), + }; + let moonbeam_native_token = MultiLocation { + parents: 1, + interior: X2(Parachain(PARA_ID_MOONBEAM), GeneralKey(vec![0, 1])), + }; + + Development::execute_with(|| { + // We need to set the Transact info for Moonbeam in the XcmTransact pallet + assert_ok!(XcmTransactor::set_transact_info( + Origin::root(), + Box::new(VersionedMultiLocation::V1(moonbeam_location.clone())), + 1, + 8_000_000_000_000_000, + Some(3) + )); + assert_ok!(XcmTransactor::set_fee_per_second( + Origin::root(), + Box::new(VersionedMultiLocation::V1(moonbeam_native_token.clone())), + default_per_second(18), // default fee_per_second for this token which has 18 decimals + )); + //TODO(nuno): Failing with `UnableToWithdrawAsset` because we don't handle + // "Moonbeam's native token" locally. + // Register `moonbeam_native_token` in the asset-registry so that + // it can be withdrawn when sending the transact message. That expects us + // to set that balance for when setting up the emulated environment. + + Connectors::do_set_router( + Domain::Moonbeam, + Router::Xcm { + location: moonbeam_location + .clone() + .try_into() + .expect("Bad xcm version"), + }, + ); + + assert_ok!(Connectors::send_through_xcm_tests( + &Message::AddPool { pool_id: 42 }, + VersionedMultiLocation::V1(moonbeam_location), + VersionedMultiLocation::V1(moonbeam_native_token), + ALICE.into(), + )); + }); + + // message: &MessageOf, + // dest_location: VersionedMultiLocation, + // fees_location: VersionedMultiLocation, + // fee_payer: T::AccountId, + + Moonbeam::execute_with(|| { + assert_eq!(Balances::free_balance(&ALICE.into()), cfg(10)); + }); +} diff --git a/runtime/integration-tests/src/xcm/development/tests/mod.rs b/runtime/integration-tests/src/xcm/development/tests/mod.rs new file mode 100644 index 0000000000..ac836b2611 --- /dev/null +++ b/runtime/integration-tests/src/xcm/development/tests/mod.rs @@ -0,0 +1 @@ +mod connectors; diff --git a/runtime/integration-tests/src/xcm/mod.rs b/runtime/integration-tests/src/xcm/mod.rs index b283ad27df..112833ead3 100644 --- a/runtime/integration-tests/src/xcm/mod.rs +++ b/runtime/integration-tests/src/xcm/mod.rs @@ -10,5 +10,6 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. +mod development; mod kusama; mod polkadot;