From 5a1d70ffcf854eb98ab685687f537b0524cbe3d5 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:05:15 +0100 Subject: [PATCH 01/12] feat(funding): Loosely couple pallet_asset --- pallets/funding/Cargo.toml | 1 + pallets/funding/src/lib.rs | 25 ++++++++++++++++++++++--- pallets/funding/src/mock.rs | 28 ++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/pallets/funding/Cargo.toml b/pallets/funding/Cargo.toml index be65de40c..767d71204 100644 --- a/pallets/funding/Cargo.toml +++ b/pallets/funding/Cargo.toml @@ -31,6 +31,7 @@ sp-core = { default-features = false, git = "https://github.com/paritytech/subst sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.32" } pallet-balances = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.32" } pallet-randomness-collective-flip = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.32" } +pallet-assets = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.32" } pallet-credentials = { path = "../credentials", default-features = false } [features] diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 4866b1f3f..c264d9031 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -63,8 +63,14 @@ use codec::HasCompact; use frame_support::{ pallet_prelude::ValueQuery, traits::{ - tokens::Balance, Currency, Get, LockIdentifier, LockableCurrency, Randomness, - ReservableCurrency, WithdrawReasons, + tokens::{ + fungibles::{ + metadata::Mutate as MetadataMutate, Create, Inspect, InspectMetadata, Mutate, + }, + Balance, + }, + Currency, Get, LockIdentifier, LockableCurrency, Randomness, ReservableCurrency, + WithdrawReasons, }, PalletId, }; @@ -83,6 +89,12 @@ pub type ProjectOf = Project< ::Hash, >; +type AssetIdOf = + <::Assets as Inspect<::AccountId>>::AssetId; + +type AssetBalanceOf = + <::Assets as Inspect<::AccountId>>::Balance; + // TODO: Add multiple locks const LOCKING_ID: LockIdentifier = *b"evaluate"; @@ -114,7 +126,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Identifier for the collection of item. - type ProjectIdentifier: Identifiable; + type ProjectIdentifier: Identifiable + From> + Into>; /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to /// `From`. @@ -136,6 +148,13 @@ pub mod pallet { /// Something that provides the members of the Polimec type HandleMembers: PolimecMembers; + /// Something that provides the ability to create, mint and burn fungible assets. + type Assets: Create + + Inspect + + Mutate + + MetadataMutate + + InspectMetadata; + /// The maximum length of data stored on-chain. #[pallet::constant] type StringLimit: Get; diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 3cdb7707c..cf19d253f 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -1,7 +1,11 @@ use super::*; use crate as pallet_funding; -use frame_support::{pallet_prelude::ConstU32, parameter_types, traits::ConstU16, PalletId}; +use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU16, ConstU32}, + PalletId, +}; use frame_system as system; use sp_core::H256; use sp_runtime::{ @@ -17,6 +21,7 @@ type Block = frame_system::mocking::MockBlock; pub type AccountId = u64; pub type Balance = u128; pub type BlockNumber = u64; +pub type Identifier = u32; pub const PLMC: u128 = 10_000_000_000_u128; // Configure a mock runtime to test the pallet. @@ -28,6 +33,7 @@ frame_support::construct_runtime!( { System: frame_system, RandomnessCollectiveFlip: pallet_randomness_collective_flip, + Assets: pallet_assets, Balances: pallet_balances, FundingModule: pallet_funding, Credentials: pallet_credentials @@ -94,6 +100,23 @@ impl pallet_credentials::Config for Test { type MembershipChanged = (); } +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = u64; + type AssetId = Identifier; + type Currency = Balances; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type WeightInfo = (); + type Extra = (); +} + parameter_types! { pub const EvaluationDuration: BlockNumber = 28; pub const EnglishAuctionDuration: BlockNumber = 10; @@ -105,9 +128,10 @@ parameter_types! { impl pallet_funding::Config for Test { type RuntimeEvent = RuntimeEvent; type StringLimit = ConstU32<64>; - type ProjectIdentifier = u32; + type ProjectIdentifier = Identifier; type Currency = Balances; type BiddingCurrency = Balances; + type Assets = Assets; type CurrencyBalance = ::Balance; type EvaluationDuration = EvaluationDuration; type EnglishAuctionDuration = EnglishAuctionDuration; From 038fcac44c1b5c1524191342e29d5a14cf2bef7f Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:05:46 +0100 Subject: [PATCH 02/12] refactor(funding): Relax Identifiable trait --- pallets/funding/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index c264d9031..be231bd39 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -106,8 +106,6 @@ pub trait Identifiable = Member + MaybeSerializeDeserialize + MaxEncodedLen + AddAssign - + From - + From + From + TypeInfo; From 7e8b88874b9af5cbc2793aba645d7f54ee1dbf8b Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:07:22 +0100 Subject: [PATCH 03/12] fix(funding): Remove `unwrap()` inside hooks --- pallets/funding/src/lib.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index be231bd39..a67110151 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -674,20 +674,26 @@ pub mod pallet { // Check if we need to start the Funding Round // EvaluationEnded -> AuctionRound ProjectStatus::EvaluationEnded => { - let evaluation_period_ends = project_info.evaluation_period_ends.unwrap(); + let evaluation_period_ends = project_info + .evaluation_period_ends + .expect("In EvaluationEnded there always exist evaluation_period_ends"); Self::handle_auction_start(project_id, now, evaluation_period_ends); }, // Check if we need to move to the Candle Phase of the Auction Round // AuctionRound(AuctionPhase::English) -> AuctionRound(AuctionPhase::Candle) ProjectStatus::AuctionRound(AuctionPhase::English) => { - let english_ending_block = - project_info.auction_metadata.unwrap().english_ending_block; + let english_ending_block = project_info + .auction_metadata + .expect("In AuctionRound there always exist auction_metadata") + .english_ending_block; Self::handle_auction_candle(project_id, now, english_ending_block); }, // Check if we need to move from the Auction Round of the Community Round // AuctionRound(AuctionPhase::Candle) -> CommunityRound ProjectStatus::AuctionRound(AuctionPhase::Candle) => { - let auction_metadata = project_info.auction_metadata.unwrap(); + let auction_metadata = project_info + .auction_metadata + .expect("In AuctionRound there always exist auction_metadata"); let candle_ending_block = auction_metadata.candle_ending_block; let english_ending_block = auction_metadata.english_ending_block; Self::handle_community_start( @@ -712,15 +718,19 @@ pub mod pallet { // Check if Evaluation Round have to end, if true, end it // EvaluationRound -> EvaluationEnded ProjectStatus::EvaluationRound => { - let evaluation_period_ends = project_info.evaluation_period_ends.unwrap(); + let evaluation_period_ends = project_info + .evaluation_period_ends + .expect("In EvaluationRound there always exist evaluation_period_ends"); Self::handle_evaluation_end(project_id, now, evaluation_period_ends); }, // Check if we need to end the Fundind Round // CommunityRound -> FundingEnded ProjectStatus::CommunityRound => { - let community_ending_block = - project_info.auction_metadata.unwrap().community_ending_block; - Self::handle_community_end(project_id, now, community_ending_block); + let community_ending_block = project_info + .auction_metadata + .expect("In CommunityRound there always exist auction_metadata") + .community_ending_block; + Self::handle_community_end(*project_id, now, community_ending_block); }, _ => (), } From e715fba6b0530aeb558134effc5d57ba732ca792 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:08:18 +0100 Subject: [PATCH 04/12] fix(funding): The Identifier is now accepting only u32 --- pallets/funding/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index a67110151..37ade6a34 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -830,7 +830,7 @@ impl Pallet { Projects::::insert(project_id, project); ProjectsInfo::::insert(project_id, project_info); ProjectsIssuers::::insert(project_id, issuer); - ProjectId::::mutate(|n| *n += 1_u8.into()); + ProjectId::::mutate(|n| *n += 1_u32.into()); Self::deposit_event(Event::::Created { project_id }); Ok(()) From 90012fc2f4b386a80d4f37cb5ea4da8f73066854 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:12:45 +0100 Subject: [PATCH 05/12] feat(funding): Create an Asset and set its metadata --- pallets/funding/src/lib.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 37ade6a34..b1d8b1bc8 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -939,7 +939,7 @@ impl Pallet { } pub fn handle_community_end( - project_id: &T::ProjectIdentifier, + project_id: T::ProjectIdentifier, now: T::BlockNumber, community_ending_block: T::BlockNumber, ) { @@ -947,10 +947,24 @@ impl Pallet { ProjectsInfo::::mutate(project_id, |project_info| { project_info.project_status = ProjectStatus::FundingEnded; }); + }; - // TODO: Mint the "Contribution Tokens" - // TODO: Assign the CTs to the participants of the Funding Round - } + let issuer = + ProjectsIssuers::::get(project_id).expect("The issuer exists, already tested."); + let project = Projects::::get(project_id).expect("The project exists, already tested."); + let token_information = project.token_information; + let id: AssetIdOf = project_id.into(); + + // TODO: Unused result + let _ = T::Assets::create(id, issuer.clone(), false, 1_u32.into()); + // TODO: Unused result + let _ = T::Assets::set( + id, + &issuer, + token_information.name.into(), + token_information.symbol.into(), + token_information.decimals, + ); } pub fn handle_fuding_end(project_id: &T::ProjectIdentifier, _now: T::BlockNumber) { From 6e3fa9d7a4953c6a3b1043fc4410ca1199a9e565 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:25:12 +0100 Subject: [PATCH 06/12] feat(funding): Add extrinsic to claim CT after the end of the Community Round\ --- pallets/funding/src/lib.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index b1d8b1bc8..d770e45c1 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -89,9 +89,11 @@ pub type ProjectOf = Project< ::Hash, >; +/// Contribution Token identifier type AssetIdOf = <::Assets as Inspect<::AccountId>>::AssetId; +/// Contribution Token balance type AssetBalanceOf = <::Assets as Inspect<::AccountId>>::Balance; @@ -663,6 +665,29 @@ pub mod pallet { Ok(()) } + + #[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().reads_writes(1,1))] + pub fn claim_contribution_tokens( + origin: OriginFor, + project_id: T::ProjectIdentifier, + ) -> DispatchResult { + let issuer = ensure_signed(origin)?; + + ensure!(ProjectsIssuers::::contains_key(project_id), Error::::ProjectNotExists); + + // TODO: Check the right credential status + // ensure!( + // T::HandleMembers::is_in(&MemberRole::Issuer, &issuer), + // Error::::NotAuthorized + // ); + + let project_info = ProjectsInfo::::get(project_id); + ensure!( + project_info.project_status == ProjectStatus::FundingEnded, + Error::::EvaluationNotStarted + ); + Self::do_claim_contribution_tokens(project_id, issuer) + } } #[pallet::hooks] From c9300c050d9e82f9e8784543c1f954ed000d0ca9 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:32:41 +0100 Subject: [PATCH 07/12] feat(funding): Add skeleton functions for the claiming process --- pallets/funding/src/lib.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index d770e45c1..f00e88789 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -686,6 +686,9 @@ pub mod pallet { project_info.project_status == ProjectStatus::FundingEnded, Error::::EvaluationNotStarted ); + + // TODO: Add logic to check if the `issuer` can actually claim the tokens + Self::do_claim_contribution_tokens(project_id, issuer) } } @@ -1063,4 +1066,25 @@ impl Pallet { Nonce::::put(nonce.wrapping_add(1)); nonce.encode() } + + fn do_claim_contribution_tokens( + project_id: T::ProjectIdentifier, + claimer: T::AccountId, + ) -> Result<(), DispatchError> { + let amount = Self::calculate_claimable_tokens(project_id, &claimer); + let id: AssetIdOf = project_id.into(); + T::Assets::mint_into(id, &claimer, amount)?; + Ok(()) + } + + fn calculate_claimable_tokens( + project_id: T::ProjectIdentifier, + _claimer: &T::AccountId, + ) -> AssetBalanceOf { + let _project = Projects::::get(project_id).expect("The project exists, already tested."); + let _project_info = ProjectsInfo::::get(project_id); + + // TODO: Compute the right amount of tokens to claim + AssetBalanceOf::::from(0_u32) + } } From 956a74dd5b5efa1ecf3f6fd73d390c5d2e5907ea Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 13:53:56 +0100 Subject: [PATCH 08/12] test(funding): Test if the asset is actually created during the hook --- Cargo.lock | 1 + pallets/funding/src/tests.rs | 66 +++++++++++++++++++++--------------- runtimes/testnet/src/lib.rs | 8 +++-- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77e41f0a7..fa8129000 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5325,6 +5325,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "pallet-assets", "pallet-balances", "pallet-credentials", "pallet-randomness-collective-flip", diff --git a/pallets/funding/src/tests.rs b/pallets/funding/src/tests.rs index ffee09f95..7181036c4 100644 --- a/pallets/funding/src/tests.rs +++ b/pallets/funding/src/tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{mock::*, Error, ParticipantsSize, Project, TicketSize, Weight}; +use crate::{mock::*, CurrencyMetadata, Error, ParticipantsSize, Project, TicketSize, Weight}; use frame_support::{assert_noop, assert_ok}; pub fn last_event() -> RuntimeEvent { @@ -52,12 +52,19 @@ fn create_project() -> Project>, u128, sp_core: fn create_on_chain_project() { let metadata_hash = store_and_return_metadata_hash(); + let bounded_name = BoundedVec::try_from("Contribution Token TEST".as_bytes().to_vec()).unwrap(); + let bounded_symbol = BoundedVec::try_from("CTEST".as_bytes().to_vec()).unwrap(); let _ = FundingModule::create( RuntimeOrigin::signed(ALICE), Project { minimum_price: 1_u128, ticket_size: TicketSize { minimum: Some(1), maximum: None }, participants_size: ParticipantsSize { minimum: Some(2), maximum: None }, + token_information: CurrencyMetadata { + name: bounded_name, + symbol: bounded_symbol, + decimals: 10, + }, metadata: metadata_hash, ..Default::default() }, @@ -222,8 +229,7 @@ mod evaluation_round { assert_ok!(FundingModule::start_evaluation(RuntimeOrigin::signed(ALICE), 0)); let ed = FundingModule::project_info(0); assert!(ed.project_status == ProjectStatus::EvaluationRound); - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); let ed = FundingModule::project_info(0); assert!(ed.project_status == ProjectStatus::EvaluationEnded); }) @@ -285,8 +291,7 @@ mod auction_round { create_on_chain_project(); assert_ok!(FundingModule::start_evaluation(RuntimeOrigin::signed(ALICE), 0)); - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); assert_ok!(FundingModule::start_auction(RuntimeOrigin::signed(ALICE), 0)); }) } @@ -307,8 +312,7 @@ mod auction_round { new_test_ext().execute_with(|| { create_on_chain_project(); assert_ok!(FundingModule::start_evaluation(RuntimeOrigin::signed(ALICE), 0)); - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); assert_ok!(FundingModule::start_auction(RuntimeOrigin::signed(ALICE), 0)); let free_balance = Balances::free_balance(&CHARLIE); @@ -317,7 +321,7 @@ mod auction_round { let bids = FundingModule::auctions_info(0); assert!(bids .iter() - .any(|(when, bid)| *when == block_number + 29 && + .any(|(when, bid)| *when == System::block_number() && bid.amount == 100 && bid.market_cap == 1)); let free_balance_after_bid = Balances::free_balance(&CHARLIE); @@ -347,8 +351,7 @@ mod auction_round { new_test_ext().execute_with(|| { create_on_chain_project(); assert_ok!(FundingModule::start_evaluation(RuntimeOrigin::signed(ALICE), 0)); - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); assert_ok!(FundingModule::start_auction(RuntimeOrigin::signed(ALICE), 0)); assert_noop!( FundingModule::contribute(RuntimeOrigin::signed(BOB), 0, 100), @@ -384,8 +387,7 @@ mod flow { assert_ok!(FundingModule::bond(RuntimeOrigin::signed(BOB), 0, 128)); // Evaluation Round ends automatically - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); let project_info = FundingModule::project_info(0); assert!(project_info.project_status == ProjectStatus::EvaluationEnded); @@ -398,8 +400,7 @@ mod flow { assert_ok!(FundingModule::bid(RuntimeOrigin::signed(CHARLIE), 0, 1, 100, None)); // Second phase of Funding Round: 2) Candle Auction Round - let block_number = System::block_number(); - run_to_block(block_number + 10); + run_to_block(System::block_number() + 10); let project_info = FundingModule::project_info(0); assert!( project_info.project_status == ProjectStatus::AuctionRound(AuctionPhase::Candle) @@ -407,20 +408,34 @@ mod flow { assert_ok!(FundingModule::bid(RuntimeOrigin::signed(DAVE), 0, 2, 200, None)); // Third phase of Funding Round: 3) Community Round - let block_number = System::block_number(); - run_to_block(block_number + 5); + run_to_block(System::block_number() + 5); let project_info = FundingModule::project_info(0); assert!(project_info.project_status == ProjectStatus::CommunityRound); assert_ok!(FundingModule::contribute(RuntimeOrigin::signed(BOB), 0, 100)); // Funding Round ends - let block_number = System::block_number(); - run_to_block(block_number + 11); + run_to_block(System::block_number() + 11); let project_info = FundingModule::project_info(0); assert!(project_info.project_status == ProjectStatus::ReadyToLaunch); // Project is no longer "active" let active_projects = FundingModule::projects_active(); assert!(active_projects.len() == 0); + + // TODO: There exists certanly a better/easier way to test the pallet_asset functionalties + + // Naive way to check if the Contribution Token is actually created + // TODO: Replace with `asset_exists` given by the `Inspect` trait when the codebase is updated to >= v0.9.35 + assert!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1).is_err()); + // Check if the the metadata are set correctly + let metadata_name = + as InspectMetadata>::name(&0); + assert_eq!(metadata_name, b"Contribution Token TEST".to_vec()); + let metadata_symbol = + as InspectMetadata>::symbol(&0); + assert_eq!(metadata_symbol, b"CTEST".to_vec()); + let metadata_decimals = + as InspectMetadata>::decimals(&0); + assert_eq!(metadata_decimals, 10); }) } @@ -447,8 +462,7 @@ mod flow { let project_info = FundingModule::project_info(0); assert!(project_info.project_status == ProjectStatus::EvaluationRound); assert_ok!(FundingModule::bond(RuntimeOrigin::signed(BOB), 0, 128)); - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); let project_info = FundingModule::project_info(0); assert!(project_info.project_status == ProjectStatus::EvaluationEnded); @@ -467,8 +481,7 @@ mod flow { )); // Second phase of Funding Round: 2) Candle Auction Round - let block_number = System::block_number(); - run_to_block(block_number + 10); + run_to_block(System::block_number() + 10); let project_info = FundingModule::project_info(0); assert!( project_info.project_status == ProjectStatus::AuctionRound(AuctionPhase::Candle) @@ -484,8 +497,7 @@ mod flow { assert_ok!(FundingModule::bid(RuntimeOrigin::signed(4), 0, 15 * PLMC, 20 * PLMC, None)); assert_ok!(FundingModule::bid(RuntimeOrigin::signed(4), 0, 12 * PLMC, 55 * PLMC, None)); - let block_number = System::block_number(); - run_to_block(block_number + 10); + run_to_block(System::block_number() + 10); let project_info = FundingModule::project_info(0); assert!(project_info.final_price != Some(0)); }) @@ -507,12 +519,10 @@ mod flow { }; assert_ok!(FundingModule::create(RuntimeOrigin::signed(ALICE), project)); assert_ok!(FundingModule::start_evaluation(RuntimeOrigin::signed(ALICE), 0)); - let block_number = System::block_number(); - run_to_block(block_number + 29); + run_to_block(System::block_number() + 29); assert_ok!(FundingModule::start_auction(RuntimeOrigin::signed(ALICE), 0)); // Second phase of Funding Round: 2) Candle Auction Round - let block_number = System::block_number(); - run_to_block(block_number + 10); + run_to_block(System::block_number() + 10); let project_info = FundingModule::project_info(0); assert!( project_info.project_status == ProjectStatus::AuctionRound(AuctionPhase::Candle) diff --git a/runtimes/testnet/src/lib.rs b/runtimes/testnet/src/lib.rs index 45ff0320c..9d47f58c6 100644 --- a/runtimes/testnet/src/lib.rs +++ b/runtimes/testnet/src/lib.rs @@ -820,6 +820,10 @@ pub const fn deposit(items: u32, bytes: u32) -> Balance { items as Balance * 15 * MICRO_PLMC + (bytes as Balance) * 6 * MICRO_PLMC } +pub const fn free_deposit() -> Balance { + 0 * MICRO_PLMC +} + parameter_types! { // One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. pub const DepositBase: Balance = deposit(1, 88); @@ -854,8 +858,8 @@ parameter_types! { pub const AssetsStringLimit: u32 = 50; /// Key = 32 bytes, Value = 36 bytes (32+1+1+1+1) // https://github.com/paritytech/substrate/blob/069917b/frame/assets/src/lib.rs#L257L271 - pub const MetadataDepositBase: Balance = deposit(1, 68); - pub const MetadataDepositPerByte: Balance = deposit(0, 1); + pub const MetadataDepositBase: Balance = free_deposit(); + pub const MetadataDepositPerByte: Balance = free_deposit(); } impl pallet_assets::Config for Runtime { From 2d0b56635f13f5f631f9e86f9796752b25762e49 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Thu, 12 Jan 2023 14:02:12 +0100 Subject: [PATCH 09/12] feat(funding): Use `#[pallet::compact]` where possible --- pallets/funding/src/lib.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index f00e88789..1e608ae8b 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -180,7 +180,7 @@ pub mod pallet { #[pallet::constant] type ActiveProjectsLimit: Get; - /// The maximum number of bids per block + /// The maximum number of bids per project #[pallet::constant] type MaximumBidsPerProject: Get; @@ -201,7 +201,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn nonce)] /// A global counter used in the randomness generation - /// OnEmpty in this case is GetDefault, so 0. + // TODO: Remove it after using the Randomness from BABE's VRF pub type Nonce = StorageValue<_, u32, ValueQuery>; @@ -353,9 +353,6 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Validate a preimage on-chain and store the image. - /// - /// If the preimage was previously requested, no fees or deposits are taken for providing - /// the preimage. Otherwise, a deposit is taken proportional to the size of the preimage. #[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().reads_writes(1,1))] pub fn note_image(origin: OriginFor, bytes: Vec) -> DispatchResult { let issuer = ensure_signed(origin)?; @@ -400,7 +397,7 @@ pub mod pallet { pub fn edit_metadata( origin: OriginFor, project_metadata_hash: T::Hash, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, ) -> DispatchResult { let issuer = ensure_signed(origin)?; @@ -422,7 +419,7 @@ pub mod pallet { /// Start the "Evaluation Round" of a `project_id` pub fn start_evaluation( origin: OriginFor, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, ) -> DispatchResult { let issuer = ensure_signed(origin)?; @@ -439,7 +436,7 @@ pub mod pallet { /// Evaluators can bond `amount` PLMC to evaluate a `project_id` in the "Evaluation Round" pub fn bond( origin: OriginFor, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let from = ensure_signed(origin)?; @@ -477,7 +474,7 @@ pub mod pallet { /// Evaluators can bond more `amount` PLMC to evaluate a `project_id` in the "Evaluation Round" pub fn rebond( _origin: OriginFor, - _project_id: T::ProjectIdentifier, + #[pallet::compact] _project_id: T::ProjectIdentifier, #[pallet::compact] _amount: BalanceOf, ) -> DispatchResult { Ok(()) @@ -487,7 +484,7 @@ pub mod pallet { /// Start the "Funding Round" of a `project_id` pub fn start_auction( origin: OriginFor, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, ) -> DispatchResult { let issuer = ensure_signed(origin)?; @@ -509,7 +506,7 @@ pub mod pallet { /// Place a bid in the "Auction Round" pub fn bid( origin: OriginFor, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, #[pallet::compact] price: BalanceOf, #[pallet::compact] market_cap: BalanceOf, multiplier: Option, @@ -615,7 +612,7 @@ pub mod pallet { /// Contribute to the "Community Round" pub fn contribute( origin: OriginFor, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let contributor = ensure_signed(origin)?; @@ -669,7 +666,7 @@ pub mod pallet { #[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().reads_writes(1,1))] pub fn claim_contribution_tokens( origin: OriginFor, - project_id: T::ProjectIdentifier, + #[pallet::compact] project_id: T::ProjectIdentifier, ) -> DispatchResult { let issuer = ensure_signed(origin)?; From 07baace224d1e81d73fdc1a8bc6b89d439e78a38 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:08:05 +0100 Subject: [PATCH 10/12] feat(funding): Add `ProjectIdParameter` to reflect upcoming changes --- pallets/funding/src/lib.rs | 52 +++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 1e608ae8b..711f20aaf 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -59,7 +59,6 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; -use codec::HasCompact; use frame_support::{ pallet_prelude::ValueQuery, traits::{ @@ -102,14 +101,12 @@ const LOCKING_ID: LockIdentifier = *b"evaluate"; pub trait Identifiable = Member + Parameter - + Default + Copy - + HasCompact - + MaybeSerializeDeserialize + MaxEncodedLen + + Default + AddAssign - + From - + TypeInfo; + + From; + // TODO: + MaybeSerializeDeserialize: Maybe needed for JSON serialization @ Genesis: https://github.com/paritytech/substrate/issues/12738#issuecomment-1320921201 #[frame_support::pallet] pub mod pallet { @@ -125,9 +122,23 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Identifier for the collection of item. + /// Global identifier for the projects. type ProjectIdentifier: Identifiable + From> + Into>; + /// Wrapper around `Self::ProjectIdentifier` to use in dispatchable call signatures. Allows the use + /// of compact encoding in instances of the pallet, which will prevent breaking changes + /// resulting from the removal of `HasCompact` from `Self::ProjectIdentifier`. + /// + /// This type includes the `From` bound, since tightly coupled pallets may + /// want to convert an `ProjectIdentifier` into a parameter for calling dispatchable functions + /// directly. + type ProjectIdParameter: Parameter + + Copy + + From + + Into + + From + + MaxEncodedLen; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to /// `From`. type CurrencyBalance: Balance + From; @@ -397,9 +408,10 @@ pub mod pallet { pub fn edit_metadata( origin: OriginFor, project_metadata_hash: T::Hash, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, ) -> DispatchResult { let issuer = ensure_signed(origin)?; + let project_id = project_id.into(); ensure!(ProjectsIssuers::::contains_key(project_id), Error::::ProjectNotExists); ensure!(ProjectsIssuers::::get(project_id) == Some(issuer), Error::::NotAllowed); @@ -419,9 +431,10 @@ pub mod pallet { /// Start the "Evaluation Round" of a `project_id` pub fn start_evaluation( origin: OriginFor, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, ) -> DispatchResult { let issuer = ensure_signed(origin)?; + let project_id = project_id.into(); ensure!(ProjectsIssuers::::contains_key(project_id), Error::::ProjectNotExists); ensure!(ProjectsIssuers::::get(project_id) == Some(issuer), Error::::NotAllowed); @@ -436,10 +449,11 @@ pub mod pallet { /// Evaluators can bond `amount` PLMC to evaluate a `project_id` in the "Evaluation Round" pub fn bond( origin: OriginFor, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let from = ensure_signed(origin)?; + let project_id = project_id.into(); let project_issuer = ProjectsIssuers::::get(project_id).ok_or(Error::::ProjectNotExists)?; @@ -474,7 +488,7 @@ pub mod pallet { /// Evaluators can bond more `amount` PLMC to evaluate a `project_id` in the "Evaluation Round" pub fn rebond( _origin: OriginFor, - #[pallet::compact] _project_id: T::ProjectIdentifier, + _project_id: T::ProjectIdentifier, #[pallet::compact] _amount: BalanceOf, ) -> DispatchResult { Ok(()) @@ -484,9 +498,10 @@ pub mod pallet { /// Start the "Funding Round" of a `project_id` pub fn start_auction( origin: OriginFor, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, ) -> DispatchResult { let issuer = ensure_signed(origin)?; + let project_id = project_id.into(); ensure!(ProjectsIssuers::::contains_key(project_id), Error::::ProjectNotExists); ensure!( @@ -506,7 +521,7 @@ pub mod pallet { /// Place a bid in the "Auction Round" pub fn bid( origin: OriginFor, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, #[pallet::compact] price: BalanceOf, #[pallet::compact] market_cap: BalanceOf, multiplier: Option, @@ -514,6 +529,7 @@ pub mod pallet { // specified in `participation_currencies` ) -> DispatchResult { let bidder = ensure_signed(origin)?; + let project_id = project_id.into(); ensure!( T::HandleMembers::is_in(&MemberRole::Professional, &bidder) || @@ -612,10 +628,11 @@ pub mod pallet { /// Contribute to the "Community Round" pub fn contribute( origin: OriginFor, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, #[pallet::compact] amount: BalanceOf, ) -> DispatchResult { let contributor = ensure_signed(origin)?; + let project_id = project_id.into(); // TODO: Add the "Retail before, Institutional and Professionals after, if there are still tokens" logic ensure!( @@ -666,9 +683,10 @@ pub mod pallet { #[pallet::weight(Weight::from_ref_time(10_000) + T::DbWeight::get().reads_writes(1,1))] pub fn claim_contribution_tokens( origin: OriginFor, - #[pallet::compact] project_id: T::ProjectIdentifier, + project_id: T::ProjectIdParameter, ) -> DispatchResult { let issuer = ensure_signed(origin)?; + let project_id = project_id.into(); ensure!(ProjectsIssuers::::contains_key(project_id), Error::::ProjectNotExists); @@ -777,7 +795,7 @@ pub mod pallet { #[cfg(feature = "runtime-benchmarks")] pub trait BenchmarkHelper { - fn create_project_id_parameter(id: u32) -> T::ProjectIdentifier; + fn create_project_id_parameter(id: u32) -> T::ProjectIdParameter; fn create_dummy_project( destinations_account: T::AccountId, metadata_hash: T::Hash, @@ -786,7 +804,7 @@ pub mod pallet { #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for () { - fn create_project_id_parameter(id: u32) -> T::ProjectIdentifier { + fn create_project_id_parameter(id: u32) -> T::ProjectIdParameter { id.into() } fn create_dummy_project( From 7c36ad04abf506be81beaa34b03293b7d95b69f8 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:10:41 +0100 Subject: [PATCH 11/12] test(funding): Use the `ProjectIdParameter` in benchmarks --- pallets/funding/src/benchmarking.rs | 8 ++++---- pallets/funding/src/mock.rs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index cb107d854..b6d060fe4 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -33,7 +33,7 @@ fn get_events() -> frame_benchmarking::Vec<( id: Option, -) -> (T::ProjectIdentifier, T::AccountId, ProjectOf) { +) -> (T::ProjectIdParameter, T::AccountId, ProjectOf) { let issuer: T::AccountId = account::("Alice", 1, 1); let project_id_parameter = id.unwrap_or(0); let project_id = T::BenchmarkHelper::create_project_id_parameter(project_id_parameter); @@ -44,7 +44,7 @@ fn create_default_project( fn create_default_minted_project( id: Option, -) -> (T::ProjectIdentifier, T::AccountId) { +) -> (T::ProjectIdParameter, T::AccountId) { let (project_id, issuer, project) = create_default_project::(id); assert!( PolimecFunding::::create(SystemOrigin::Signed(issuer.clone()).into(), project,).is_ok() @@ -76,7 +76,7 @@ benchmarks! { verify { // assert_last_event::(Event::ProjectCreated(0).into()); let project_id = T::BenchmarkHelper::create_project_id_parameter(1); - let project_info = PolimecFunding::::project_info(project_id); + let project_info = PolimecFunding::::project_info(project_id.into()); assert_eq!(project_info.project_status, ProjectStatus::Application); } @@ -108,7 +108,7 @@ benchmarks! { let p = T::ActiveProjectsLimit::get(); for i in 0 .. p { let project_id = T::BenchmarkHelper::create_project_id_parameter(i); - let project_info = PolimecFunding::::project_info(project_id); + let project_info = PolimecFunding::::project_info(project_id.into()); assert_eq!(project_info.project_status, ProjectStatus::EvaluationEnded); } diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index cf19d253f..beed473b1 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -129,6 +129,7 @@ impl pallet_funding::Config for Test { type RuntimeEvent = RuntimeEvent; type StringLimit = ConstU32<64>; type ProjectIdentifier = Identifier; + type ProjectIdParameter = Identifier; type Currency = Balances; type BiddingCurrency = Balances; type Assets = Assets; From fc6b0ebc377139d7c1ebe8af3498deb916abddb6 Mon Sep 17 00:00:00 2001 From: Leonardo Razovic <4128940+lrazovic@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:32:57 +0100 Subject: [PATCH 12/12] feat(runtimes): Update the runtime to reflect latest changes --- Cargo.lock | 1 + nodes/standalone/src/chain_spec.rs | 1 + runtimes/standalone/Cargo.toml | 2 ++ runtimes/standalone/src/lib.rs | 47 ++++++++++++++++++++++++++---- runtimes/testnet/src/lib.rs | 2 ++ 5 files changed, 47 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa8129000..bc8011f4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6563,6 +6563,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", + "pallet-assets", "pallet-aura", "pallet-authorship", "pallet-balances", diff --git a/nodes/standalone/src/chain_spec.rs b/nodes/standalone/src/chain_spec.rs index 9f532b46c..8c5a726a4 100644 --- a/nodes/standalone/src/chain_spec.rs +++ b/nodes/standalone/src/chain_spec.rs @@ -180,5 +180,6 @@ fn testnet_genesis( }) .collect::>(), }, + ..Default::default() } } diff --git a/runtimes/standalone/Cargo.toml b/runtimes/standalone/Cargo.toml index b35e3d2be..950838026 100644 --- a/runtimes/standalone/Cargo.toml +++ b/runtimes/standalone/Cargo.toml @@ -54,6 +54,7 @@ pallet-randomness-collective-flip = { git = "https://github.com/paritytech/subst pallet-treasury = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.32" } pallet-utility = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.32" } pallet-multisig = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.32" } +pallet-assets = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.32" } # Used for the node"s RPCs frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.32" } @@ -84,6 +85,7 @@ std = [ "frame-system/std", "frame-try-runtime/std", "pallet-aura/std", + "pallet-assets/std", "pallet-balances/std", "pallet-grandpa/std", "pallet-sudo/std", diff --git a/runtimes/standalone/src/lib.rs b/runtimes/standalone/src/lib.rs index 768b030a9..24173a3c1 100644 --- a/runtimes/standalone/src/lib.rs +++ b/runtimes/standalone/src/lib.rs @@ -295,6 +295,42 @@ impl pallet_sudo::Config for Runtime { type RuntimeCall = RuntimeCall; } +pub const fn deposit(items: u32, bytes: u32) -> Balance { + items as Balance * 15 * MICRO_PLMC + (bytes as Balance) * 6 * MICRO_PLMC +} + +pub const fn free_deposit() -> Balance { + 0 * MICRO_PLMC +} + +parameter_types! { + pub const AssetDeposit: Balance = PLMC; // 1 UNIT deposit to create asset + pub const ApprovalDeposit: Balance = EXISTENTIAL_DEPOSIT; + pub const AssetAccountDeposit: Balance = deposit(1, 16); + pub const AssetsStringLimit: u32 = 50; + /// Key = 32 bytes, Value = 36 bytes (32+1+1+1+1) + // https://github.com/paritytech/substrate/blob/069917b/frame/assets/src/lib.rs#L257L271 + pub const MetadataDepositBase: Balance = free_deposit(); + pub const MetadataDepositPerByte: Balance = free_deposit(); +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = u32; + type Currency = Balances; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = AssetsStringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type AssetAccountDeposit = AssetAccountDeposit; +} + parameter_types! { pub const EvaluationDuration: BlockNumber = 28; pub const EnglishAuctionDuration: BlockNumber = 5; @@ -307,9 +343,11 @@ impl pallet_funding::Config for Runtime { type RuntimeEvent = RuntimeEvent; type StringLimit = ConstU32<64>; type Currency = Balances; + type ProjectIdentifier = u32; + type ProjectIdParameter = codec::Compact; type BiddingCurrency = Balances; + type Assets = Assets; type CurrencyBalance = ::Balance; - type ProjectIdentifier = u32; type EvaluationDuration = EvaluationDuration; type PalletId = FundingPalletId; type ActiveProjectsLimit = ConstU32<100>; @@ -318,7 +356,7 @@ impl pallet_funding::Config for Runtime { type CommunityRoundDuration = CommunityRoundDuration; type Randomness = Random; type HandleMembers = Credentials; - type MaximumBidsPerProject = ConstU32<128>; + type MaximumBidsPerProject = ConstU32<256>; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = (); @@ -476,10 +514,6 @@ impl pallet_authorship::Config for Runtime { type EventHandler = (); } -pub const fn deposit(items: u32, bytes: u32) -> Balance { - items as Balance * 15 * MICRO_PLMC + (bytes as Balance) * 6 * MICRO_PLMC -} - parameter_types! { // One storage item; key size is 32; value is size 4+4+16+32 bytes = 56 bytes. pub const DepositBase: Balance = deposit(1, 88); @@ -521,6 +555,7 @@ construct_runtime!( Sudo: pallet_sudo, Utility: pallet_utility, Multisig: pallet_multisig, + Assets: pallet_assets, Aura: pallet_aura, Grandpa: pallet_grandpa, diff --git a/runtimes/testnet/src/lib.rs b/runtimes/testnet/src/lib.rs index 9d47f58c6..569048232 100644 --- a/runtimes/testnet/src/lib.rs +++ b/runtimes/testnet/src/lib.rs @@ -630,7 +630,9 @@ impl pallet_funding::Config for Runtime { type StringLimit = ConstU32<64>; type Currency = Balances; type ProjectIdentifier = u32; + type ProjectIdParameter = codec::Compact; type BiddingCurrency = Balances; + type Assets = Assets; type CurrencyBalance = ::Balance; type EvaluationDuration = EvaluationDuration; type PalletId = FundingPalletId;