diff --git a/Cargo.lock b/Cargo.lock index 5a0afd535..762f77161 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,12 +437,17 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", + "kusama-runtime", "log", "nimbus-primitives", + "orml-asset-registry", "orml-benchmarking", "orml-currencies", "orml-tokens", "orml-traits", + "orml-unknown-tokens", + "orml-xcm-support", + "orml-xtokens", "pallet-aura", "pallet-author-inherent", "pallet-author-mapping", @@ -472,6 +477,8 @@ dependencies = [ "parachain-info", "parity-scale-codec", "polkadot-parachain", + "polkadot-primitives", + "polkadot-runtime-parachains", "scale-info", "session-keys-primitives", "sp-api", @@ -490,6 +497,7 @@ dependencies = [ "substrate-wasm-builder", "xcm", "xcm-builder", + "xcm-emulator", "xcm-executor", "zeitgeist-primitives", "zrml-authorized", @@ -5120,6 +5128,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "orml-asset-registry" +version = "0.4.1-dev" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library?branch=polkadot-v0.9.26#33dbc5e35305d0cf5937c896dae8655ca7da95d8" +dependencies = [ + "frame-support", + "frame-system", + "orml-traits", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "serde", + "sp-runtime", + "sp-std", + "xcm", + "xcm-builder", + "xcm-executor", +] + [[package]] name = "orml-benchmarking" version = "0.4.1-dev" @@ -5190,6 +5217,21 @@ dependencies = [ "xcm", ] +[[package]] +name = "orml-unknown-tokens" +version = "0.4.1-dev" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library?branch=polkadot-v0.9.26#33dbc5e35305d0cf5937c896dae8655ca7da95d8" +dependencies = [ + "frame-support", + "frame-system", + "orml-xcm-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "xcm", +] + [[package]] name = "orml-utilities" version = "0.4.1-dev" @@ -5204,6 +5246,41 @@ dependencies = [ "sp-std", ] +[[package]] +name = "orml-xcm-support" +version = "0.4.1-dev" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library?branch=polkadot-v0.9.26#33dbc5e35305d0cf5937c896dae8655ca7da95d8" +dependencies = [ + "frame-support", + "orml-traits", + "parity-scale-codec", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + +[[package]] +name = "orml-xtokens" +version = "0.4.1-dev" +source = "git+https://github.com/open-web3-stack/open-runtime-module-library?branch=polkadot-v0.9.26#33dbc5e35305d0cf5937c896dae8655ca7da95d8" +dependencies = [ + "cumulus-primitives-core", + "frame-support", + "frame-system", + "orml-traits", + "orml-xcm-support", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "serde", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -11296,9 +11373,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "pin-project-lite 0.2.9", @@ -11319,11 +11396,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -12414,6 +12491,31 @@ dependencies = [ "xcm-executor", ] +[[package]] +name = "xcm-emulator" +version = "0.1.0" +source = "git+https://github.com/shaunxw/xcm-simulator?rev=ab5cd6c5fabe6ddda52ed6803ee1bf54c258fefe#ab5cd6c5fabe6ddda52ed6803ee1bf54c258fefe" +dependencies = [ + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "cumulus-primitives-parachain-inherent", + "cumulus-test-relay-sproof-builder", + "frame-support", + "frame-system", + "parachain-info", + "parity-scale-codec", + "paste", + "polkadot-primitives", + "polkadot-runtime-parachains", + "quote", + "sp-io", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "xcm-executor" version = "0.9.26" @@ -12581,12 +12683,17 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", + "kusama-runtime", "log", "nimbus-primitives", + "orml-asset-registry", "orml-benchmarking", "orml-currencies", "orml-tokens", "orml-traits", + "orml-unknown-tokens", + "orml-xcm-support", + "orml-xtokens", "pallet-aura", "pallet-author-inherent", "pallet-author-mapping", @@ -12615,6 +12722,8 @@ dependencies = [ "parachain-info", "parity-scale-codec", "polkadot-parachain", + "polkadot-primitives", + "polkadot-runtime-parachains", "scale-info", "session-keys-primitives", "sp-api", @@ -12633,6 +12742,7 @@ dependencies = [ "substrate-wasm-builder", "xcm", "xcm-builder", + "xcm-emulator", "xcm-executor", "zeitgeist-primitives", "zrml-authorized", diff --git a/docs/changelog_for_devs.md b/docs/changelog_for_devs.md index ff9163c5f..93f473bfd 100644 --- a/docs/changelog_for_devs.md +++ b/docs/changelog_for_devs.md @@ -9,6 +9,7 @@ prize pool (mint-sell/buy-burn resp.) and the amount of full sets minted/burned. Note that in addition to these events, the low-level `tokens.Deposited` and `tokens.Transfer` events are also emitted. + - Added new pallet: GlobalDisputes. Dispatchable calls are: - `add_vote_outcome` - Add voting outcome to a global dispute in exchange for a constant fee. Errors if the voting outcome already exists or if the global @@ -29,28 +30,40 @@ - `OutcomesPartiallyCleaned` (outcomes storage item partially cleaned) - `OutcomesFullyCleaned` (outcomes storage item fully cleaned) - `VotedOnOutcome` (user voted on outcome) + - Authorized pallet now has `AuthorizedDisputeResolutionOrigin` hence `MarketDisputeMechanism::Authorized` does not need account_id. To create market with Authorized MDM specifying account_id for Authorized MDM is not required, any user satisfying `AuthorizedDisputeResolutionOrigin` can use Authorized MDM for resolving market. + +- Properly configured reserve asset transfers via XCM: + - Added xTokens pallet to transfer tokens accross chains + - Added AssetRegistry pallet to register foreign asset + - Added UnknownTokens pallet to handle unknown foreign assets + - More information at https://github.com/zeitgeistpm/zeitgeist/pull/661#top + - Transformed integer scalar markets to fixed point with ten digits after the decimal point. As soon as this update is deployed, the interpretation of the scalar values must be changed. + - `reject_market` extrinsic now requires `reject_reason` parameter which is `Vec`. The config constant `MaxRejectReasonLen` defines maximum length of above parameter. `MarketRejected` event also contains `reject_reason` so that it can be cached for market creator. + - `request_edit` extrinsic added, which enables a user satisfying `RequestEditOrigin` to request edit in market with `Proposed` state, when successful it emits `MarketRequestedEdit` event. `request_edit` requires `edit_reason` parameter which is `Vec`. The config constant `MaxEditReasonLen` defines maximum length of above parameter. The `MarketRequestedEdit` event also contains `edit_reason`. + - `edit_market` extrinsic added, which enables creator of the market to edit market. It has same parameters as `create_market` except market_creation, on success it returns `MarketEdited` event. -- get_spot_price() RPC from Swaps support `with_fees` boolean parameter to + +- `get_spot_price()` RPC from Swaps support `with_fees` boolean parameter to include/exclude swap_fees in spot price, Currently this flag only works for CPMM. diff --git a/misc/types.json b/misc/types.json index 171eef842..1484a1354 100644 --- a/misc/types.json +++ b/misc/types.json @@ -19,7 +19,7 @@ "ScalarOutcome": "(MarketId, ScalarPosition)", "CombinatorialOutcome": null, "PoolShare": "u128", - "Ztg": null + "ZTG": null } }, "AuthorId": "AccountId", diff --git a/node/Cargo.toml b/node/Cargo.toml index 39bd7866d..84e16cfc7 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -99,7 +99,7 @@ hex-literal = { version = "0.3.4" } jsonrpsee = { version = "0.14.0", features = ["server"] } log = { optional = true, version = "0.4.17" } # TODO(#865): Remove in future Polkadot release -tracing-core = "=0.1.26" +tracing-core = "=0.1.30" # Zeitgeist diff --git a/node/src/chain_spec/battery_station.rs b/node/src/chain_spec/battery_station.rs index 3937b7118..7c8835db4 100644 --- a/node/src/chain_spec/battery_station.rs +++ b/node/src/chain_spec/battery_station.rs @@ -115,7 +115,9 @@ pub(super) fn get_wasm() -> Result<&'static [u8], String> { generate_generic_genesis_function!( battery_station_runtime, - sudo: battery_station_runtime::SudoConfig { key: Some(root_key_staging_battery_station()) }, + sudo: battery_station_runtime::SudoConfig { + key: Some(root_key_staging_battery_station()), + }, ); pub fn battery_station_staging_config() -> Result { diff --git a/node/src/chain_spec/dev.rs b/node/src/chain_spec/dev.rs index a4fe214d1..319b25403 100644 --- a/node/src/chain_spec/dev.rs +++ b/node/src/chain_spec/dev.rs @@ -44,8 +44,11 @@ fn authority_keys_from_seed( ) } -generate_generic_genesis_function! {battery_station_runtime, - sudo: battery_station_runtime::SudoConfig { key: Some(get_account_id_from_seed::("Alice")) }, +generate_generic_genesis_function! { + battery_station_runtime, + sudo: battery_station_runtime::SudoConfig { + key: Some(get_account_id_from_seed::("Alice")), + }, } // Dev currently uses battery station runtime for the following reasons: diff --git a/node/src/chain_spec/mod.rs b/node/src/chain_spec/mod.rs index ed0a69156..fa2ebaa97 100644 --- a/node/src/chain_spec/mod.rs +++ b/node/src/chain_spec/mod.rs @@ -108,6 +108,8 @@ macro_rules! generate_generic_genesis_function { members: vec![].try_into().unwrap(), phantom: Default::default(), }, + #[cfg(feature = "parachain")] + asset_registry: Default::default(), #[cfg(not(feature = "parachain"))] aura: $runtime::AuraConfig { authorities: acs.initial_authorities.iter().map(|x| (x.0.clone())).collect(), diff --git a/primitives/src/asset.rs b/primitives/src/asset.rs index 551bbfb8a..92a99dc23 100644 --- a/primitives/src/asset.rs +++ b/primitives/src/asset.rs @@ -28,14 +28,27 @@ use scale_info::TypeInfo; #[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[derive( - Clone, Copy, Debug, Decode, Eq, Encode, MaxEncodedLen, Ord, PartialEq, PartialOrd, TypeInfo, + Clone, + Copy, + Debug, + Decode, + Default, + Eq, + Encode, + MaxEncodedLen, + Ord, + PartialEq, + PartialOrd, + TypeInfo, )] pub enum Asset { CategoricalOutcome(MI, CategoryIndex), ScalarOutcome(MI, ScalarPosition), CombinatorialOutcome, PoolShare(SerdeWrapper), + #[default] Ztg, + ForeignAsset(u32), } /// In a scalar market, users can either choose a `Long` position, diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index 2711f57e4..5675bbc16 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -104,3 +104,7 @@ pub const SD_PALLET_ID: PalletId = PalletId(*b"zge/sedp"); pub const MAX_ASSETS: u16 = MAX_CATEGORIES + 1; /// Pallet identifier, mainly used for named balance reserves. pub const SWAPS_PALLET_ID: PalletId = PalletId(*b"zge/swap"); + +// Treasury +/// Pallet identifier, used to derive treasury account +pub const TREASURY_PALLET_ID: PalletId = PalletId(*b"zge/tsry"); diff --git a/primitives/src/types.rs b/primitives/src/types.rs index c06478a7e..2d0a331d2 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -21,7 +21,8 @@ pub use crate::{ }; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Result, Unstructured}; -use frame_support::dispatch::{Decode, Encode, Weight}; +use frame_support::dispatch::Weight; +use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{ generic, diff --git a/runtime/battery-station/Cargo.toml b/runtime/battery-station/Cargo.toml index d56d92511..c0b8d2407 100644 --- a/runtime/battery-station/Cargo.toml +++ b/runtime/battery-station/Cargo.toml @@ -70,15 +70,8 @@ pallet-author-mapping = { tag = "v0.26.1", default-features = false, git = "http pallet-author-slot-filter = { branch = "moonbeam-polkadot-v0.9.26", default-features = false, git = "https://github.com/purestake/nimbus", optional = true } pallet-crowdloan-rewards = { branch = "moonbeam-polkadot-v0.9.26", default-features = false, git = "https://github.com/purestake/crowdloan-rewards", optional = true } pallet-parachain-staking = { tag = "v0.26.1", default-features = false, git = "https://github.com/purestake/moonbeam", optional = true } -session-keys-primitives = { tag = "v0.26.1", default-features = false, git = "https://github.com/purestake/moonbeam", optional = true } - -# Polkadot - -pallet-xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } polkadot-parachain = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } -xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } -xcm-builder = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } -xcm-executor = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +session-keys-primitives = { tag = "v0.26.1", default-features = false, git = "https://github.com/purestake/moonbeam", optional = true } # Standalone @@ -92,6 +85,19 @@ cfg-if = { version = "1.0.0" } hex-literal = { default-features = false, optional = true, version = "0.3.4" } log = { version = "0.4.17", default-features = false, optional = true } +# XCM +kusama-runtime = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +orml-asset-registry = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +orml-unknown-tokens = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +orml-xcm-support = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +orml-xtokens = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +pallet-xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +polkadot-primitives = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +polkadot-runtime-parachains = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +xcm-builder = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +xcm-executor = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } + # Zeitgeist common-runtime = { default-features = false, path = "../common" } @@ -110,6 +116,7 @@ zrml-swaps-runtime-api = { default-features = false, path = "../../zrml/swaps/ru [dev-dependencies] sp-io = { branch = "polkadot-v0.9.26", git = "https://github.com/paritytech/substrate" } +xcm-emulator = { rev = "ab5cd6c5fabe6ddda52ed6803ee1bf54c258fefe", git = "https://github.com/shaunxw/xcm-simulator" } [features] default = ["std"] @@ -133,12 +140,19 @@ parachain = [ "pallet-author-slot-filter", "pallet-crowdloan-rewards", "pallet-parachain-staking", + "polkadot-parachain", "session-keys-primitives", - # Polkadot + # XCM + "kusama-runtime", + "polkadot-primitives", + "polkadot-runtime-parachains", + "orml-asset-registry", + "orml-unknown-tokens", + "orml-xcm-support", + "orml-xtokens", "pallet-xcm", - "polkadot-parachain", "xcm-builder", "xcm-executor", "xcm", @@ -156,8 +170,11 @@ runtime-benchmarks = [ "frame-system-benchmarking", "frame-system/runtime-benchmarks", "hex-literal", + "kusama-runtime?/runtime-benchmarks", + "orml-asset-registry?/runtime-benchmarks", "orml-tokens/runtime-benchmarks", "orml-benchmarking", + "orml-xtokens?/runtime-benchmarks", "pallet-author-inherent?/runtime-benchmarks", "pallet-author-mapping?/runtime-benchmarks", "pallet-author-slot-filter?/runtime-benchmarks", @@ -267,9 +284,6 @@ std = [ "pallet-xcm?/std", "polkadot-parachain?/std", - "xcm-builder?/std", - "xcm-executor?/std", - "xcm?/std", # Standalone @@ -278,6 +292,20 @@ std = [ "sp-consensus-aura/std", "sp-finality-grandpa/std", + # XCM + + "kusama-runtime?/std", + "polkadot-primitives?/std", + "polkadot-runtime-parachains?/std", + "orml-asset-registry?/std", + "orml-unknown-tokens?/std", + "orml-xcm-support?/std", + "orml-xtokens?/std", + "pallet-xcm?/std", + "xcm-builder?/std", + "xcm-executor?/std", + "xcm?/std", + # Zeitgeist "zeitgeist-primitives/std", @@ -324,8 +352,11 @@ try-runtime = [ "pallet-utility/try-runtime", # ORML runtime pallets + "orml-asset-registry?/try-runtime", "orml-currencies/try-runtime", "orml-tokens/try-runtime", + "orml-unknown-tokens?/try-runtime", + "orml-xtokens?/try-runtime", # Zeitgeist runtime pallets "zrml-authorized/try-runtime", diff --git a/runtime/battery-station/src/integration_tests/mod.rs b/runtime/battery-station/src/integration_tests/mod.rs new file mode 100644 index 000000000..64f89683b --- /dev/null +++ b/runtime/battery-station/src/integration_tests/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2022 Forecasting Technologies LTD. +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(test)] + +mod xcm; diff --git a/runtime/battery-station/src/integration_tests/xcm/mod.rs b/runtime/battery-station/src/integration_tests/xcm/mod.rs new file mode 100644 index 000000000..d2226363f --- /dev/null +++ b/runtime/battery-station/src/integration_tests/xcm/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2022 Forecasting Technologies LTD. +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "parachain", test))] + +mod setup; +mod test_net; +mod tests; diff --git a/runtime/battery-station/src/integration_tests/xcm/setup.rs b/runtime/battery-station/src/integration_tests/xcm/setup.rs new file mode 100644 index 000000000..510c5b58d --- /dev/null +++ b/runtime/battery-station/src/integration_tests/xcm/setup.rs @@ -0,0 +1,205 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + xcm_config::{ + asset_registry::CustomMetadata, + config::{battery_station, general_key}, + }, + AccountId, AssetRegistry, Balance, CurrencyId, ExistentialDeposit, Origin, Runtime, System, +}; +use frame_support::{assert_ok, traits::GenesisBuild}; +use orml_traits::asset_registry::AssetMetadata; +use xcm::{ + latest::{Junction::Parachain, Junctions::X2, MultiLocation}, + VersionedMultiLocation, +}; +use zeitgeist_primitives::types::Asset; + +pub(super) struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, + parachain_id: u32, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![], parachain_id: battery_station::ID } + } +} + +impl ExtBuilder { + pub fn set_balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn set_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 = CurrencyId::Ztg; + 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 + } +} + +/// Accounts +pub const ALICE: [u8; 32] = [4u8; 32]; +pub const BOB: [u8; 32] = [5u8; 32]; + +/// A PARA ID used for a sibling parachain. +/// It must be one that doesn't collide with any other in use. +pub const PARA_ID_SIBLING: u32 = 3000; + +/// IDs that are used to represent tokens from other chains +pub const FOREIGN_ZTG_ID: Asset = CurrencyId::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: Asset = CurrencyId::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: Asset = CurrencyId::ForeignAsset(2); + +// Multilocations that are used to represent tokens from other chains +#[inline] +pub(super) fn foreign_ztg_multilocation() -> MultiLocation { + MultiLocation::new(1, X2(Parachain(battery_station::ID), general_key(battery_station::KEY))) +} + +#[inline] +pub(super) fn foreign_sibling_multilocation() -> MultiLocation { + MultiLocation::new(1, X2(Parachain(PARA_ID_SIBLING), general_key(battery_station::KEY))) +} + +#[inline] +pub(super) fn foreign_parent_multilocation() -> MultiLocation { + MultiLocation::parent() +} + +pub(super) fn register_foreign_ztg(additional_meta: Option) { + // Register ZTG as foreign asset. + let meta: AssetMetadata = AssetMetadata { + decimals: 10, + name: "Zeitgeist".into(), + symbol: "ZTG".into(), + existential_deposit: ExistentialDeposit::get(), + location: Some(VersionedMultiLocation::V1(foreign_ztg_multilocation())), + additional: additional_meta.unwrap_or_default(), + }; + + assert_ok!(AssetRegistry::register_asset(Origin::root(), meta, Some(FOREIGN_ZTG_ID))); +} + +pub(super) fn register_foreign_sibling(additional_meta: Option) { + // Register native Sibling token as foreign asset. + let meta: AssetMetadata = AssetMetadata { + decimals: 10, + name: "Sibling".into(), + symbol: "SBL".into(), + existential_deposit: ExistentialDeposit::get(), + location: Some(VersionedMultiLocation::V1(foreign_sibling_multilocation())), + additional: additional_meta.unwrap_or_default(), + }; + + assert_ok!(AssetRegistry::register_asset(Origin::root(), meta, Some(FOREIGN_SIBLING_ID))); +} + +pub(super) fn register_foreign_parent(additional_meta: Option) { + // Register KSM as foreign asset in the sibling parachain + let meta: AssetMetadata = AssetMetadata { + decimals: 12, + name: "Kusama".into(), + symbol: "KSM".into(), + existential_deposit: 10_000_000_000, // 0.01 + location: Some(VersionedMultiLocation::V1(foreign_parent_multilocation())), + additional: additional_meta.unwrap_or_default(), + }; + + assert_ok!(AssetRegistry::register_asset(Origin::root(), meta, Some(FOREIGN_PARENT_ID))); +} + +#[inline] +pub(super) fn ztg(amount: Balance) -> Balance { + amount * dollar(10) +} + +#[inline] +pub(super) fn ksm(amount: Balance) -> Balance { + foreign(amount, 12) +} + +#[inline] +pub(super) fn foreign(amount: Balance, decimals: u32) -> Balance { + amount * dollar(decimals) +} + +#[inline] +pub(super) fn dollar(decimals: u32) -> Balance { + 10u128.saturating_pow(decimals) +} + +#[inline] +pub(super) fn sibling_parachain_account() -> AccountId { + parachain_account(PARA_ID_SIBLING) +} + +#[inline] +pub(super) fn zeitgeist_parachain_account() -> AccountId { + parachain_account(battery_station::ID) +} + +#[inline] +fn parachain_account(id: u32) -> AccountId { + use sp_runtime::traits::AccountIdConversion; + + polkadot_parachain::primitives::Sibling::from(id).into_account_truncating() +} diff --git a/runtime/battery-station/src/integration_tests/xcm/test_net.rs b/runtime/battery-station/src/integration_tests/xcm/test_net.rs new file mode 100644 index 000000000..8d6d8351d --- /dev/null +++ b/runtime/battery-station/src/integration_tests/xcm/test_net.rs @@ -0,0 +1,147 @@ +// Copyright 2021-2022 Centrifuge GmbH (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + parameters::ZeitgeistTreasuryAccount, xcm_config::config::battery_station, AccountId, + CurrencyId, DmpQueue, Origin, Runtime, XcmpQueue, +}; +use frame_support::{traits::GenesisBuild, weights::Weight}; +use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; +use polkadot_runtime_parachains::configuration::HostConfiguration; +use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +use super::setup::{ksm, ztg, ExtBuilder, ALICE, FOREIGN_PARENT_ID, PARA_ID_SIBLING}; + +decl_test_relay_chain! { + pub struct KusamaNet { + Runtime = kusama_runtime::Runtime, + XcmConfig = kusama_runtime::xcm_config::XcmConfig, + new_ext = relay_ext(), + } +} + +decl_test_parachain! { + pub struct Zeitgeist { + Runtime = Runtime, + Origin = Origin, + XcmpMessageHandler = XcmpQueue, + DmpMessageHandler = DmpQueue, + new_ext = para_ext(battery_station::ID), + } +} + +decl_test_parachain! { + pub struct Sibling { + Runtime = Runtime, + Origin = Origin, + XcmpMessageHandler = XcmpQueue, + DmpMessageHandler = DmpQueue, + new_ext = para_ext(PARA_ID_SIBLING), + } +} + +decl_test_network! { + pub struct TestNet { + relay_chain = KusamaNet, + 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 `xcm_config::config::battery_station::ID` + (2050, Zeitgeist), + // Be sure to use `PARA_ID_SIBLING` + (3000, Sibling), + ], + } +} + +pub(super) fn relay_ext() -> sp_io::TestExternalities { + use kusama_runtime::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(AccountId::from(ALICE), ksm(2002))], + } + .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(super) fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { + ExtBuilder::default() + .set_balances(vec![ + (AccountId::from(ALICE), CurrencyId::Ztg, ztg(10)), + (AccountId::from(ALICE), FOREIGN_PARENT_ID, ksm(10)), + (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, ksm(1)), + ]) + .set_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: Weight::from(4 * 1_000_000_000u32), + 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/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs new file mode 100644 index 000000000..a2d744b5a --- /dev/null +++ b/runtime/battery-station/src/integration_tests/xcm/tests/currency_id_convert.rs @@ -0,0 +1,122 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + integration_tests::xcm::{ + setup::{ + foreign_parent_multilocation, foreign_sibling_multilocation, foreign_ztg_multilocation, + register_foreign_parent, register_foreign_sibling, FOREIGN_PARENT_ID, + FOREIGN_SIBLING_ID, + }, + test_net::Zeitgeist, + }, + xcm_config::config::{battery_station, general_key, AssetConvert}, + CurrencyId, +}; + +use frame_support::assert_err; +use sp_runtime::traits::Convert as C2; +use xcm::latest::{Junction::*, Junctions::*, MultiLocation}; +use xcm_emulator::TestExt; +use xcm_executor::traits::Convert as C1; + +#[test] +fn convert_native() { + assert_eq!(battery_station::KEY.to_vec(), vec![0, 1]); + + // The way Ztg is represented relative within the Zeitgeist runtime + let ztg_location_inner: MultiLocation = + MultiLocation::new(0, X1(general_key(battery_station::KEY))); + + assert_eq!(>::convert(ztg_location_inner), Ok(CurrencyId::Ztg)); + + // The canonical way Ztg is represented out in the wild + Zeitgeist::execute_with(|| { + assert_eq!( + >::convert(CurrencyId::Ztg), + Some(foreign_ztg_multilocation()) + ) + }); +} + +#[test] +fn convert_any_registered_parent_multilocation() { + Zeitgeist::execute_with(|| { + assert_err!( + >::convert(foreign_parent_multilocation()), + foreign_parent_multilocation() + ); + + assert_eq!(>::convert(FOREIGN_PARENT_ID), None); + + // Register parent as foreign asset in the Zeitgeist parachain + register_foreign_parent(None); + + assert_eq!( + >::convert(foreign_parent_multilocation()), + Ok(FOREIGN_PARENT_ID), + ); + + assert_eq!( + >::convert(FOREIGN_PARENT_ID), + Some(foreign_parent_multilocation()) + ); + }); +} + +#[test] +fn convert_any_registered_sibling_multilocation() { + Zeitgeist::execute_with(|| { + assert_err!( + >::convert(foreign_sibling_multilocation()), + foreign_sibling_multilocation() + ); + + assert_eq!(>::convert(FOREIGN_SIBLING_ID), None); + + // Register sibling as foreign asset in the Zeitgeist parachain + register_foreign_sibling(None); + + assert_eq!( + >::convert(foreign_sibling_multilocation()), + Ok(FOREIGN_SIBLING_ID), + ); + + assert_eq!( + >::convert(FOREIGN_SIBLING_ID), + Some(foreign_sibling_multilocation()) + ); + }); +} + +#[test] +fn convert_unkown_multilocation() { + let unknown_location: MultiLocation = + MultiLocation::new(1, X2(Parachain(battery_station::ID), general_key(&[42]))); + + Zeitgeist::execute_with(|| { + assert!(>::convert(unknown_location.clone()).is_err()); + }); +} + +#[test] +fn convert_unsupported_currency() { + Zeitgeist::execute_with(|| { + assert_eq!(>::convert(CurrencyId::CombinatorialOutcome), None) + }); +} diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/mod.rs b/runtime/battery-station/src/integration_tests/xcm/tests/mod.rs new file mode 100644 index 000000000..8194bed88 --- /dev/null +++ b/runtime/battery-station/src/integration_tests/xcm/tests/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +mod currency_id_convert; +mod transfers; diff --git a/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs new file mode 100644 index 000000000..ffa956c08 --- /dev/null +++ b/runtime/battery-station/src/integration_tests/xcm/tests/transfers.rs @@ -0,0 +1,347 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + integration_tests::xcm::{ + setup::{ + ksm, register_foreign_parent, register_foreign_ztg, sibling_parachain_account, + zeitgeist_parachain_account, ztg, ALICE, BOB, FOREIGN_PARENT_ID, FOREIGN_ZTG_ID, + PARA_ID_SIBLING, + }, + test_net::{KusamaNet, Sibling, TestNet, Zeitgeist}, + }, + xcm_config::{ + asset_registry::{CustomMetadata, XcmMetadata}, + config::battery_station, + fees::default_per_second, + }, + AssetRegistry, Balance, Balances, CurrencyId, Origin, Tokens, XTokens, + ZeitgeistTreasuryAccount, +}; + +use frame_support::assert_ok; +use orml_traits::MultiCurrency; +use xcm::latest::{Junction, Junction::*, Junctions::*, MultiLocation, NetworkId}; +use xcm_emulator::TestExt; +use zeitgeist_primitives::constants::BalanceFractionalDecimals; + +#[test] +fn transfer_ztg_to_sibling() { + TestNet::reset(); + + let alice_initial_balance = ztg(10); + let transfer_amount = ztg(5); + let mut treasury_initial_balance = 0; + + Sibling::execute_with(|| { + treasury_initial_balance = + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); + assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), 0); + register_foreign_ztg(None); + }); + + Zeitgeist::execute_with(|| { + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance); + assert_eq!(Balances::free_balance(&sibling_parachain_account()), 0); + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + CurrencyId::Ztg, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(PARA_ID_SIBLING), + Junction::AccountId32 { network: NetworkId::Any, id: BOB } + ) + ) + .into() + ), + 4_000_000_000, + )); + + // Confirm that Alice's balance is initial_balance - amount_transferred + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance - transfer_amount); + + // Verify that the amount transferred is now part of the sibling account here + assert_eq!(Balances::free_balance(&sibling_parachain_account()), transfer_amount); + }); + + Sibling::execute_with(|| { + let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()); + + // Verify that BOB now has (amount transferred - fee) + assert_eq!(current_balance, transfer_amount - ztg_fee()); + + // Sanity check for the actual amount BOB ends up with + assert_eq!(current_balance, 49_907_304_000); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + treasury_initial_balance + ztg_fee() + ) + }); +} + +#[test] +fn transfer_ztg_sibling_to_zeitgeist() { + TestNet::reset(); + + // In order to be able to transfer ZTG from Sibling to Zeitgeist, we need to first send + // ZTG from Zeitgeist to Sibling, or else it fails since it'd be like Sibling had minted + // ZTG on their side. + transfer_ztg_to_sibling(); + + let alice_initial_balance = ztg(5); + let bob_initial_balance = ztg(5) - ztg_fee(); + let mut treasury_initial_balance = 0; + let sibling_sovereign_initial_balance = ztg(5); + let transfer_amount = ztg(1); + // Note: This asset was registered in `transfer_ztg_to_sibling` + + Zeitgeist::execute_with(|| { + treasury_initial_balance = Balances::free_balance(ZeitgeistTreasuryAccount::get()); + + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance); + assert_eq!( + Balances::free_balance(&sibling_parachain_account()), + sibling_sovereign_initial_balance + ); + }); + + Sibling::execute_with(|| { + assert_eq!(Balances::free_balance(&zeitgeist_parachain_account()), 0); + assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), bob_initial_balance); + assert_ok!(XTokens::transfer( + Origin::signed(BOB.into()), + FOREIGN_ZTG_ID, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(battery_station::ID), + Junction::AccountId32 { network: NetworkId::Any, id: ALICE } + ) + ) + .into() + ), + 4_000_000_000, + )); + + // Confirm that Bobs's balance is initial balance - amount transferred + assert_eq!( + Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), + bob_initial_balance - transfer_amount + ); + }); + + Zeitgeist::execute_with(|| { + // Verify that ALICE now has initial balance + amount transferred - fee + assert_eq!( + Balances::free_balance(&ALICE.into()), + alice_initial_balance + transfer_amount - ztg_fee(), + ); + + // Verify that the reserve has been adjusted properly + assert_eq!( + Balances::free_balance(&sibling_parachain_account()), + sibling_sovereign_initial_balance - transfer_amount + ); + + // Verify that fees (of native currency) have been put into treasury + assert_eq!( + Balances::free_balance(ZeitgeistTreasuryAccount::get()), + treasury_initial_balance + ztg_fee() + ) + }); +} + +#[test] +fn transfer_ksm_from_relay_chain() { + TestNet::reset(); + + let transfer_amount: Balance = ksm(1); + + Zeitgeist::execute_with(|| { + register_foreign_parent(None); + }); + + KusamaNet::execute_with(|| { + let initial_balance = kusama_runtime::Balances::free_balance(&ALICE.into()); + assert!(initial_balance >= transfer_amount); + + assert_ok!(kusama_runtime::XcmPallet::reserve_transfer_assets( + kusama_runtime::Origin::signed(ALICE.into()), + Box::new(Parachain(battery_station::ID).into().into()), + Box::new(Junction::AccountId32 { network: NetworkId::Any, id: BOB }.into().into()), + Box::new((Here, transfer_amount).into()), + 0 + )); + }); + + Zeitgeist::execute_with(|| { + assert_eq!( + Tokens::free_balance(FOREIGN_PARENT_ID, &BOB.into()), + transfer_amount - ksm_fee() + ); + }); +} + +#[test] +fn transfer_ksm_to_relay_chain() { + TestNet::reset(); + + let transfer_amount: Balance = ksm(1); + transfer_ksm_from_relay_chain(); + + Zeitgeist::execute_with(|| { + let initial_balance = Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE.into()); + assert!(initial_balance >= transfer_amount); + + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + FOREIGN_PARENT_ID, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { id: BOB, network: NetworkId::Any }) + ) + .into() + ), + 4_000_000_000 + )); + + assert_eq!( + Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE.into()), + initial_balance - transfer_amount + ) + }); + + KusamaNet::execute_with(|| { + assert_eq!(kusama_runtime::Balances::free_balance(&BOB.into()), 999_988_476_752); + }); +} + +#[test] +fn transfer_ztg_to_sibling_with_custom_fee() { + TestNet::reset(); + + let alice_initial_balance = ztg(10); + // 10x fee factor, so ZTG has 10x the worth of sibling currency. + let fee_factor = 100_000_000_000; + let transfer_amount = ztg(5); + let mut treasury_initial_balance = 0; + + Sibling::execute_with(|| { + treasury_initial_balance = + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); + assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), 0); + + register_foreign_ztg(None); + let custom_metadata = CustomMetadata { + xcm: XcmMetadata { fee_factor: Some(fee_factor) }, + ..Default::default() + }; + assert_ok!(AssetRegistry::do_update_asset( + FOREIGN_ZTG_ID, + None, + None, + None, + None, + None, + Some(custom_metadata) + )); + }); + + Zeitgeist::execute_with(|| { + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance); + assert_eq!(Balances::free_balance(&sibling_parachain_account()), 0); + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + CurrencyId::Ztg, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(PARA_ID_SIBLING), + Junction::AccountId32 { network: NetworkId::Any, id: BOB } + ) + ) + .into() + ), + 4_000_000_000, + )); + + // Confirm that Alice's balance is initial_balance - amount_transferred + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance - transfer_amount); + + // Verify that the amount transferred is now part of the sibling account here + assert_eq!(Balances::free_balance(&sibling_parachain_account()), transfer_amount); + }); + + Sibling::execute_with(|| { + let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()); + let custom_fee = calc_fee(default_per_second(10) * 10); + + // Verify that BOB now has (amount transferred - fee) + assert_eq!(current_balance, transfer_amount - custom_fee); + + // Sanity check for the actual amount BOB ends up with + assert_eq!(current_balance, 49_073_040_000); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + treasury_initial_balance + custom_fee + ) + }); +} + +#[test] +fn test_total_fee() { + assert_eq!(ztg_fee(), 92_696_000); + assert_eq!(ksm_fee(), 9_269_600_000); +} + +#[inline] +fn ztg_fee() -> Balance { + fee(BalanceFractionalDecimals::get().into()) +} + +#[inline] +fn fee(decimals: u32) -> Balance { + calc_fee(default_per_second(decimals)) +} + +// The fee associated with transferring KSM tokens +#[inline] +fn ksm_fee() -> Balance { + fee(12) +} + +#[inline] +fn calc_fee(fee_per_second: Balance) -> Balance { + // We divide the fee to align its unit and multiply by 8 as that seems to be the unit of + // time the tests take. + // NOTE: it is possible that in different machines this value may differ. We shall see. + fee_per_second / 10_000 * 8 +} diff --git a/runtime/battery-station/src/lib.rs b/runtime/battery-station/src/lib.rs index 8af54f007..f28a4be77 100644 --- a/runtime/battery-station/src/lib.rs +++ b/runtime/battery-station/src/lib.rs @@ -53,10 +53,13 @@ use zeitgeist_primitives::{constants::*, types::*}; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; #[cfg(feature = "parachain")] use { - frame_support::traits::{Everything, Nothing}, + frame_support::traits::{AsEnsureOriginWithArg, Everything, Nothing}, frame_system::EnsureSigned, xcm_builder::{EnsureXcmOrigin, FixedWeightBounds, LocationInverter}, - xcm_config::XcmConfig, + xcm_config::{ + asset_registry::{CustomAssetProcessor, CustomMetadata}, + config::{LocalOriginToLocation, XcmConfig, XcmOriginToTransactDispatchOrigin, XcmRouter}, + }, }; use frame_support::construct_runtime; @@ -74,6 +77,9 @@ use sp_runtime::{ use nimbus_primitives::{CanAuthor, NimbusId}; use sp_version::RuntimeVersion; +#[cfg(feature = "parachain")] +#[cfg(test)] +pub mod integration_tests; #[cfg(feature = "parachain")] pub mod parachain_params; pub mod parameters; diff --git a/runtime/battery-station/src/parachain_params.rs b/runtime/battery-station/src/parachain_params.rs index 733f4ea16..1d5092151 100644 --- a/runtime/battery-station/src/parachain_params.rs +++ b/runtime/battery-station/src/parachain_params.rs @@ -22,33 +22,16 @@ )] #![cfg(feature = "parachain")] -use super::{ - parameters::MAXIMUM_BLOCK_WEIGHT, AccountId, Balances, Origin, ParachainInfo, ParachainSystem, - XcmpQueue, -}; -use frame_support::{match_types, parameter_types, traits::Everything, weights::Weight}; -use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; -use sp_runtime::{Perbill, Percent, SaturatedConversion}; -use xcm::latest::{BodyId, Junction, Junctions, MultiLocation, NetworkId}; -use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - IsConcrete, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, -}; +use super::{parameters::MAXIMUM_BLOCK_WEIGHT, Origin, ParachainInfo}; +use frame_support::{parameter_types, weights::Weight}; +use orml_traits::parameter_type_with_key; +use sp_runtime::{Perbill, Percent}; +use xcm::latest::{prelude::X1, Junction::Parachain, MultiLocation, NetworkId}; use zeitgeist_primitives::{ - constants::{BASE, BLOCKS_PER_MINUTE, MICRO}, + constants::{BASE, BLOCKS_PER_MINUTE}, types::Balance, }; -match_types! { - pub type ParentOrParentsUnitPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Junctions::Here } | - // Potentially change "Unit" to "Executive" for mainnet once we have separate runtimes - MultiLocation { parents: 1, interior: Junctions::X1(Junction::Plurality { id: BodyId::Unit, .. }) } - }; -} parameter_types! { // Author-Mapping /// The amount that should be taken as a security deposit when registering a NimbusId. @@ -63,14 +46,13 @@ parameter_types! { pub const SignatureNetworkIdentifier: &'static [u8] = b"zeitgeist-"; // Cumulus and Polkadot - pub Ancestry: MultiLocation = Junction::Parachain(ParachainInfo::parachain_id().into()).into(); + pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub const RelayLocation: MultiLocation = MultiLocation::parent(); - // Have to change "Any" to "Kusama" for mainnet once we have separate runtimes pub const RelayNetwork: NetworkId = NetworkId::Any; pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UnitWeightCost: Weight = MICRO.saturated_into(); + pub UnitWeightCost: Weight = 200_000_000; // Staking /// Rounds before the candidate bond increase/decrease can be executed @@ -107,71 +89,20 @@ parameter_types! { pub const RewardPaymentDelay: u32 = 2; // XCM + /// Base weight for XCM execution + pub const BaseXcmWeight: Weight = 200_000_000; + /// The maximum number of distinct assets allowed to be transferred in a + /// single helper extrinsic. + pub const MaxAssetsForTransfer: usize = 2; + /// Max instructions per XCM pub const MaxInstructions: u32 = 100; + // Relative self location + pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::parachain_id().into()))); } -pub type Barrier = ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - AllowUnpaidExecutionFrom, - // ^^^ Parent and its exec plurality get free execution -); - -/// Means for transacting assets on this chain. -pub type LocalAssetTransactor = CurrencyAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: - LocationToAccountId, - // Our chain's account ID type (we can't get away without mentioning it explicitly): - AccountId, - // We don't track any teleports. - (), ->; - -/// No local origins on this chain are allowed to dispatch XCM sends/executions. -pub type LocalOriginToLocation = SignedToAccountId32; - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. -pub type LocationToAccountId = ( - // The parent (Relay-chain) origin converts to the parent `AccountId`. - ParentIsPreset, - // Sibling parachain origins convert to AccountId via the `ParaId::into`. - SiblingParachainConvertsVia, - // Straight up local `AccountId32` origins just alias directly to `AccountId`. - AccountId32Aliases, -); - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when - // recognized. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognized. - SiblingParachainAsNative, - // Native signed account converter; this just converts an `AccountId32` origin into a normal - // `Origin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, - // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, -); - -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = ( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -); +parameter_type_with_key! { + // XCM + pub ParachainMinFee: |_location: MultiLocation| -> Option { + None + }; +} diff --git a/runtime/battery-station/src/parameters.rs b/runtime/battery-station/src/parameters.rs index f6d605143..c5403f65f 100644 --- a/runtime/battery-station/src/parameters.rs +++ b/runtime/battery-station/src/parameters.rs @@ -56,7 +56,7 @@ parameter_types! { pub const MaxAuthorities: u32 = 32; // Balance - pub const ExistentialDeposit: u128 = 5 * CENT; + pub const ExistentialDeposit: u128 = 5 * MILLI; pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; @@ -311,7 +311,9 @@ parameter_types! { /// Period between successive spends. pub const SpendPeriod: BlockNumber = 24 * BLOCKS_PER_DAY; /// Pallet identifier, mainly used for named balance reserves. - pub const TreasuryPalletId: PalletId = PalletId(*b"zge/tsry"); + pub const TreasuryPalletId: PalletId = TREASURY_PALLET_ID; + /// Treasury account. + pub ZeitgeistTreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); // Bounties /// The amount held on deposit for placing a bounty proposal. diff --git a/runtime/battery-station/src/xcm_config.rs b/runtime/battery-station/src/xcm_config.rs deleted file mode 100644 index 6a70b65b6..000000000 --- a/runtime/battery-station/src/xcm_config.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021-2022 Zeitgeist PM LLC. -// -// This file is part of Zeitgeist. -// -// Zeitgeist 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. -// -// Zeitgeist 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. -// -// You should have received a copy of the GNU General Public License -// along with Zeitgeist. If not, see . - -use super::{ - AccountId, Ancestry, Balance, Balances, Barrier, Call, LocalAssetTransactor, MaxInstructions, - PolkadotXcm, RelayLocation, UnitWeightCost, XcmOriginToTransactDispatchOrigin, XcmRouter, -}; -use frame_support::weights::IdentityFee; -use xcm_builder::{FixedWeightBounds, LocationInverter, NativeAsset, UsingComponents}; -use xcm_executor::Config; - -pub struct XcmConfig; - -impl Config for XcmConfig { - type AssetClaims = PolkadotXcm; - type AssetTransactor = LocalAssetTransactor; - type AssetTrap = PolkadotXcm; - type Barrier = Barrier; - type Call = Call; - type IsReserve = NativeAsset; - type IsTeleporter = (); - type LocationInverter = LocationInverter; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type ResponseHandler = PolkadotXcm; - type SubscriptionService = PolkadotXcm; - type Trader = UsingComponents, RelayLocation, AccountId, Balances, ()>; - type Weigher = FixedWeightBounds; - type XcmSender = XcmRouter; -} diff --git a/runtime/battery-station/src/xcm_config/asset_registry.rs b/runtime/battery-station/src/xcm_config/asset_registry.rs new file mode 100644 index 000000000..ba7807a8c --- /dev/null +++ b/runtime/battery-station/src/xcm_config/asset_registry.rs @@ -0,0 +1,94 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{Balance, CurrencyId}; +use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::DispatchError; + +#[derive( + Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +/// Implements orml_traits::asset_registry::AssetProcessor. Does not apply any post checks. +/// Only pre check is to ensure an asset id was passed. +pub struct CustomAssetProcessor; + +impl AssetProcessor> for CustomAssetProcessor { + fn pre_register( + id: Option, + metadata: AssetMetadata, + ) -> Result<(CurrencyId, AssetMetadata), DispatchError> { + match id { + Some(id) => Ok((id, metadata)), + None => Err(DispatchError::Other("asset-registry: AssetId is required")), + } + } + + fn post_register( + _id: CurrencyId, + _asset_metadata: AssetMetadata, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + +#[derive( + Clone, + Copy, + Default, + PartialOrd, + Ord, + PartialEq, + Eq, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +/// Custom XC asset metadata +pub struct CustomMetadata { + /// XCM-related metadata. + pub xcm: XcmMetadata, + + /// Whether an asset can be used in pools. + pub allow_in_pool: bool, +} + +#[derive( + Clone, + Copy, + Default, + PartialOrd, + Ord, + PartialEq, + Eq, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +pub struct XcmMetadata { + /// The factor used to determine the fee. + /// It is multiplied by the fee that would have been paid in native currency, so it represents + /// the ratio `native_price / other_asset_price`. It is a fixed point decimal number containing + /// as many fractional decimals as the asset it is used for contains. + /// Should be updated regularly. + pub fee_factor: Option, +} diff --git a/runtime/battery-station/src/xcm_config/config.rs b/runtime/battery-station/src/xcm_config/config.rs new file mode 100644 index 000000000..4e09b741c --- /dev/null +++ b/runtime/battery-station/src/xcm_config/config.rs @@ -0,0 +1,311 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::fees::{native_per_second, FixedConversionRateProvider}; +use crate::{ + AccountId, Ancestry, AssetManager, AssetRegistry, Balance, Call, CurrencyId, MaxInstructions, + Origin, ParachainInfo, ParachainSystem, PolkadotXcm, RelayChainOrigin, RelayNetwork, + UnitWeightCost, UnknownTokens, XcmpQueue, ZeitgeistTreasuryAccount, +}; + +use frame_support::{parameter_types, traits::Everything, WeakBoundedVec}; +use orml_asset_registry::{AssetRegistryTrader, FixedRateAssetRegistryTrader}; +use orml_traits::{location::AbsoluteReserveProvider, MultiCurrency}; +use orml_xcm_support::{ + DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset, +}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use sp_runtime::traits::Convert; +use xcm::{ + latest::{ + prelude::{AccountId32, AssetId, Concrete, GeneralKey, MultiAsset, NetworkId, X1, X2}, + Junction, MultiLocation, + }, + opaque::latest::Fungibility::Fungible, +}; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, LocationInverter, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, + TakeWeightCredit, +}; +use xcm_executor::Config; +use zeitgeist_primitives::types::Asset; + +pub mod battery_station { + #[cfg(test)] + pub const ID: u32 = 2050; + pub const KEY: &[u8] = &[0, 1]; +} + +pub struct XcmConfig; + +/// The main XCM config +/// This is where we configure the core of our XCM integrations: how tokens are transferred, +/// how fees are calculated, what barriers we impose on incoming XCM messages, etc. +impl Config for XcmConfig { + /// The handler for when there is an instruction to claim assets. + type AssetClaims = PolkadotXcm; + /// How to withdraw and deposit an asset. + type AssetTransactor = MultiAssetTransactor; + /// The general asset trap - handler for when assets are left in the Holding Register at the + /// end of execution. + type AssetTrap = PolkadotXcm; + /// Additional filters that specify whether the XCM instruction should be executed at all. + type Barrier = Barrier; + /// The outer call dispatch type. + type Call = Call; + /// Combinations of (Location, Asset) pairs which are trusted as reserves. + // Trust the parent chain, sibling parachains and children chains of this chain. + type IsReserve = MultiNativeAsset; + /// Combinations of (Location, Asset) pairs which we trust as teleporters. + type IsTeleporter = (); + /// Means of inverting a location. + type LocationInverter = LocationInverter; + /// How to get a call origin from a `OriginKind` value. + type OriginConverter = XcmOriginToTransactDispatchOrigin; + /// Module that handles responses of queries. + type ResponseHandler = PolkadotXcm; + /// Module that handles subscription requests. + type SubscriptionService = PolkadotXcm; + /// The means of purchasing weight credit for XCM execution. + type Trader = Trader; + /// The means of determining an XCM message's weight. + // Adds UnitWeightCost per instruction plus the weight of each instruction. + // The total number of instructions are bounded by MaxInstructions + type Weigher = FixedWeightBounds; + /// How to send an onward XCM message. + type XcmSender = XcmRouter; +} + +/// Additional filters that specify whether the XCM instruction should be executed at all. +pub type Barrier = ( + // Execution barrier that just takes max_weight from weight_credit + TakeWeightCredit, + // Ensures that execution time is bought with BuyExecution instruction + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, +); + +/// The means of purchasing weight credit for XCM execution. +/// Every token that is accepted for XC transfers should be handled here. +pub type Trader = ( + // In case the asset in question is the native currency, it will charge + // the default base fee per second and deposits them into treasury + FixedRateOfFungible, + FixedRateOfFungible, + // For all other assets the base fee per second will tried to be derived + // through the `fee_factor` entry in the asset registry. If the asset is + // not present in the asset registry, the default base fee per second is used. + // Deposits all fees into the treasury. + AssetRegistryTrader< + FixedRateAssetRegistryTrader>, + ToTreasury, + >, +); + +pub struct ToTreasury; +impl TakeRevenue for ToTreasury { + fn take_revenue(revenue: MultiAsset) { + use xcm_executor::traits::Convert; + + if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = revenue { + if let Ok(asset_id) = + >::convert(location) + { + let _ = AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); + } + } + } +} + +parameter_types! { + pub CheckAccount: AccountId = PolkadotXcm::check_account(); + /// The amount of ZTG charged per second of execution (canonical multilocation). + pub ZtgPerSecondCanonical: (AssetId, u128) = ( + MultiLocation::new( + 0, + X1(general_key(battery_station::KEY)), + ).into(), + native_per_second(), + ); + /// The amount of canonical ZTG charged per second of execution. + pub ZtgPerSecond: (AssetId, u128) = ( + MultiLocation::new( + 1, + X2(Junction::Parachain(ParachainInfo::parachain_id().into()), general_key(battery_station::KEY)), + ).into(), + native_per_second(), + ); +} + +/// Means for transacting assets on this chain. +pub type MultiAssetTransactor = MultiCurrencyAdapter< + // All known Assets will be processed by the following MultiCurrency implementation. + AssetManager, + // Any unknown Assets will be processed by the following UnknownAsset implementation. + UnknownTokens, + // This means that this adapter should handle any token that `AssetConvert` can convert + // using AssetManager and UnknownTokens in all other cases. + IsNativeConcrete, + // Our chain's account ID type (we can't get away without mentioning it explicitly). + AccountId, + // Convert an XCM `MultiLocation` into a local account id. + LocationToAccountId, + // The AssetId that corresponds to the native currency. + CurrencyId, + // Struct that provides functions to convert `Asset` <=> `MultiLocation`. + AssetConvert, + // In case of deposit failure, known assets will be placed in treasury. + DepositToAlternative, +>; + +/// AssetConvert +/// This type implements conversions from our `Asset` type into `MultiLocation` and vice-versa. +/// A currency locally is identified with a `Asset` variant but in the network it is identified +/// in the form of a `MultiLocation`, in this case a pair (Para-Id, Currency-Id). +pub struct AssetConvert; + +/// Convert our `Asset` type into its `MultiLocation` representation. +/// Other chains need to know how this conversion takes place in order to +/// handle it on their side. +impl Convert> for AssetConvert { + fn convert(id: CurrencyId) -> Option { + match id { + Asset::Ztg => Some(MultiLocation::new( + 1, + X2( + Junction::Parachain(ParachainInfo::parachain_id().into()), + general_key(battery_station::KEY), + ), + )), + Asset::ForeignAsset(_) => AssetRegistry::multilocation(&id).ok()?, + _ => None, + } + } +} + +/// Convert an incoming `MultiLocation` into a `Asset` if possible. +/// Here we need to know the canonical representation of all the tokens we handle in order to +/// correctly convert their `MultiLocation` representation into our internal `Asset` type. +impl xcm_executor::traits::Convert for AssetConvert { + fn convert(location: MultiLocation) -> Result { + match location.clone() { + MultiLocation { parents: 0, interior: X1(GeneralKey(key)) } => { + if &key[..] == battery_station::KEY { + return Ok(CurrencyId::Ztg); + } + + Err(location) + } + MultiLocation { + parents: 1, + interior: X2(Junction::Parachain(para_id), GeneralKey(key)), + } => { + if para_id == u32::from(ParachainInfo::parachain_id()) { + if &key[..] == battery_station::KEY { + return Ok(CurrencyId::Ztg); + } + + return Err(location); + } + + AssetRegistry::location_to_asset_id(location.clone()).ok_or(location) + } + _ => AssetRegistry::location_to_asset_id(location.clone()).ok_or(location), + } + } +} + +impl Convert> for AssetConvert { + fn convert(asset: MultiAsset) -> Option { + if let MultiAsset { id: Concrete(location), .. } = asset { + >::convert(location).ok() + } else { + None + } + } +} + +impl Convert> for AssetConvert { + fn convert(location: MultiLocation) -> Option { + >::convert(location).ok() + } +} + +pub struct AccountIdToMultiLocation; + +impl Convert for AccountIdToMultiLocation { + fn convert(account: AccountId) -> MultiLocation { + X1(AccountId32 { network: NetworkId::Any, id: account.into() }).into() + } +} + +/// No local origins on this chain are allowed to dispatch XCM sends/executions. +pub type LocalOriginToLocation = SignedToAccountId32; + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `Origin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = ( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +); + +#[inline] +pub(crate) fn general_key(key: &[u8]) -> Junction { + GeneralKey(WeakBoundedVec::force_from(key.to_vec(), None)) +} diff --git a/runtime/battery-station/src/xcm_config/fees.rs b/runtime/battery-station/src/xcm_config/fees.rs new file mode 100644 index 000000000..b70484d97 --- /dev/null +++ b/runtime/battery-station/src/xcm_config/fees.rs @@ -0,0 +1,79 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{xcm_config::asset_registry::CustomMetadata, Balance, CurrencyId}; +use core::marker::PhantomData; +use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_PER_SECOND}; +use xcm::latest::MultiLocation; +use zeitgeist_primitives::constants::BalanceFractionalDecimals; +use zrml_swaps::check_arithm_rslt::CheckArithmRslt; + +/// The fee cost per second for transferring the native token in cents. +pub fn native_per_second() -> Balance { + default_per_second(BalanceFractionalDecimals::get().into()) +} + +pub fn default_per_second(decimals: u32) -> Balance { + let base_weight = Balance::from(ExtrinsicBaseWeight::get()); + let default_per_second = (WEIGHT_PER_SECOND as u128) / base_weight; + default_per_second * base_fee(decimals) +} + +fn base_fee(decimals: u32) -> Balance { + cent(decimals).saturating_div(10) +} + +/// 1 Asset in fixed point decimal representation +pub fn dollar(decimals: u32) -> Balance { + 10u128.saturating_pow(decimals) +} + +/// 0.01 Asset in fixed point decimal presentation +pub fn cent(decimals: u32) -> Balance { + dollar(decimals).saturating_div(100) +} + +pub fn bmul(a: u128, b: u128, base: u128) -> Option { + let c0 = a.check_mul_rslt(&b).ok()?; + let c1 = c0.check_add_rslt(&base.check_div_rslt(&2).ok()?).ok()?; + c1.check_div_rslt(&base).ok() +} + +/// Our FixedConversionRateProvider, used to charge XCM-related fees for tokens registered in +/// the asset registry that were not already handled by native Trader rules. +pub struct FixedConversionRateProvider(PhantomData); + +impl< + AssetRegistry: orml_traits::asset_registry::Inspect< + AssetId = CurrencyId, + Balance = Balance, + CustomMetadata = CustomMetadata, + >, +> orml_traits::FixedConversionRateProvider for FixedConversionRateProvider +{ + fn get_fee_per_second(location: &MultiLocation) -> Option { + let metadata = AssetRegistry::metadata_by_location(location)?; + let default_per_second = default_per_second(metadata.decimals); + + if let Some(fee_factor) = metadata.additional.xcm.fee_factor { + let base = 10u128.checked_pow(metadata.decimals)?; + bmul(default_per_second, fee_factor, base) + } else { + Some(default_per_second) + } + } +} diff --git a/runtime/battery-station/src/xcm_config/mod.rs b/runtime/battery-station/src/xcm_config/mod.rs new file mode 100644 index 000000000..363ac87b1 --- /dev/null +++ b/runtime/battery-station/src/xcm_config/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(feature = "parachain")] + +pub mod asset_registry; +pub mod config; +pub mod fees; diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 6581b40ed..3ac87127c 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -178,6 +178,7 @@ macro_rules! decl_common_types { PmPalletId::get(), SimpleDisputesPalletId::get(), SwapsPalletId::get(), + TreasuryPalletId::get(), ]; #[cfg(feature = "with-global-disputes")] @@ -335,6 +336,9 @@ macro_rules! create_runtime_with_additional_pallets { DmpQueue: cumulus_pallet_dmp_queue::{Call, Event, Pallet, Storage} = 121, PolkadotXcm: pallet_xcm::{Call, Config, Event, Origin, Pallet, Storage} = 122, XcmpQueue: cumulus_pallet_xcmp_queue::{Call, Event, Pallet, Storage} = 123, + AssetRegistry: orml_asset_registry::{Call, Config, Event, Pallet, Storage} = 124, + UnknownTokens: orml_unknown_tokens::{Pallet, Storage, Event} = 125, + XTokens: orml_xtokens::{Pallet, Storage, Call, Event} = 126, // Third-party Crowdloan: pallet_crowdloan_rewards::{Call, Config, Event, Pallet, Storage} = 130, @@ -359,6 +363,8 @@ macro_rules! create_runtime_with_additional_pallets { macro_rules! impl_config_traits { {} => { use common_runtime::weights; + #[cfg(feature = "parachain")] + use xcm_config::config::*; // Configure Pallets #[cfg(feature = "parachain")] @@ -534,6 +540,17 @@ macro_rules! impl_config_traits { type WeightInfo = weights::pallet_parachain_staking::WeightInfo; } + #[cfg(feature = "parachain")] + impl orml_asset_registry::Config for Runtime { + type AssetId = CurrencyId; + type AssetProcessor = CustomAssetProcessor; + type AuthorityOrigin = AsEnsureOriginWithArg; + type Balance = Balance; + type CustomMetadata = CustomMetadata; + type Event = Event; + type WeightInfo = (); + } + impl orml_currencies::Config for Runtime { type GetNativeCurrencyId = GetNativeCurrencyId; type MultiCurrency = Tokens; @@ -557,6 +574,29 @@ macro_rules! impl_config_traits { type WeightInfo = weights::orml_tokens::WeightInfo; } + #[cfg(feature = "parachain")] + impl orml_unknown_tokens::Config for Runtime { + type Event = Event; + } + + #[cfg(feature = "parachain")] + impl orml_xtokens::Config for Runtime { + type AccountIdToMultiLocation = AccountIdToMultiLocation; + type Balance = Balance; + type BaseXcmWeight = BaseXcmWeight; + type CurrencyId = CurrencyId; + type CurrencyIdConvert = AssetConvert; + type Event = Event; + type LocationInverter = LocationInverter; + type MaxAssetsForTransfer = MaxAssetsForTransfer; + type MinXcmFee = ParachainMinFee; + type MultiLocationsFilter = Everything; + type ReserveProvider = orml_traits::location::AbsoluteReserveProvider; + type SelfLocation = SelfLocation; + type Weigher = FixedWeightBounds; + type XcmExecutor = xcm_executor::XcmExecutor; + } + #[cfg(feature = "parachain")] impl pallet_crowdloan_rewards::Config for Runtime { type Event = Event; diff --git a/runtime/zeitgeist/Cargo.toml b/runtime/zeitgeist/Cargo.toml index fc82dc2b2..eadfa3d5c 100644 --- a/runtime/zeitgeist/Cargo.toml +++ b/runtime/zeitgeist/Cargo.toml @@ -68,15 +68,8 @@ pallet-author-mapping = { tag = "v0.26.1", default-features = false, git = "http pallet-author-slot-filter = { branch = "moonbeam-polkadot-v0.9.26", default-features = false, git = "https://github.com/purestake/nimbus", optional = true } pallet-crowdloan-rewards = { branch = "moonbeam-polkadot-v0.9.26", default-features = false, git = "https://github.com/purestake/crowdloan-rewards", optional = true } pallet-parachain-staking = { tag = "v0.26.1", default-features = false, git = "https://github.com/purestake/moonbeam", optional = true } -session-keys-primitives = { tag = "v0.26.1", default-features = false, git = "https://github.com/purestake/moonbeam", optional = true } - -# Polkadot - -pallet-xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } polkadot-parachain = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } -xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } -xcm-builder = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } -xcm-executor = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +session-keys-primitives = { tag = "v0.26.1", default-features = false, git = "https://github.com/purestake/moonbeam", optional = true } # Standalone @@ -90,6 +83,19 @@ cfg-if = { version = "1.0.0" } hex-literal = { default-features = false, optional = true, version = "0.3.4" } log = { version = "0.4.17", default-features = false, optional = true } +# XCM +kusama-runtime = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +orml-asset-registry = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +orml-unknown-tokens = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +orml-xcm-support = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +orml-xtokens = { branch = "polkadot-v0.9.26", default-features = false, git = "https://github.com/open-web3-stack/open-runtime-module-library", optional = true } +pallet-xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +polkadot-primitives = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +polkadot-runtime-parachains = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +xcm = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +xcm-builder = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } +xcm-executor = { branch = "release-v0.9.26", default-features = false, git = "https://github.com/paritytech/polkadot", optional = true } + # Zeitgeist common-runtime = { default-features = false, path = "../common" } @@ -108,6 +114,7 @@ zrml-swaps-runtime-api = { default-features = false, path = "../../zrml/swaps/ru [dev-dependencies] sp-io = { branch = "polkadot-v0.9.26", git = "https://github.com/paritytech/substrate" } +xcm-emulator = { rev = "ab5cd6c5fabe6ddda52ed6803ee1bf54c258fefe", git = "https://github.com/shaunxw/xcm-simulator" } [features] default = ["std"] @@ -131,12 +138,19 @@ parachain = [ "pallet-author-slot-filter", "pallet-crowdloan-rewards", "pallet-parachain-staking", + "polkadot-parachain", "session-keys-primitives", - # Polkadot + # XCM + "kusama-runtime", + "polkadot-primitives", + "polkadot-runtime-parachains", + "orml-asset-registry", + "orml-unknown-tokens", + "orml-xcm-support", + "orml-xtokens", "pallet-xcm", - "polkadot-parachain", "xcm-builder", "xcm-executor", "xcm", @@ -154,7 +168,10 @@ runtime-benchmarks = [ "frame-system-benchmarking", "frame-system/runtime-benchmarks", "hex-literal", + "kusama-runtime?/runtime-benchmarks", + "orml-asset-registry?/runtime-benchmarks", "orml-tokens/runtime-benchmarks", + "orml-xtokens?/runtime-benchmarks", "orml-benchmarking", "pallet-author-inherent?/runtime-benchmarks", "pallet-author-mapping?/runtime-benchmarks", @@ -259,10 +276,16 @@ std = [ "pallet-parachain-staking?/std", "session-keys-primitives?/std", - # Polkadot + # XCM + "kusama-runtime?/std", + "polkadot-primitives?/std", + "polkadot-runtime-parachains?/std", + "orml-asset-registry?/std", + "orml-unknown-tokens?/std", + "orml-xcm-support?/std", + "orml-xtokens?/std", "pallet-xcm?/std", - "polkadot-parachain?/std", "xcm-builder?/std", "xcm-executor?/std", "xcm?/std", @@ -320,8 +343,11 @@ try-runtime = [ "pallet-utility/try-runtime", # ORML runtime pallets + "orml-asset-registry?/try-runtime", "orml-currencies/try-runtime", "orml-tokens/try-runtime", + "orml-unknown-tokens?/try-runtime", + "orml-xtokens?/try-runtime", # Zeitgeist runtime pallets "zrml-authorized/try-runtime", diff --git a/runtime/zeitgeist/src/integration_tests/mod.rs b/runtime/zeitgeist/src/integration_tests/mod.rs new file mode 100644 index 000000000..64f89683b --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2022 Forecasting Technologies LTD. +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(test)] + +mod xcm; diff --git a/runtime/zeitgeist/src/integration_tests/xcm/mod.rs b/runtime/zeitgeist/src/integration_tests/xcm/mod.rs new file mode 100644 index 000000000..d2226363f --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/xcm/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2022 Forecasting Technologies LTD. +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(all(feature = "parachain", test))] + +mod setup; +mod test_net; +mod tests; diff --git a/runtime/zeitgeist/src/integration_tests/xcm/setup.rs b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs new file mode 100644 index 000000000..e3da82f91 --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/xcm/setup.rs @@ -0,0 +1,205 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + xcm_config::{ + asset_registry::CustomMetadata, + config::{general_key, zeitgeist}, + }, + AccountId, AssetRegistry, Balance, CurrencyId, ExistentialDeposit, Origin, Runtime, System, +}; +use frame_support::{assert_ok, traits::GenesisBuild}; +use orml_traits::asset_registry::AssetMetadata; +use xcm::{ + latest::{Junction::Parachain, Junctions::X2, MultiLocation}, + VersionedMultiLocation, +}; +use zeitgeist_primitives::types::Asset; + +pub(super) struct ExtBuilder { + balances: Vec<(AccountId, CurrencyId, Balance)>, + parachain_id: u32, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { balances: vec![], parachain_id: zeitgeist::ID } + } +} + +impl ExtBuilder { + pub fn set_balances(mut self, balances: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub fn set_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 = CurrencyId::Ztg; + 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 + } +} + +/// Accounts +pub const ALICE: [u8; 32] = [4u8; 32]; +pub const BOB: [u8; 32] = [5u8; 32]; + +/// A PARA ID used for a sibling parachain. +/// It must be one that doesn't collide with any other in use. +pub const PARA_ID_SIBLING: u32 = 3000; + +/// IDs that are used to represent tokens from other chains +pub const FOREIGN_ZTG_ID: Asset = CurrencyId::ForeignAsset(0); +pub const FOREIGN_PARENT_ID: Asset = CurrencyId::ForeignAsset(1); +pub const FOREIGN_SIBLING_ID: Asset = CurrencyId::ForeignAsset(2); + +// Multilocations that are used to represent tokens from other chains +#[inline] +pub(super) fn foreign_ztg_multilocation() -> MultiLocation { + MultiLocation::new(1, X2(Parachain(zeitgeist::ID), general_key(zeitgeist::KEY))) +} + +#[inline] +pub(super) fn foreign_sibling_multilocation() -> MultiLocation { + MultiLocation::new(1, X2(Parachain(PARA_ID_SIBLING), general_key(zeitgeist::KEY))) +} + +#[inline] +pub(super) fn foreign_parent_multilocation() -> MultiLocation { + MultiLocation::parent() +} + +pub(super) fn register_foreign_ztg(additional_meta: Option) { + // Register ZTG as foreign asset. + let meta: AssetMetadata = AssetMetadata { + decimals: 10, + name: "Zeitgeist".into(), + symbol: "ZTG".into(), + existential_deposit: ExistentialDeposit::get(), + location: Some(VersionedMultiLocation::V1(foreign_ztg_multilocation())), + additional: additional_meta.unwrap_or_default(), + }; + + assert_ok!(AssetRegistry::register_asset(Origin::root(), meta, Some(FOREIGN_ZTG_ID))); +} + +pub(super) fn register_foreign_sibling(additional_meta: Option) { + // Register native Sibling token as foreign asset. + let meta: AssetMetadata = AssetMetadata { + decimals: 10, + name: "Sibling".into(), + symbol: "SBL".into(), + existential_deposit: ExistentialDeposit::get(), + location: Some(VersionedMultiLocation::V1(foreign_sibling_multilocation())), + additional: additional_meta.unwrap_or_default(), + }; + + assert_ok!(AssetRegistry::register_asset(Origin::root(), meta, Some(FOREIGN_SIBLING_ID))); +} + +pub(super) fn register_foreign_parent(additional_meta: Option) { + // Register KSM as foreign asset in the sibling parachain + let meta: AssetMetadata = AssetMetadata { + decimals: 12, + name: "Kusama".into(), + symbol: "KSM".into(), + existential_deposit: 10_000_000_000, // 0.01 + location: Some(VersionedMultiLocation::V1(foreign_parent_multilocation())), + additional: additional_meta.unwrap_or_default(), + }; + + assert_ok!(AssetRegistry::register_asset(Origin::root(), meta, Some(FOREIGN_PARENT_ID))); +} + +#[inline] +pub(super) fn ztg(amount: Balance) -> Balance { + amount * dollar(10) +} + +#[inline] +pub(super) fn ksm(amount: Balance) -> Balance { + foreign(amount, 12) +} + +#[inline] +pub(super) fn foreign(amount: Balance, decimals: u32) -> Balance { + amount * dollar(decimals) +} + +#[inline] +pub(super) fn dollar(decimals: u32) -> Balance { + 10u128.saturating_pow(decimals) +} + +#[inline] +pub(super) fn sibling_parachain_account() -> AccountId { + parachain_account(PARA_ID_SIBLING) +} + +#[inline] +pub(super) fn zeitgeist_parachain_account() -> AccountId { + parachain_account(zeitgeist::ID) +} + +#[inline] +fn parachain_account(id: u32) -> AccountId { + use sp_runtime::traits::AccountIdConversion; + + polkadot_parachain::primitives::Sibling::from(id).into_account_truncating() +} diff --git a/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs new file mode 100644 index 000000000..ead4ca132 --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/xcm/test_net.rs @@ -0,0 +1,147 @@ +// Copyright 2021-2022 Centrifuge GmbH (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + parameters::ZeitgeistTreasuryAccount, xcm_config::config::zeitgeist, AccountId, CurrencyId, + DmpQueue, Origin, Runtime, XcmpQueue, +}; +use frame_support::{traits::GenesisBuild, weights::Weight}; +use polkadot_primitives::v2::{BlockNumber, MAX_CODE_SIZE, MAX_POV_SIZE}; +use polkadot_runtime_parachains::configuration::HostConfiguration; +use xcm_emulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; + +use super::setup::{ksm, ztg, ExtBuilder, ALICE, FOREIGN_PARENT_ID, PARA_ID_SIBLING}; + +decl_test_relay_chain! { + pub struct KusamaNet { + Runtime = kusama_runtime::Runtime, + XcmConfig = kusama_runtime::xcm_config::XcmConfig, + new_ext = relay_ext(), + } +} + +decl_test_parachain! { + pub struct Zeitgeist { + Runtime = Runtime, + Origin = Origin, + XcmpMessageHandler = XcmpQueue, + DmpMessageHandler = DmpQueue, + new_ext = para_ext(zeitgeist::ID), + } +} + +decl_test_parachain! { + pub struct Sibling { + Runtime = Runtime, + Origin = Origin, + XcmpMessageHandler = XcmpQueue, + DmpMessageHandler = DmpQueue, + new_ext = para_ext(PARA_ID_SIBLING), + } +} + +decl_test_network! { + pub struct TestNet { + relay_chain = KusamaNet, + 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 `xcm_config::config::zeitgeist::ID` + (2101, Zeitgeist), + // Be sure to use `PARA_ID_SIBLING` + (3000, Sibling), + ], + } +} + +pub(super) fn relay_ext() -> sp_io::TestExternalities { + use kusama_runtime::{Runtime, System}; + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(AccountId::from(ALICE), ksm(2002))], + } + .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(super) fn para_ext(parachain_id: u32) -> sp_io::TestExternalities { + ExtBuilder::default() + .set_balances(vec![ + (AccountId::from(ALICE), CurrencyId::Ztg, ztg(10)), + (AccountId::from(ALICE), FOREIGN_PARENT_ID, ksm(10)), + (ZeitgeistTreasuryAccount::get(), FOREIGN_PARENT_ID, ksm(1)), + ]) + .set_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: Weight::from(4 * 1_000_000_000u32), + 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/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs new file mode 100644 index 000000000..757e13be5 --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/currency_id_convert.rs @@ -0,0 +1,121 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + integration_tests::xcm::{ + setup::{ + foreign_parent_multilocation, foreign_sibling_multilocation, foreign_ztg_multilocation, + register_foreign_parent, register_foreign_sibling, FOREIGN_PARENT_ID, + FOREIGN_SIBLING_ID, + }, + test_net::Zeitgeist, + }, + xcm_config::config::{general_key, zeitgeist, AssetConvert}, + CurrencyId, +}; + +use frame_support::assert_err; +use sp_runtime::traits::Convert as C2; +use xcm::latest::{Junction::*, Junctions::*, MultiLocation}; +use xcm_emulator::TestExt; +use xcm_executor::traits::Convert as C1; + +#[test] +fn convert_native() { + assert_eq!(zeitgeist::KEY.to_vec(), vec![0, 1]); + + // The way Ztg is represented relative within the Zeitgeist runtime + let ztg_location_inner: MultiLocation = MultiLocation::new(0, X1(general_key(zeitgeist::KEY))); + + assert_eq!(>::convert(ztg_location_inner), Ok(CurrencyId::Ztg)); + + // The canonical way Ztg is represented out in the wild + Zeitgeist::execute_with(|| { + assert_eq!( + >::convert(CurrencyId::Ztg), + Some(foreign_ztg_multilocation()) + ) + }); +} + +#[test] +fn convert_any_registered_parent_multilocation() { + Zeitgeist::execute_with(|| { + assert_err!( + >::convert(foreign_parent_multilocation()), + foreign_parent_multilocation() + ); + + assert_eq!(>::convert(FOREIGN_PARENT_ID), None); + + // Register parent as foreign asset in the Zeitgeist parachain + register_foreign_parent(None); + + assert_eq!( + >::convert(foreign_parent_multilocation()), + Ok(FOREIGN_PARENT_ID), + ); + + assert_eq!( + >::convert(FOREIGN_PARENT_ID), + Some(foreign_parent_multilocation()) + ); + }); +} + +#[test] +fn convert_any_registered_sibling_multilocation() { + Zeitgeist::execute_with(|| { + assert_err!( + >::convert(foreign_sibling_multilocation()), + foreign_sibling_multilocation() + ); + + assert_eq!(>::convert(FOREIGN_SIBLING_ID), None); + + // Register sibling as foreign asset in the Zeitgeist parachain + register_foreign_sibling(None); + + assert_eq!( + >::convert(foreign_sibling_multilocation()), + Ok(FOREIGN_SIBLING_ID), + ); + + assert_eq!( + >::convert(FOREIGN_SIBLING_ID), + Some(foreign_sibling_multilocation()) + ); + }); +} + +#[test] +fn convert_unkown_multilocation() { + let unknown_location: MultiLocation = + MultiLocation::new(1, X2(Parachain(zeitgeist::ID), general_key(&[42]))); + + Zeitgeist::execute_with(|| { + assert!(>::convert(unknown_location.clone()).is_err()); + }); +} + +#[test] +fn convert_unsupported_currency() { + Zeitgeist::execute_with(|| { + assert_eq!(>::convert(CurrencyId::CombinatorialOutcome), None) + }); +} diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/mod.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/mod.rs new file mode 100644 index 000000000..8194bed88 --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/mod.rs @@ -0,0 +1,19 @@ +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +mod currency_id_convert; +mod transfers; diff --git a/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs new file mode 100644 index 000000000..25543b4e1 --- /dev/null +++ b/runtime/zeitgeist/src/integration_tests/xcm/tests/transfers.rs @@ -0,0 +1,347 @@ +// Copyright 2021 Centrifuge Foundation (centrifuge.io). +// Copyright 2022 Forecasting Technologies LTD. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{ + integration_tests::xcm::{ + setup::{ + ksm, register_foreign_parent, register_foreign_ztg, sibling_parachain_account, + zeitgeist_parachain_account, ztg, ALICE, BOB, FOREIGN_PARENT_ID, FOREIGN_ZTG_ID, + PARA_ID_SIBLING, + }, + test_net::{KusamaNet, Sibling, TestNet, Zeitgeist}, + }, + xcm_config::{ + asset_registry::{CustomMetadata, XcmMetadata}, + config::zeitgeist, + fees::default_per_second, + }, + AssetRegistry, Balance, Balances, CurrencyId, Origin, Tokens, XTokens, + ZeitgeistTreasuryAccount, +}; + +use frame_support::assert_ok; +use orml_traits::MultiCurrency; +use xcm::latest::{Junction, Junction::*, Junctions::*, MultiLocation, NetworkId}; +use xcm_emulator::TestExt; +use zeitgeist_primitives::constants::BalanceFractionalDecimals; + +#[test] +fn transfer_ztg_to_sibling() { + TestNet::reset(); + + let alice_initial_balance = ztg(10); + let transfer_amount = ztg(5); + let mut treasury_initial_balance = 0; + + Sibling::execute_with(|| { + treasury_initial_balance = + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); + assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), 0); + register_foreign_ztg(None); + }); + + Zeitgeist::execute_with(|| { + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance); + assert_eq!(Balances::free_balance(&sibling_parachain_account()), 0); + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + CurrencyId::Ztg, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(PARA_ID_SIBLING), + Junction::AccountId32 { network: NetworkId::Any, id: BOB } + ) + ) + .into() + ), + 4_000_000_000, + )); + + // Confirm that Alice's balance is initial_balance - amount_transferred + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance - transfer_amount); + + // Verify that the amount transferred is now part of the sibling account here + assert_eq!(Balances::free_balance(&sibling_parachain_account()), transfer_amount); + }); + + Sibling::execute_with(|| { + let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()); + + // Verify that BOB now has (amount transferred - fee) + assert_eq!(current_balance, transfer_amount - ztg_fee()); + + // Sanity check for the actual amount BOB ends up with + assert_eq!(current_balance, 49_907_304_000); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + treasury_initial_balance + ztg_fee() + ) + }); +} + +#[test] +fn transfer_ztg_sibling_to_zeitgeist() { + TestNet::reset(); + + // In order to be able to transfer ZTG from Sibling to Zeitgeist, we need to first send + // ZTG from Zeitgeist to Sibling, or else it fails since it'd be like Sibling had minted + // ZTG on their side. + transfer_ztg_to_sibling(); + + let alice_initial_balance = ztg(5); + let bob_initial_balance = ztg(5) - ztg_fee(); + let mut treasury_initial_balance = 0; + let sibling_sovereign_initial_balance = ztg(5); + let transfer_amount = ztg(1); + // Note: This asset was registered in `transfer_ztg_to_sibling` + + Zeitgeist::execute_with(|| { + treasury_initial_balance = Balances::free_balance(ZeitgeistTreasuryAccount::get()); + + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance); + assert_eq!( + Balances::free_balance(&sibling_parachain_account()), + sibling_sovereign_initial_balance + ); + }); + + Sibling::execute_with(|| { + assert_eq!(Balances::free_balance(&zeitgeist_parachain_account()), 0); + assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), bob_initial_balance); + assert_ok!(XTokens::transfer( + Origin::signed(BOB.into()), + FOREIGN_ZTG_ID, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(zeitgeist::ID), + Junction::AccountId32 { network: NetworkId::Any, id: ALICE } + ) + ) + .into() + ), + 4_000_000_000, + )); + + // Confirm that Bobs's balance is initial balance - amount transferred + assert_eq!( + Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), + bob_initial_balance - transfer_amount + ); + }); + + Zeitgeist::execute_with(|| { + // Verify that ALICE now has initial balance + amount transferred - fee + assert_eq!( + Balances::free_balance(&ALICE.into()), + alice_initial_balance + transfer_amount - ztg_fee(), + ); + + // Verify that the reserve has been adjusted properly + assert_eq!( + Balances::free_balance(&sibling_parachain_account()), + sibling_sovereign_initial_balance - transfer_amount + ); + + // Verify that fees (of native currency) have been put into treasury + assert_eq!( + Balances::free_balance(ZeitgeistTreasuryAccount::get()), + treasury_initial_balance + ztg_fee() + ) + }); +} + +#[test] +fn transfer_ksm_from_relay_chain() { + TestNet::reset(); + + let transfer_amount: Balance = ksm(1); + + Zeitgeist::execute_with(|| { + register_foreign_parent(None); + }); + + KusamaNet::execute_with(|| { + let initial_balance = kusama_runtime::Balances::free_balance(&ALICE.into()); + assert!(initial_balance >= transfer_amount); + + assert_ok!(kusama_runtime::XcmPallet::reserve_transfer_assets( + kusama_runtime::Origin::signed(ALICE.into()), + Box::new(Parachain(zeitgeist::ID).into().into()), + Box::new(Junction::AccountId32 { network: NetworkId::Any, id: BOB }.into().into()), + Box::new((Here, transfer_amount).into()), + 0 + )); + }); + + Zeitgeist::execute_with(|| { + assert_eq!( + Tokens::free_balance(FOREIGN_PARENT_ID, &BOB.into()), + transfer_amount - ksm_fee() + ); + }); +} + +#[test] +fn transfer_ksm_to_relay_chain() { + TestNet::reset(); + + let transfer_amount: Balance = ksm(1); + transfer_ksm_from_relay_chain(); + + Zeitgeist::execute_with(|| { + let initial_balance = Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE.into()); + assert!(initial_balance >= transfer_amount); + + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + FOREIGN_PARENT_ID, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { id: BOB, network: NetworkId::Any }) + ) + .into() + ), + 4_000_000_000 + )); + + assert_eq!( + Tokens::free_balance(FOREIGN_PARENT_ID, &ALICE.into()), + initial_balance - transfer_amount + ) + }); + + KusamaNet::execute_with(|| { + assert_eq!(kusama_runtime::Balances::free_balance(&BOB.into()), 999_988_476_752); + }); +} + +#[test] +fn transfer_ztg_to_sibling_with_custom_fee() { + TestNet::reset(); + + let alice_initial_balance = ztg(10); + // 10x fee factor, so ZTG has 10x the worth of sibling currency. + let fee_factor = 100_000_000_000; + let transfer_amount = ztg(5); + let mut treasury_initial_balance = 0; + + Sibling::execute_with(|| { + treasury_initial_balance = + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()); + assert_eq!(Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()), 0); + + register_foreign_ztg(None); + let custom_metadata = CustomMetadata { + xcm: XcmMetadata { fee_factor: Some(fee_factor) }, + ..Default::default() + }; + assert_ok!(AssetRegistry::do_update_asset( + FOREIGN_ZTG_ID, + None, + None, + None, + None, + None, + Some(custom_metadata) + )); + }); + + Zeitgeist::execute_with(|| { + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance); + assert_eq!(Balances::free_balance(&sibling_parachain_account()), 0); + assert_ok!(XTokens::transfer( + Origin::signed(ALICE.into()), + CurrencyId::Ztg, + transfer_amount, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(PARA_ID_SIBLING), + Junction::AccountId32 { network: NetworkId::Any, id: BOB } + ) + ) + .into() + ), + 4_000_000_000, + )); + + // Confirm that Alice's balance is initial_balance - amount_transferred + assert_eq!(Balances::free_balance(&ALICE.into()), alice_initial_balance - transfer_amount); + + // Verify that the amount transferred is now part of the sibling account here + assert_eq!(Balances::free_balance(&sibling_parachain_account()), transfer_amount); + }); + + Sibling::execute_with(|| { + let current_balance = Tokens::free_balance(FOREIGN_ZTG_ID, &BOB.into()); + let custom_fee = calc_fee(default_per_second(10) * 10); + + // Verify that BOB now has (amount transferred - fee) + assert_eq!(current_balance, transfer_amount - custom_fee); + + // Sanity check for the actual amount BOB ends up with + assert_eq!(current_balance, 49_073_040_000); + + // Verify that fees (of foreign currency) have been put into treasury + assert_eq!( + Tokens::free_balance(FOREIGN_ZTG_ID, &ZeitgeistTreasuryAccount::get()), + treasury_initial_balance + custom_fee + ) + }); +} + +#[test] +fn test_total_fee() { + assert_eq!(ztg_fee(), 92_696_000); + assert_eq!(ksm_fee(), 9_269_600_000); +} + +#[inline] +fn ztg_fee() -> Balance { + fee(BalanceFractionalDecimals::get().into()) +} + +#[inline] +fn fee(decimals: u32) -> Balance { + calc_fee(default_per_second(decimals)) +} + +// The fee associated with transferring KSM tokens +#[inline] +fn ksm_fee() -> Balance { + fee(12) +} + +#[inline] +fn calc_fee(fee_per_second: Balance) -> Balance { + // We divide the fee to align its unit and multiply by 8 as that seems to be the unit of + // time the tests take. + // NOTE: it is possible that in different machines this value may differ. We shall see. + fee_per_second / 10_000 * 8 +} diff --git a/runtime/zeitgeist/src/lib.rs b/runtime/zeitgeist/src/lib.rs index 117ca70be..35287d249 100644 --- a/runtime/zeitgeist/src/lib.rs +++ b/runtime/zeitgeist/src/lib.rs @@ -53,10 +53,13 @@ use zeitgeist_primitives::{constants::*, types::*}; use zrml_rikiddo::types::{EmaMarketVolume, FeeSigmoid, RikiddoSigmoidMV}; #[cfg(feature = "parachain")] use { - frame_support::traits::{Everything, Nothing}, + frame_support::traits::{AsEnsureOriginWithArg, Everything, Nothing}, frame_system::EnsureSigned, xcm_builder::{EnsureXcmOrigin, FixedWeightBounds, LocationInverter}, - xcm_config::XcmConfig, + xcm_config::{ + asset_registry::{CustomAssetProcessor, CustomMetadata}, + config::{LocalOriginToLocation, XcmConfig, XcmOriginToTransactDispatchOrigin, XcmRouter}, + }, }; use frame_support::construct_runtime; @@ -74,6 +77,8 @@ use sp_runtime::{ use nimbus_primitives::{CanAuthor, NimbusId}; use sp_version::RuntimeVersion; +#[cfg(test)] +pub mod integration_tests; #[cfg(feature = "parachain")] pub mod parachain_params; pub mod parameters; diff --git a/runtime/zeitgeist/src/parachain_params.rs b/runtime/zeitgeist/src/parachain_params.rs index 733f4ea16..3e80d851c 100644 --- a/runtime/zeitgeist/src/parachain_params.rs +++ b/runtime/zeitgeist/src/parachain_params.rs @@ -22,33 +22,16 @@ )] #![cfg(feature = "parachain")] -use super::{ - parameters::MAXIMUM_BLOCK_WEIGHT, AccountId, Balances, Origin, ParachainInfo, ParachainSystem, - XcmpQueue, -}; -use frame_support::{match_types, parameter_types, traits::Everything, weights::Weight}; -use pallet_xcm::XcmPassthrough; -use polkadot_parachain::primitives::Sibling; -use sp_runtime::{Perbill, Percent, SaturatedConversion}; -use xcm::latest::{BodyId, Junction, Junctions, MultiLocation, NetworkId}; -use xcm_builder::{ - AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter, - IsConcrete, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, -}; +use super::{parameters::MAXIMUM_BLOCK_WEIGHT, Origin, ParachainInfo}; +use frame_support::{parameter_types, weights::Weight}; +use orml_traits::parameter_type_with_key; +use sp_runtime::{Perbill, Percent}; +use xcm::latest::{prelude::X1, Junction::Parachain, MultiLocation, NetworkId}; use zeitgeist_primitives::{ - constants::{BASE, BLOCKS_PER_MINUTE, MICRO}, + constants::{BASE, BLOCKS_PER_MINUTE}, types::Balance, }; -match_types! { - pub type ParentOrParentsUnitPlurality: impl Contains = { - MultiLocation { parents: 1, interior: Junctions::Here } | - // Potentially change "Unit" to "Executive" for mainnet once we have separate runtimes - MultiLocation { parents: 1, interior: Junctions::X1(Junction::Plurality { id: BodyId::Unit, .. }) } - }; -} parameter_types! { // Author-Mapping /// The amount that should be taken as a security deposit when registering a NimbusId. @@ -63,14 +46,13 @@ parameter_types! { pub const SignatureNetworkIdentifier: &'static [u8] = b"zeitgeist-"; // Cumulus and Polkadot - pub Ancestry: MultiLocation = Junction::Parachain(ParachainInfo::parachain_id().into()).into(); + pub Ancestry: MultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); pub const RelayLocation: MultiLocation = MultiLocation::parent(); - // Have to change "Any" to "Kusama" for mainnet once we have separate runtimes - pub const RelayNetwork: NetworkId = NetworkId::Any; + pub const RelayNetwork: NetworkId = NetworkId::Kusama; pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT / 4; pub RelayChainOrigin: Origin = cumulus_pallet_xcm::Origin::Relay.into(); - pub UnitWeightCost: Weight = MICRO.saturated_into(); + pub UnitWeightCost: Weight = 200_000_000; // Staking /// Rounds before the candidate bond increase/decrease can be executed @@ -107,71 +89,20 @@ parameter_types! { pub const RewardPaymentDelay: u32 = 2; // XCM + /// Base weight for XCM execution + pub const BaseXcmWeight: Weight = 200_000_000; + /// The maximum number of distinct assets allowed to be transferred in a + /// single helper extrinsic. + pub const MaxAssetsForTransfer: usize = 2; + /// Max instructions per XCM pub const MaxInstructions: u32 = 100; + // Relative self location + pub SelfLocation: MultiLocation = MultiLocation::new(1, X1(Parachain(ParachainInfo::parachain_id().into()))); } -pub type Barrier = ( - TakeWeightCredit, - AllowTopLevelPaidExecutionFrom, - AllowUnpaidExecutionFrom, - // ^^^ Parent and its exec plurality get free execution -); - -/// Means for transacting assets on this chain. -pub type LocalAssetTransactor = CurrencyAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 MultiLocation into a native chain account ID: - LocationToAccountId, - // Our chain's account ID type (we can't get away without mentioning it explicitly): - AccountId, - // We don't track any teleports. - (), ->; - -/// No local origins on this chain are allowed to dispatch XCM sends/executions. -pub type LocalOriginToLocation = SignedToAccountId32; - -/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. -pub type LocationToAccountId = ( - // The parent (Relay-chain) origin converts to the parent `AccountId`. - ParentIsPreset, - // Sibling parachain origins convert to AccountId via the `ParaId::into`. - SiblingParachainConvertsVia, - // Straight up local `AccountId32` origins just alias directly to `AccountId`. - AccountId32Aliases, -); - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will converts to a `Relay` origin when - // recognized. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognized. - SiblingParachainAsNative, - // Native signed account converter; this just converts an `AccountId32` origin into a normal - // `Origin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, - // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, -); - -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = ( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -); +parameter_type_with_key! { + // XCM + pub ParachainMinFee: |_location: MultiLocation| -> Option { + None + }; +} diff --git a/runtime/zeitgeist/src/parameters.rs b/runtime/zeitgeist/src/parameters.rs index 2cc2c3132..76cf826a8 100644 --- a/runtime/zeitgeist/src/parameters.rs +++ b/runtime/zeitgeist/src/parameters.rs @@ -56,7 +56,7 @@ parameter_types! { pub const MaxAuthorities: u32 = 32; // Balance - pub const ExistentialDeposit: u128 = 5 * CENT; + pub const ExistentialDeposit: u128 = 5 * MILLI; pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; @@ -311,7 +311,9 @@ parameter_types! { /// Period between successive spends. pub const SpendPeriod: BlockNumber = 24 * BLOCKS_PER_DAY; /// Pallet identifier, mainly used for named balance reserves. DO NOT CHANGE. - pub const TreasuryPalletId: PalletId = PalletId(*b"zge/tsry"); + pub const TreasuryPalletId: PalletId = TREASURY_PALLET_ID; + /// Treasury account. + pub ZeitgeistTreasuryAccount: AccountId = TreasuryPalletId::get().into_account_truncating(); // Bounties /// The amount held on deposit for placing a bounty proposal. diff --git a/runtime/zeitgeist/src/xcm_config.rs b/runtime/zeitgeist/src/xcm_config.rs deleted file mode 100644 index 6a70b65b6..000000000 --- a/runtime/zeitgeist/src/xcm_config.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021-2022 Zeitgeist PM LLC. -// -// This file is part of Zeitgeist. -// -// Zeitgeist 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. -// -// Zeitgeist 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. -// -// You should have received a copy of the GNU General Public License -// along with Zeitgeist. If not, see . - -use super::{ - AccountId, Ancestry, Balance, Balances, Barrier, Call, LocalAssetTransactor, MaxInstructions, - PolkadotXcm, RelayLocation, UnitWeightCost, XcmOriginToTransactDispatchOrigin, XcmRouter, -}; -use frame_support::weights::IdentityFee; -use xcm_builder::{FixedWeightBounds, LocationInverter, NativeAsset, UsingComponents}; -use xcm_executor::Config; - -pub struct XcmConfig; - -impl Config for XcmConfig { - type AssetClaims = PolkadotXcm; - type AssetTransactor = LocalAssetTransactor; - type AssetTrap = PolkadotXcm; - type Barrier = Barrier; - type Call = Call; - type IsReserve = NativeAsset; - type IsTeleporter = (); - type LocationInverter = LocationInverter; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type ResponseHandler = PolkadotXcm; - type SubscriptionService = PolkadotXcm; - type Trader = UsingComponents, RelayLocation, AccountId, Balances, ()>; - type Weigher = FixedWeightBounds; - type XcmSender = XcmRouter; -} diff --git a/runtime/zeitgeist/src/xcm_config/asset_registry.rs b/runtime/zeitgeist/src/xcm_config/asset_registry.rs new file mode 100644 index 000000000..ba7807a8c --- /dev/null +++ b/runtime/zeitgeist/src/xcm_config/asset_registry.rs @@ -0,0 +1,94 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{Balance, CurrencyId}; +use orml_traits::asset_registry::{AssetMetadata, AssetProcessor}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::DispatchError; + +#[derive( + Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +/// Implements orml_traits::asset_registry::AssetProcessor. Does not apply any post checks. +/// Only pre check is to ensure an asset id was passed. +pub struct CustomAssetProcessor; + +impl AssetProcessor> for CustomAssetProcessor { + fn pre_register( + id: Option, + metadata: AssetMetadata, + ) -> Result<(CurrencyId, AssetMetadata), DispatchError> { + match id { + Some(id) => Ok((id, metadata)), + None => Err(DispatchError::Other("asset-registry: AssetId is required")), + } + } + + fn post_register( + _id: CurrencyId, + _asset_metadata: AssetMetadata, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + +#[derive( + Clone, + Copy, + Default, + PartialOrd, + Ord, + PartialEq, + Eq, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +/// Custom XC asset metadata +pub struct CustomMetadata { + /// XCM-related metadata. + pub xcm: XcmMetadata, + + /// Whether an asset can be used in pools. + pub allow_in_pool: bool, +} + +#[derive( + Clone, + Copy, + Default, + PartialOrd, + Ord, + PartialEq, + Eq, + Debug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +pub struct XcmMetadata { + /// The factor used to determine the fee. + /// It is multiplied by the fee that would have been paid in native currency, so it represents + /// the ratio `native_price / other_asset_price`. It is a fixed point decimal number containing + /// as many fractional decimals as the asset it is used for contains. + /// Should be updated regularly. + pub fee_factor: Option, +} diff --git a/runtime/zeitgeist/src/xcm_config/config.rs b/runtime/zeitgeist/src/xcm_config/config.rs new file mode 100644 index 000000000..8520f5a86 --- /dev/null +++ b/runtime/zeitgeist/src/xcm_config/config.rs @@ -0,0 +1,311 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use super::fees::{native_per_second, FixedConversionRateProvider}; +use crate::{ + AccountId, Ancestry, AssetManager, AssetRegistry, Balance, Call, CurrencyId, MaxInstructions, + Origin, ParachainInfo, ParachainSystem, PolkadotXcm, RelayChainOrigin, RelayNetwork, + UnitWeightCost, UnknownTokens, XcmpQueue, ZeitgeistTreasuryAccount, +}; + +use frame_support::{parameter_types, traits::Everything, WeakBoundedVec}; +use orml_asset_registry::{AssetRegistryTrader, FixedRateAssetRegistryTrader}; +use orml_traits::{location::AbsoluteReserveProvider, MultiCurrency}; +use orml_xcm_support::{ + DepositToAlternative, IsNativeConcrete, MultiCurrencyAdapter, MultiNativeAsset, +}; +use pallet_xcm::XcmPassthrough; +use polkadot_parachain::primitives::Sibling; +use sp_runtime::traits::Convert; +use xcm::{ + latest::{ + prelude::{AccountId32, AssetId, Concrete, GeneralKey, MultiAsset, NetworkId, X1, X2}, + Junction, MultiLocation, + }, + opaque::latest::Fungibility::Fungible, +}; +use xcm_builder::{ + AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, LocationInverter, + ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeRevenue, + TakeWeightCredit, +}; +use xcm_executor::Config; +use zeitgeist_primitives::types::Asset; + +pub mod zeitgeist { + #[cfg(test)] + pub const ID: u32 = 2101; + pub const KEY: &[u8] = &[0, 1]; +} + +pub struct XcmConfig; + +/// The main XCM config +/// This is where we configure the core of our XCM integrations: how tokens are transferred, +/// how fees are calculated, what barriers we impose on incoming XCM messages, etc. +impl Config for XcmConfig { + /// The handler for when there is an instruction to claim assets. + type AssetClaims = PolkadotXcm; + /// How to withdraw and deposit an asset. + type AssetTransactor = MultiAssetTransactor; + /// The general asset trap - handler for when assets are left in the Holding Register at the + /// end of execution. + type AssetTrap = PolkadotXcm; + /// Additional filters that specify whether the XCM instruction should be executed at all. + type Barrier = Barrier; + /// The outer call dispatch type. + type Call = Call; + /// Combinations of (Location, Asset) pairs which are trusted as reserves. + // Trust the parent chain, sibling parachains and children chains of this chain. + type IsReserve = MultiNativeAsset; + /// Combinations of (Location, Asset) pairs which we trust as teleporters. + type IsTeleporter = (); + /// Means of inverting a location. + type LocationInverter = LocationInverter; + /// How to get a call origin from a `OriginKind` value. + type OriginConverter = XcmOriginToTransactDispatchOrigin; + /// Module that handles responses of queries. + type ResponseHandler = PolkadotXcm; + /// Module that handles subscription requests. + type SubscriptionService = PolkadotXcm; + /// The means of purchasing weight credit for XCM execution. + type Trader = Trader; + /// The means of determining an XCM message's weight. + // Adds UnitWeightCost per instruction plus the weight of each instruction. + // The total number of instructions are bounded by MaxInstructions + type Weigher = FixedWeightBounds; + /// How to send an onward XCM message. + type XcmSender = XcmRouter; +} + +/// Additional filters that specify whether the XCM instruction should be executed at all. +pub type Barrier = ( + // Execution barrier that just takes max_weight from weight_credit + TakeWeightCredit, + // Ensures that execution time is bought with BuyExecution instruction + AllowTopLevelPaidExecutionFrom, + // Expected responses are OK. + AllowKnownQueryResponses, + // Subscriptions for version tracking are OK. + AllowSubscriptionsFrom, +); + +/// The means of purchasing weight credit for XCM execution. +/// Every token that is accepted for XC transfers should be handled here. +pub type Trader = ( + // In case the asset in question is the native currency, it will charge + // the default base fee per second and deposits them into treasury + FixedRateOfFungible, + FixedRateOfFungible, + // For all other assets the base fee per second will tried to be derived + // through the `fee_factor` entry in the asset registry. If the asset is + // not present in the asset registry, the default base fee per second is used. + // Deposits all fees into the treasury. + AssetRegistryTrader< + FixedRateAssetRegistryTrader>, + ToTreasury, + >, +); + +pub struct ToTreasury; +impl TakeRevenue for ToTreasury { + fn take_revenue(revenue: MultiAsset) { + use xcm_executor::traits::Convert; + + if let MultiAsset { id: Concrete(location), fun: Fungible(amount) } = revenue { + if let Ok(asset_id) = + >::convert(location) + { + let _ = AssetManager::deposit(asset_id, &ZeitgeistTreasuryAccount::get(), amount); + } + } + } +} + +parameter_types! { + pub CheckAccount: AccountId = PolkadotXcm::check_account(); + /// The amount of ZTG charged per second of execution (canonical multilocation). + pub ZtgPerSecondCanonical: (AssetId, u128) = ( + MultiLocation::new( + 0, + X1(general_key(zeitgeist::KEY)), + ).into(), + native_per_second(), + ); + /// The amount of ZTG charged per second of execution. + pub ZtgPerSecond: (AssetId, u128) = ( + MultiLocation::new( + 1, + X2(Junction::Parachain(ParachainInfo::parachain_id().into()), general_key(zeitgeist::KEY)), + ).into(), + native_per_second(), + ); +} + +/// Means for transacting assets on this chain. +pub type MultiAssetTransactor = MultiCurrencyAdapter< + // All known Assets will be processed by the following MultiCurrency implementation. + AssetManager, + // Any unknown Assets will be processed by the following UnknownAsset implementation. + UnknownTokens, + // This means that this adapter should handle any token that `AssetConvert` can convert + // using AssetManager and UnknownTokens in all other cases. + IsNativeConcrete, + // Our chain's account ID type (we can't get away without mentioning it explicitly). + AccountId, + // Convert an XCM `MultiLocation` into a local account id. + LocationToAccountId, + // The AssetId that corresponds to the native currency. + CurrencyId, + // Struct that provides functions to convert `Asset` <=> `MultiLocation`. + AssetConvert, + // In case of deposit failure, known assets will be placed in treasury. + DepositToAlternative, +>; + +/// AssetConvert +/// This type implements conversions from our `Asset` type into `MultiLocation` and vice-versa. +/// A currency locally is identified with a `Asset` variant but in the network it is identified +/// in the form of a `MultiLocation`, in this case a pair (Para-Id, Currency-Id). +pub struct AssetConvert; + +/// Convert our `Asset` type into its `MultiLocation` representation. +/// Other chains need to know how this conversion takes place in order to +/// handle it on their side. +impl Convert> for AssetConvert { + fn convert(id: CurrencyId) -> Option { + match id { + Asset::Ztg => Some(MultiLocation::new( + 1, + X2( + Junction::Parachain(ParachainInfo::parachain_id().into()), + general_key(zeitgeist::KEY), + ), + )), + Asset::ForeignAsset(_) => AssetRegistry::multilocation(&id).ok()?, + _ => None, + } + } +} + +/// Convert an incoming `MultiLocation` into a `Asset` if possible. +/// Here we need to know the canonical representation of all the tokens we handle in order to +/// correctly convert their `MultiLocation` representation into our internal `Asset` type. +impl xcm_executor::traits::Convert for AssetConvert { + fn convert(location: MultiLocation) -> Result { + match location.clone() { + MultiLocation { parents: 0, interior: X1(GeneralKey(key)) } => { + if &key[..] == zeitgeist::KEY { + return Ok(CurrencyId::Ztg); + } + + Err(location) + } + MultiLocation { + parents: 1, + interior: X2(Junction::Parachain(para_id), GeneralKey(key)), + } => { + if para_id == u32::from(ParachainInfo::parachain_id()) { + if &key[..] == zeitgeist::KEY { + return Ok(CurrencyId::Ztg); + } + + return Err(location); + } + + AssetRegistry::location_to_asset_id(location.clone()).ok_or(location) + } + _ => AssetRegistry::location_to_asset_id(location.clone()).ok_or(location), + } + } +} + +impl Convert> for AssetConvert { + fn convert(asset: MultiAsset) -> Option { + if let MultiAsset { id: Concrete(location), .. } = asset { + >::convert(location).ok() + } else { + None + } + } +} + +impl Convert> for AssetConvert { + fn convert(location: MultiLocation) -> Option { + >::convert(location).ok() + } +} + +pub struct AccountIdToMultiLocation; + +impl Convert for AccountIdToMultiLocation { + fn convert(account: AccountId) -> MultiLocation { + X1(AccountId32 { network: NetworkId::Any, id: account.into() }).into() + } +} + +/// No local origins on this chain are allowed to dispatch XCM sends/executions. +pub type LocalOriginToLocation = SignedToAccountId32; + +/// Type for specifying how a `MultiLocation` can be converted into an `AccountId`. This is used +/// when determining ownership of accounts for asset transacting and when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin. +pub type LocationToAccountId = ( + // The parent (Relay-chain) origin converts to the parent `AccountId`. + ParentIsPreset, + // Sibling parachain origins convert to AccountId via the `ParaId::into`. + SiblingParachainConvertsVia, + // Straight up local `AccountId32` origins just alias directly to `AccountId`. + AccountId32Aliases, +); + +/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, +/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can +/// biases the kind of local `Origin` it will become. +pub type XcmOriginToTransactDispatchOrigin = ( + // Sovereign account converter; this attempts to derive an `AccountId` from the origin location + // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for + // foreign chains who want to have a local sovereign account on this chain which they control. + SovereignSignedViaLocation, + // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when + // recognized. + RelayChainAsNative, + // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when + // recognized. + SiblingParachainAsNative, + // Native signed account converter; this just converts an `AccountId32` origin into a normal + // `Origin::Signed` origin of the same 32-byte value. + SignedAccountId32AsNative, + // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. + XcmPassthrough, +); + +/// The means for routing XCM messages which are not for local execution into the right message +/// queues. +pub type XcmRouter = ( + // Two routers - use UMP to communicate with the relay chain: + cumulus_primitives_utility::ParentAsUmp, + // ..and XCMP to communicate with the sibling chains. + XcmpQueue, +); + +#[inline] +pub(crate) fn general_key(key: &[u8]) -> Junction { + GeneralKey(WeakBoundedVec::force_from(key.to_vec(), None)) +} diff --git a/runtime/zeitgeist/src/xcm_config/fees.rs b/runtime/zeitgeist/src/xcm_config/fees.rs new file mode 100644 index 000000000..b70484d97 --- /dev/null +++ b/runtime/zeitgeist/src/xcm_config/fees.rs @@ -0,0 +1,79 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +use crate::{xcm_config::asset_registry::CustomMetadata, Balance, CurrencyId}; +use core::marker::PhantomData; +use frame_support::weights::constants::{ExtrinsicBaseWeight, WEIGHT_PER_SECOND}; +use xcm::latest::MultiLocation; +use zeitgeist_primitives::constants::BalanceFractionalDecimals; +use zrml_swaps::check_arithm_rslt::CheckArithmRslt; + +/// The fee cost per second for transferring the native token in cents. +pub fn native_per_second() -> Balance { + default_per_second(BalanceFractionalDecimals::get().into()) +} + +pub fn default_per_second(decimals: u32) -> Balance { + let base_weight = Balance::from(ExtrinsicBaseWeight::get()); + let default_per_second = (WEIGHT_PER_SECOND as u128) / base_weight; + default_per_second * base_fee(decimals) +} + +fn base_fee(decimals: u32) -> Balance { + cent(decimals).saturating_div(10) +} + +/// 1 Asset in fixed point decimal representation +pub fn dollar(decimals: u32) -> Balance { + 10u128.saturating_pow(decimals) +} + +/// 0.01 Asset in fixed point decimal presentation +pub fn cent(decimals: u32) -> Balance { + dollar(decimals).saturating_div(100) +} + +pub fn bmul(a: u128, b: u128, base: u128) -> Option { + let c0 = a.check_mul_rslt(&b).ok()?; + let c1 = c0.check_add_rslt(&base.check_div_rslt(&2).ok()?).ok()?; + c1.check_div_rslt(&base).ok() +} + +/// Our FixedConversionRateProvider, used to charge XCM-related fees for tokens registered in +/// the asset registry that were not already handled by native Trader rules. +pub struct FixedConversionRateProvider(PhantomData); + +impl< + AssetRegistry: orml_traits::asset_registry::Inspect< + AssetId = CurrencyId, + Balance = Balance, + CustomMetadata = CustomMetadata, + >, +> orml_traits::FixedConversionRateProvider for FixedConversionRateProvider +{ + fn get_fee_per_second(location: &MultiLocation) -> Option { + let metadata = AssetRegistry::metadata_by_location(location)?; + let default_per_second = default_per_second(metadata.decimals); + + if let Some(fee_factor) = metadata.additional.xcm.fee_factor { + let base = 10u128.checked_pow(metadata.decimals)?; + bmul(default_per_second, fee_factor, base) + } else { + Some(default_per_second) + } + } +} diff --git a/runtime/zeitgeist/src/xcm_config/mod.rs b/runtime/zeitgeist/src/xcm_config/mod.rs new file mode 100644 index 000000000..363ac87b1 --- /dev/null +++ b/runtime/zeitgeist/src/xcm_config/mod.rs @@ -0,0 +1,22 @@ +// Copyright 2022 Zeitgeist PM LLC. +// +// This file is part of Zeitgeist. +// +// Zeitgeist 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. +// +// Zeitgeist 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. +// +// You should have received a copy of the GNU General Public License +// along with Zeitgeist. If not, see . + +#![cfg(feature = "parachain")] + +pub mod asset_registry; +pub mod config; +pub mod fees; diff --git a/zrml/swaps/src/lib.rs b/zrml/swaps/src/lib.rs index a3d1d254c..9164b2abd 100644 --- a/zrml/swaps/src/lib.rs +++ b/zrml/swaps/src/lib.rs @@ -32,11 +32,11 @@ mod utils; mod arbitrage; mod benchmarks; -mod check_arithm_rslt; +pub mod check_arithm_rslt; mod consts; mod events; -mod fixed; -mod math; +pub mod fixed; +pub mod math; pub mod migrations; pub mod mock; mod root;