diff --git a/Cargo.lock b/Cargo.lock index 80f8dea218..a37a5b9897 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3808,6 +3808,7 @@ dependencies = [ "pswap-distribution-runtime-api", "qa-tools", "referrals", + "regulated-assets", "rewards", "rewards-runtime-api", "scale-info", @@ -8455,6 +8456,29 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "regulated-assets" +version = "1.0.0" +dependencies = [ + "assets", + "common", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "orml-currencies", + "orml-tokens", + "pallet-balances", + "parity-scale-codec", + "permissions", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "technical", +] + [[package]] name = "remote-ext" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5a12700814..3ee7b45490 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -279,6 +279,7 @@ members = [ "pallets/kensetsu", "pallets/band", "pallets/qa-tools", + "pallets/regulated-assets", "node/", "utils/parse", "utils/generate-bags", diff --git a/common/src/mock.rs b/common/src/mock.rs index a060edbead..99ede9dc99 100644 --- a/common/src/mock.rs +++ b/common/src/mock.rs @@ -217,10 +217,10 @@ macro_rules! mock_assets_config { type GetBuyBackAccountId = GetBuyBackAccountId; type GetBuyBackDexId = GetBuyBackDexId; type BuyBackLiquidityProxy = (); - type Currency = currencies::Pallet; + type Currency = currencies::Pallet<$runtime>; type GetTotalBalance = (); type WeightInfo = (); - type AssetRegulator = permissions::Pallet; + type AssetRegulator = permissions::Pallet<$runtime>; } }; } diff --git a/common/src/permissions.rs b/common/src/permissions.rs index 9cfec1ac96..3fea108020 100644 --- a/common/src/permissions.rs +++ b/common/src/permissions.rs @@ -42,3 +42,5 @@ pub const UNLOCK_FROM_FARM: PermissionId = 13; pub const CLAIM_FROM_FARM: PermissionId = 10; pub const GET_FARM_INFO: PermissionId = 11; pub const GET_FARMER_INFO: PermissionId = 12; +pub const ISSUE_SBT: PermissionId = 13; +pub const TRANSFER: PermissionId = 14; diff --git a/common/src/primitives.rs b/common/src/primitives.rs index 5f5a92e884..5bc2c8b45c 100644 --- a/common/src/primitives.rs +++ b/common/src/primitives.rs @@ -553,6 +553,12 @@ impl IsValid for AssetName { } } +impl MaxEncodedLen for AssetName { + fn max_encoded_len() -> usize { + ASSET_NAME_MAX_LENGTH + } +} + #[derive( Encode, Decode, @@ -631,6 +637,12 @@ impl IsValid for Description { } } +impl MaxEncodedLen for Description { + fn max_encoded_len() -> usize { + ASSET_DESCRIPTION_MAX_LENGTH + } +} + #[derive( Encode, Decode, diff --git a/common/src/traits.rs b/common/src/traits.rs index b5e15c4505..c16697724e 100644 --- a/common/src/traits.rs +++ b/common/src/traits.rs @@ -1166,6 +1166,7 @@ pub trait AssetManager< + MaybeSerializeDeserialize + Ord + Default + + Clone + From> + From + Into diff --git a/pallets/assets/src/lib.rs b/pallets/assets/src/lib.rs index f7b09cc259..9eccaf7f27 100644 --- a/pallets/assets/src/lib.rs +++ b/pallets/assets/src/lib.rs @@ -53,7 +53,7 @@ mod mock; mod tests; use codec::{Decode, Encode}; -use common::permissions::{BURN, MINT}; +use common::permissions::{BURN, MINT, TRANSFER}; use common::prelude::{Balance, SwapAmount}; use common::{ Amount, AssetInfoProvider, AssetManager, AssetName, AssetRegulator, AssetSymbol, @@ -761,6 +761,7 @@ impl Pallet { to: &T::AccountId, amount: Balance, ) -> DispatchResult { + T::AssetRegulator::check_permission(from, to, asset_id, &TRANSFER)?; let r = T::Currency::transfer(*asset_id, from, to, amount); if r.is_err() { Self::ensure_asset_exists(asset_id)?; diff --git a/pallets/permissions/src/lib.rs b/pallets/permissions/src/lib.rs index dda1be55a4..ef5b5f2872 100644 --- a/pallets/permissions/src/lib.rs +++ b/pallets/permissions/src/lib.rs @@ -48,6 +48,7 @@ // TODO #167: fix clippy warnings #![allow(clippy::all)] +use common::permissions::TRANSFER; use common::{hash, AssetRegulator}; use frame_support::codec::{Decode, Encode}; use frame_support::sp_runtime::DispatchError; @@ -382,6 +383,9 @@ impl AssetRegulator, AssetId> for Pal if permission_id == &BURN && affected_account == issuer { return Ok(()); } + if permission_id == &TRANSFER { + return Ok(()); + } Self::check_permission_with_scope( issuer.clone(), *permission_id, diff --git a/pallets/regulated-assets/Cargo.toml b/pallets/regulated-assets/Cargo.toml new file mode 100644 index 0000000000..cf525b63e2 --- /dev/null +++ b/pallets/regulated-assets/Cargo.toml @@ -0,0 +1,64 @@ +[package] +authors = ["Polka Biome Ltd. "] +license = "BSD-4-Clause" +homepage = "https://sora.org" +repository = "https://github.com/sora-xor/sora2-network" +edition = "2021" +name = "regulated-assets" +version = "1.0.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2", default-features = false, features = ["derive"] } +frame-benchmarking = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false, optional = true } +frame-support = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false } +frame-system = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false } +sp-std = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false } +sp-core = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false } +hex-literal = { version = "0.4.1", optional = true } + +common = { path = "../../common", default-features = false } +permissions = { path = "../permissions", default-features = false } +technical = { path = "../technical", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false } +sp-runtime = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38", default-features = false } +pallet-balances = { git = "https://github.com/sora-xor/substrate.git", branch = "polkadot-v0.9.38" } +currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", package = "orml-currencies" } +tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", package = "orml-tokens", default-features = false } +hex-literal = { version = "0.4.1"} +common = { path = "../../common", features = ['test']} +assets = { path = "../assets" } +permissions = { path = "../permissions" } +technical = { path = "../technical" } + +[features] +default = ["std"] + +std = [ + "scale-info/std", + "frame-support/std", + "frame-benchmarking/std", + "frame-system/std", + "sp-core/std", + "sp-std/std", +] + +runtime-benchmarks = [ + "frame-benchmarking", + "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "hex-literal", +] + +try-runtime = [ + "frame-support/try-runtime", +] + +wip = [] diff --git a/pallets/regulated-assets/src/benchmarking.rs b/pallets/regulated-assets/src/benchmarking.rs new file mode 100644 index 0000000000..8194358049 --- /dev/null +++ b/pallets/regulated-assets/src/benchmarking.rs @@ -0,0 +1,137 @@ +// This file is part of the SORA network and Polkaswap app. + +// Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +// SPDX-License-Identifier: BSD-4-Clause + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// All advertising materials mentioning features or use of this software must display +// the following acknowledgement: This product includes software developed by Polka Biome +// Ltd., SORA, and Polkaswap. +// +// Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Regulated Assets module benchmarking. +#![cfg(feature = "runtime-benchmarks")] +#![cfg(feature = "wip")] // DEFI-R + +use codec::Decode; +use frame_benchmarking::benchmarks; +use frame_system::EventRecord; +use frame_system::RawOrigin; +use hex_literal::hex; +use sp_std::prelude::*; + +use super::*; + +use common::{AssetManager, AssetName, AssetSymbol, Balance, DEFAULT_BALANCE_PRECISION}; + +// Support Functions +fn asset_owner() -> T::AccountId { + let bytes = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); + T::AccountId::decode(&mut &bytes[..]).expect("Failed to decode account ID") +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn add_asset() -> AssetIdOf { + let owner = asset_owner::(); + frame_system::Pallet::::inc_providers(&owner); + + T::AssetManager::register_from( + &owner, + AssetSymbol(b"TOKEN".to_vec()), + AssetName(b"TOKEN".to_vec()), + DEFAULT_BALANCE_PRECISION, + Balance::from(0u32), + true, + None, + None, + ) + .expect("Failed to register asset") +} + +fn assign_issue_sbt_permission(owner: T::AccountId, holder: T::AccountId) { + frame_system::Pallet::::inc_providers(&owner); + permissions::Pallet::::assign_permission( + owner, + &holder, + common::permissions::ISSUE_SBT, + permissions::Scope::Unlimited, + ) + .unwrap(); +} + +benchmarks! { + regulate_asset { + let owner = asset_owner::(); + let owner_origin: ::RuntimeOrigin = RawOrigin::Signed(owner).into(); + let asset_id = add_asset::(); + }: { + Pallet::::regulate_asset(owner_origin, asset_id).unwrap(); + } + verify{ + assert_last_event::(Event::AssetRegulated{ + asset_id + }.into() + ); + } + + issue_sbt{ + let owner = asset_owner::(); + assign_issue_sbt_permission::(owner.clone(), owner.clone()); + let owner_origin: ::RuntimeOrigin = RawOrigin::Signed(owner).into(); + let asset_id = add_asset::(); + let asset_name = AssetName(b"Soulbound Token".to_vec()); + let asset_symbol = AssetSymbol(b"SBT".to_vec()); + let bounded_vec_assets = BoundedVec::try_from(vec![asset_id]).unwrap(); + }: { + Pallet::::issue_sbt( + owner_origin, + asset_symbol, + asset_name.clone(), + bounded_vec_assets.clone(), + None + ).unwrap(); + } + verify{ + let sbts = Pallet::::sbts_by_asset(asset_id); + let sbt_asset_id = sbts.first().ok_or("No SBT asset found").unwrap(); + + assert_last_event::(Event::SoulboundTokenIssued { + asset_id: *sbt_asset_id, + owner: asset_owner::(), + allowed_assets: vec![asset_id] + }.into() + ); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::TestRuntime + ); +} diff --git a/pallets/regulated-assets/src/lib.rs b/pallets/regulated-assets/src/lib.rs new file mode 100644 index 0000000000..7fb6d94d23 --- /dev/null +++ b/pallets/regulated-assets/src/lib.rs @@ -0,0 +1,327 @@ +// This file is part of the SORA network and Polkaswap app. + +// Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +// SPDX-License-Identifier: BSD-4-Clause + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// All advertising materials mentioning features or use of this software must display +// the following acknowledgement: This product includes software developed by Polka Biome +// Ltd., SORA, and Polkaswap. +// +// Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! The Regulated Assets pallet allows for the configuration and management of access to regulated assets. +//! It provides functionalities to issue Soulbound Tokens (SBTs) and regulate assets, ensuring only +//! authorized users can operate with these assets. +//! The pallet checks permissions based on asset ownership and SBT holdings, preventing unauthorized operations and transfers. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub mod weights; + +use codec::{Decode, Encode, MaxEncodedLen}; +use common::{ + permissions::{PermissionId, ISSUE_SBT, TRANSFER}, + AssetIdOf, AssetInfoProvider, AssetManager, AssetName, AssetRegulator, AssetSymbol, + BalancePrecision, ContentSource, Description, +}; +use frame_support::sp_runtime::DispatchError; +use frame_support::{BoundedBTreeSet, BoundedVec}; +use sp_core::Get; +use sp_std::vec::Vec; +use weights::WeightInfo; + +type AccountIdOf = ::AccountId; +type Permissions = permissions::Pallet; +type Technical = technical::Pallet; + +pub use pallet::*; + +#[derive(Clone, Eq, Encode, Decode, scale_info::TypeInfo, PartialEq, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxAllowedTokensPerSBT))] +pub struct SoulboundTokenMetadata> { + name: AssetName, + description: Option, + allowed_assets: BoundedVec, +} + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + use common::DEFAULT_BALANCE_PRECISION; + use frame_support::pallet_prelude::{OptionQuery, ValueQuery, *}; + use frame_support::traits::StorageVersion; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: + frame_system::Config + common::Config + permissions::Config + technical::Config + { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Max number of allowed tokens per one Soulbound Token + #[pallet::constant] + type MaxAllowedTokensPerSBT: Get; + + /// Max number of SBTs per one Soulbound Token + #[pallet::constant] + type MaxSBTsPerAsset: Get; + + /// To retrieve asset info + type AssetInfoProvider: AssetInfoProvider< + AssetIdOf, + AccountIdOf, + AssetSymbol, + AssetName, + BalancePrecision, + ContentSource, + Description, + >; + + /// Weight information for extrinsics in this pallet + type WeightInfo: WeightInfo; + } + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Marks an asset as regulated, representing that the asset will only operate between KYC-verified wallets. + /// + /// ## Parameters + /// + /// - `origin`: The origin of the transaction. + /// - `asset_id`: The identifier of the asset. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::regulate_asset())] + pub fn regulate_asset(origin: OriginFor, asset_id: AssetIdOf) -> DispatchResult { + let who = ensure_signed(origin)?; + ::AssetInfoProvider::ensure_asset_exists(&asset_id)?; + ensure!( + ::AssetInfoProvider::is_asset_owner(&asset_id, &who), + >::OnlyAssetOwnerCanRegulate + ); + ensure!( + !Self::regulated_asset(asset_id), + >::AssetAlreadyRegulated + ); + + >::set(asset_id, true); + Self::deposit_event(Event::AssetRegulated { asset_id }); + + Ok(()) + } + + /// Issues a new Soulbound Token (SBT). + /// + /// ## Parameters + /// + /// - `origin`: The origin of the transaction. + /// - `symbol`: The symbol of the SBT which should represent string with only uppercase latin chars with max length of 7. + /// - `name`: The name of the SBT should represent string with only uppercase or lowercase latin chars or numbers or spaces, with max length of 33. + /// - `allowed_assets`: TThe list of assets allowed to be operated with by holding the SBT. + /// - `description`: The description of the SBT. (Optional) + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::issue_sbt())] + pub fn issue_sbt( + origin: OriginFor, + symbol: AssetSymbol, + name: AssetName, + allowed_assets: BoundedVec, T::MaxAllowedTokensPerSBT>, + description: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Check permission `who` can issue SBT + Permissions::::check_permission(who.clone(), ISSUE_SBT)?; + + let sbt_asset_id = T::AssetManager::register_from( + &who, + symbol, + name.clone(), + DEFAULT_BALANCE_PRECISION, + 0, + true, + None, + description.clone(), + )?; + let metadata = SoulboundTokenMetadata { + name, + description, + allowed_assets: allowed_assets.clone(), + }; + >::insert(sbt_asset_id, &metadata); + + for allowed_asset in allowed_assets.clone().into_iter() { + >::mutate(allowed_asset, |sbts| { + sbts.try_insert(sbt_asset_id).ok(); + }); + } + + Self::deposit_event(Event::SoulboundTokenIssued { + asset_id: sbt_asset_id, + owner: who, + allowed_assets: allowed_assets.clone().into(), + }); + + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Emits When an asset is regulated + AssetRegulated { asset_id: AssetIdOf }, + /// Emits When an SBT is issued + SoulboundTokenIssued { + asset_id: AssetIdOf, + owner: AccountIdOf, + allowed_assets: Vec>, + }, + } + + #[pallet::error] + pub enum Error { + /// SBT is not operationable by any asset operation + SoulboundAssetNotOperationable, + /// SBT is not transferable + SoulboundAssetNotTransferable, + /// Only asset owner can regulate + OnlyAssetOwnerCanRegulate, + /// Asset is already regulated + AssetAlreadyRegulated, + /// All involved users of a regulated asset operation should hold SBT + AllInvolvedUsersShouldHoldSBT, + } + + #[pallet::type_value] + pub fn DefaultRegulatedAsset() -> bool { + false + } + + /// Mapping from asset id to whether it is regulated or not + #[pallet::storage] + #[pallet::getter(fn regulated_asset)] + pub type RegulatedAsset = + StorageMap<_, Identity, AssetIdOf, bool, ValueQuery, DefaultRegulatedAsset>; + + /// Mapping from SBT (asset_id) to its metadata + #[pallet::storage] + #[pallet::getter(fn soulbound_asset)] + pub type SoulboundAsset = StorageMap< + _, + Identity, + AssetIdOf, + SoulboundTokenMetadata, T::MaxAllowedTokensPerSBT>, + OptionQuery, + >; + + /// Mapping from `asset_id` to its SBTs which grant permission to transfer, mint, and burn the `asset_id` + #[pallet::storage] + #[pallet::getter(fn sbts_by_asset)] + pub type SBTsByAsset = StorageMap< + _, + Identity, + AssetIdOf, + BoundedBTreeSet, T::MaxSBTsPerAsset>, + ValueQuery, + >; +} + +impl AssetRegulator, AssetIdOf> for Pallet { + fn assign_permission( + _owner: &AccountIdOf, + _asset_id: &AssetIdOf, + _permission_id: &PermissionId, + ) -> Result<(), DispatchError> { + Ok(()) + } + + fn check_permission( + issuer: &AccountIdOf, + affected_account: &AccountIdOf, + asset_id: &AssetIdOf, + permission_id: &PermissionId, + ) -> Result<(), DispatchError> { + if Self::soulbound_asset(asset_id).is_some() { + // Check if the issuer is the asset owner + let is_asset_owner = ::AssetInfoProvider::is_asset_owner(asset_id, issuer); + + if is_asset_owner { + // Asset owner of the SBT can do all asset operations except transfer + if permission_id == &TRANSFER { + return Err(Error::::SoulboundAssetNotTransferable.into()); + } + return Ok(()); + } else { + return Err(Error::::SoulboundAssetNotOperationable.into()); + } + } + + // If asset is not regulated, then no need to check permissions + if !Self::regulated_asset(asset_id) { + return Ok(()); + } + + // If the account is a technical account, then it can do all operations + if Technical::::lookup_tech_account_id(issuer).is_ok() { + return Ok(()); + } + + let sbts = Self::sbts_by_asset(asset_id); + + let issuer_has_sbt = sbts.iter().any(|sbt| { + ::AssetInfoProvider::total_balance(sbt, issuer) + .map_or(false, |balance| balance > 0) + }); + + let affected_account_has_sbt = sbts.iter().any(|sbt| { + ::AssetInfoProvider::total_balance(sbt, affected_account) + .map_or(false, |balance| balance > 0) + }); + + if !issuer_has_sbt || !affected_account_has_sbt { + return Err(Error::::AllInvolvedUsersShouldHoldSBT.into()); + } + + Ok(()) + } +} diff --git a/pallets/regulated-assets/src/mock.rs b/pallets/regulated-assets/src/mock.rs new file mode 100644 index 0000000000..0a680c904b --- /dev/null +++ b/pallets/regulated-assets/src/mock.rs @@ -0,0 +1,129 @@ +// This file is part of the SORA network and Polkaswap app. + +// Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +// SPDX-License-Identifier: BSD-4-Clause + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// All advertising materials mentioning features or use of this software must display +// the following acknowledgement: This product includes software developed by Polka Biome +// Ltd., SORA, and Polkaswap. +// +// Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{self as regulated_assets}; +use common::mock::ExistentialDeposits; +use common::{ + mock_common_config, mock_currencies_config, mock_frame_system_config, + mock_pallet_balances_config, mock_permissions_config, mock_technical_config, + mock_tokens_config, Amount, AssetId32, DEXId, LiquiditySourceType, PredefinedAssetId, XOR, XST, +}; +use currencies::BasicCurrencyAdapter; +use frame_support::traits::{ConstU16, ConstU64, Everything}; +use frame_support::{construct_runtime, parameter_types}; +use hex_literal::hex; +use sp_core::{ConstU32, H256}; +use sp_runtime::testing::Header; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; + +use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_runtime::MultiSignature; + +type AccountId = <::Signer as IdentifyAccount>::AccountId; +type AssetId = AssetId32; +type Balance = u128; +type Signature = MultiSignature; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type TechAssetId = common::TechAssetId; +type TechAccountId = common::TechAccountId; + +mock_common_config!(TestRuntime); +mock_currencies_config!(TestRuntime); +mock_tokens_config!(TestRuntime); +mock_pallet_balances_config!(TestRuntime); +mock_frame_system_config!(TestRuntime); +mock_permissions_config!(TestRuntime); +mock_technical_config!(TestRuntime); + +parameter_types! { + pub const GetBaseAssetId: AssetId = XOR; + pub const GetBuyBackAssetId: AssetId = XST; + pub GetBuyBackSupplyAssets: Vec = vec![]; + pub const GetBuyBackPercentage: u8 = 0; + pub const GetBuyBackAccountId: AccountId = AccountId::new(hex!( + "0000000000000000000000000000000000000000000000000000000000000023" + )); + pub const GetBuyBackDexId: DEXId = DEXId::Polkaswap; +} +impl assets::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type ExtraAccountId = [u8; 32]; + type ExtraAssetRecordArg = + common::AssetIdExtraAssetRecordArg; + type AssetId = AssetId; + type GetBaseAssetId = GetBaseAssetId; + type GetBuyBackAssetId = GetBuyBackAssetId; + type GetBuyBackSupplyAssets = GetBuyBackSupplyAssets; + type GetBuyBackPercentage = GetBuyBackPercentage; + type GetBuyBackAccountId = GetBuyBackAccountId; + type GetBuyBackDexId = GetBuyBackDexId; + type BuyBackLiquidityProxy = (); + type Currency = currencies::Pallet; + type GetTotalBalance = (); + type WeightInfo = (); + type AssetRegulator = ( + regulated_assets::Pallet, + permissions::Pallet, + ); +} + +impl regulated_assets::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type AssetInfoProvider = assets::Pallet; + type MaxAllowedTokensPerSBT = ConstU32<10000>; + type MaxSBTsPerAsset = ConstU32<10000>; + type WeightInfo = (); +} + +construct_runtime! { + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + + System: frame_system::{Pallet, Call, Storage, Event}, + Assets: assets::{Pallet, Call, Storage, Config, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Event}, + Tokens: tokens::{Pallet, Call, Config, Storage, Event}, + Permissions: permissions::{Pallet, Call, Config, Storage, Event}, + RegulatedAssets: regulated_assets::{Pallet, Storage, Event, Call}, + Technical: technical::{Pallet, Call, Config, Event}, + } +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into() +} diff --git a/pallets/regulated-assets/src/tests.rs b/pallets/regulated-assets/src/tests.rs new file mode 100644 index 0000000000..1292f17000 --- /dev/null +++ b/pallets/regulated-assets/src/tests.rs @@ -0,0 +1,372 @@ +// This file is part of the SORA network and Polkaswap app. + +// Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +// SPDX-License-Identifier: BSD-4-Clause + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// All advertising materials mentioning features or use of this software must display +// the following acknowledgement: This product includes software developed by Polka Biome +// Ltd., SORA, and Polkaswap. +// +// Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#![cfg(feature = "wip")] // DEFI-R + +use crate::mock::*; +use crate::*; +use common::{Balance, TechAccountId, DEFAULT_BALANCE_PRECISION, XOR}; +use frame_support::{assert_err, assert_ok}; +use permissions::MINT; +use sp_core::crypto::AccountId32; + +type RegulatedAssets = Pallet; + +pub fn alice() -> AccountId32 { + AccountId32::from([1; 32]) +} + +pub fn bob() -> AccountId32 { + AccountId32::from([2; 32]) +} + +pub fn add_asset(owner: &T::AccountId) -> AssetIdOf { + frame_system::Pallet::::inc_providers(owner); + + T::AssetManager::register_from( + owner, + AssetSymbol(b"TOKEN".to_vec()), + AssetName(b"TOKEN".to_vec()), + DEFAULT_BALANCE_PRECISION, + Balance::from(0u32), + true, + None, + None, + ) + .expect("Failed to register asset") +} + +pub fn assign_issue_sbt_permission(owner: T::AccountId, holder: T::AccountId) { + frame_system::Pallet::::inc_providers(&owner); + permissions::Pallet::::assign_permission( + owner, + &holder, + common::permissions::ISSUE_SBT, + permissions::Scope::Unlimited, + ) + .unwrap(); +} + +#[test] +fn test_default_value_asset_regulated() { + new_test_ext().execute_with(|| { + let default_value = RegulatedAssets::regulated_asset(XOR); + assert!(!default_value); + }) +} + +#[test] +fn test_only_permissioned_account_can_issue_sbt() { + new_test_ext().execute_with(|| { + let owner = bob(); + let non_owner = alice(); + let asset_name = AssetName(b"Soulbound Token".to_vec()); + let asset_symbol = AssetSymbol(b"SBT".to_vec()); + + let bounded_vec_assets = BoundedVec::try_from(vec![XOR]).unwrap(); + // Non-owner cannot issue SBT + assert_err!( + RegulatedAssets::issue_sbt( + RuntimeOrigin::signed(non_owner), + asset_symbol.clone(), + asset_name.clone(), + bounded_vec_assets.clone(), + None + ), + permissions::Error::::Forbidden + ); + + // Assign permission to owner + assign_issue_sbt_permission::(owner.clone(), owner.clone()); + + // Owner can issue SBT + assert_ok!(RegulatedAssets::issue_sbt( + RuntimeOrigin::signed(owner), + asset_symbol, + asset_name, + bounded_vec_assets, + None + )); + }) +} + +#[test] +fn test_only_asset_owner_can_regulate_asset() { + new_test_ext().execute_with(|| { + let owner = bob(); + let non_owner = alice(); + let asset_id = add_asset::(&owner); + + // Non-owner cannot regulate asset + assert_err!( + RegulatedAssets::regulate_asset(RuntimeOrigin::signed(non_owner), asset_id), + Error::::OnlyAssetOwnerCanRegulate + ); + + // Owner can regulate asset + assert_ok!(RegulatedAssets::regulate_asset( + RuntimeOrigin::signed(owner), + asset_id + )); + }) +} + +#[test] +fn test_cannot_regulate_already_regulated_asset() { + new_test_ext().execute_with(|| { + let owner = bob(); + let asset_id = add_asset::(&owner); + + // Regulate the asset for the first time + assert_ok!(RegulatedAssets::regulate_asset( + RuntimeOrigin::signed(owner.clone()), + asset_id + )); + + // Try to regulate the already regulated asset + assert_err!( + RegulatedAssets::regulate_asset(RuntimeOrigin::signed(owner), asset_id), + Error::::AssetAlreadyRegulated + ); + }) +} + +#[test] +fn test_tech_account_can_pass_check_permission() { + new_test_ext().execute_with(|| { + let owner = bob(); + let non_owner = alice(); + + let tech_account = TechAccountId::Generic("tech".into(), "account".into()); + + let asset_id = add_asset::(&owner); + + // Regulate the asset + assert_ok!(RegulatedAssets::regulate_asset( + RuntimeOrigin::signed(owner), + asset_id + )); + + mock::Technical::register_tech_account_id(tech_account.clone()).unwrap(); + let account_id = mock::Technical::tech_account_id_to_account_id(&tech_account).unwrap(); + + // Tech account can pass permission check for unregulated asset + assert_ok!(RegulatedAssets::check_permission( + &account_id, + &non_owner, + &asset_id, + &ISSUE_SBT + )); + }) +} + +#[test] +fn test_unregulated_asset_can_pass_check_permission() { + new_test_ext().execute_with(|| { + let owner = bob(); + let non_owner = alice(); + let asset_id = add_asset::(&owner); + + // Unregulated asset can pass permission check + assert_ok!(RegulatedAssets::check_permission( + &owner, &non_owner, &asset_id, &ISSUE_SBT + )); + }) +} + +#[test] +fn test_sbt_only_operationable_by_its_owner() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let owner = bob(); + let non_owner = alice(); + let asset_name = AssetName(b"Soulbound Token".to_vec()); + let asset_symbol = AssetSymbol(b"SBT".to_vec()); + + // Assign permission to owner + assign_issue_sbt_permission::(owner.clone(), owner.clone()); + + let bounded_vec_assets = BoundedVec::try_from(vec![XOR]).unwrap(); + // Issue SBT + assert_ok!(RegulatedAssets::issue_sbt( + RuntimeOrigin::signed(owner.clone()), + asset_symbol, + asset_name, + bounded_vec_assets, + None, + )); + + // Extract the issued SBT asset ID + let event = frame_system::Pallet::::events() + .pop() + .expect("Expected at least one event") + .event; + let sbt_asset_id = match event { + RuntimeEvent::RegulatedAssets(crate::Event::SoulboundTokenIssued { + asset_id, .. + }) => asset_id, + _ => panic!("Unexpected event: {:?}", event), + }; + + // SBT operations by non-owner should fail + assert_err!( + RegulatedAssets::check_permission(&non_owner, &non_owner, &sbt_asset_id, &ISSUE_SBT), + Error::::SoulboundAssetNotOperationable + ); + + assert_ok!(Assets::mint_to(&sbt_asset_id, &owner, &non_owner, 1)); + + // SBT operations by non-owner should fail + assert_err!( + Assets::transfer(RuntimeOrigin::signed(non_owner), sbt_asset_id, owner, 1), + Error::::SoulboundAssetNotOperationable + ); + }) +} + +#[test] +fn test_check_permission_pass_only_if_all_invloved_accounts_have_sbt() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let owner = bob(); + let non_owner = alice(); + let another_account = AccountId32::from([3u8; 32]); + let asset_name = AssetName(b"Soulbound Token".to_vec()); + let asset_symbol = AssetSymbol(b"SBT".to_vec()); + + // Assign permission to owner + assign_issue_sbt_permission::(owner.clone(), owner.clone()); + + // Regulate an asset + let asset_id = add_asset::(&owner); + assert_ok!(RegulatedAssets::regulate_asset( + RuntimeOrigin::signed(owner.clone()), + asset_id + )); + + let bounded_vec_assets = BoundedVec::try_from(vec![asset_id]).unwrap(); + + // Issue SBT + let result = RegulatedAssets::issue_sbt( + RuntimeOrigin::signed(owner.clone()), + asset_symbol, + asset_name, + bounded_vec_assets, + None, + ); + assert_ok!(result); + + // Extract the issued SBT asset ID + let event = frame_system::Pallet::::events() + .pop() + .expect("Expected at least one event") + .event; + let sbt_asset_id = match event { + RuntimeEvent::RegulatedAssets(crate::Event::SoulboundTokenIssued { + asset_id, .. + }) => asset_id, + _ => panic!("Unexpected event: {:?}", event), + }; + + // Give SBT to another_account + assert_ok!(Assets::mint_to(&sbt_asset_id, &owner, &another_account, 1)); + assert_ok!(Assets::mint_to(&sbt_asset_id, &owner, &owner, 1)); + + // Check permission should pass only if all involved accounts have SBT + assert_ok!(RegulatedAssets::check_permission( + &owner, + &another_account, + &asset_id, + &MINT + )); + assert_err!( + RegulatedAssets::check_permission(&owner, &non_owner, &asset_id, &MINT), + Error::::AllInvolvedUsersShouldHoldSBT + ); + assert_err!( + RegulatedAssets::check_permission(&non_owner, &another_account, &asset_id, &MINT), + Error::::AllInvolvedUsersShouldHoldSBT + ); + + // Owner can burn SBT from another_account (revoke) + assert_ok!(Assets::burn_from( + &sbt_asset_id, + &owner, + &another_account, + 1 + )); + + assert_err!( + RegulatedAssets::check_permission(&owner, &another_account, &asset_id, &MINT), + Error::::AllInvolvedUsersShouldHoldSBT + ); + }) +} + +#[test] +fn test_sbt_cannot_be_transferred() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + let owner = bob(); + let non_owner = alice(); + let asset_name = AssetName(b"Soulbound Token".to_vec()); + let asset_symbol = AssetSymbol(b"SBT".to_vec()); + + // Assign permission to owner + assign_issue_sbt_permission::(owner.clone(), owner.clone()); + + let bounded_vec_assets = BoundedVec::try_from(vec![XOR]).unwrap(); + // Owner can issue SBT + assert_ok!(RegulatedAssets::issue_sbt( + RuntimeOrigin::signed(owner.clone()), + asset_symbol, + asset_name, + bounded_vec_assets, + None + )); + + // Extract the issued SBT asset ID + let event = frame_system::Pallet::::events() + .pop() + .expect("Expected at least one event") + .event; + let sbt_asset_id = match event { + RuntimeEvent::RegulatedAssets(crate::Event::SoulboundTokenIssued { + asset_id, .. + }) => asset_id, + _ => panic!("Unexpected event: {:?}", event), + }; + + assert_err!( + Assets::transfer(RuntimeOrigin::signed(owner), sbt_asset_id, non_owner, 1), + Error::::SoulboundAssetNotTransferable + ); + }) +} diff --git a/pallets/regulated-assets/src/weights.rs b/pallets/regulated-assets/src/weights.rs new file mode 100644 index 0000000000..4718d92c80 --- /dev/null +++ b/pallets/regulated-assets/src/weights.rs @@ -0,0 +1,127 @@ +// This file is part of the SORA network and Polkaswap app. + +// Copyright (c) 2020, 2021, Polka Biome Ltd. All rights reserved. +// SPDX-License-Identifier: BSD-4-Clause + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// Redistributions of source code must retain the above copyright notice, this list +// of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, this +// list of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// All advertising materials mentioning features or use of this software must display +// the following acknowledgement: This product includes software developed by Polka Biome +// Ltd., SORA, and Polkaswap. +// +// Neither the name of the Polka Biome Ltd. nor the names of its contributors may be used +// to endorse or promote products derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY Polka Biome Ltd. AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Polka Biome Ltd. BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Autogenerated weights for `regulated_assets` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-05-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `MacBook-Pro.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("local"), DB CACHE: 1024 + +// Executed Command: +// target/release/framenode +// benchmark +// pallet +// --chain=local +// --execution=wasm +// --wasm-execution=compiled +// --pallet +// regulated_assets +// --extrinsic +// * +// --steps +// 50 +// --repeat +// 20 +// --output +// ./benches + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::weights::constants::RocksDbWeight; +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +pub trait WeightInfo { + fn regulate_asset() -> Weight; + fn issue_sbt() -> Weight; +} + +/// Weight functions for `regulated_assets`. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Assets AssetOwners (r:1 w:0) + /// Proof Skipped: Assets AssetOwners (max_values: None, max_size: None, mode: Measured) + /// Storage: RegulatedAssets RegulatedAsset (r:1 w:1) + /// Proof Skipped: RegulatedAssets RegulatedAsset (max_values: None, max_size: None, mode: Measured) + fn regulate_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `691` + // Estimated: `6332` + // Minimum execution time: 16_000 nanoseconds. + Weight::from_parts(17_000_000, 6332) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + + /// Storage: Permissions Permissions (r:4 w:1) + /// Proof Skipped: Permissions Permissions (max_values: None, max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Assets AssetOwners (r:1 w:1) + /// Proof Skipped: Assets AssetOwners (max_values: None, max_size: None, mode: Measured) + /// Storage: Permissions Owners (r:2 w:2) + /// Proof Skipped: Permissions Owners (max_values: None, max_size: None, mode: Measured) + /// Storage: RegulatedAssets SBTsByAsset (r:1 w:1) + /// Proof Skipped: RegulatedAssets SBTsByAsset (max_values: None, max_size: None, mode: Measured) + /// Storage: RegulatedAssets SoulboundAsset (r:0 w:1) + /// Proof Skipped: RegulatedAssets SoulboundAsset (max_values: None, max_size: None, mode: Measured) + /// Storage: Assets AssetInfos (r:0 w:1) + /// Proof Skipped: Assets AssetInfos (max_values: None, max_size: None, mode: Measured) + fn issue_sbt() -> Weight { + // Proof Size summary in bytes: + // Measured: `2294` + // Estimated: `36167` + // Minimum execution time: 97_000 nanoseconds. + Weight::from_parts(108_000_000, 36167) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(8)) + } +} + + + +impl WeightInfo for () { + fn regulate_asset() -> Weight { + Weight::from_parts(17_000_000, 6332) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + + fn issue_sbt() -> Weight { + Weight::from_parts(124_000_000, 44201) + .saturating_add(RocksDbWeight::get().reads(11)) + .saturating_add(RocksDbWeight::get().writes(10)) + } +} + diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 4edc6f87c0..af42f94522 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -92,6 +92,7 @@ pswap-distribution-benchmarking = { path = "../pallets/pswap-distribution/benchm pswap-distribution-runtime-api = { path = "../pallets/pswap-distribution/runtime-api", default-features = false } qa-tools = { path = "../pallets/qa-tools", default-features = false, optional = true } referrals = { path = "../pallets/referrals", default-features = false } +regulated-assets = { path = "../pallets/regulated-assets", default-features = false, optional = true } rewards = { path = "../pallets/rewards", default-features = false } rewards-runtime-api = { path = "../pallets/rewards/runtime-api", default-features = false } technical = { path = "../pallets/technical", default-features = false } @@ -263,6 +264,7 @@ std = [ "pswap-distribution/std", "qa-tools/std", "referrals/std", + "regulated-assets/std", "rewards-runtime-api/std", "rewards/std", "serde/std", @@ -310,6 +312,7 @@ wip = [ "bridge-channel", "pallet-beefy", "pallet-beefy-mmr", + "regulated-assets/wip", ] ready-to-test = ["framenode-chain-spec/ready-to-test", "apollo-platform"] @@ -369,6 +372,7 @@ runtime-benchmarks = [ "beefy-light-client/runtime-benchmarks", "multisig-verifier/runtime-benchmarks", "bridge-data-signer/runtime-benchmarks", + "regulated-assets/runtime-benchmarks" ] reduced-pswap-reward-periods = [] @@ -455,6 +459,7 @@ try-runtime = [ "bridge-data-signer/try-runtime", "multisig-verifier/try-runtime", "qa-tools/try-runtime", + "regulated-assets/try-runtime", ] test = ["framenode-chain-spec/test", "liquidity-proxy/test", "order-book/test"] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3acf409949..458c4634a0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -961,7 +961,13 @@ impl assets::Config for Runtime { type Currency = currencies::Pallet; type GetTotalBalance = GetTotalBalance; type WeightInfo = assets::weights::SubstrateWeight; + #[cfg(not(feature = "wip"))] // DEFI-R type AssetRegulator = permissions::Pallet; + #[cfg(feature = "wip")] // DEFI-R + type AssetRegulator = ( + permissions::Pallet, + regulated_assets::Pallet, + ); } impl trading_pair::Config for Runtime { @@ -2383,6 +2389,15 @@ impl multisig_verifier::Config for Runtime { type ThisNetworkId = ThisNetworkId; } +#[cfg(feature = "wip")] // DEFI-R +impl regulated_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxAllowedTokensPerSBT = ConstU32<10000>; + type MaxSBTsPerAsset = ConstU32<10000>; + type AssetInfoProvider = Assets; + type WeightInfo = regulated_assets::weights::SubstrateWeight; +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -2508,6 +2523,8 @@ construct_runtime! { #[cfg(feature = "ready-to-test")] // Apollo ApolloPlatform: apollo_platform::{Pallet, Call, Storage, Event, ValidateUnsigned} = 114, + #[cfg(feature = "wip")] // DEFI-R + RegulatedAssets: regulated_assets::{Pallet, Call, Storage, Event} = 115, } } @@ -3271,6 +3288,8 @@ impl_runtime_apis! { list_benchmark!(list, extra, substrate_bridge_app, SubstrateBridgeApp); list_benchmark!(list, extra, bridge_data_signer, BridgeDataSigner); list_benchmark!(list, extra, multisig_verifier, MultisigVerifier); + #[cfg(feature = "wip")] // DEFI-R + list_benchmark!(list, extra, regulated_assets, RegulatedAssets); let storage_info = AllPalletsWithSystem::storage_info(); @@ -3367,6 +3386,8 @@ impl_runtime_apis! { add_benchmark!(params, batches, substrate_bridge_app, SubstrateBridgeApp); add_benchmark!(params, batches, bridge_data_signer, BridgeDataSigner); add_benchmark!(params, batches, multisig_verifier, MultisigVerifier); + #[cfg(feature = "wip")] // DEFI-R + add_benchmark!(params, batches, regulated_assets, RegulatedAssets); if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches)