From 38c9f9672ee92d12301f4429c6581a691df8fd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 23 Apr 2025 22:51:34 -0500 Subject: [PATCH 1/8] feat(contracts-store): add `pallet-contracts-store`, full path, and tests. Missing benchmarks. --- Cargo.toml | 3 + pallets/contracts-store/Cargo.toml | 66 ++++ pallets/contracts-store/src/lib.rs | 387 +++++++++++++++++++++ pallets/contracts-store/src/mock.rs | 172 ++++++++++ pallets/contracts-store/src/tests.rs | 446 +++++++++++++++++++++++++ pallets/contracts-store/src/types.rs | 36 ++ pallets/contracts-store/src/weights.rs | 95 ++++++ 7 files changed, 1205 insertions(+) create mode 100644 pallets/contracts-store/Cargo.toml create mode 100644 pallets/contracts-store/src/lib.rs create mode 100644 pallets/contracts-store/src/mock.rs create mode 100644 pallets/contracts-store/src/tests.rs create mode 100644 pallets/contracts-store/src/types.rs create mode 100644 pallets/contracts-store/src/weights.rs diff --git a/Cargo.toml b/Cargo.toml index eadaa338..29b1897f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,14 @@ virto-common = { default-features = false, path = "common" } pallet-communities = { default-features = false, path = "pallets/communities" } pallet-communities-manager = { default-features = false, path = "pallets/communities-manager" } +pallet-contracts-store = { default-features = false, path = "pallets/contracts-store" } runtime-constants = { default-features = false, path = "runtime/runtime-constants" } runtime-common = { default-features = false, path = "runtime/common" } # Frame Contrib frame-contrib-traits = { git = "https://github.com/virto-network/frame-contrib", default-features = false } +mock-helpers = { git = "https://github.com/virto-network/frame-contrib", default-features = false } pallet-gas-transaction-payment = { git = "https://github.com/virto-network/frame-contrib", default-features = false, package = "fc-pallet-gas-transaction-payment" } pallet-listings = { git = "https://github.com/virto-network/frame-contrib", default-features = false, package = "fc-pallet-listings" } @@ -104,6 +106,7 @@ pallet-aura = { default-features = false, git = "https://github.com/virto-networ pallet-authorship = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } pallet-balances = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } pallet-contracts = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } +pallet-contracts-fixtures = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } pallet-multisig = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } pallet-nfts = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } pallet-preimage = { default-features = false, git = "https://github.com/virto-network/polkadot-sdk", branch = "release-virto-stable2409-6" } diff --git a/pallets/contracts-store/Cargo.toml b/pallets/contracts-store/Cargo.toml new file mode 100644 index 00000000..54b54479 --- /dev/null +++ b/pallets/contracts-store/Cargo.toml @@ -0,0 +1,66 @@ +[package] +authors.workspace = true +description = "This pallet helps with all the necesary steps to publish and acquire 'apps' (contracts) from publishers." +edition.workspace = true +license.workspace = true +homepage.workspace = true +name = "pallet-contracts-store" +repository.workspace = true +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +frame-contrib-traits.workspace = true +frame-benchmarking.workspace = true +frame-support.workspace = true +frame-system.workspace = true +pallet-contracts.workspace = true +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } +sp-runtime.workspace = true + +[dev-dependencies] +mock-helpers = { workspace = true, features = ["pallet-assets", "pallet-balances"] } +pallet-assets.workspace = true +pallet-balances.workspace = true +pallet-contracts-fixtures.workspace = true +sp-core.workspace = true +sp-io.workspace = true + +[features] +default = ["std"] +std = [ + "frame-benchmarking/std", + "frame-contrib-traits/std", + "frame-support/std", + "frame-system/std", + "mock-helpers/std", + "pallet-assets/std", + "pallet-balances/std", + "pallet-contracts/std", + "parity-scale-codec/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std" +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-contrib-traits/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-contracts/runtime-benchmarks", + "sp-runtime/runtime-benchmarks" +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "pallet-contracts/try-runtime", + "sp-runtime/try-runtime" +] diff --git a/pallets/contracts-store/src/lib.rs b/pallets/contracts-store/src/lib.rs new file mode 100644 index 00000000..cecf72c0 --- /dev/null +++ b/pallets/contracts-store/src/lib.rs @@ -0,0 +1,387 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! # Contracts Store Pallet +//! +//! This pallet handles the publishing + +extern crate alloc; +extern crate core; + +use alloc::vec::Vec; +use frame_contrib_traits::listings::{item::Item, InspectInventory, InspectItem, InventoryLifecycle, MutateItem}; +use frame_support::{pallet_prelude::*, traits::Incrementable}; +use frame_system::pallet_prelude::*; +use pallet_contracts::{CodeUploadReturnValue, Determinism}; +use sp_runtime::traits::StaticLookup; + +#[cfg(test)] +pub(crate) mod mock; +#[cfg(test)] +mod tests; + +mod types; +pub mod weights; + +pub use pallet::*; +pub use types::*; +pub use weights::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use core::fmt::Debug; + use pallet_contracts::{Code, CollectEvents, DebugInfo, InstantiateReturnValue}; + use parity_scale_codec::HasCompact; + + #[pallet::config] + pub trait Config: pallet_contracts::Config + frame_system::Config { + // Primitives: Some overarching types that are aggregated in the system. + + /// Overarching event type + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The weight information for this pallet. + type WeightInfo: WeightInfo; + + // Origins: Types that manage authorization rules to allow or deny some caller + // origins to execute a method. + + /// An origin to allowed to request copies of an application, and + /// instantiate it once proven they own a copy of the application. + type InstantiateOrigin: EnsureOrigin< + Self::RuntimeOrigin, + Success = (Self::AccountId, ListingsMerchantIdOf), + >; + + // Types: A set of parameter types that the pallet uses to handle information. + + /// An unique identification for an application. + type AppId: Parameter + MaxEncodedLen + Default + Incrementable; + /// An unique identification for a license of the application. + type LicenseId: Parameter + MaxEncodedLen + Default + Incrementable; + + // Dependencies: The external components this pallet depends on. + /// The `Listings` component of a `Marketplace` system. + type Listings: InventoryLifecycle + + InspectItem< + Self::AccountId, + MerchantId = ListingsMerchantIdOf, + InventoryId = Self::AppId, + ItemId = Self::LicenseId, + > + MutateItem; + + // Parameters: A set of constant parameters to configure limits. + + /// The `MerchantId` associated to the contracts store. + #[pallet::constant] + type ContractsStoreMerchantId: Get>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + // Errors inform users that something worked or went wrong. + #[pallet::error] + pub enum Error { + /// The specified app is not found. + AppNotFound, + /// The caller does not have permissions to mutate the specified app. + NoPermission, + /// The given change of the price is invalid. + InvalidPriceChange, + /// Incrementing a parameter failed. + CannotIncrement, + /// The maximum amount of licenses for an application has been already + /// issued. Please contact the publisher of this application. + MaxLicensesExceeded, + /// It is not possible to issue a license, due to a problem issuing the + /// item that represents the license. Contact the publisher of this + /// application. + CannotIssueLicense, + /// The specified license is not found. + LicenseNotFound, + /// The address associated to an app license is not a valid instance. + AppInstanceNotFound, + /// The application instance is up to date. + AppInstanceUpToDate, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new app has been published in the store. + AppPublished { + id: T::AppId, + publisher: AccountIdOf, + max_instances: Option, + price: Option>, + }, + /// A new app has been published in the store. + AppUpdated { id: T::AppId, version: u32 }, + /// The price of an application has been updated. + AppPriceUpdated { id: T::AppId, price: ItemPriceOf }, + /// A new license of the app has been emitted and is ready to be + /// acquired. + AppLicenseEmitted { + app_id: T::AppId, + license_id: LicenseIdFor, + }, + /// An app has been instanced. This follows the instantiation a new + /// contract using the application `CodeHash`, using a valid app + /// license. + AppInstantiated { + app_id: T::AppId, + license_id: T::LicenseId, + caller: T::AccountId, + }, + } + + /// The next `AppId` to be used when publishing a new app. + #[pallet::storage] + pub type NextAppId = StorageValue<_, T::AppId, ValueQuery>; + + /// The next `LicenseId` to be used when emitting a new license for an app. + #[pallet::storage] + pub type NextLicenseId = StorageMap<_, Blake2_128Concat, T::AppId, T::LicenseId, ValueQuery>; + + /// The information of registered apps. + #[pallet::storage] + pub type Apps = StorageMap<_, Blake2_128Concat, T::AppId, AppInfoFor>; + + #[pallet::call(weight(::WeightInfo))] + impl Pallet + where + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, + { + /// Publish a new application, + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::publish(code.len() as u32))] + pub fn publish( + origin: OriginFor, + code: Vec, + max_instances: Option, + price: Option>, + ) -> DispatchResult { + let publisher = T::UploadOrigin::ensure_origin(origin)?; + let id = Self::generate_app_id()?; + + Apps::::try_mutate_exists(id.clone(), |app| -> DispatchResult { + let mut app_info = AppInfo { + code_hash: Default::default(), + publisher: publisher.clone(), + max_instances, + instances: 0, + price: price.clone(), + version: 0, + }; + + Self::upload_code(&mut app_info, &publisher, code)?; + *app = Some(app_info); + + let inventory_id = (T::ContractsStoreMerchantId::get(), id.clone()); + T::Listings::create(inventory_id, &publisher)?; + + Self::deposit_event(Event::AppPublished { + id, + publisher, + max_instances, + price, + }); + + Ok(()) + }) + } + + /// Sets the price for an existing application. + /// + /// The caller must be a valid [`UploadOrigin`][T::UploadOrigin], and + /// the account derived from it must be publisher of the application. + #[pallet::call_index(1)] + pub fn set_parameters( + origin: OriginFor, + app_id: T::AppId, + max_instances: Option, + price: Option>, + ) -> DispatchResult { + let who = T::UploadOrigin::ensure_origin(origin)?; + Apps::::try_mutate(app_id.clone(), |maybe_app| { + let Some(app) = maybe_app else { + Err(Error::::AppNotFound)? + }; + + ensure!(app.publisher == who, Error::::NoPermission); + + app.max_instances = max_instances; + // Can't remove the price for an app. + if let Some(price) = price { + app.price = Some(price.clone()); + Self::deposit_event(Event::::AppPriceUpdated { id: app_id, price }) + } + + Ok(()) + }) + } + + /// Publishes the code version of an existing application. Then, every + /// app instance can upgrade to the latest version. + /// + /// The caller must be a valid [`UploadOrigin`][T::UploadOrigin], and + /// the account derived from it must be publisher of the application. + /// + /// This would call a migration to set the new code on for every app + /// instance. + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::publish_upgrade(code.len() as u32))] + pub fn publish_upgrade(origin: OriginFor, app_id: T::AppId, code: Vec) -> DispatchResult { + let who = &T::UploadOrigin::ensure_origin(origin)?; + Apps::::try_mutate(app_id, |maybe_app| { + let Some(app_info) = maybe_app else { + Err(Error::::AppNotFound)? + }; + + ensure!(&app_info.publisher == who, Error::::NoPermission); + Self::upload_code(app_info, who, code) + }) + } + + /// Request a license for instantiating an application. + /// + /// The caller must be a valid + /// [`InstantiateOrigin`][T::InstantiateOrigin]. + /// + /// When successful, a new license would be issued, available for + /// purchase or transferred to the caller, if the application is free. + #[pallet::call_index(3)] + pub fn request_license(origin: OriginFor, app_id: T::AppId) -> DispatchResult { + let (who, _) = &<::InstantiateOrigin>::ensure_origin(origin)?; + Apps::::try_mutate(app_id.clone(), |app_info| { + let Some(app) = app_info else { + Err(Error::::AppNotFound)? + }; + let inventory_id = (T::ContractsStoreMerchantId::get(), app_id.clone()); + let license_id = Self::generate_license_id(app_id.clone())?; + + match app.max_instances { + Some(max_instances) if app.instances == max_instances => Err(Error::::MaxLicensesExceeded), + _ => { + app.instances += 1; + Ok(()) + } + }?; + + T::Listings::publish(&inventory_id, &license_id, b"".to_vec(), app.price.clone())?; + + if app.price.is_none() { + T::Listings::transfer(&inventory_id, &license_id, who)?; + } + + Self::deposit_event(Event::::AppLicenseEmitted { app_id, license_id }); + + Ok(()) + }) + } + + #[pallet::call_index(4)] + pub fn instantiate( + origin: OriginFor, + app_id: T::AppId, + license_id: T::LicenseId, + #[pallet::compact] value: BalanceOf, + data: Vec, + salt: Vec, + ) -> DispatchResult { + let (caller, merchant_id) = <::InstantiateOrigin>::ensure_origin(origin)?; + let inventory_id = (T::ContractsStoreMerchantId::get(), app_id.clone()); + + let AppInfo { code_hash, .. } = Apps::::get(&app_id).ok_or(Error::::AppNotFound)?; + let Item { owner, .. } = + T::Listings::item(&inventory_id, &license_id).ok_or(Error::::LicenseNotFound)?; + + ensure!(caller == owner, Error::::NoPermission); + + let InstantiateReturnValue { account_id, .. } = Contracts::::bare_instantiate( + caller.clone(), + value, + Weight::MAX, // TODO: Replace with something reasonable. + None, // Again, charging the uploader with the deposit for instantiating the contract. + Code::Existing(code_hash), + data, + salt, + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result?; + + // Now that the contract is enacted, the new license owner is the app itself. + // That way, we can map the contract account with the actual license (and find + // the contract account via the app instance). + T::Listings::transfer(&inventory_id, &license_id, &account_id)?; + // Also, we set the `merchant_id` derived from the origin, to ensure that the + // contract gets access to the Kreivo Merchants API. + T::Listings::set_attribute(&inventory_id, &license_id, &b"CONTRACT_MERCHANT_ID", merchant_id)?; + + Self::deposit_event(Event::::AppInstantiated { + app_id, + license_id, + caller, + }); + + Ok(()) + } + + #[pallet::call_index(5)] + pub fn upgrade(origin: OriginFor, app_id: T::AppId, license_id: T::LicenseId) -> DispatchResult { + ensure_signed_or_root(origin)?; + let AppInfo { code_hash, .. } = Apps::::get(&app_id).ok_or(Error::::AppNotFound)?; + + let inventory_id = (T::ContractsStoreMerchantId::get(), app_id.clone()); + let Item { + owner: contract_account, + .. + } = T::Listings::item(&inventory_id, &license_id).ok_or(Error::::LicenseNotFound)?; + let instance_hash = Contracts::::code_hash(&contract_account).ok_or(Error::::AppInstanceNotFound)?; + + ensure!(code_hash != instance_hash, Error::::AppInstanceUpToDate); + + Contracts::::set_code( + frame_system::Origin::::Root.into(), + T::Lookup::unlookup(contract_account), + code_hash, + )?; + + Ok(()) + } + } +} + +impl Pallet { + fn generate_app_id() -> Result { + NextAppId::::try_mutate(|next_id| { + let id = next_id.clone(); + *next_id = id.increment().ok_or(Error::::CannotIncrement)?; + Ok(id) + }) + } + + fn generate_license_id(app_id: T::AppId) -> Result { + NextLicenseId::::try_mutate(app_id, |next_id| { + let id = next_id.clone(); + *next_id = id.increment().ok_or(Error::::CannotIncrement)?; + Ok(id) + }) + } + + /// Uploads the code of an app, and increases the version of such app. + /// + /// To achieve this as briefly as possible, we take two considerations: + /// + /// 1. No deposit limit: publishers must be aware of this. + /// 2. Enforced determinism: every contract must be executable on-chain. + fn upload_code(app_info: &mut AppInfoFor, publisher: &AccountIdOf, code: Vec) -> DispatchResult { + // Uploads the code: if successful, would return a new `CodeHash` for the + // application. + let CodeUploadReturnValue { code_hash, .. } = + Contracts::::bare_upload_code(publisher.clone(), code, None, Determinism::Enforced)?; + app_info.bump_version(code_hash).ok_or(Error::::CannotIncrement)?; + Ok(()) + } +} diff --git a/pallets/contracts-store/src/mock.rs b/pallets/contracts-store/src/mock.rs new file mode 100644 index 00000000..117e5a41 --- /dev/null +++ b/pallets/contracts-store/src/mock.rs @@ -0,0 +1,172 @@ +//! Test environment for contracts store pallet. + +use crate as pallet_contracts_store; + +use frame_contrib_traits::listings::test_utils::{self, MockListings}; +use frame_support::traits::Time; +use frame_support::{derive_impl, pallet_prelude::ConstU32, traits::EnsureOrigin}; +use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::EnsureSigned; +use pallet_contracts::{AddressGenerator, Frame, Schedule}; +use sp_core::parameter_types; +use sp_runtime::{traits::IdentityLookup, BuildStorage, SaturatedConversion}; + +use mock_helpers::ExtHelper; +pub use sp_io::TestExternalities; + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; + +pub type Block = frame_system::mocking::MockBlock; +pub type AccountId = u128; +pub type AssetId = u32; +pub type Balance = ::Balance; + +pub type Listings = MockListings; + +// Configure a mock runtime to test the pallet. +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeHoldReason, + RuntimeFreezeReason + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system; + #[runtime::pallet_index(10)] + pub type Balances = pallet_balances; + #[runtime::pallet_index(30)] + pub type Contracts = pallet_contracts; + #[runtime::pallet_index(31)] + pub type ContractStore = pallet_contracts_store; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Block = Block; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub MySchedule: Schedule = >::default(); +} + +type MerchantId = u32; +pub type AppId = u64; +pub type LicenseId = u16; + +impl test_utils::Config for Test { + type MerchantId = MerchantId; + type InventoryId = AppId; + type ItemId = LicenseId; + type AssetId = AssetId; + type Balance = Balance; + type MaxKeyLen = ConstU32<32>; + type MaxValueLen = ConstU32<64>; +} + +pub struct SimpleAddressGenerator; + +impl AddressGenerator for SimpleAddressGenerator { + fn contract_address( + deploying_address: &AccountId, + code_hash: &::Hash, + input_data: &[u8], + salt: &[u8], + ) -> AccountId { + let deploying_address = deploying_address << 64; + let code_hash = (code_hash.0.into_iter().filter(|b| *b == 0).count() as AccountId) << 32; + let input_data = (input_data.len() as AccountId) << 16; + let salt = salt.len() as AccountId; + + deploying_address + code_hash + input_data + salt + } +} + +impl Time for Test { + type Moment = BlockNumberFor; + + fn now() -> Self::Moment { + System::block_number() + } +} + +#[derive_impl(pallet_contracts::config_preludes::TestDefaultConfig)] +impl pallet_contracts::Config for Test { + type Time = Self; + type Currency = Balances; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type AddressGenerator = SimpleAddressGenerator; + type Schedule = MySchedule; + type CallStack = [Frame; 5]; +} + +pub struct EnsureSignedMerchant; + +impl EnsureOrigin for EnsureSignedMerchant { + type Success = (AccountId, u32); + + fn try_origin(o: RuntimeOrigin) -> Result { + match o.clone().caller { + OriginCaller::system(frame_system::RawOrigin::Signed(who)) => Ok((who, who.saturated_into::())), + _ => Err(o), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(RuntimeOrigin::signed(ALICE)) + } +} + +impl pallet_contracts_store::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type InstantiateOrigin = EnsureSignedMerchant; + type AppId = AppId; + type LicenseId = LicenseId; + type Listings = Listings; + type ContractsStoreMerchantId = ConstU32<0>; +} + +#[derive(Default)] +pub struct ExtBuilder { + accounts: mock_helpers::BalancesExtBuilder, +} + +impl ExtBuilder { + fn with_account(mut self, who: AccountId, amount: Balance) -> Self { + self.accounts = self.accounts.with_account(who, amount); + self + } + + fn build(self) -> TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + self.accounts.as_storage().assimilate_storage(&mut storage).unwrap(); + + let mut ext = TestExternalities::from(storage); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} + +pub fn new_test_ext() -> TestExternalities { + ExtBuilder::default().with_account(ALICE, Balance::MAX / 2).build() +} diff --git a/pallets/contracts-store/src/tests.rs b/pallets/contracts-store/src/tests.rs new file mode 100644 index 00000000..d9ad98eb --- /dev/null +++ b/pallets/contracts-store/src/tests.rs @@ -0,0 +1,446 @@ +use crate::{ + mock::{self, *}, + Apps, Error, Event, +}; +use frame_contrib_traits::listings::item::ItemPrice; +use frame_contrib_traits::listings::*; +use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate}; +use pallet_contracts_fixtures::compile_module; +use sp_runtime::DispatchError; + +pub const APP_ID: AppId = 0; +pub const LICENSE_ID: LicenseId = 0; + +fn contract(name: &'static str) -> Vec { + compile_module::(name).unwrap().0 +} + +fn code_hash(name: &'static str) -> ::Hash { + compile_module::(name).unwrap().1 +} + +mod publish { + use super::*; + + #[test] + fn fails_if_bad_origin() { + mock::new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::publish(RuntimeOrigin::root(), vec![], None, None), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn fails_if_caller_cannot_reserve_storage_deposit() { + mock::new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::publish(RuntimeOrigin::signed(BOB), contract("call"), None, None), + pallet_contracts::Error::::StorageDepositNotEnoughFunds + ); + }) + } + + #[test] + fn it_works() { + mock::new_test_ext().execute_with(|| { + assert_ok!(ContractStore::publish( + RuntimeOrigin::signed(ALICE), + contract("call"), + None, + None + )); + + // The app was published. + System::assert_has_event( + Event::::AppPublished { + id: APP_ID, + publisher: ALICE, + max_instances: None, + price: None, + } + .into(), + ); + + // An inventory for storing the licenses was created. + assert!(Listings::exists(&(0, APP_ID))); + }) + } +} + +fn new_test_ext() -> TestExternalities { + let mut t = mock::new_test_ext(); + t.execute_with(|| { + assert_ok!(ContractStore::publish( + RuntimeOrigin::signed(ALICE), + contract("call"), + None, + None + )); + }); + t +} + +mod set_parameters { + use super::*; + use crate::AppInfo; + use frame_support::assert_storage_noop; + + #[test] + fn fails_if_bad_origin() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::set_parameters(RuntimeOrigin::root(), APP_ID, None, None), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn fails_if_app_not_found() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::set_parameters(RuntimeOrigin::signed(ALICE), 1, None, None), + Error::::AppNotFound + ); + }) + } + + #[test] + fn fails_if_not_the_publisher() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::set_parameters(RuntimeOrigin::signed(BOB), APP_ID, None, None), + Error::::NoPermission + ); + }) + } + + #[test] + fn it_works() { + new_test_ext().execute_with(|| { + assert_storage_noop!({ + let _ = ContractStore::set_parameters(RuntimeOrigin::signed(ALICE), APP_ID, None, None); + }); + }); + + new_test_ext().execute_with(|| { + assert_ok!(ContractStore::set_parameters( + RuntimeOrigin::signed(ALICE), + APP_ID, + Some(10), + None + )); + + assert!(matches!( + Apps::::get(0), + Some(AppInfo { + max_instances: Some(10), + .. + }) + )); + }); + + new_test_ext().execute_with(|| { + assert_ok!(ContractStore::set_parameters( + RuntimeOrigin::signed(ALICE), + APP_ID, + None, + Some(ItemPrice { asset: 0, amount: 10 }) + )); + + System::assert_has_event( + Event::::AppPriceUpdated { + id: APP_ID, + price: ItemPrice { asset: 0, amount: 10 }, + } + .into(), + ) + }) + } +} + +mod publish_upgrade { + use super::*; + + #[test] + fn fails_if_bad_origin() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::publish_upgrade(RuntimeOrigin::root(), APP_ID, contract("balance")), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn fails_if_app_not_found() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::publish_upgrade(RuntimeOrigin::signed(ALICE), 1, contract("balance")), + Error::::AppNotFound + ); + }) + } + + #[test] + fn fails_if_caller_cannot_reserve_storage_deposit() { + mock::new_test_ext().execute_with(|| { + Balances::set_balance(&BOB, Balance::MAX / 2); + assert_ok!(ContractStore::publish( + RuntimeOrigin::signed(BOB), + contract("call"), + None, + None + )); + Balances::set_balance(&BOB, 0); + assert_noop!( + ContractStore::publish_upgrade(RuntimeOrigin::signed(BOB), APP_ID, contract("balance")), + pallet_contracts::Error::::StorageDepositNotEnoughFunds, + ); + }) + } + + #[test] + fn it_works() { + new_test_ext().execute_with(|| { + assert_ok!(ContractStore::publish_upgrade( + RuntimeOrigin::signed(ALICE), + APP_ID, + contract("balance") + )); + }) + } +} + +mod request_license { + use super::*; + + #[test] + fn fails_if_bad_origin() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::request_license(RuntimeOrigin::root(), APP_ID), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn fails_if_app_not_found() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::request_license(RuntimeOrigin::signed(ALICE), 1), + Error::::AppNotFound + ); + }) + } + + #[test] + fn fails_if_max_licenses_exceeded() { + mock::new_test_ext().execute_with(|| { + assert_ok!(ContractStore::publish( + RuntimeOrigin::signed(ALICE), + contract("call"), + Some(0), + None + )); + + assert_noop!( + ContractStore::request_license(RuntimeOrigin::signed(BOB), APP_ID), + Error::::MaxLicensesExceeded + ); + }) + } + + #[test] + fn it_works() { + // App is free. + new_test_ext().execute_with(|| { + assert_ok!(ContractStore::request_license(RuntimeOrigin::signed(BOB), APP_ID)); + assert!(matches!( + Listings::item(&(0, APP_ID), &LICENSE_ID), + Some(item::Item { owner: BOB, .. }) + )); + }); + + // App has a price. + mock::new_test_ext().execute_with(|| { + assert_ok!(ContractStore::publish( + RuntimeOrigin::signed(ALICE), + contract("call"), + None, + Some(ItemPrice { asset: 0, amount: 10 }) + )); + assert_ok!(ContractStore::request_license(RuntimeOrigin::signed(BOB), APP_ID)); + + System::assert_has_event( + Event::::AppLicenseEmitted { + app_id: APP_ID, + license_id: LICENSE_ID, + } + .into(), + ); + + assert!(matches!( + Listings::item(&(0, 0), &0), + Some(item::Item { + owner: ALICE, + price: Some(ItemPrice { asset: 0, amount: 10 }), + .. + }) + )); + }) + } +} + +fn test_ext_post_license() -> TestExternalities { + let mut t = new_test_ext(); + t.execute_with(|| { + assert_ok!(ContractStore::request_license(RuntimeOrigin::signed(BOB), APP_ID)); + }); + t +} + +mod instantiate { + use super::*; + use pallet_contracts::AddressGenerator; + use sp_runtime::TokenError; + + #[test] + fn fails_if_bad_origin() { + test_ext_post_license().execute_with(|| { + assert_noop!( + ContractStore::instantiate(RuntimeOrigin::root(), APP_ID, LICENSE_ID, 0, vec![], vec![]), + DispatchError::BadOrigin + ); + }) + } + + #[test] + fn fails_if_instantiation_data_not_found() { + test_ext_post_license().execute_with(|| { + assert_noop!( + ContractStore::instantiate(RuntimeOrigin::signed(ALICE), 1, LICENSE_ID, 0, vec![], vec![]), + Error::::AppNotFound + ); + }); + + test_ext_post_license().execute_with(|| { + assert_noop!( + ContractStore::instantiate(RuntimeOrigin::signed(ALICE), APP_ID, 1, 0, vec![], vec![]), + Error::::LicenseNotFound + ); + }) + } + + #[test] + fn fails_if_caller_is_not_the_license_owner() { + test_ext_post_license().execute_with(|| { + assert_noop!( + ContractStore::instantiate(RuntimeOrigin::signed(ALICE), APP_ID, LICENSE_ID, 0, vec![], vec![]), + Error::::NoPermission + ); + }) + } + + #[test] + fn fails_if_caller_cannot_reserve_storage_deposit() { + test_ext_post_license().execute_with(|| { + assert_noop!( + ContractStore::instantiate(RuntimeOrigin::signed(BOB), APP_ID, LICENSE_ID, 0, vec![], vec![]), + TokenError::FundsUnavailable + ); + }) + } + + #[test] + fn it_works() { + test_ext_post_license().execute_with(|| { + Balances::set_balance(&BOB, Balance::MAX / 2); + assert_ok!(ContractStore::instantiate( + RuntimeOrigin::signed(BOB), + APP_ID, + LICENSE_ID, + 0, + vec![], + vec![] + )); + + System::assert_has_event( + Event::::AppInstantiated { + app_id: APP_ID, + license_id: LICENSE_ID, + caller: BOB, + } + .into(), + ); + + let contract_address = SimpleAddressGenerator::contract_address(&BOB, &code_hash("call"), &[], &[]); + + assert!(matches!( + Listings::item(&(0, APP_ID), &LICENSE_ID), + Some(item::Item { owner, .. }) if owner == contract_address + )); + }) + } +} + +mod upgrade { + use super::*; + + fn new_test_ext() -> TestExternalities { + let mut t = test_ext_post_license(); + t.execute_with(|| { + Balances::set_balance(&BOB, Balance::MAX / 2); + assert_ok!(ContractStore::instantiate( + RuntimeOrigin::signed(BOB), + APP_ID, + LICENSE_ID, + 0, + vec![], + vec![] + )); + }); + t + } + + #[test] + fn fails_if_app_instance_not_found() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::upgrade(RuntimeOrigin::signed(BOB), 1, LICENSE_ID), + Error::::AppNotFound + ); + + assert_noop!( + ContractStore::upgrade(RuntimeOrigin::signed(BOB), APP_ID, 1), + Error::::LicenseNotFound + ); + }) + } + + #[test] + fn fails_if_app_is_up_to_date() { + new_test_ext().execute_with(|| { + assert_noop!( + ContractStore::upgrade(RuntimeOrigin::root(), APP_ID, LICENSE_ID), + Error::::AppInstanceUpToDate + ); + }) + } + + #[test] + fn it_works() { + new_test_ext().execute_with(|| { + assert_ok!(ContractStore::publish_upgrade( + RuntimeOrigin::signed(ALICE), + APP_ID, + contract("balance") + )); + + assert_ok!(ContractStore::upgrade(RuntimeOrigin::root(), APP_ID, LICENSE_ID)); + }) + } +} diff --git a/pallets/contracts-store/src/types.rs b/pallets/contracts-store/src/types.rs new file mode 100644 index 00000000..8852dc46 --- /dev/null +++ b/pallets/contracts-store/src/types.rs @@ -0,0 +1,36 @@ +use super::*; + +use frame_contrib_traits::listings::item::ItemPrice; +use frame_support::traits::fungible::Inspect; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + +pub type CodeHash = ::Hash; +pub type AccountIdOf = ::AccountId; +pub type AppInfoFor = AppInfo, AccountIdOf, ItemPriceOf>; +pub type BalanceOf = <::Currency as Inspect>>::Balance; +pub type Contracts = pallet_contracts::Pallet; +pub type ListingsMerchantIdOf = <::Listings as InspectInventory>::MerchantId; +pub type ListingsAssetOf = <::Listings as InspectItem>>::Asset; +pub type ListingsBalanceOf = <::Listings as InspectItem>>::Balance; +pub type ItemPriceOf = ItemPrice, ListingsBalanceOf>; + +type ListingsOf = ::Listings; +pub type LicenseIdFor = as InspectItem>>::ItemId; + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Clone, PartialEq, Eq, Debug)] +pub struct AppInfo { + pub(crate) code_hash: Hash, + pub(crate) publisher: AccountId, + pub(crate) max_instances: Option, + pub(crate) instances: u64, + pub(crate) price: Option, + pub(crate) version: u32, +} + +impl AppInfo { + pub(crate) fn bump_version(&mut self, code_hash: Hash) -> Option<()> { + self.code_hash = code_hash; + self.version = self.version.checked_add(1)?; + Some(()) + } +} diff --git a/pallets/contracts-store/src/weights.rs b/pallets/contracts-store/src/weights.rs new file mode 100644 index 00000000..2b56795b --- /dev/null +++ b/pallets/contracts-store/src/weights.rs @@ -0,0 +1,95 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +pub trait WeightInfo { + fn publish(q: u32,) -> Weight; + fn set_parameters() -> Weight; + fn publish_upgrade(q: u32,) -> Weight; + fn request_license() -> Weight; + fn instantiate() -> Weight; + fn upgrade() -> Weight; +} + +/// Weights for pallet_contracts_store using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn publish(_: u32,) -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(14)) + } + fn set_parameters() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(14)) + } + fn publish_upgrade(_: u32,) -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(14)) + } + fn request_license() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(14)) + } + fn instantiate() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(14)) + } + fn upgrade() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(14)) + } +} + +impl WeightInfo for () { + fn publish(_: u32,) -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(14)) + } + fn set_parameters() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(14)) + } + fn publish_upgrade(_: u32,) -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(14)) + } + fn request_license() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(14)) + } + fn instantiate() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(14)) + } + fn upgrade() -> Weight { + Weight::from_parts(181_851_000, 0) + .saturating_add(Weight::from_parts(0, 132561)) + .saturating_add(RocksDbWeight::get().reads(8)) + .saturating_add(RocksDbWeight::get().writes(14)) + } +} \ No newline at end of file From df316a7eb0fcb3dc4eec8e6b0c3e7259179ffcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Wed, 23 Apr 2025 23:03:49 -0500 Subject: [PATCH 2/8] feat(kreivo-runtime): configure `pallet-contracts-store` --- Cargo.lock | 49 +++ pallets/communities/Cargo.toml | 67 ++-- runtime/kreivo/Cargo.toml | 370 +++++++++++---------- runtime/kreivo/src/config/contracts/mod.rs | 47 ++- runtime/kreivo/src/lib.rs | 2 + 5 files changed, 305 insertions(+), 230 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52479a50..07dde2f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4013,6 +4013,7 @@ dependencies = [ "pallet-communities", "pallet-communities-manager", "pallet-contracts", + "pallet-contracts-store", "pallet-message-queue", "pallet-multisig", "pallet-nfts", @@ -4912,6 +4913,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mock-helpers" +version = "0.1.0" +source = "git+https://github.com/virto-network/frame-contrib#b6a1ac91c45b02f2a468e4f1c39b0386b9e47a16" +dependencies = [ + "fc-pallet-listings", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "sp-io", + "sp-runtime", +] + [[package]] name = "mockall" version = "0.11.4" @@ -5756,6 +5771,20 @@ dependencies = [ "wasmi", ] +[[package]] +name = "pallet-contracts-fixtures" +version = "1.0.0" +source = "git+https://github.com/virto-network/polkadot-sdk?branch=release-virto-stable2409-6#d93cd013a45b96c9be233efc0aaaf75d020fe53f" +dependencies = [ + "anyhow", + "frame-system", + "parity-wasm", + "sp-runtime", + "tempfile", + "toml 0.8.20", + "twox-hash", +] + [[package]] name = "pallet-contracts-proc-macro" version = "23.0.1" @@ -5766,6 +5795,26 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "pallet-contracts-store" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-contrib-traits", + "frame-support", + "frame-system", + "mock-helpers", + "pallet-assets", + "pallet-balances", + "pallet-contracts", + "pallet-contracts-fixtures", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-contracts-uapi" version = "9.0.0" diff --git a/pallets/communities/Cargo.toml b/pallets/communities/Cargo.toml index 70fd8100..71f92b4e 100644 --- a/pallets/communities/Cargo.toml +++ b/pallets/communities/Cargo.toml @@ -44,41 +44,42 @@ virto-common = { workspace = true, default-features = false, features = [ default = ["std", "xcm", "serde"] serde = ["dep:serde", "scale-info/serde"] std = [ - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-assets/std", - "pallet-assets-freezer/std", - "pallet-balances/std", - "pallet-nfts/std", - "pallet-preimage/std", - "pallet-referenda/std", - "pallet-referenda-tracks/std", - "pallet-scheduler/std", - "parity-scale-codec/std", - "scale-info/std", - "serde?/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "virto-common/std", - "xcm?/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-assets/std", + "pallet-assets-freezer/std", + "pallet-balances/std", + "pallet-nfts/std", + "pallet-preimage/std", + "pallet-referenda/std", + "pallet-referenda-tracks/std", + "pallet-scheduler/std", + "parity-scale-codec/std", + "scale-info/std", + "serde?/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "virto-common/std", + "xcm?/std", + "frame-contrib-traits/std" ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-assets-freezer/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-nfts/runtime-benchmarks", - "pallet-preimage/runtime-benchmarks", - "pallet-referenda/runtime-benchmarks", - "pallet-referenda-tracks/runtime-benchmarks", - "pallet-scheduler/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-assets-freezer/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-referenda-tracks/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "frame-contrib-traits/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", diff --git a/runtime/kreivo/Cargo.toml b/runtime/kreivo/Cargo.toml index 8ffe80f1..18ad2433 100644 --- a/runtime/kreivo/Cargo.toml +++ b/runtime/kreivo/Cargo.toml @@ -27,6 +27,7 @@ kreivo-apis = { workspace = true, features = ["runtime"] } pallet-communities = { workspace = true, features = ["xcm"] } pallet-communities-manager.workspace = true +pallet-contracts-store.workspace = true # Local: Common runtime-common.workspace = true @@ -121,193 +122,196 @@ pass-webauthn = { workspace = true, features = ["runtime"] } default = ["std"] paseo = ["runtime-constants/paseo"] std = [ - "assets-common/std", - "cumulus-pallet-aura-ext/std", - "cumulus-pallet-parachain-system/std", - "cumulus-pallet-session-benchmarking/std", - "cumulus-pallet-xcm/std", - "cumulus-pallet-xcmp-queue/std", - "cumulus-primitives-aura/std", - "cumulus-primitives-core/std", - "cumulus-primitives-timestamp/std", - "cumulus-primitives-utility/std", - "frame-benchmarking/std", - "frame-executive/std", - "frame-support/std", - "frame-system-benchmarking/std", - "frame-system-rpc-runtime-api/std", - "frame-try-runtime/std", - "frame-system/std", - "kreivo-apis/std", - "log/std", - "runtime-constants/std", - "pallet-assets-freezer/std", - "pallet-asset-tx-payment/std", - "pallet-assets/std", - "pallet-aura/std", - "pallet-authorship/std", - "pallet-balances/std", - "pallet-contracts/std", - "pallet-communities-manager/std", - "pallet-communities/std", - "pallet-collator-selection/std", - "pallet-gas-transaction-payment/std", - "pallet-message-queue/std", - "pallet-multisig/std", - "pallet-nfts/std", - "pallet-pass/std", - "pallet-payments/std", - "pallet-preimage/std", - "pallet-proxy/std", - "pallet-ranked-collective/std", - "pallet-referenda/std", - "pallet-referenda-tracks/std", - "pallet-scheduler/std", - "pallet-session/std", - "pallet-skip-feeless-payment/std", - "pallet-sudo/std", - "pallet-timestamp/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "pallet-transaction-payment/std", - "pallet-treasury/std", - "pallet-utility/std", - "pallet-xcm/std", - "pallet-xcm-benchmarks/std", - "parachain-info/std", - "parachains-common/std", - "parity-scale-codec/std", - "pass-webauthn/std", - "polkadot-core-primitives/std", - "polkadot-parachain-primitives/std", - "polkadot-runtime-common/std", - "runtime-common/std", - "scale-info/std", - "sp-api/std", - "sp-block-builder/std", - "sp-consensus-aura/std", - "sp-core/std", - "sp-genesis-builder/std", - "sp-inherents/std", - "sp-io?/std", - "sp-offchain/std", - "sp-runtime/std", - "sp-session/std", - "sp-transaction-pool/std", - "sp-version/std", - "sp-weights/std", - "virto-common/std", - "xcm-builder/std", - "xcm-executor/std", - "xcm/std", - "pallet-listings/std", - "pallet-vesting/std", - "serde_json/std", - "pallet-assets-holder/std", - "pallet-orders/std", - "frame-contrib-traits/std", + "assets-common/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", + "cumulus-primitives-core/std", + "cumulus-primitives-timestamp/std", + "cumulus-primitives-utility/std", + "frame-benchmarking/std", + "frame-executive/std", + "frame-support/std", + "frame-system-benchmarking/std", + "frame-system-rpc-runtime-api/std", + "frame-try-runtime/std", + "frame-system/std", + "kreivo-apis/std", + "log/std", + "runtime-constants/std", + "pallet-assets-freezer/std", + "pallet-asset-tx-payment/std", + "pallet-assets/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/std", + "pallet-contracts/std", + "pallet-communities-manager/std", + "pallet-communities/std", + "pallet-collator-selection/std", + "pallet-gas-transaction-payment/std", + "pallet-message-queue/std", + "pallet-multisig/std", + "pallet-nfts/std", + "pallet-pass/std", + "pallet-payments/std", + "pallet-preimage/std", + "pallet-proxy/std", + "pallet-ranked-collective/std", + "pallet-referenda/std", + "pallet-referenda-tracks/std", + "pallet-scheduler/std", + "pallet-session/std", + "pallet-skip-feeless-payment/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-treasury/std", + "pallet-utility/std", + "pallet-xcm/std", + "pallet-xcm-benchmarks/std", + "parachain-info/std", + "parachains-common/std", + "parity-scale-codec/std", + "pass-webauthn/std", + "polkadot-core-primitives/std", + "polkadot-parachain-primitives/std", + "polkadot-runtime-common/std", + "runtime-common/std", + "scale-info/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-io?/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-transaction-pool/std", + "sp-version/std", + "sp-weights/std", + "virto-common/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", + "pallet-listings/std", + "pallet-vesting/std", + "serde_json/std", + "pallet-assets-holder/std", + "pallet-orders/std", + "frame-contrib-traits/std", + "pallet-contracts-store/std" ] runtime-benchmarks = [ - "assets-common/runtime-benchmarks", - "cumulus-pallet-session-benchmarking/runtime-benchmarks", - "cumulus-pallet-parachain-system/runtime-benchmarks", - "cumulus-pallet-xcmp-queue/runtime-benchmarks", - "cumulus-primitives-core/runtime-benchmarks", - "cumulus-primitives-utility/runtime-benchmarks", - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "hex-literal", - "pallet-assets-freezer/runtime-benchmarks", - "pallet-asset-tx-payment/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-contracts/runtime-benchmarks", - "pallet-communities-manager/runtime-benchmarks", - "pallet-communities/runtime-benchmarks", - "pallet-collator-selection/runtime-benchmarks", - "pallet-gas-transaction-payment/runtime-benchmarks", - "pallet-message-queue/runtime-benchmarks", - "pallet-multisig/runtime-benchmarks", - "pallet-nfts/runtime-benchmarks", - "pallet-payments/runtime-benchmarks", - "pallet-pass/runtime-benchmarks", - "pallet-preimage/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", - "pallet-ranked-collective/runtime-benchmarks", - "pallet-referenda/runtime-benchmarks", - "pallet-referenda-tracks/runtime-benchmarks", - "pallet-scheduler/runtime-benchmarks", - "pallet-skip-feeless-payment/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", - "pallet-treasury/runtime-benchmarks", - "pallet-utility/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", - "pallet-xcm/runtime-benchmarks", - "pass-webauthn/runtime-benchmarks", - "parachains-common/runtime-benchmarks", - "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-runtime-common/runtime-benchmarks", - "runtime-common/runtime-benchmarks", - "sp-io", - "sp-runtime/runtime-benchmarks", - "xcm-builder/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", - "pallet-listings/runtime-benchmarks", - "pallet-vesting/runtime-benchmarks", - "pallet-assets-holder/runtime-benchmarks", - "pallet-orders/runtime-benchmarks", - "frame-contrib-traits/runtime-benchmarks", + "assets-common/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-assets-freezer/runtime-benchmarks", + "pallet-asset-tx-payment/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-contracts/runtime-benchmarks", + "pallet-communities-manager/runtime-benchmarks", + "pallet-communities/runtime-benchmarks", + "pallet-collator-selection/runtime-benchmarks", + "pallet-gas-transaction-payment/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-multisig/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", + "pallet-payments/runtime-benchmarks", + "pallet-pass/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", + "pallet-ranked-collective/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-referenda-tracks/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", + "pallet-skip-feeless-payment/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-treasury/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "pass-webauthn/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "runtime-common/runtime-benchmarks", + "sp-io", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", + "pallet-listings/runtime-benchmarks", + "pallet-vesting/runtime-benchmarks", + "pallet-assets-holder/runtime-benchmarks", + "pallet-orders/runtime-benchmarks", + "frame-contrib-traits/runtime-benchmarks", + "pallet-contracts-store/runtime-benchmarks" ] try-runtime = [ - "cumulus-pallet-aura-ext/try-runtime", - "cumulus-pallet-parachain-system/try-runtime", - "cumulus-pallet-xcm/try-runtime", - "cumulus-pallet-xcmp-queue/try-runtime", - "frame-executive/try-runtime", - "frame-system/try-runtime", - "frame-support/try-runtime", - "frame-try-runtime/try-runtime", - "pallet-assets-freezer/try-runtime", - "pallet-asset-tx-payment/try-runtime", - "pallet-assets/try-runtime", - "pallet-aura/try-runtime", - "pallet-authorship/try-runtime", - "pallet-balances/try-runtime", - "pallet-contracts/try-runtime", - "pallet-communities-manager/try-runtime", - "pallet-communities/try-runtime", - "pallet-collator-selection/try-runtime", - "pallet-gas-transaction-payment/try-runtime", - "pallet-message-queue/try-runtime", - "pallet-nfts/try-runtime", - "pallet-multisig/try-runtime", - "pallet-pass/try-runtime", - "pallet-payments/try-runtime", - "pallet-preimage/try-runtime", - "pallet-proxy/try-runtime", - "pallet-ranked-collective/try-runtime", - "pallet-referenda/try-runtime", - "pallet-referenda-tracks/try-runtime", - "pallet-scheduler/try-runtime", - "pallet-session/try-runtime", - "pallet-skip-feeless-payment/try-runtime", - "pallet-sudo/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", - "pallet-treasury/try-runtime", - "pallet-utility/try-runtime", - "pallet-xcm/try-runtime", - "pass-webauthn/try-runtime", - "parachain-info/try-runtime", - "polkadot-runtime-common/try-runtime", - "runtime-common/try-runtime", - "sp-runtime/try-runtime", - "pallet-listings/try-runtime", - "pallet-vesting/try-runtime", - "pallet-assets-holder/try-runtime", - "pallet-orders/try-runtime", + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-system/try-runtime", + "frame-support/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-assets-freezer/try-runtime", + "pallet-asset-tx-payment/try-runtime", + "pallet-assets/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-balances/try-runtime", + "pallet-contracts/try-runtime", + "pallet-communities-manager/try-runtime", + "pallet-communities/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-gas-transaction-payment/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-nfts/try-runtime", + "pallet-multisig/try-runtime", + "pallet-pass/try-runtime", + "pallet-payments/try-runtime", + "pallet-preimage/try-runtime", + "pallet-proxy/try-runtime", + "pallet-ranked-collective/try-runtime", + "pallet-referenda/try-runtime", + "pallet-referenda-tracks/try-runtime", + "pallet-scheduler/try-runtime", + "pallet-session/try-runtime", + "pallet-skip-feeless-payment/try-runtime", + "pallet-sudo/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-treasury/try-runtime", + "pallet-utility/try-runtime", + "pallet-xcm/try-runtime", + "pass-webauthn/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "runtime-common/try-runtime", + "sp-runtime/try-runtime", + "pallet-listings/try-runtime", + "pallet-vesting/try-runtime", + "pallet-assets-holder/try-runtime", + "pallet-orders/try-runtime", + "pallet-contracts-store/try-runtime" ] diff --git a/runtime/kreivo/src/config/contracts/mod.rs b/runtime/kreivo/src/config/contracts/mod.rs index f97435a4..a6ac7d24 100644 --- a/runtime/kreivo/src/config/contracts/mod.rs +++ b/runtime/kreivo/src/config/contracts/mod.rs @@ -1,20 +1,19 @@ use super::*; +use frame_support::traits::{Get, MapSuccess}; use frame_support::{ parameter_types, traits::{ConstBool, ConstU32, Randomness}, }; use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::EnsureNever; use kreivo_apis::KreivoChainExtensions; use pallet_balances::Call as BalancesCall; -#[cfg(not(feature = "runtime-benchmarks"))] -use { - frame_support::traits::EitherOf, frame_system::EnsureRootWithSuccess, - pallet_communities::origin::AsSignedByStaticCommunity, sp_core::ConstU16, -}; - #[cfg(feature = "runtime-benchmarks")] use frame_system::EnsureSigned; +use pallet_communities::origin::EnsureCommunity; +use sp_runtime::morph_types; +use virto_common::listings; pub enum CallFilter {} @@ -74,6 +73,12 @@ parameter_types! { pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); } +morph_types! { + pub type ReplaceWithCommunityAccount = |c: CommunityId| -> AccountId { + Communities::community_account(&c) + }; +} + impl pallet_contracts::Config for Runtime { type Time = Timestamp; type Randomness = DummyRandomness; @@ -118,17 +123,11 @@ impl pallet_contracts::Config for Runtime { type UnsafeUnstableInterface = ConstBool; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; #[cfg(not(feature = "runtime-benchmarks"))] - type UploadOrigin = EnsureRootWithSuccess; + type UploadOrigin = MapSuccess, ReplaceWithCommunityAccount>; #[cfg(feature = "runtime-benchmarks")] type UploadOrigin = EnsureSigned; #[cfg(not(feature = "runtime-benchmarks"))] - type InstantiateOrigin = EitherOf< - EnsureRootWithSuccess, - EitherOf< - AsSignedByStaticCommunity>, // Virto - AsSignedByStaticCommunity>, // Kippu - >, - >; + type InstantiateOrigin = EnsureNever; #[cfg(feature = "runtime-benchmarks")] type InstantiateOrigin = EnsureSigned; #[cfg(not(feature = "runtime-benchmarks"))] @@ -144,3 +143,23 @@ impl pallet_contracts::Config for Runtime { type ApiVersion = (); type Xcm = pallet_xcm::Pallet; } + +parameter_types! { + pub ContractsStoreMerchantId: CommunityId = 0; +} + +morph_types! { + pub type AppInstantiationParams: Morph = |id: CommunityId| -> (AccountId, CommunityId) { + (Communities::community_account(&id), id) + }; +} + +impl pallet_contracts_store::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type InstantiateOrigin = MapSuccess, AppInstantiationParams>; + type AppId = listings::InventoryId; + type LicenseId = listings::ItemId; + type Listings = Listings; + type ContractsStoreMerchantId = ContractsStoreMerchantId; +} diff --git a/runtime/kreivo/src/lib.rs b/runtime/kreivo/src/lib.rs index 01a16b94..be820970 100644 --- a/runtime/kreivo/src/lib.rs +++ b/runtime/kreivo/src/lib.rs @@ -263,6 +263,8 @@ mod runtime { // Contracts #[runtime::pallet_index(80)] pub type Contracts = pallet_contracts; + #[runtime::pallet_index(81)] + pub type ContractsStore = pallet_contracts_store; } cumulus_pallet_parachain_system::register_validate_block! { From f1d1f407301c66909116fac0f661a948b3b068b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 24 Apr 2025 09:27:34 -0500 Subject: [PATCH 3/8] fix(kreivo-runtime): make clippy happy --- runtime/kreivo/src/config/contracts/mod.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/runtime/kreivo/src/config/contracts/mod.rs b/runtime/kreivo/src/config/contracts/mod.rs index a6ac7d24..77682dd9 100644 --- a/runtime/kreivo/src/config/contracts/mod.rs +++ b/runtime/kreivo/src/config/contracts/mod.rs @@ -1,19 +1,21 @@ use super::*; -use frame_support::traits::{Get, MapSuccess}; + use frame_support::{ parameter_types, - traits::{ConstBool, ConstU32, Randomness}, + traits::{ConstBool, ConstU32, MapSuccess, Randomness}, }; use frame_system::pallet_prelude::BlockNumberFor; -use frame_system::EnsureNever; +use sp_runtime::morph_types; + use kreivo_apis::KreivoChainExtensions; use pallet_balances::Call as BalancesCall; +use pallet_communities::origin::EnsureCommunity; +use virto_common::listings; +#[cfg(not(feature = "runtime-benchmarks"))] +use frame_system::EnsureNever; #[cfg(feature = "runtime-benchmarks")] use frame_system::EnsureSigned; -use pallet_communities::origin::EnsureCommunity; -use sp_runtime::morph_types; -use virto_common::listings; pub enum CallFilter {} From b1fed7ddfb61670e9b549e3e6b8d67560cf67736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 24 Apr 2025 09:39:05 -0500 Subject: [PATCH 4/8] chore(pallet-contracts-store): expose `CONTRACT_MERCHANT_ID` const. --- pallets/contracts-store/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallets/contracts-store/src/lib.rs b/pallets/contracts-store/src/lib.rs index cecf72c0..05878893 100644 --- a/pallets/contracts-store/src/lib.rs +++ b/pallets/contracts-store/src/lib.rs @@ -33,6 +33,8 @@ pub mod pallet { use pallet_contracts::{Code, CollectEvents, DebugInfo, InstantiateReturnValue}; use parity_scale_codec::HasCompact; + pub const CONTRACT_MERCHANT_ID: [u8; 20] = *b"CONTRACT_MERCHANT_ID"; + #[pallet::config] pub trait Config: pallet_contracts::Config + frame_system::Config { // Primitives: Some overarching types that are aggregated in the system. @@ -317,7 +319,7 @@ pub mod pallet { T::Listings::transfer(&inventory_id, &license_id, &account_id)?; // Also, we set the `merchant_id` derived from the origin, to ensure that the // contract gets access to the Kreivo Merchants API. - T::Listings::set_attribute(&inventory_id, &license_id, &b"CONTRACT_MERCHANT_ID", merchant_id)?; + T::Listings::set_attribute(&inventory_id, &license_id, &CONTRACT_MERCHANT_ID, merchant_id)?; Self::deposit_event(Event::::AppInstantiated { app_id, From 1699b843ffe8be511556ec66a23bec6f4769f7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 24 Apr 2025 09:39:27 -0500 Subject: [PATCH 5/8] chore(kreivo-runtime): implement `MerchantIdInfo` for runtime. --- runtime/kreivo/src/config/contracts/mod.rs | 43 +++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/runtime/kreivo/src/config/contracts/mod.rs b/runtime/kreivo/src/config/contracts/mod.rs index 77682dd9..dd4f40ab 100644 --- a/runtime/kreivo/src/config/contracts/mod.rs +++ b/runtime/kreivo/src/config/contracts/mod.rs @@ -2,14 +2,16 @@ use super::*; use frame_support::{ parameter_types, - traits::{ConstBool, ConstU32, MapSuccess, Randomness}, + traits::{nonfungibles_v2::InspectEnumerable, ConstBool, ConstU32, MapSuccess, Randomness}, }; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::morph_types; +use frame_contrib_traits::listings::InspectItem; use kreivo_apis::KreivoChainExtensions; use pallet_balances::Call as BalancesCall; use pallet_communities::origin::EnsureCommunity; +use pallet_listings::InventoryId; use virto_common::listings; #[cfg(not(feature = "runtime-benchmarks"))] @@ -17,8 +19,7 @@ use frame_system::EnsureNever; #[cfg(feature = "runtime-benchmarks")] use frame_system::EnsureSigned; -pub enum CallFilter {} - +pub struct CallFilter; impl Contains for CallFilter { fn contains(call: &RuntimeCall) -> bool { matches!( @@ -28,30 +29,16 @@ impl Contains for CallFilter { } } -fn schedule() -> pallet_contracts::Schedule { - const MB: u32 = 1024 * 1024; - pallet_contracts::Schedule { - limits: pallet_contracts::Limits { - validator_runtime_memory: 1024 * MB, - // Current `max_storage_size`: 138 MB - // Constraint: `runtime_memory <= validator_runtime_memory - 2 * max_storage_size` - runtime_memory: 748 * MB, - ..Default::default() - }, - ..Default::default() - } -} - // randomness-collective-flip is insecure. Provide dummy randomness as // placeholder for the deprecated trait. https://github.com/paritytech/polkadot-sdk/blob/9bf1a5e23884921498b381728bfddaae93f83744/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs#L45 pub struct DummyRandomness(core::marker::PhantomData); - impl Randomness> for DummyRandomness { fn random(_subject: &[u8]) -> (T::Hash, BlockNumberFor) { (Default::default(), Default::default()) } } +// Use Kreivo APIs for Chain Extensions impl kreivo_apis::Config for Runtime { type Balances = Balances; type Assets = Assets; @@ -62,15 +49,29 @@ impl kreivo_apis::Config for Runtime { impl kreivo_apis::MerchantIdInfo for Runtime { type MerchantId = CommunityId; - fn maybe_merchant_id(_who: &AccountId) -> Option { - None + fn maybe_merchant_id(who: &AccountId) -> Option { + ListingsCatalog::owned(&who).find_map(|(InventoryId(m, app_id), license)| { + Listings::attribute(&(m, app_id), &license, &pallet_contracts_store::CONTRACT_MERCHANT_ID) + }) } } parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); - pub Schedule: pallet_contracts::Schedule = schedule::(); + pub Schedule: pallet_contracts::Schedule = { + const MB: u32 = 1024 * 1024; + pallet_contracts::Schedule { + limits: pallet_contracts::Limits { + validator_runtime_memory: 1024 * MB, + // Current `max_storage_size`: 138 MB + // Constraint: `runtime_memory <= validator_runtime_memory - 2 * max_storage_size` + runtime_memory: 748 * MB, + ..Default::default() + }, + ..Default::default() + } + }; pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); } From 9c38035f3e0a183f1a3c9212e1cd2b3c47c6f5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Thu, 24 Apr 2025 09:42:42 -0500 Subject: [PATCH 6/8] fix(runtime-constants): remove unused `EPOCH_DURATION_IN_SLOTS`. --- runtime/runtime-constants/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/runtime/runtime-constants/src/lib.rs b/runtime/runtime-constants/src/lib.rs index e9a79d54..9d091bf1 100644 --- a/runtime/runtime-constants/src/lib.rs +++ b/runtime/runtime-constants/src/lib.rs @@ -56,10 +56,8 @@ pub mod currency { /// Time and blocks. pub mod time { use polkadot_primitives::{BlockNumber, Moment}; - use polkadot_runtime_common::prod_or_fast; pub const MILLISECS_PER_BLOCK: Moment = 6000; pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; - pub const EPOCH_DURATION_IN_SLOTS: BlockNumber = prod_or_fast!(HOURS, MINUTES); // These time units are defined in number of blocks. pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); pub const HOURS: BlockNumber = MINUTES * 60; From 00fe6816dd6c019f9c98860b216391ac20fa2a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 25 Apr 2025 10:50:17 -0500 Subject: [PATCH 7/8] feat(pallet-contracts-store): split contract instance info with license item, so the license holder preserves it. --- pallets/contracts-store/src/lib.rs | 47 +++++++++++++++++++--------- pallets/contracts-store/src/tests.rs | 9 ++---- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/pallets/contracts-store/src/lib.rs b/pallets/contracts-store/src/lib.rs index 05878893..9c084758 100644 --- a/pallets/contracts-store/src/lib.rs +++ b/pallets/contracts-store/src/lib.rs @@ -103,6 +103,8 @@ pub mod pallet { LicenseNotFound, /// The address associated to an app license is not a valid instance. AppInstanceNotFound, + /// The contract was not instantiated due to being reverted. + ContractReverted, /// The application instance is up to date. AppInstanceUpToDate, } @@ -149,6 +151,14 @@ pub mod pallet { #[pallet::storage] pub type Apps = StorageMap<_, Blake2_128Concat, T::AppId, AppInfoFor>; + /// The `MerchantId` associated to a contract account. + #[pallet::storage] + pub type ContractMerchantId = StorageMap<_, Blake2_128Concat, T::AccountId, ListingsMerchantIdOf>; + + /// The contract account of an app instance. + #[pallet::storage] + pub type ContractAccount = StorageMap<_, Blake2_128Concat, (T::AppId, T::LicenseId), AccountIdOf>; + #[pallet::call(weight(::WeightInfo))] impl Pallet where @@ -300,7 +310,7 @@ pub mod pallet { ensure!(caller == owner, Error::::NoPermission); - let InstantiateReturnValue { account_id, .. } = Contracts::::bare_instantiate( + let InstantiateReturnValue { result, account_id } = Contracts::::bare_instantiate( caller.clone(), value, Weight::MAX, // TODO: Replace with something reasonable. @@ -313,13 +323,12 @@ pub mod pallet { ) .result?; - // Now that the contract is enacted, the new license owner is the app itself. - // That way, we can map the contract account with the actual license (and find - // the contract account via the app instance). - T::Listings::transfer(&inventory_id, &license_id, &account_id)?; - // Also, we set the `merchant_id` derived from the origin, to ensure that the - // contract gets access to the Kreivo Merchants API. - T::Listings::set_attribute(&inventory_id, &license_id, &CONTRACT_MERCHANT_ID, merchant_id)?; + if result.did_revert() { + Err(Error::::ContractReverted)? + } + + ContractAccount::::insert((app_id.clone(), license_id.clone()), account_id.clone()); + ContractMerchantId::::insert(account_id, merchant_id); Self::deposit_event(Event::::AppInstantiated { app_id, @@ -332,14 +341,18 @@ pub mod pallet { #[pallet::call_index(5)] pub fn upgrade(origin: OriginFor, app_id: T::AppId, license_id: T::LicenseId) -> DispatchResult { - ensure_signed_or_root(origin)?; - let AppInfo { code_hash, .. } = Apps::::get(&app_id).ok_or(Error::::AppNotFound)?; + let (who, _) = &<::InstantiateOrigin>::ensure_origin(origin)?; + let AppInfo { code_hash, .. } = Apps::::get(&app_id).ok_or(Error::::AppNotFound)?; let inventory_id = (T::ContractsStoreMerchantId::get(), app_id.clone()); - let Item { - owner: contract_account, - .. - } = T::Listings::item(&inventory_id, &license_id).ok_or(Error::::LicenseNotFound)?; + let Item { ref owner, .. } = + T::Listings::item(&inventory_id, &license_id).ok_or(Error::::LicenseNotFound)?; + + ensure!(who == owner, Error::::NoPermission); + + let contract_account = + ContractAccount::::get(&(app_id, license_id)).ok_or(Error::::AppInstanceNotFound)?; + let instance_hash = Contracts::::code_hash(&contract_account).ok_or(Error::::AppInstanceNotFound)?; ensure!(code_hash != instance_hash, Error::::AppInstanceUpToDate); @@ -387,3 +400,9 @@ impl Pallet { Ok(()) } } + +impl Pallet { + pub fn maybe_merchant_id(who: &T::AccountId) -> Option> { + ContractMerchantId::::get(who) + } +} diff --git a/pallets/contracts-store/src/tests.rs b/pallets/contracts-store/src/tests.rs index d9ad98eb..cffa5d6c 100644 --- a/pallets/contracts-store/src/tests.rs +++ b/pallets/contracts-store/src/tests.rs @@ -379,10 +379,7 @@ mod instantiate { let contract_address = SimpleAddressGenerator::contract_address(&BOB, &code_hash("call"), &[], &[]); - assert!(matches!( - Listings::item(&(0, APP_ID), &LICENSE_ID), - Some(item::Item { owner, .. }) if owner == contract_address - )); + assert_eq!(ContractStore::maybe_merchant_id(&contract_address), Some(2)); }) } } @@ -425,7 +422,7 @@ mod upgrade { fn fails_if_app_is_up_to_date() { new_test_ext().execute_with(|| { assert_noop!( - ContractStore::upgrade(RuntimeOrigin::root(), APP_ID, LICENSE_ID), + ContractStore::upgrade(RuntimeOrigin::signed(BOB), APP_ID, LICENSE_ID), Error::::AppInstanceUpToDate ); }) @@ -440,7 +437,7 @@ mod upgrade { contract("balance") )); - assert_ok!(ContractStore::upgrade(RuntimeOrigin::root(), APP_ID, LICENSE_ID)); + assert_ok!(ContractStore::upgrade(RuntimeOrigin::signed(BOB), APP_ID, LICENSE_ID)); }) } } From e3eddecb60188bb4eab78dd5b3cf73c549b21093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Andr=C3=A9s=20Dorado=20Su=C3=A1rez?= Date: Fri, 25 Apr 2025 10:54:13 -0500 Subject: [PATCH 8/8] chore(kreivo-runtime): minor improvements on usability and dev environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement runtime `ContractApis` 🛠️ - use `ContractsStore::maybe_merchant_id` 🪪 - load first community with some balance 🤑 --- runtime/kreivo/src/apis.rs | 71 ++++++++++++++++++- runtime/kreivo/src/config/contracts/mod.rs | 8 +-- .../runtime-constants/src/genesis_presets.rs | 2 + 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/runtime/kreivo/src/apis.rs b/runtime/kreivo/src/apis.rs index 8e8b8827..3ff544d7 100644 --- a/runtime/kreivo/src/apis.rs +++ b/runtime/kreivo/src/apis.rs @@ -1,5 +1,6 @@ use super::*; - +use frame_system::EventRecord; +use pallet_contracts::{CollectEvents, DebugInfo, Determinism}; use sp_api::impl_runtime_apis; use sp_core::OpaqueMetadata; use sp_runtime::{ @@ -9,6 +10,8 @@ use sp_runtime::{ }; use sp_version::RuntimeVersion; +type EvtRecord = EventRecord; + impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { @@ -119,6 +122,72 @@ impl_runtime_apis! { } } + impl pallet_contracts::ContractsApi for Runtime { + fn call( + origin: AccountId, + dest: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + input_data: Vec, + ) -> pallet_contracts::ContractExecResult { + Contracts::bare_call( + origin, + dest, + value, + gas_limit.unwrap_or_default(), + storage_deposit_limit, + input_data, + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Relaxed + ) + } + + fn instantiate( + origin: AccountId, + value: Balance, + gas_limit: Option, + storage_deposit_limit: Option, + code: pallet_contracts::Code, + data: Vec, + salt: Vec, + ) -> pallet_contracts::ContractInstantiateResult { + Contracts::bare_instantiate( + origin, + value, + gas_limit.unwrap_or_default(), + storage_deposit_limit, + code, + data, + salt, + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + ) + } + + fn upload_code( + origin: AccountId, + code: Vec, + storage_deposit_limit: Option, + determinism: Determinism, + ) -> pallet_contracts::CodeUploadResult { + Contracts::bare_upload_code( + origin, + code, + storage_deposit_limit, + determinism + ) + } + + fn get_storage( + address: AccountId, + key: Vec, + ) -> pallet_contracts::GetStorageResult { + Contracts::get_storage(address, key ) + } + } + impl sp_offchain::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { Executive::offchain_worker(header) diff --git a/runtime/kreivo/src/config/contracts/mod.rs b/runtime/kreivo/src/config/contracts/mod.rs index dd4f40ab..b403f81e 100644 --- a/runtime/kreivo/src/config/contracts/mod.rs +++ b/runtime/kreivo/src/config/contracts/mod.rs @@ -2,16 +2,14 @@ use super::*; use frame_support::{ parameter_types, - traits::{nonfungibles_v2::InspectEnumerable, ConstBool, ConstU32, MapSuccess, Randomness}, + traits::{ConstBool, ConstU32, MapSuccess, Randomness}, }; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::morph_types; -use frame_contrib_traits::listings::InspectItem; use kreivo_apis::KreivoChainExtensions; use pallet_balances::Call as BalancesCall; use pallet_communities::origin::EnsureCommunity; -use pallet_listings::InventoryId; use virto_common::listings; #[cfg(not(feature = "runtime-benchmarks"))] @@ -50,9 +48,7 @@ impl kreivo_apis::MerchantIdInfo for Runtime { type MerchantId = CommunityId; fn maybe_merchant_id(who: &AccountId) -> Option { - ListingsCatalog::owned(&who).find_map(|(InventoryId(m, app_id), license)| { - Listings::attribute(&(m, app_id), &license, &pallet_contracts_store::CONTRACT_MERCHANT_ID) - }) + ContractsStore::maybe_merchant_id(who) } } diff --git a/runtime/runtime-constants/src/genesis_presets.rs b/runtime/runtime-constants/src/genesis_presets.rs index 2aa884da..f343cd8b 100644 --- a/runtime/runtime-constants/src/genesis_presets.rs +++ b/runtime/runtime-constants/src/genesis_presets.rs @@ -20,6 +20,7 @@ use alloc::format; use alloc::vec::Vec; use parachains_common::AuraId; use polkadot_primitives::{AccountId, AccountPublic}; +use sp_core::crypto::Ss58Codec; use sp_core::{sr25519, Pair, Public}; use sp_runtime::traits::IdentifyAccount; @@ -56,6 +57,7 @@ pub fn testnet_accounts() -> Vec { get_account_id_from_seed::("Dave//stash"), get_account_id_from_seed::("Eve//stash"), get_account_id_from_seed::("Ferdie//stash"), + AccountId::from_ss58check("F3opxRaMqPWKwA5yup6vZy2GLA28aJ3XSEX31Uf8qrhmaQt").expect("address is correct"), ]) }