diff --git a/Cargo.lock b/Cargo.lock index aeaf832b..6a331ca8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,10 +1611,8 @@ dependencies = [ "common-traits", "common-types", "cumulus-primitives-core", - "frame-benchmarking", "frame-support", "frame-system", - "frame-system-benchmarking", "hex", "hex-literal 0.3.4", "orml-traits", @@ -4688,6 +4686,7 @@ dependencies = [ "pallet-collective", "pallet-democracy", "pallet-deposits", + "pallet-disputes", "pallet-fellowship", "pallet-grants", "pallet-identity", @@ -7029,6 +7028,8 @@ dependencies = [ "orml-traits", "pallet-balances", "pallet-deposits", + "pallet-disputes", + "pallet-fellowship", "pallet-identity", "pallet-proposals", "pallet-timestamp", @@ -7146,7 +7147,6 @@ dependencies = [ "frame-system", "orml-tokens", "orml-traits", - "pallet-xcm", "parity-scale-codec", "scale-info", "sp-core", @@ -7311,6 +7311,8 @@ dependencies = [ "orml-traits", "pallet-balances", "pallet-deposits", + "pallet-disputes", + "pallet-fellowship", "pallet-identity", "pallet-proposals", "pallet-timestamp", @@ -7594,9 +7596,10 @@ dependencies = [ "orml-xtokens", "pallet-balances", "pallet-deposits", + "pallet-disputes", + "pallet-fellowship", "pallet-identity", "pallet-timestamp", - "pallet-transaction-payment", "pallet-xcm", "parity-scale-codec", "scale-info", diff --git a/libs/common-types/src/milestone_origin.rs b/libs/common-types/src/milestone_origin.rs index af1429d0..8ffd7b09 100644 --- a/libs/common-types/src/milestone_origin.rs +++ b/libs/common-types/src/milestone_origin.rs @@ -1,32 +1,27 @@ use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{sp_runtime::traits::AccountIdConversion, PalletId}; use scale_info::TypeInfo; use xcm::latest::{Junction, Junctions::*, MultiLocation}; /// A wrapper around -pub trait TreasuryOriginConverter> { - fn get_multi_location( - &self, - recipiant: AccountId, - ) -> Result; +pub trait TreasuryOriginConverter { + fn get_multi_location(&self) -> Result; } -impl> TreasuryOriginConverter for TreasuryOrigin { - fn get_multi_location( - &self, - recipiant: AccountId, - ) -> Result { +impl TreasuryOriginConverter for TreasuryOrigin { + fn get_multi_location(&self) -> Result { match &self { TreasuryOrigin::Kusama => Ok(MultiLocation::new( 1, X1(Junction::AccountId32 { - id: recipiant.into(), + id: PalletId(*b"py/trsry").into_account_truncating(), network: None, }), )), TreasuryOrigin::Imbue => Ok(MultiLocation::new( 0, X1(Junction::AccountId32 { - id: recipiant.into(), + id: PalletId(*b"py/trsry").into_account_truncating(), network: None, }), )), @@ -43,15 +38,6 @@ pub enum TreasuryOriginError { NetworkUnsupported, } -#[derive( - Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, -)] -pub enum FundingType { - Proposal, - Brief, - Grant(TreasuryOrigin), -} - #[derive( Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, )] diff --git a/pallets/briefs/Cargo.toml b/pallets/briefs/Cargo.toml index 27e17e13..b5d3f858 100644 --- a/pallets/briefs/Cargo.toml +++ b/pallets/briefs/Cargo.toml @@ -33,6 +33,7 @@ orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-li common-traits = { path = "../../libs/common-traits", default-features = false } common-types = { path = "../../libs/common-types", default-features = false } pallet-deposits = {path= "../deposits", default-features = false } +pallet-fellowship = { path = "../fellowship", default-features = false } pallet-proposals = {path= "../proposals", default-features = false } [dev-dependencies] @@ -54,7 +55,7 @@ common-types = { path = "../../libs/common-types" } common-runtime = { path = "../../runtime/common"} pallet-proposals = {path= "../proposals"} pallet-deposits = {path= "../deposits"} - +pallet-disputes = {path= "../disputes"} [features] default = [ 'std' ] @@ -70,7 +71,7 @@ std = [ "pallet-balances/std", "pallet-deposits/std", "pallet-proposals/std", - "pallet-timestamp/std", + "pallet-fellowship/std", "scale-info/std", "serde/std", "sp-api/std", @@ -102,6 +103,7 @@ try-runtime = [ "pallet-proposals/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", + "pallet-fellowship/try-runtime", "pallet-xcm/try-runtime", "sp-runtime/try-runtime", ] diff --git a/pallets/briefs/src/lib.rs b/pallets/briefs/src/lib.rs index 1d816ea3..88588a5f 100644 --- a/pallets/briefs/src/lib.rs +++ b/pallets/briefs/src/lib.rs @@ -20,19 +20,20 @@ mod benchmarking; #[cfg(any(feature = "runtime-benchmarks", test))] mod test_utils; -pub mod migrations; +//pub mod migrations; #[frame_support::pallet] pub mod pallet { - use common_types::{milestone_origin::FundingType, CurrencyId}; + use common_types::CurrencyId; use frame_support::{ pallet_prelude::*, sp_runtime::Saturating, traits::Get, weights::Weight, BoundedBTreeMap, }; use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use pallet_deposits::traits::DepositHandler; + use pallet_fellowship::traits::SelectJury; use pallet_proposals::traits::IntoProposal; - use pallet_proposals::{Contribution, ProposedMilestone}; + use pallet_proposals::{Contribution, FundingPath, ProposedMilestone}; use sp_arithmetic::per_things::Percent; use sp_core::H256; use sp_runtime::traits::Zero; @@ -70,8 +71,8 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The currency type. type RMultiCurrency: MultiReservableCurrency, CurrencyId = CurrencyId>; - type AuthorityOrigin: EnsureOrigin; /// The type that allows for evolution from brief to proposal. type IntoProposal: IntoProposal, BalanceOf, BlockNumberFor>; /// The maximum amount of owners to a brief. @@ -82,7 +83,9 @@ pub mod pallet { /// Storage deposits. type BriefStorageItem: Get>; type DepositHandler: DepositHandler, AccountIdOf>; - + /// The type that selects a list of jury members. + type JurySelector: SelectJury>; + /// The weight info for the extrinsics. type WeightInfo: WeightInfoT; } @@ -147,6 +150,8 @@ pub mod pallet { FreelancerApprovalRequired, /// Milestones total do not add up to 100%. MilestonesTotalPercentageMustEqual100, + /// too many milestones here mate fixed with https://github.com/ImbueNetwork/imbue/issues/267 + TooManyMilestones, } #[pallet::call] @@ -291,13 +296,35 @@ pub mod pallet { let contributions = BriefContributions::::get(brief_id); ::DepositHandler::return_deposit(brief.deposit_id)?; + + let refund_locations = + ::IntoProposal::convert_contributions_to_refund_locations( + &contributions + .clone() + .into_inner() + .try_into() + .map_err(|_| Error::::TooManyBriefOwners)?, + ); + ::IntoProposal::convert_to_proposal( brief.currency_id, - contributions.into_inner(), + contributions + .into_inner() + .try_into() + .map_err(|_| Error::::TooManyBriefOwners)?, brief_id, brief.applicant, - brief.milestones.into(), - FundingType::Brief, + brief + .milestones + .to_vec() + .try_into() + .map_err(|_| Error::::TooManyMilestones)?, + refund_locations, + >>::select_jury() + .to_vec() + .try_into() + .map_err(|_| Error::::TooManyMilestones)?, + FundingPath::TakeFromReserved, )?; BriefContributions::::remove(brief_id); diff --git a/pallets/briefs/src/mock.rs b/pallets/briefs/src/mock.rs index d8b2adc1..2d67a24b 100644 --- a/pallets/briefs/src/mock.rs +++ b/pallets/briefs/src/mock.rs @@ -202,19 +202,17 @@ parameter_types! { impl pallet_briefs::Config for Test { type RuntimeEvent = RuntimeEvent; type RMultiCurrency = Tokens; - type AuthorityOrigin = EnsureRoot; type IntoProposal = pallet_proposals::Pallet; type MaxBriefOwners = MaxBriefOwners; type MaxMilestonesPerBrief = MaxMilestonesPerProject; type BriefStorageItem = BriefStorageItem; type DepositHandler = MockDepositHandler; type WeightInfo = pallet_briefs::WeightInfo; + type JurySelector = MockJurySelector; } parameter_types! { - pub const TwoWeekBlockUnit: u32 = 100800u32; pub const ProposalsPalletId: PalletId = PalletId(*b"imbgrant"); - pub NoConfidenceTimeLimit: BlockNumber = 100800u32.into(); pub PercentRequiredForVoteToPass: Percent = Percent::from_percent(75u8); pub MaximumContributorsPerProject: u32 = 50; pub RefundsPerBlock: u8 = 2; @@ -226,30 +224,31 @@ parameter_types! { pub ExpiringProjectRoundsPerBlock: u32 = 10; pub ProjectStorageItem: StorageItem = StorageItem::Project; pub MaxProjectsPerAccount: u16 = 100; - pub PercentRequiredForVoteNoConfidenceToPass: Percent = Percent::from_percent(75u8); + pub ImbueFeeAccount: AccountId = TREASURY; + pub MaxJuryMembers: u32 = 100; } impl pallet_proposals::Config for Test { type RuntimeEvent = RuntimeEvent; type PalletId = ProposalsPalletId; - type AuthorityOrigin = EnsureRoot; type MultiCurrency = Tokens; type WeightInfo = pallet_proposals::WeightInfo; - // Adding 2 weeks as th expiration time - type MaxWithdrawalExpiration = TwoWeekBlockUnit; - type NoConfidenceTimeLimit = NoConfidenceTimeLimit; type PercentRequiredForVoteToPass = PercentRequiredForVoteToPass; type MaximumContributorsPerProject = MaximumContributorsPerProject; type MilestoneVotingWindow = MilestoneVotingWindow; - type RefundHandler = pallet_proposals::traits::MockRefundHandler; + type ExternalRefundHandler = pallet_proposals::traits::MockRefundHandler; type MaxMilestonesPerProject = MaxMilestonesPerProject; type ImbueFee = ImbueFee; + type ImbueFeeAccount = ImbueFeeAccount; type ExpiringProjectRoundsPerBlock = ExpiringProjectRoundsPerBlock; - type ProjectStorageItem = ProjectStorageItem; type DepositHandler = MockDepositHandler; + type ProjectStorageItem = ProjectStorageItem; type MaxProjectsPerAccount = MaxProjectsPerAccount; - type PercentRequiredForVoteNoConfidenceToPass = PercentRequiredForVoteNoConfidenceToPass; + type DisputeRaiser = MockDisputeRaiser; + type JurySelector = MockJurySelector; + type AssetSignerOrigin = EnsureRoot; } + parameter_types! { pub const BasicDeposit: u64 = 10; pub const FieldDeposit: u64 = 10; @@ -281,6 +280,7 @@ parameter_types! { pub static ALICE: AccountId = 125; pub static BOB: AccountId = 126; pub static CHARLIE: AccountId = 127; +pub static TREASURY: AccountId = 200; pub(crate) fn build_test_externality() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default() @@ -307,3 +307,27 @@ pub(crate) fn build_test_externality() -> sp_io::TestExternalities { }); ext } + +pub struct MockJurySelector; +impl pallet_fellowship::traits::SelectJury for MockJurySelector { + type JurySize = MaxJuryMembers; + fn select_jury() -> BoundedVec { + BoundedVec::new() + } +} + +pub struct MockDisputeRaiser; +impl pallet_disputes::traits::DisputeRaiser for MockDisputeRaiser { + type DisputeKey = u32; + type SpecificId = u32; + type MaxJurySize = MaxJuryMembers; + type MaxSpecifics = MaxMilestonesPerProject; + fn raise_dispute( + _dispute_key: Self::DisputeKey, + _raised_by: AccountId, + _jury: BoundedVec, + _specific_ids: BoundedVec, + ) -> Result<(), DispatchError> { + Ok(()) + } +} diff --git a/pallets/briefs/src/weights.rs b/pallets/briefs/src/weights.rs index b5294f90..ddb169ec 100644 --- a/pallets/briefs/src/weights.rs +++ b/pallets/briefs/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_briefs` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `Justs-MacBook-Pro.local`, CPU: `` +//! HOSTNAME: `user`, CPU: `12th Gen Intel(R) Core(TM) i9-12900H` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: 1024 // Executed Command: @@ -22,7 +22,7 @@ // --extrinsic // * // --output -// ./pallets/briefs/src/weights.rs +// weights.rs // --steps // 50 // --repeat @@ -40,51 +40,55 @@ use core::marker::PhantomData; pub struct WeightInfo(PhantomData); impl crate::WeightInfoT for WeightInfo { /// Storage: `ImbueBriefs::Briefs` (r:1 w:1) - /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(3366), added: 5841, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(1725), added: 4200, mode: `MaxEncodedLen`) /// Storage: `Deposits::TicketId` (r:1 w:1) /// Proof: `Deposits::TicketId` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ImbueBriefs::BriefContributions` (r:1 w:1) - /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(5250), added: 7725, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(2649), added: 5124, mode: `MaxEncodedLen`) /// Storage: `ImbueBriefs::CounterForBriefs` (r:1 w:1) /// Proof: `ImbueBriefs::CounterForBriefs` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Deposits::CurrentDeposits` (r:0 w:1) /// Proof: `Deposits::CurrentDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) fn create_brief() -> Weight { // Proof Size summary in bytes: - // Measured: `434` - // Estimated: `8715` - // Minimum execution time: 678_000_000 picoseconds. - Weight::from_parts(702_000_000, 0) - .saturating_add(Weight::from_parts(0, 8715)) + // Measured: `334` + // Estimated: `6114` + // Minimum execution time: 674_565_000 picoseconds. + Weight::from_parts(699_090_000, 0) + .saturating_add(Weight::from_parts(0, 6114)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `ImbueBriefs::Briefs` (r:1 w:0) - /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(3366), added: 5841, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(1725), added: 4200, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ImbueBriefs::BriefContributions` (r:1 w:1) - /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(5250), added: 7725, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(2649), added: 5124, mode: `MaxEncodedLen`) fn contribute_to_brief() -> Weight { // Proof Size summary in bytes: - // Measured: `3860` - // Estimated: `8715` - // Minimum execution time: 373_000_000 picoseconds. - Weight::from_parts(403_000_000, 0) - .saturating_add(Weight::from_parts(0, 8715)) + // Measured: `2119` + // Estimated: `6114` + // Minimum execution time: 388_641_000 picoseconds. + Weight::from_parts(404_811_000, 0) + .saturating_add(Weight::from_parts(0, 6114)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ImbueBriefs::Briefs` (r:1 w:1) - /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(3366), added: 5841, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(1725), added: 4200, mode: `MaxEncodedLen`) /// Storage: `ImbueBriefs::BriefContributions` (r:1 w:1) - /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(5250), added: 7725, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(2649), added: 5124, mode: `MaxEncodedLen`) /// Storage: `Deposits::CurrentDeposits` (r:1 w:2) /// Proof: `Deposits::CurrentDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ImbueFellowship::Roles` (r:1 w:0) + /// Proof: `ImbueFellowship::Roles` (`max_values`: None, `max_size`: Some(51), added: 2526, mode: `MaxEncodedLen`) + /// Storage: `ImbueFellowship::JuryPointer` (r:1 w:1) + /// Proof: `ImbueFellowship::JuryPointer` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::ProjectCount` (r:1 w:1) /// Proof: `ImbueProposals::ProjectCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Deposits::TicketId` (r:1 w:1) @@ -92,36 +96,36 @@ impl crate::WeightInfoT for WeightInfo { /// Storage: `ImbueBriefs::CounterForBriefs` (r:1 w:1) /// Proof: `ImbueBriefs::CounterForBriefs` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::Projects` (r:0 w:1) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(260823), added: 263298, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::IndividualVoteStore` (r:0 w:1) - /// Proof: `ImbueProposals::IndividualVoteStore` (`max_values`: None, `max_size`: Some(8250321), added: 8252796, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::IndividualVoteStore` (`max_values`: None, `max_size`: Some(16571), added: 19046, mode: `MaxEncodedLen`) fn commence_work() -> Weight { // Proof Size summary in bytes: - // Measured: `4520` + // Measured: `2607` // Estimated: `8799` - // Minimum execution time: 1_763_000_000 picoseconds. - Weight::from_parts(1_805_000_000, 0) + // Minimum execution time: 1_739_406_000 picoseconds. + Weight::from_parts(1_778_978_000, 0) .saturating_add(Weight::from_parts(0, 8799)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(13)) } /// Storage: `ImbueBriefs::Briefs` (r:1 w:1) - /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(3366), added: 5841, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::Briefs` (`max_values`: None, `max_size`: Some(1725), added: 4200, mode: `MaxEncodedLen`) /// Storage: `Deposits::CurrentDeposits` (r:1 w:1) /// Proof: `Deposits::CurrentDeposits` (`max_values`: None, `max_size`: Some(69), added: 2544, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ImbueBriefs::BriefContributions` (r:1 w:1) - /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(5250), added: 7725, mode: `MaxEncodedLen`) + /// Proof: `ImbueBriefs::BriefContributions` (`max_values`: None, `max_size`: Some(2649), added: 5124, mode: `MaxEncodedLen`) /// Storage: `ImbueBriefs::CounterForBriefs` (r:1 w:1) /// Proof: `ImbueBriefs::CounterForBriefs` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn cancel_brief() -> Weight { // Proof Size summary in bytes: - // Measured: `4094` - // Estimated: `8715` - // Minimum execution time: 727_000_000 picoseconds. - Weight::from_parts(738_000_000, 0) - .saturating_add(Weight::from_parts(0, 8715)) + // Measured: `2353` + // Estimated: `6114` + // Minimum execution time: 721_794_000 picoseconds. + Weight::from_parts(753_268_000, 0) + .saturating_add(Weight::from_parts(0, 6114)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) } diff --git a/pallets/crowdfunding/src/benchmarking.rs b/pallets/crowdfunding/src/benchmarking.rs index 3c9261d0..0a48b4cc 100644 --- a/pallets/crowdfunding/src/benchmarking.rs +++ b/pallets/crowdfunding/src/benchmarking.rs @@ -153,7 +153,7 @@ fn create_funded_user( ) -> T::AccountId { let user = account(string, n, 99); let balance: BalanceOf = balance_factor.into(); - let _ = ::AccountId>>::deposit( + let _ = >>::deposit( CurrencyId::Native, &user, balance, diff --git a/pallets/crowdfunding/src/lib.rs b/pallets/crowdfunding/src/lib.rs index 8dc8b6f0..000707a0 100644 --- a/pallets/crowdfunding/src/lib.rs +++ b/pallets/crowdfunding/src/lib.rs @@ -15,7 +15,7 @@ mod benchmarking; #[frame_support::pallet] pub mod pallet { use crate::weights::WeightInfo; - use common_types::{CurrencyId, FundingType}; + use common_types::{CurrencyId}; use frame_support::sp_runtime::Saturating; use frame_support::{pallet_prelude::*, transactional, BoundedBTreeMap}; use frame_system::pallet_prelude::*; @@ -40,7 +40,7 @@ pub mod pallet { pub type BoundedMilestoneKeys = BoundedVec::MaxMilestonesPerCrowdFund>; pub type BoundedMilestones = - BoundedBTreeMap::MaxMilestonesPerCrowdFund>; + BoundedBTreeMap>, ::MaxMilestonesPerCrowdFund>; pub type BoundedWhitelistSpots = BoundedBTreeMap, BalanceOf, ::MaxWhitelistPerCrowdFund>; pub type BoundedProposedMilestones = diff --git a/pallets/crowdfunding/src/mock.rs b/pallets/crowdfunding/src/mock.rs index 1170859e..96d63a3c 100644 --- a/pallets/crowdfunding/src/mock.rs +++ b/pallets/crowdfunding/src/mock.rs @@ -161,7 +161,6 @@ impl pallet_balances::Config for Test { parameter_types! { pub const TwoWeekBlockUnit: u32 = 100800u32; pub const ProposalsPalletId: PalletId = PalletId(*b"imbgrant"); - pub NoConfidenceTimeLimit: BlockNumber = 100800u32.into(); pub PercentRequiredForVoteToPass: Percent = Percent::from_percent(75u8); pub MaximumContributorsPerProject: u32 = 50; pub RefundsPerBlock: u8 = 2; @@ -173,28 +172,28 @@ parameter_types! { pub ExpiringProjectRoundsPerBlock: u32 = 10; pub ProjectStorageItem: StorageItem = StorageItem::Project; pub MaxProjectsPerAccount: u16 = 100; - pub PercentRequiredForVoteNoConfidenceToPass: Percent = Percent::from_percent(75u8); + pub FeeAccount: AccountId = TREASURY; + pub MaxJuryMembers: u32 = 100; } impl pallet_proposals::Config for Test { type RuntimeEvent = RuntimeEvent; type PalletId = ProposalsPalletId; - type AuthorityOrigin = EnsureRoot; type MultiCurrency = Tokens; type WeightInfo = pallet_proposals::WeightInfo; - type MaxWithdrawalExpiration = TwoWeekBlockUnit; - type NoConfidenceTimeLimit = NoConfidenceTimeLimit; type PercentRequiredForVoteToPass = PercentRequiredForVoteToPass; type MaximumContributorsPerProject = MaximumContributorsPerProject; type MilestoneVotingWindow = MilestoneVotingWindow; - type RefundHandler = pallet_proposals::traits::MockRefundHandler; + type ExternalRefundHandler = pallet_proposals::traits::MockRefundHandler; type MaxMilestonesPerProject = MaxMilestonesPerProject; type ImbueFee = ImbueFee; + type ImbueFeeAccount = FeeAccount; type ExpiringProjectRoundsPerBlock = ExpiringProjectRoundsPerBlock; - type ProjectStorageItem = ProjectStorageItem; type DepositHandler = MockDepositHandler; + type ProjectStorageItem = ProjectStorageItem; type MaxProjectsPerAccount = MaxProjectsPerAccount; - type PercentRequiredForVoteNoConfidenceToPass = PercentRequiredForVoteNoConfidenceToPass; + type DisputeRaiser = MockDisputeRaiser; + type JurySelector = MockJurySelector; } #[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, MaxEncodedLen, TypeInfo, Copy)] @@ -224,9 +223,16 @@ impl DepositHandler for MockDepositHandler { } } +<<<<<<< HEAD +pub static ALICE: Lazy = Lazy::new(|| Public::from_raw([125u8; 32])); +pub static BOB: Lazy = Lazy::new(|| Public::from_raw([126u8; 32])); +pub static CHARLIE: Lazy = Lazy::new(|| Public::from_raw([127u8; 32])); +pub static TREASURY: Lazy = Lazy::new(|| Public::from_raw([127u8; 32])); +======= pub static ALICE: AccountId = 125; pub static BOB: AccountId = 126; pub static CHARLIE: AccountId = 127; +>>>>>>> 691fff9d503ce4e6ba20c8cc81f451649c04c07e pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::::default() @@ -243,3 +249,28 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { }); ext } + +pub struct MockDisputeRaiser; +impl pallet_disputes::traits::DisputeRaiser for MockDisputeRaiser { +type DisputeKey = u32; +type SpecificId = u32; +type MaxJurySize = MaxJuryMembers; +type MaxSpecifics = MaxMilestonesPerProject; + fn raise_dispute( + dispute_key: Self::DisputeKey, + raised_by: AccountId, + jury: BoundedVec, + specific_ids: BoundedVec, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + + +pub struct MockJurySelector; +impl pallet_fellowship::traits::SelectJury for MockJurySelector { + type JurySize = MaxJuryMembers; + fn select_jury() -> BoundedVec { + BoundedVec::new() + } +} diff --git a/pallets/deposits/Cargo.toml b/pallets/deposits/Cargo.toml index 53522cea..75133550 100644 --- a/pallets/deposits/Cargo.toml +++ b/pallets/deposits/Cargo.toml @@ -30,7 +30,6 @@ common-runtime = { path = "../../runtime/common", default-features = false} sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} -pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0"} orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0" } common-types = { path = "../../libs/common-types" } @@ -39,21 +38,20 @@ common-runtime = { path = "../../runtime/common"} [features] default = [ "std" ] std = [ - "codec/std", - "common-runtime/std", - "common-types/std", - "frame-benchmarking?/std", + "sp-std/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "orml-traits/std", + "common-types/std", + "common-runtime/std", + "codec/std", "scale-info/std", "sp-runtime/std", - "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "pallet-xcm/runtime-benchmarks", - "common-runtime/runtime-benchmarks" + "common-runtime/runtime-benchmarks", ] try-runtime = [ "common-runtime/try-runtime", @@ -61,6 +59,5 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "orml-tokens/try-runtime", - "pallet-xcm/try-runtime", "sp-runtime/try-runtime", ] diff --git a/pallets/deposits/src/lib.rs b/pallets/deposits/src/lib.rs index 4fd2371c..0ef12a8b 100644 --- a/pallets/deposits/src/lib.rs +++ b/pallets/deposits/src/lib.rs @@ -25,6 +25,7 @@ pub mod pallet { Saturating, }; use sp_std::fmt::Debug; + pub(crate) type AccountIdOf = ::AccountId; pub(crate) type BalanceOf = <::MultiCurrency as MultiCurrency>>::Balance; diff --git a/pallets/disputes/Cargo.toml b/pallets/disputes/Cargo.toml index 5050561a..31cb8e55 100644 --- a/pallets/disputes/Cargo.toml +++ b/pallets/disputes/Cargo.toml @@ -41,16 +41,16 @@ sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", branch = " [features] default = [ "std" ] std = [ - "codec/std", - "common-traits/std", - "common-types/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "orml-traits/std", - "scale-info/std", - "sp-runtime/std", - "sp-std/std", + "codec/std", + "common-traits/std", + "common-types/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks" ] diff --git a/pallets/disputes/src/benchmarking.rs b/pallets/disputes/src/benchmarking.rs index 60924ba7..b8f9f6ce 100644 --- a/pallets/disputes/src/benchmarking.rs +++ b/pallets/disputes/src/benchmarking.rs @@ -2,11 +2,11 @@ use super::*; use crate::traits::DisputeRaiser; -use crate::Pallet as PalletDisputes; +use crate::Pallet as DisputesPallet; use frame_benchmarking::v2::*; use frame_support::{assert_ok, traits::Get, BoundedVec}; use frame_system::Pallet as System; -use sp_std::vec::Vec; +use sp_std::{vec, vec::Vec}; #[benchmarks(where Event::: Into<::RuntimeEvent>)] mod benchmarks { @@ -154,14 +154,14 @@ mod benchmarks { } impl_benchmark_test_suite!( - PalletDisputes, + DisputesPallet, crate::mock::new_test_ext(), crate::mock::Test ); } pub fn get_jury( - accounts: Vec<::AccountId>, + accounts: Vec>, ) -> BoundedVec, ::MaxJurySize> { accounts.try_into().expect("too many jury members") } diff --git a/pallets/disputes/src/impls.rs b/pallets/disputes/src/impls.rs index 48fdd905..1393bdea 100644 --- a/pallets/disputes/src/impls.rs +++ b/pallets/disputes/src/impls.rs @@ -8,7 +8,6 @@ use traits::DisputeRaiser; impl DisputeRaiser> for Pallet { type DisputeKey = T::DisputeKey; type SpecificId = T::SpecificId; - type MaxReasonLength = ::MaxReasonLength; type MaxJurySize = ::MaxJurySize; type MaxSpecifics = ::MaxSpecifics; diff --git a/pallets/disputes/src/lib.rs b/pallets/disputes/src/lib.rs index d6a96c3e..f5eb3566 100644 --- a/pallets/disputes/src/lib.rs +++ b/pallets/disputes/src/lib.rs @@ -1,20 +1,3 @@ -//FELIX REVIEW: Eventually it will be nice to have a short introduction here explaining what this pallet does and the -// avaliable methods etc. - -// 1: Raise dispute using DisputeRaiser from pallet_proposals -// - It takes the raiser_id,project_id as dispute_key, list of jury(randomly selected upto 7 to 9 count), reason, fund_account -// - Exisiting implementation looks good, need to update the votes while inserting the new dispute - -// 2: Vote on dispute. -// Get the vote as single yes or no and divide based on the number of the voters -// Need to come up with a way to change the votes that might require the storing the votes of each voter - -// 3: finalise it in the on_initialize hook. -// Signal that this is ready for continuation. pallet-refund/pallet-proposals. -// Refund, Everythings ok. - -// 4: an extrinsic is called claim_back(parameter: who, where.) - #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; pub mod impls; @@ -38,9 +21,10 @@ pub mod pallet { use frame_system::pallet_prelude::*; use sp_runtime::traits::{AtLeast32BitUnsigned, Saturating, Zero}; use sp_std::fmt::Debug; - pub(crate) type AccountIdOf = ::AccountId; - pub type BoundedVotes = - BoundedBTreeMap<::AccountId, bool, ::MaxJurySize>; + + pub type AccountIdOf = ::AccountId; + + pub type BoundedVotes = BoundedBTreeMap, bool, ::MaxJurySize>; #[pallet::pallet] pub struct Pallet(_); @@ -67,8 +51,6 @@ pub mod pallet { + TypeInfo + Debug + Copy; - /// The max length for specifying the reason while raising the dispute - type MaxReasonLength: Get; /// The number of juries that can be assigned to a given dispute type MaxJurySize: Get; /// The number of specifics that can be assigned to a given dispute. @@ -80,7 +62,7 @@ pub mod pallet { /// The origin used to force cancel and pass disputes. type ForceOrigin: EnsureOrigin; /// External hooks to handle the completion of a dispute. - type DisputeHooks: DisputeHooks; + type DisputeHooks: DisputeHooks; } /// Used to store the disputes that is being raised, given the dispute key it returns the Dispute @@ -157,11 +139,12 @@ pub mod pallet { weight = weight.saturating_add(T::WeightInfo::calculate_winner()); let result = dispute.calculate_winner(); // TODO: actually benchmark. - let hook_weight = - >::on_dispute_complete( - *dispute_id, - result, - ); + let hook_weight = >::on_dispute_complete( + *dispute_id, dispute.specifiers.into_inner(), result + ); weight = weight.saturating_add(hook_weight); Self::deposit_event(Event::::DisputeCompleted { @@ -229,7 +212,11 @@ pub mod pallet { } // Dont mind if this fails as the autofinalise will skip. }); - T::DisputeHooks::on_dispute_complete(dispute_key, DisputeResult::Failure); + T::DisputeHooks::on_dispute_complete( + dispute_key, + dispute.specifiers.into_inner(), + DisputeResult::Failure, + ); Self::deposit_event(Event::::DisputeCompleted { dispute_key, dispute_result: DisputeResult::Failure, @@ -257,7 +244,11 @@ pub mod pallet { } // Dont mind if this fails as the autofinalise will skip. }); - T::DisputeHooks::on_dispute_complete(dispute_key, DisputeResult::Success); + T::DisputeHooks::on_dispute_complete( + dispute_key, + dispute.specifiers.into_inner(), + DisputeResult::Success, + ); Self::deposit_event(Event::::DisputeCompleted { dispute_key, dispute_result: DisputeResult::Success, @@ -415,7 +406,11 @@ pub mod pallet { }); // Dont need to return the weight here. - let _ = T::DisputeHooks::on_dispute_complete(dispute_key, result); + let _ = T::DisputeHooks::on_dispute_complete( + dispute_key, + dispute.specifiers.into_inner(), + result, + ); Ok(()) } diff --git a/pallets/disputes/src/mock.rs b/pallets/disputes/src/mock.rs index 18ece196..049e2b98 100644 --- a/pallets/disputes/src/mock.rs +++ b/pallets/disputes/src/mock.rs @@ -13,7 +13,7 @@ use sp_std::convert::{TryFrom, TryInto}; type Block = frame_system::mocking::MockBlock; pub type BlockNumber = u64; pub type Balance = u64; -pub type AccountId = u128; +pub type AccountId = u64; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -52,7 +52,6 @@ impl frame_system::Config for Test { } parameter_types! { - pub MaxReasonLength: u32 = 100; pub MaxJurySize: u32 = 3; pub MaxSpecifics: u32 = 10; pub VotingTimeLimit: BlockNumber = 10; @@ -64,7 +63,6 @@ impl pallet_disputes::Config for Test { type WeightInfo = (); type DisputeKey = u32; type SpecificId = u32; - type MaxReasonLength = MaxReasonLength; type MaxJurySize = MaxJurySize; type MaxSpecifics = MaxSpecifics; type MaxDisputesPerBlock = MaxDisputesPerBlock; @@ -120,9 +118,10 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { ext } -impl crate::traits::DisputeHooks for Test { +impl crate::traits::DisputeHooks for Test { fn on_dispute_complete( _dispute_key: u32, + _specifics: Vec, _dispute_result: crate::pallet::DisputeResult, ) -> Weight { ::default() diff --git a/pallets/disputes/src/traits.rs b/pallets/disputes/src/traits.rs index 6994a966..1f033cf5 100644 --- a/pallets/disputes/src/traits.rs +++ b/pallets/disputes/src/traits.rs @@ -2,11 +2,11 @@ use codec::{FullCodec, FullEncode, MaxEncodedLen}; use frame_support::pallet_prelude::*; use scale_info::TypeInfo; use sp_runtime::{traits::AtLeast32BitUnsigned, BoundedVec, DispatchError}; +use sp_std::vec::Vec; pub trait DisputeRaiser { type DisputeKey: AtLeast32BitUnsigned + FullEncode + FullCodec + MaxEncodedLen + TypeInfo; type SpecificId: AtLeast32BitUnsigned + FullEncode + FullCodec + MaxEncodedLen + TypeInfo; - type MaxReasonLength: Get; type MaxJurySize: Get; type MaxSpecifics: Get; @@ -18,11 +18,12 @@ pub trait DisputeRaiser { ) -> Result<(), DispatchError>; } -pub trait DisputeHooks { - // Outcome - // handle the completed dispute +pub trait DisputeHooks { + /// On the completion of a dispute, this hooks is called. + /// Returning only the key that has been handled and the result of the dispute. fn on_dispute_complete( dispute_key: DisputeKey, + specifics: Vec, dispute_result: crate::pallet::DisputeResult, ) -> Weight; } diff --git a/pallets/disputes/src/weights.rs b/pallets/disputes/src/weights.rs index 8b137891..dd672737 100644 --- a/pallets/disputes/src/weights.rs +++ b/pallets/disputes/src/weights.rs @@ -1 +1,132 @@ +//! Autogenerated weights for `pallet_disputes` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `user`, CPU: `12th Gen Intel(R) Core(TM) i9-12900H` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: 1024 + +// Executed Command: +// ./target/debug/imbue +// benchmark +// pallet +// --chain +// local +// --execution +// wasm +// --wasm-execution +// compiled +// --pallet +// pallet-disputes +// --extrinsic +// * +// --output +// weights.rs +// --steps +// 50 +// --repeat +// 20 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_disputes`. +pub struct WeightInfo(PhantomData); +impl crate::WeightInfoT for WeightInfo { + /// Storage: `ImbueDisputes::Disputes` (r:1 w:1) + /// Proof: `ImbueDisputes::Disputes` (`max_values`: None, `max_size`: Some(6602), added: 9077, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::DisputesFinaliseOn` (r:1 w:1) + /// Proof: `ImbueDisputes::DisputesFinaliseOn` (`max_values`: None, `max_size`: Some(221), added: 2696, mode: `MaxEncodedLen`) + fn raise_dispute() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `10067` + // Minimum execution time: 152_261_000 picoseconds. + Weight::from_parts(153_191_000, 0) + .saturating_add(Weight::from_parts(0, 10067)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `ImbueDisputes::Disputes` (r:1 w:1) + /// Proof: `ImbueDisputes::Disputes` (`max_values`: None, `max_size`: Some(6602), added: 9077, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::DisputesFinaliseOn` (r:2 w:2) + /// Proof: `ImbueDisputes::DisputesFinaliseOn` (`max_values`: None, `max_size`: Some(221), added: 2696, mode: `MaxEncodedLen`) + fn extend_dispute() -> Weight { + // Proof Size summary in bytes: + // Measured: `250` + // Estimated: `10067` + // Minimum execution time: 244_000_000 picoseconds. + Weight::from_parts(245_568_000, 0) + .saturating_add(Weight::from_parts(0, 10067)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `ImbueDisputes::Disputes` (r:1 w:1) + /// Proof: `ImbueDisputes::Disputes` (`max_values`: None, `max_size`: Some(6602), added: 9077, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::DisputesFinaliseOn` (r:1 w:1) + /// Proof: `ImbueDisputes::DisputesFinaliseOn` (`max_values`: None, `max_size`: Some(221), added: 2696, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::Projects` (r:1 w:1) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::ProjectsInDispute` (r:0 w:1) + /// Proof: `ImbueProposals::ProjectsInDispute` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `MaxEncodedLen`) + fn vote_on_dispute() -> Weight { + // Proof Size summary in bytes: + // Measured: `292` + // Estimated: `39815` + // Minimum execution time: 337_396_000 picoseconds. + Weight::from_parts(344_127_000, 0) + .saturating_add(Weight::from_parts(0, 39815)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `ImbueDisputes::Disputes` (r:1 w:1) + /// Proof: `ImbueDisputes::Disputes` (`max_values`: None, `max_size`: Some(6602), added: 9077, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::DisputesFinaliseOn` (r:1 w:1) + /// Proof: `ImbueDisputes::DisputesFinaliseOn` (`max_values`: None, `max_size`: Some(221), added: 2696, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::Projects` (r:1 w:1) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::ProjectsInDispute` (r:0 w:1) + /// Proof: `ImbueProposals::ProjectsInDispute` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `MaxEncodedLen`) + fn force_fail_dispute() -> Weight { + // Proof Size summary in bytes: + // Measured: `292` + // Estimated: `39815` + // Minimum execution time: 244_177_000 picoseconds. + Weight::from_parts(250_254_000, 0) + .saturating_add(Weight::from_parts(0, 39815)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `ImbueDisputes::Disputes` (r:1 w:1) + /// Proof: `ImbueDisputes::Disputes` (`max_values`: None, `max_size`: Some(6602), added: 9077, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::DisputesFinaliseOn` (r:1 w:1) + /// Proof: `ImbueDisputes::DisputesFinaliseOn` (`max_values`: None, `max_size`: Some(221), added: 2696, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::Projects` (r:1 w:1) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::ProjectsInDispute` (r:0 w:1) + /// Proof: `ImbueProposals::ProjectsInDispute` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `MaxEncodedLen`) + fn force_succeed_dispute() -> Weight { + // Proof Size summary in bytes: + // Measured: `292` + // Estimated: `39815` + // Minimum execution time: 243_762_000 picoseconds. + Weight::from_parts(250_041_000, 0) + .saturating_add(Weight::from_parts(0, 39815)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(4)) + } + fn calculate_winner() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 12_448_000 picoseconds. + Weight::from_parts(13_334_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } +} diff --git a/pallets/fellowship/src/benchmarking.rs b/pallets/fellowship/src/benchmarking.rs index 8e7fc7b1..7d4df39d 100644 --- a/pallets/fellowship/src/benchmarking.rs +++ b/pallets/fellowship/src/benchmarking.rs @@ -23,7 +23,13 @@ mod benchmarks { #[block] { - as FellowshipHandle<::AccountId>>::add_to_fellowship(&alice, Role::Vetter, 10, Some(&bob), true); + as FellowshipHandle>>::add_to_fellowship( + &alice, + Role::Vetter, + 10, + Some(&bob), + true, + ); } } @@ -47,7 +53,13 @@ mod benchmarks { let alice: T::AccountId = create_funded_user::("alice", 1, 1_000_000_000_000_000_000u128); let bob: T::AccountId = create_funded_user::("bob", 1, 1_000_000_000_000_000_000u128); - as FellowshipHandle<::AccountId>>::add_to_fellowship(&alice, Role::Vetter, 10, Some(&bob), true); + as FellowshipHandle>>::add_to_fellowship( + &alice, + Role::Vetter, + 10, + Some(&bob), + true, + ); #[extrinsic_call] leave_fellowship(RawOrigin::Signed(alice.clone())); @@ -60,7 +72,13 @@ mod benchmarks { let alice: T::AccountId = create_funded_user::("alice", 1, 1_000_000_000_000_000_000u128); let bob: T::AccountId = create_funded_user::("bob", 1, 1_000_000_000_000_000_000u128); - as FellowshipHandle<::AccountId>>::add_to_fellowship(&alice, Role::Vetter, 10, Some(&bob), true); + as FellowshipHandle>>::add_to_fellowship( + &alice, + Role::Vetter, + 10, + Some(&bob), + true, + ); #[extrinsic_call] force_remove_and_slash_fellowship(RawOrigin::Root, alice.clone()); @@ -72,7 +90,13 @@ mod benchmarks { let alice: T::AccountId = create_funded_user::("alice", 1, 1_000_000_000_000_000_000u128); let bob: T::AccountId = create_funded_user::("bob", 1, 1_000_000_000_000_000_000u128); - as FellowshipHandle<::AccountId>>::add_to_fellowship(&alice, Role::Vetter, 10, Some(&bob), true); + as FellowshipHandle>>::add_to_fellowship( + &alice, + Role::Vetter, + 10, + Some(&bob), + true, + ); #[extrinsic_call] add_candidate_to_shortlist(RawOrigin::Signed(alice), bob.clone(), Role::Vetter, 10); @@ -84,7 +108,13 @@ mod benchmarks { let alice: T::AccountId = create_funded_user::("alice", 1, 1_000_000_000_000_000_000u128); let bob: T::AccountId = create_funded_user::("bob", 1, 1_000_000_000_000_000_000u128); - as FellowshipHandle<::AccountId>>::add_to_fellowship(&alice, Role::Vetter, 10, Some(&bob), true); + as FellowshipHandle>>::add_to_fellowship( + &alice, + Role::Vetter, + 10, + Some(&bob), + true, + ); assert_ok!(Fellowship::::add_candidate_to_shortlist( RawOrigin::Signed(alice.clone()).into(), bob.clone(), @@ -105,14 +135,20 @@ mod benchmarks { let charlie: T::AccountId = create_funded_user::("alice", 1, 1_000_000_000_000_000_000u128); - as FellowshipHandle<::AccountId>>::add_to_fellowship(&bob, Role::Vetter, 10, Some(&charlie), true); - assert_ok!(::AccountId, - >>::deposit( - CurrencyId::Native, + as FellowshipHandle>>::add_to_fellowship( &bob, - 1_000_000_000_000_000_000u128.saturated_into() - )); + Role::Vetter, + 10, + Some(&charlie), + true, + ); + assert_ok!( + >>::deposit( + CurrencyId::Native, + &bob, + 1_000_000_000_000_000_000u128.saturated_into() + ) + ); #[extrinsic_call] pay_deposit_to_remove_pending_status(RawOrigin::Signed(bob.clone())); @@ -134,10 +170,12 @@ pub fn create_funded_user( balance_factor: u128, ) -> T::AccountId { let user = account(seed, n, 0); - assert_ok!(::AccountId, - >>::deposit( - CurrencyId::Native, &user, balance_factor.saturated_into() - )); + assert_ok!( + >>::deposit( + CurrencyId::Native, + &user, + balance_factor.saturated_into() + ) + ); user } diff --git a/pallets/fellowship/src/lib.rs b/pallets/fellowship/src/lib.rs index 8f5b503d..6791e02b 100644 --- a/pallets/fellowship/src/lib.rs +++ b/pallets/fellowship/src/lib.rs @@ -110,6 +110,10 @@ pub mod pallet { pub type FellowToVetter = StorageMap<_, Blake2_128Concat, AccountIdOf, VetterIdOf, OptionQuery>; + /// Jury pointer for setting the mark at which fellows have been selected for jury. + #[pallet::storage] + pub type JuryPointer = StorageValue<_, u128, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { diff --git a/pallets/fellowship/src/migration.rs b/pallets/fellowship/src/migration.rs index 92ddfabe..10fcc478 100644 --- a/pallets/fellowship/src/migration.rs +++ b/pallets/fellowship/src/migration.rs @@ -1,10 +1,11 @@ +use super::*; use frame_support::traits::OnRuntimeUpgrade; use frame_support::{pallet_prelude::*, *}; use hex_literal::hex; use sp_runtime::AccountId32; use sp_std::{vec, vec::Vec}; -use crate::{traits::*, *}; +use crate::traits::*; pub mod v0 { use super::*; @@ -16,11 +17,7 @@ pub mod v0 { { pub fn insert_initial_fellows( weight: &mut Weight, - initial_fellows: Vec<( - ::AccountId, - crate::Role, - crate::Rank, - )>, + initial_fellows: Vec<(AccountIdOf, crate::Role, crate::Rank)>, ) { for (acc, role, rank) in initial_fellows.into_iter() { as FellowshipHandle>>::add_to_fellowship( @@ -29,11 +26,7 @@ pub mod v0 { *weight = weight.saturating_add(T::WeightInfo::add_to_fellowship()) } } - pub fn get_initial_fellows() -> Vec<( - ::AccountId, - crate::Role, - crate::Rank, - )> { + pub fn get_initial_fellows() -> Vec<(AccountIdOf, crate::Role, crate::Rank)> { vec![ // EARNEST //"5Da1Fna8wvgQNmCFPhcRGR9oxmhyPd7MNhPZADq2X6GiKkkr", diff --git a/pallets/fellowship/src/mock.rs b/pallets/fellowship/src/mock.rs index fccafcde..ed6abeec 100644 --- a/pallets/fellowship/src/mock.rs +++ b/pallets/fellowship/src/mock.rs @@ -1,4 +1,5 @@ use crate as pallet_fellowship; + use common_types::CurrencyId; use frame_support::traits::{ConstU16, Nothing}; use frame_system::EnsureRoot; diff --git a/pallets/fellowship/src/tests/pallet_tests.rs b/pallets/fellowship/src/tests/pallet_tests.rs index 769e576c..5bfc29d0 100644 --- a/pallets/fellowship/src/tests/pallet_tests.rs +++ b/pallets/fellowship/src/tests/pallet_tests.rs @@ -486,7 +486,7 @@ fn on_initialize_adds_to_fellowship_from_shortlist() { Role::Vetter, 10 )); - run_to_block::( + run_to_block( frame_system::Pallet::::block_number() + ::ShortlistPeriod::get(), ); assert_eq!(Roles::::get(CHARLIE).unwrap(), (Role::Vetter, 10)); @@ -512,7 +512,7 @@ fn on_initialize_doesnt_add_removed_shortlist_members() { RuntimeOrigin::signed(ALICE), CHARLIE, )); - run_to_block::( + run_to_block( frame_system::Pallet::::block_number() + ::ShortlistPeriod::get(), ); assert!(Roles::::get(CHARLIE).is_none()); @@ -541,7 +541,7 @@ fn on_initialize_cleans_storage_for_next_round() { .len() == 1 ); - run_to_block::( + run_to_block( frame_system::Pallet::::block_number() + ::ShortlistPeriod::get(), ); @@ -600,7 +600,7 @@ fn e2e() { )); // wait for blocks to pass - run_to_block::( + run_to_block( frame_system::Pallet::::block_number() + ::ShortlistPeriod::get(), ); diff --git a/pallets/fellowship/src/tests/test_utils.rs b/pallets/fellowship/src/tests/test_utils.rs index c78a6919..f62bbfbf 100644 --- a/pallets/fellowship/src/tests/test_utils.rs +++ b/pallets/fellowship/src/tests/test_utils.rs @@ -20,7 +20,7 @@ pub(crate) fn revoke_fellowship( >>::revoke_fellowship(who, slash_deposit) } -pub fn run_to_block(n: BlockNumber) { +pub fn run_to_block(n: BlockNumber) { while System::block_number() < n { Tokens::on_finalize(System::block_number()); Fellowship::on_finalize(System::block_number()); diff --git a/pallets/fellowship/src/traits.rs b/pallets/fellowship/src/traits.rs index 36decdfb..de1cb52a 100644 --- a/pallets/fellowship/src/traits.rs +++ b/pallets/fellowship/src/traits.rs @@ -33,6 +33,12 @@ pub trait EnsureRole { ) -> Result; } +/// Select a pseudo-random jury of a specified amount. +pub trait SelectJury { + type JurySize: Get; + fn select_jury() -> BoundedVec; +} + /// Custom definition for permissions for each role. pub trait FellowshipPermissions { fn has_permission(role: Role, permission: Permission) -> bool; diff --git a/pallets/fellowship/src/weights.rs b/pallets/fellowship/src/weights.rs index 4f9c1fbe..b1a781e9 100644 --- a/pallets/fellowship/src/weights.rs +++ b/pallets/fellowship/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_fellowship` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `Justs-MacBook-Pro.local`, CPU: `` +//! HOSTNAME: `user`, CPU: `12th Gen Intel(R) Core(TM) i9-12900H` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: 1024 // Executed Command: @@ -22,7 +22,7 @@ // --extrinsic // * // --output -// ./pallets/fellowship/src/weights.rs +// weights.rs // --steps // 50 // --repeat @@ -51,8 +51,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3593` - // Minimum execution time: 308_000_000 picoseconds. - Weight::from_parts(322_000_000, 0) + // Minimum execution time: 297_927_000 picoseconds. + Weight::from_parts(299_772_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) @@ -63,8 +63,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3516` - // Minimum execution time: 126_000_000 picoseconds. - Weight::from_parts(136_000_000, 0) + // Minimum execution time: 153_043_000 picoseconds. + Weight::from_parts(154_780_000, 0) .saturating_add(Weight::from_parts(0, 3516)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -83,8 +83,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `410` // Estimated: `3593` - // Minimum execution time: 478_000_000 picoseconds. - Weight::from_parts(488_000_000, 0) + // Minimum execution time: 463_315_000 picoseconds. + Weight::from_parts(479_038_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) @@ -103,8 +103,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `513` // Estimated: `6196` - // Minimum execution time: 947_000_000 picoseconds. - Weight::from_parts(1_025_000_000, 0) + // Minimum execution time: 894_392_000 picoseconds. + Weight::from_parts(915_373_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) @@ -121,8 +121,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `289` // Estimated: `6886` - // Minimum execution time: 253_000_000 picoseconds. - Weight::from_parts(278_000_000, 0) + // Minimum execution time: 270_206_000 picoseconds. + Weight::from_parts(278_894_000, 0) .saturating_add(Weight::from_parts(0, 6886)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -137,8 +137,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `299` // Estimated: `6886` - // Minimum execution time: 196_000_000 picoseconds. - Weight::from_parts(199_000_000, 0) + // Minimum execution time: 205_598_000 picoseconds. + Weight::from_parts(207_340_000, 0) .saturating_add(Weight::from_parts(0, 6886)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -155,8 +155,8 @@ impl crate::traits::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `259` // Estimated: `3593` - // Minimum execution time: 407_000_000 picoseconds. - Weight::from_parts(411_000_000, 0) + // Minimum execution time: 392_591_000 picoseconds. + Weight::from_parts(401_207_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(4)) diff --git a/pallets/grants/Cargo.toml b/pallets/grants/Cargo.toml index dd09ab40..cb52826f 100644 --- a/pallets/grants/Cargo.toml +++ b/pallets/grants/Cargo.toml @@ -44,6 +44,8 @@ orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-li common-types = { path = "../../libs/common-types" } common-runtime = { path = "../../runtime/common" } pallet-proposals = { path = "../proposals" } +pallet-disputes = { path = "../disputes" } +pallet-fellowship = { path = "../fellowship" } [features] default = [ "std" ] diff --git a/pallets/grants/src/benchmarking.rs b/pallets/grants/src/benchmarking.rs index ffec521a..0c8450b7 100644 --- a/pallets/grants/src/benchmarking.rs +++ b/pallets/grants/src/benchmarking.rs @@ -8,6 +8,7 @@ use crate::{BoundedApprovers, BoundedPMilestones, Config}; use common_types::{CurrencyId, TreasuryOrigin}; use frame_benchmarking::v2::*; use frame_support::{assert_ok, traits::Get}; +use frame_system::pallet_prelude::BlockNumberFor; use frame_system::RawOrigin; use orml_traits::MultiCurrency; use pallet_proposals::ProposedMilestone; diff --git a/pallets/grants/src/lib.rs b/pallets/grants/src/lib.rs index 8acdb320..27f5cf3c 100644 --- a/pallets/grants/src/lib.rs +++ b/pallets/grants/src/lib.rs @@ -25,15 +25,15 @@ pub use weights::*; #[frame_support::pallet] pub mod pallet { use super::*; - use common_types::{milestone_origin::FundingType, CurrencyId, TreasuryOrigin}; + use common_types::{CurrencyId, TreasuryOrigin, TreasuryOriginConverter}; use frame_support::{pallet_prelude::*, BoundedVec}; use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, MultiReservableCurrency}; - use pallet_proposals::{traits::IntoProposal, Contribution, ProposedMilestone}; + use pallet_proposals::{traits::IntoProposal, Contribution, Locality, ProposedMilestone}; use sp_arithmetic::{per_things::Percent, traits::One}; use sp_core::H256; use sp_runtime::Saturating; - use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + use sp_std::collections::btree_map::BTreeMap; pub(crate) type AccountIdOf = ::AccountId; pub(crate) type BalanceOf = @@ -97,6 +97,10 @@ pub mod pallet { GrantAlreadyExists, /// There are too many milestones. TooManyMilestones, + /// This is an invalid Treasury origin. + InvalidTreasuryOrigin, + /// Too many approvers + TooManyApprovers, } #[pallet::call] @@ -127,28 +131,43 @@ pub mod pallet { ); let mut contributions = BTreeMap::new(); - let _ = assigned_approvers - .iter() - .map(|approver_id| { - contributions.insert( - approver_id.clone(), - Contribution { - value: amount_requested / (assigned_approvers.len() as u32).into(), - timestamp: frame_system::Pallet::::block_number(), - }, - ) - }) - .collect::>(); + let _ = assigned_approvers.iter().for_each(|approver_id| { + contributions.insert( + approver_id.clone(), + Contribution { + value: amount_requested / (assigned_approvers.len() as u32).into(), + timestamp: frame_system::Pallet::::block_number(), + }, + ); + }); + + let treasury_multilocation = + ::get_multi_location(&treasury_origin) + .map_err(|_| Error::::InvalidTreasuryOrigin)?; + let refund_locations = sp_std::vec![( + Locality::Foreign(treasury_multilocation), + Percent::from_parts(100u8) + )]; ::IntoProposal::convert_to_proposal( currency_id, - contributions, + contributions + .try_into() + .map_err(|_| Error::::TooManyApprovers)?, grant_id, submitter.clone(), proposed_milestones + .to_vec() .try_into() .map_err(|_| Error::::TooManyMilestones)?, - FundingType::Grant(treasury_origin), + refund_locations + .try_into() + .map_err(|_| Error::::TooManyApprovers)?, + assigned_approvers + .to_vec() + .try_into() + .map_err(|_| Error::::TooManyApprovers)?, + pallet_proposals::FundingPath::WaitForFunding, )?; GrantsSubmittedBy::::insert(&submitter, grant_id, ()); diff --git a/pallets/grants/src/mock.rs b/pallets/grants/src/mock.rs index 17cd77be..9b295952 100644 --- a/pallets/grants/src/mock.rs +++ b/pallets/grants/src/mock.rs @@ -106,7 +106,7 @@ impl orml_tokens::Config for Test { } parameter_types! { - pub MaxMilestonesPerGrant: u32 = 50; + pub MaxMilestonesPerGrant: u32 = 100; pub MaxApprovers: u32 = 100; pub GrantStorageItem: StorageItem = StorageItem::Grant; } @@ -158,43 +158,41 @@ impl pallet_timestamp::Config for Test { } parameter_types! { - pub const TwoWeekBlockUnit: u32 = 100800u32; pub const ProposalsPalletId: PalletId = PalletId(*b"imbgrant"); - pub NoConfidenceTimeLimit: BlockNumber = 100800u32.into(); pub PercentRequiredForVoteToPass: Percent = Percent::from_percent(75u8); - pub MaximumContributorsPerProject: u32 = 50; + pub MaximumContributorsPerProject: u32 = 100; pub RefundsPerBlock: u8 = 2; pub IsIdentityRequired: bool = false; pub MilestoneVotingWindow: BlockNumber = 100800u64; - pub MaxMilestonesPerProject: u32 = 10; + pub MaxMilestonesPerProject: u32 = 100; pub ProjectStorageDeposit: Balance = 100; pub ImbueFee: Percent = Percent::from_percent(5u8); pub ExpiringProjectRoundsPerBlock: u32 = 10; pub ProjectStorageItem: StorageItem = StorageItem::Project; pub MaxProjectsPerAccount: u16 = 100; - pub PercentRequiredForVoteNoConfidenceToPass: Percent = Percent::from_percent(75u8); + pub MaxJuryMembers: u32 = 100; + pub FeeAccount: AccountId = TREASURY; } impl pallet_proposals::Config for Test { type RuntimeEvent = RuntimeEvent; type PalletId = ProposalsPalletId; - type AuthorityOrigin = EnsureRoot; type MultiCurrency = Tokens; type WeightInfo = pallet_proposals::WeightInfo; - // Adding 2 weeks as th expiration time - type MaxWithdrawalExpiration = TwoWeekBlockUnit; - type NoConfidenceTimeLimit = NoConfidenceTimeLimit; type PercentRequiredForVoteToPass = PercentRequiredForVoteToPass; type MaximumContributorsPerProject = MaximumContributorsPerProject; type MilestoneVotingWindow = MilestoneVotingWindow; - type RefundHandler = pallet_proposals::traits::MockRefundHandler; + type ExternalRefundHandler = pallet_proposals::traits::MockRefundHandler; type MaxMilestonesPerProject = MaxMilestonesPerProject; type ImbueFee = ImbueFee; + type ImbueFeeAccount = FeeAccount; type ExpiringProjectRoundsPerBlock = ExpiringProjectRoundsPerBlock; type DepositHandler = MockDepositHandler; type ProjectStorageItem = ProjectStorageItem; type MaxProjectsPerAccount = MaxProjectsPerAccount; - type PercentRequiredForVoteNoConfidenceToPass = PercentRequiredForVoteNoConfidenceToPass; + type DisputeRaiser = MockDisputeRaiser; + type JurySelector = MockJurySelector; + type AssetSignerOrigin = EnsureRoot; } parameter_types! { @@ -224,6 +222,7 @@ impl pallet_identity::Config for Test { pub static ALICE: AccountId = 125; pub static BOB: AccountId = 126; pub static CHARLIE: AccountId = 127; +pub static TREASURY: AccountId = 222; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::::default() @@ -240,3 +239,27 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { }); ext } + +pub struct MockDisputeRaiser; +impl pallet_disputes::traits::DisputeRaiser for MockDisputeRaiser { + type DisputeKey = pallet_proposals::ProjectKey; + type SpecificId = pallet_proposals::MilestoneKey; + type MaxJurySize = MaxJuryMembers; + type MaxSpecifics = MaxMilestonesPerProject; + fn raise_dispute( + dispute_key: Self::DisputeKey, + raised_by: AccountId, + jury: BoundedVec, + specific_ids: BoundedVec, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + +pub struct MockJurySelector; +impl pallet_fellowship::traits::SelectJury for MockJurySelector { + type JurySize = MaxJuryMembers; + fn select_jury() -> BoundedVec { + BoundedVec::new() + } +} diff --git a/pallets/grants/src/weights.rs b/pallets/grants/src/weights.rs index 36e03508..4a84dc3b 100644 --- a/pallets/grants/src/weights.rs +++ b/pallets/grants/src/weights.rs @@ -2,9 +2,9 @@ //! Autogenerated weights for `pallet_grants` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `Justs-MacBook-Pro.local`, CPU: `` +//! HOSTNAME: `user`, CPU: `12th Gen Intel(R) Core(TM) i9-12900H` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: 1024 // Executed Command: @@ -22,7 +22,7 @@ // --extrinsic // * // --output -// ./pallets/grants/src/weights.rs +// weights.rs // --steps // 50 // --repeat @@ -52,15 +52,15 @@ impl crate::WeightInfoT for WeightInfo { /// Storage: `ImbueGrants::GrantsSubmittedBy` (r:0 w:1) /// Proof: `ImbueGrants::GrantsSubmittedBy` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::Projects` (r:0 w:1) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(260823), added: 263298, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::IndividualVoteStore` (r:0 w:1) - /// Proof: `ImbueProposals::IndividualVoteStore` (`max_values`: None, `max_size`: Some(8250321), added: 8252796, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::IndividualVoteStore` (`max_values`: None, `max_size`: Some(16571), added: 19046, mode: `MaxEncodedLen`) fn create_and_convert() -> Weight { // Proof Size summary in bytes: // Measured: `369` // Estimated: `3593` - // Minimum execution time: 1_094_000_000 picoseconds. - Weight::from_parts(1_137_000_000, 0) + // Minimum execution time: 1_038_238_000 picoseconds. + Weight::from_parts(1_072_638_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(8)) diff --git a/pallets/proposals/Cargo.toml b/pallets/proposals/Cargo.toml index ad6108aa..40f713c6 100644 --- a/pallets/proposals/Cargo.toml +++ b/pallets/proposals/Cargo.toml @@ -36,6 +36,7 @@ cumulus-pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", branc cumulus-primitives-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } +orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0", default-features = false } orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0", default-features = false } orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0", default-features = false } orml-xtokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0", default-features = false } @@ -45,19 +46,21 @@ common-traits = { path = "../../libs/common-traits", default-features = false } common-types = { path = "../../libs/common-types", default-features = false } common-runtime = { path = "../../runtime/common", default-features = false } pallet-deposits = { path = "../deposits", default-features = false } +pallet-fellowship = { path = "../fellowship", default-features = false } +pallet-disputes = { path = "../disputes", default-features = false} [dev-dependencies] serde = { version = "1.0.101" } sp-core = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} sp-io = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} sp-keystore = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} -pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0" } orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0" } orml-xtokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", branch = "polkadot-v1.1.0" } common-runtime = { path = "../../runtime/common"} +pallet-fellowship = { path = "../fellowship"} +pallet-disputes = { path = "../disputes"} [features] default = [ 'std' ] @@ -82,6 +85,7 @@ std = [ "pallet-deposits/std", "pallet-identity/std", "pallet-timestamp/std", + "orml-currencies/std", "scale-info/std", "serde/std", "sp-api/std", @@ -96,7 +100,7 @@ runtime-benchmarks = [ "common-runtime/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", - "pallet-deposits/runtime-benchmarks" + "pallet-deposits/runtime-benchmarks", ] try-runtime = [ "common-runtime/try-runtime", @@ -113,7 +117,6 @@ try-runtime = [ "pallet-deposits/try-runtime", "pallet-identity/try-runtime", "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", "pallet-xcm/try-runtime", "sp-runtime/try-runtime", ] diff --git a/pallets/proposals/src/benchmarking.rs b/pallets/proposals/src/benchmarking.rs index 522ea92b..b3deb780 100644 --- a/pallets/proposals/src/benchmarking.rs +++ b/pallets/proposals/src/benchmarking.rs @@ -1,7 +1,5 @@ #![cfg(feature = "runtime-benchmarks")] - use super::*; -use crate::test_utils::*; use crate::Pallet as Proposals; use common_types::CurrencyId; use frame_benchmarking::v2::*; @@ -13,6 +11,14 @@ use sp_runtime::SaturatedConversion; use sp_runtime::Saturating; use sp_std::convert::TryInto; +use pallet_disputes::traits::DisputeHooks; +use pallet_disputes::DisputeResult; + +use test_utils::{ + assert_last_event, create_and_fund_project, create_funded_user, get_contributions, + get_max_milestones, get_milestones, +}; + #[benchmarks] mod benchmarks { use super::*; @@ -25,12 +31,13 @@ mod benchmarks { create_funded_user::("initiator", 1, 1_000_000_000_000_000_000u128); let contributions = get_contributions::(vec![alice], 100_000_000_000_000_000u128); let prop_milestones = get_max_milestones::(); - let project_key = create_project::( + let project_key = create_and_fund_project::( bob.clone(), contributions, prop_milestones, CurrencyId::Native, - ); + ) + .unwrap(); #[extrinsic_call] submit_milestone(RawOrigin::Signed(bob), project_key, 0); @@ -44,14 +51,15 @@ mod benchmarks { let bob: T::AccountId = create_funded_user::("contributor", 1, 1_000_000_000_000_000_000u128); // TODO: should update the contributors list to have maximum available length - let contributions = get_contributions::(vec![bob.clone()], 100_000_000_000_000_000u128); + let contributions = get_contributions::(vec![bob.clone()], 1_000_000_000_000u128); let prop_milestones = get_max_milestones::(); - let project_key = create_project::( + let project_key = create_and_fund_project::( alice.clone(), contributions, prop_milestones, CurrencyId::Native, - ); + ) + .unwrap(); assert_ok!(Proposals::::submit_milestone( RawOrigin::Signed(alice).into(), @@ -63,7 +71,7 @@ mod benchmarks { vote_on_milestone(RawOrigin::Signed(bob.clone()), project_key, 0, true); let current_block = frame_system::Pallet::::block_number(); assert_last_event::( - Event::::VoteSubmitted(bob, project_key, 0, true, current_block).into(), + Event::::MilestoneApproved(bob, project_key, 0, current_block).into(), ) } @@ -74,17 +82,18 @@ mod benchmarks { let bob: T::AccountId = create_funded_user::("contributor", 1, 1_000_000_000_000_000_000u128); let contributions = get_contributions::(vec![bob.clone()], 100_000_000_000_000_000u128); - let raised_funds: BalanceOf = 100_000_000_000_000_000u128.saturated_into(); + let raised_funds = 100_000_000_000_000_000u128.saturated_into(); let milestone_count = ::MaxMilestonesPerProject::get(); let prop_milestones = get_milestones(milestone_count as u8); - let project_key = create_project::( + let project_key = create_and_fund_project::( alice.clone(), contributions, prop_milestones, CurrencyId::Native, - ); + ) + .unwrap(); for milestone_key in 0..milestone_count { // The initiator submits a milestone @@ -103,77 +112,110 @@ mod benchmarks { )); } - // All the milestones are approved now - let fee: BalanceOf = ::ImbueFee::get().mul_floor(raised_funds); - let withdrawn: BalanceOf = raised_funds.saturating_sub(fee); - #[extrinsic_call] withdraw(RawOrigin::Signed(alice.clone()), project_key); assert_last_event::( - Event::::ProjectFundsWithdrawn(alice, project_key, withdrawn, CurrencyId::Native) + Event::::ProjectFundsWithdrawn(alice, project_key, raised_funds, CurrencyId::Native) .into(), ); } + // Benchmark for a single loop of on_initialise as a voting round (most expensive). #[benchmark] - fn raise_vote_of_no_confidence() { + fn on_initialize() { + let block_number = 100u32.into(); + let keys: BoundedVec< + (ProjectKey, RoundType, MilestoneKey), + ::ExpiringProjectRoundsPerBlock, + > = vec![(0, RoundType::VotingRound, 0)] + .try_into() + .expect("bound will be larger than 1;"); + + RoundsExpiring::::insert(block_number, keys); + #[block] + { + crate::Pallet::::on_initialize(block_number); + } + } + + #[benchmark] + fn raise_dispute() { + let contribution_amount = 1_000_000_000_000u128; let alice: T::AccountId = create_funded_user::("initiator", 1, 1_000_000_000_000_000_000u128); let bob: T::AccountId = - create_funded_user::("contributor", 1, 1_000_000_000_000_000_000u128); - // TODO: should update the contributors list to have maximum available length - let contributions = get_contributions::(vec![bob.clone()], 100_000_000_000_000_000u128); - let prop_milestones = get_max_milestones::(); + create_funded_user::("contributor", 0, 1_000_000_000_000_000_000u128); + + let contributors: Vec = (0 + ..::MaximumContributorsPerProject::get()) + .map(|i| create_funded_user::("contributor", i, 1_000_000_000_000_000_000u128)) + .collect(); + + let contributions = get_contributions::(contributors, contribution_amount); + let milestone_count = ::MaxMilestonesPerProject::get(); + let prop_milestones = get_milestones(milestone_count as u8); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..prop_milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + let project_key = - create_project::(alice, contributions, prop_milestones, CurrencyId::Native); + create_and_fund_project::(alice, contributions, prop_milestones, CurrencyId::Native) + .unwrap(); + #[extrinsic_call] - raise_vote_of_no_confidence(RawOrigin::Signed(bob.clone()), project_key); - assert_last_event::(Event::::NoConfidenceRoundCreated(bob, project_key).into()); + raise_dispute(RawOrigin::Signed(bob), project_key, milestone_keys); } #[benchmark] - fn vote_on_no_confidence_round() { + fn refund() { + let contribution_amount = 1_000_000_000_000u128.saturated_into(); let alice: T::AccountId = create_funded_user::("initiator", 1, 1_000_000_000_000_000_000u128); let bob: T::AccountId = - create_funded_user::("contributor", 1, 1_000_000_000_000_000_000u128); - let charlie: T::AccountId = - create_funded_user::("contributor", 2, 1_000_000_000_000_000_000u128); - // TODO: should update the contributors list to have maximum available length - let contributions = get_contributions::( - vec![bob.clone(), charlie.clone()], - 100_000_000_000_000_000u128, - ); - let prop_milestones = get_max_milestones::(); + create_funded_user::("contributor", 0, 1_000_000_000_000_000_000u128); + + let contributors: Vec = (0 + ..::MaximumContributorsPerProject::get()) + .map(|i| create_funded_user::("contributor", i, 1_000_000_000_000_000_000u128)) + .collect(); + + let contributions = get_contributions::(contributors, contribution_amount); + let total_amount = + contribution_amount * ::MaximumContributorsPerProject::get() as u128; + let milestone_count = ::MaxMilestonesPerProject::get(); + let prop_milestones = get_milestones(milestone_count as u8); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..prop_milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + let project_key = - create_project::(alice, contributions, prop_milestones, CurrencyId::Native); + create_and_fund_project::(alice, contributions, prop_milestones, CurrencyId::Native) + .unwrap(); - assert_ok!(Pallet::::raise_vote_of_no_confidence( - RawOrigin::Signed(bob).into(), - project_key + assert_ok!(crate::Pallet::::raise_dispute( + RawOrigin::Signed(bob.clone()).into(), + project_key, + milestone_keys.clone() )); + let _ = as DisputeHooks>::on_dispute_complete( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); #[extrinsic_call] - vote_on_no_confidence_round(RawOrigin::Signed(charlie.clone()), project_key, true); - assert_last_event::(Event::::NoConfidenceRoundVotedUpon(charlie, project_key).into()); - } - - // Benchmark for a single loop of on_initialise as a voting round (most expensive). - #[benchmark] - fn on_initialize() { - let block_number = 100u32.into(); - let keys: BoundedVec< - (ProjectKey, RoundType, MilestoneKey), - ::ExpiringProjectRoundsPerBlock, - > = vec![(0, RoundType::VotingRound, 0)] - .try_into() - .expect("bound will be larger than 1;"); - - RoundsExpiring::::insert(block_number, keys); - #[block] - { - crate::Pallet::::on_initialize(block_number); - } + refund(RawOrigin::Signed(bob), project_key); + assert_last_event::( + Event::::ProjectRefunded { + project_key, + total_amount: total_amount.saturated_into(), + } + .into(), + ); } impl_benchmark_test_suite!( diff --git a/pallets/proposals/src/impls/pallet_impls.rs b/pallets/proposals/src/impls/pallet_impls.rs index 8e45ca19..a2f26a27 100644 --- a/pallets/proposals/src/impls/pallet_impls.rs +++ b/pallets/proposals/src/impls/pallet_impls.rs @@ -1,5 +1,5 @@ use crate::*; -use common_types::milestone_origin::FundingType; +use pallet_disputes::{traits::DisputeHooks, DisputeResult}; use scale_info::prelude::format; use sp_runtime::traits::{Saturating, Zero}; @@ -8,10 +8,6 @@ impl Pallet { /// /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. - pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account_truncating() - } - pub fn project_account_id(key: ProjectKey) -> AccountIdOf { T::PalletId::get().into_sub_account_truncating(format!("//{key}")) } @@ -77,6 +73,7 @@ impl Pallet { Rounds::::contains_key((project_key, milestone_key), RoundType::VotingRound), Error::::VotingRoundNotStarted ); + let contribution_amount = project .contributions .get(&who) @@ -138,43 +135,59 @@ impl Pallet { ensure!(!project.cancelled, Error::::ProjectWithdrawn); ensure!(who == project.initiator, Error::::UserIsNotInitiator); - let mut unlocked_funds: BalanceOf = Zero::zero(); - for (_, ms) in project.milestones.iter() { - if ms.is_approved { - let per_milestone = ms.percentage_to_unlock.mul_floor(project.raised_funds); - unlocked_funds = unlocked_funds.saturating_add(per_milestone); - } - } - - let withdrawable: BalanceOf = unlocked_funds.saturating_sub(project.withdrawn_funds); - ensure!( - withdrawable != Zero::zero(), - Error::::NoAvailableFundsToWithdraw - ); - - let fee = ::ImbueFee::get().mul_floor(withdrawable); - let withdrawn = withdrawable.saturating_sub(fee); - - let project_account = Self::project_account_id(project_key); - let pallet_account = Self::account_id(); - - // Take the fee - T::MultiCurrency::transfer(project.currency_id, &project_account, &pallet_account, fee)?; - - T::MultiCurrency::transfer( - project.currency_id, - &project_account, - &project.initiator, - withdrawn, - )?; - - Projects::::mutate_exists(project_key, |project| -> DispatchResult { - if let Some(p) = project { - p.withdrawn_funds = p.withdrawn_funds.saturating_add(withdrawable); - if p.withdrawn_funds == p.raised_funds { - ::DepositHandler::return_deposit(p.deposit_id)?; + let withdrawable = Projects::::try_mutate_exists(project_key, |maybe_project| { + if let Some(project) = maybe_project { + let withdrawable_percent: Percent = project + .milestones + .iter_mut() + .map(|(_key, ms)| { + if ms.is_approved && ms.transfer_status.is_none() { + ms.transfer_status = Some(TransferStatus::Withdrawn { + on: frame_system::Pallet::::block_number(), + }); + ms.percentage_to_unlock + } else { + ::zero() + } + }) + .fold(::zero(), |acc, item| acc + item); + + ensure!( + withdrawable_percent != Zero::zero(), + Error::::NoAvailableFundsToWithdraw + ); + + let withdrawable = withdrawable_percent.mul_floor(project.raised_funds); + let fee = ::ImbueFee::get().mul_floor(withdrawable); + let initiator_payment = withdrawable.saturating_sub(fee); + let project_account = Self::project_account_id(project_key); + + // Take the fee and send to ImbueFeeAccount + T::MultiCurrency::transfer( + project.currency_id, + &project_account, + &::ImbueFeeAccount::get(), + fee, + )?; + + // Transfer to initiator + T::MultiCurrency::transfer( + project.currency_id, + &project_account, + &project.initiator, + initiator_payment, + )?; + + project.withdrawn_funds = project.withdrawn_funds.saturating_add(withdrawable); + + if project + .withdrawn_funds + .saturating_add(project.refunded_funds) + == project.raised_funds + { + ::DepositHandler::return_deposit(project.deposit_id)?; CompletedProjects::::try_mutate( - &p.initiator, + &project.initiator, |completed_projects| -> DispatchResult { completed_projects .try_push(project_key) @@ -182,248 +195,74 @@ impl Pallet { Ok(()) }, )?; - *project = None; + *maybe_project = None; } + Ok::, DispatchError>(withdrawable) + } else { + Ok::, DispatchError>( as Zero>::zero()) } - Ok(()) })?; Self::deposit_event(Event::ProjectFundsWithdrawn( who, project_key, - withdrawn, + withdrawable, project.currency_id, )); Ok(().into()) } - /// This function raises a vote of no confidence. - /// This round can only be called once and there after can only be voted on. - pub(crate) fn raise_no_confidence_round( - who: T::AccountId, - project_key: ProjectKey, - ) -> DispatchResult { - //ensure that who is a contributor or root - let project = Self::projects(project_key).ok_or(Error::::ProjectDoesNotExist)?; - let contribution = project - .contributions - .get(&who) - .ok_or(Error::::OnlyContributorsCanVote)?; - - // Also ensure that a vote has not already been raised. - ensure!( - !NoConfidenceVotes::::contains_key(project_key), - Error::::RoundStarted - ); - - let vote = Vote { - yay: Zero::zero(), - nay: contribution.value, - is_approved: false, - }; - - let expiry_block = frame_system::Pallet::::block_number() - .saturating_add(::NoConfidenceTimeLimit::get()); - - Rounds::::insert( - (project_key, 0), - RoundType::VoteOfNoConfidence, - expiry_block, - ); - RoundsExpiring::::try_mutate(expiry_block, |keys| { - // The milestone key does not matter here as we are voting on the entire project. - keys.try_push((project_key, RoundType::VoteOfNoConfidence, 0)) - .map_err(|_| Error::::Overflow)?; - Ok::<(), DispatchError>(()) - })?; - UserHasVoted::::try_mutate((project_key, RoundType::VoteOfNoConfidence, 0), |votes| { - ensure!(!votes.contains_key(&who), Error::::VotesAreImmutable); - votes - .try_insert(who.clone(), false) - .map_err(|_| Error::::Overflow)?; - Ok::<(), DispatchError>(()) - })?; - - NoConfidenceVotes::::insert(project_key, vote); - Self::deposit_event(Event::NoConfidenceRoundCreated(who, project_key)); - Ok(()) - } - - /// Allows a contributer to agree or disagree with a vote of no confidence. - pub(crate) fn add_vote_no_confidence( - who: T::AccountId, - project_key: ProjectKey, - is_yay: bool, - ) -> DispatchResult { - ensure!( - Rounds::::contains_key((project_key, 0), RoundType::VoteOfNoConfidence), - Error::::ProjectNotInRound - ); - let project = Self::projects(project_key).ok_or(Error::::ProjectDoesNotExist)?; - let contribution = project - .contributions - .get(&who) - .ok_or(Error::::OnlyContributorsCanVote)?; - - let nay_vote = NoConfidenceVotes::::try_mutate(project_key, |maybe_vote| { - if let Some(v) = maybe_vote { - if is_yay { - v.yay = v.yay.saturating_add(contribution.value); - } else { - v.nay = v.nay.saturating_add(contribution.value); - } - Ok::, DispatchError>(v.nay) - } else { - Err(Error::::VotingRoundNotStarted.into()) - } - })?; - - UserHasVoted::::try_mutate((project_key, RoundType::VoteOfNoConfidence, 0), |votes| { - ensure!(!votes.contains_key(&who), Error::::VotesAreImmutable); - votes - .try_insert(who.clone(), false) - .map_err(|_| Error::::Overflow)?; - Ok::<(), DispatchError>(()) - })?; - - Self::deposit_event(Event::NoConfidenceRoundVotedUpon(who.clone(), project_key)); - - //once the voting is complete check if the confidence vote could be auto finalized - //getting the total threshold required for the total confidence - let voting_no_confidence_threshold: BalanceOf = - T::PercentRequiredForVoteNoConfidenceToPass::get().mul_floor(project.raised_funds); - - //verifying whether the no confidence vote has passed the threshold if so then auto finalize it - if nay_vote >= voting_no_confidence_threshold { - let locked_milestone_percentage = - project.milestones.iter().fold(Percent::zero(), |acc, ms| { - if !ms.1.is_approved { - acc.saturating_add(ms.1.percentage_to_unlock) - } else { - acc - } - }); - - let project_account_id = Self::project_account_id(project_key); - - match project.funding_type { - FundingType::Proposal => { - // Handle refunds on native chain, there is no need to deal with xcm here. - for (acc_id, contribution) in project.contributions.iter() { - let refund_amount = - locked_milestone_percentage.mul_floor(contribution.value); - ::MultiCurrency::transfer( - project.currency_id, - &project_account_id, - acc_id, - refund_amount, - )?; - } - } - - FundingType::Brief => { - //Have to handle it in the dispute pallet - } - - // Must a grant be treasury funded? - FundingType::Grant(_) => { - let mut refund_amount: BalanceOf = Zero::zero(); - // Sum the contributions and send a single xcm. - for (_acc_id, contribution) in project.contributions.iter() { - let per_contributor = - locked_milestone_percentage.mul_floor(contribution.value); - refund_amount = refund_amount.saturating_add(per_contributor); - } - ::RefundHandler::send_refund_message_to_treasury( + /// Try and fund a project based on its FundingPath. + /// Will error is the + /// If the funds have actually been transferred this will return and Ok(true) + /// If the funds have not been transferred (i.e awaiting funding) then it will return Ok(false) + pub(crate) fn fund_project<'a>( + funding_path: &'a FundingPath, + contributions: &'a ContributionsFor, + project_account_id: &'a T::AccountId, + currency_id: CurrencyId, + ) -> Result { + match *funding_path { + FundingPath::TakeFromReserved => { + for (acc, cont) in contributions.iter() { + <::MultiCurrency as MultiReservableCurrency< + AccountIdOf, + >>::unreserve(currency_id, acc, cont.value); + ::MultiCurrency::transfer( + currency_id, + acc, project_account_id, - refund_amount, - project.currency_id, - project.funding_type, + cont.value, )?; } + Ok(true) } - Projects::::remove(project_key); - Rounds::::remove((project_key, 0), RoundType::VoteOfNoConfidence); - ::DepositHandler::return_deposit(project.deposit_id)?; - Self::deposit_event(Event::NoConfidenceRoundFinalised(who, project_key)); + FundingPath::WaitForFunding => Ok(false), } - Ok(()) } - #[deprecated(since = "3.1.0", note = "autofinalisation has been implemented.")] - pub(crate) fn _call_finalise_no_confidence_vote( - who: T::AccountId, + /// Try and convert some proposed milestones to milestones. + /// Will never fail so long as proposed_milestones and BoundedBTreeMilestones have the same bound. + pub(crate) fn try_convert_to_milestones( + proposed_milestones: BoundedVec, project_key: ProjectKey, - majority_required: Percent, - ) -> DispatchResultWithPostInfo { - let project = Projects::::get(project_key).ok_or(Error::::ProjectDoesNotExist)?; - ensure!( - Rounds::::contains_key((project_key, 0), RoundType::VoteOfNoConfidence), - Error::::ProjectNotInRound - ); - ensure!( - project.contributions.contains_key(&who), - Error::::OnlyContributorsCanVote - ); - - let vote = NoConfidenceVotes::::get(project_key).ok_or(Error::::NoActiveRound)?; - let threshold_votes: BalanceOf = majority_required.mul_floor(project.raised_funds); - - if vote.nay >= threshold_votes { - let locked_milestone_percentage = - project.milestones.iter().fold(Percent::zero(), |acc, ms| { - if !ms.1.is_approved { - acc.saturating_add(ms.1.percentage_to_unlock) - } else { - acc - } - }); - - let project_account_id = Self::project_account_id(project_key); - - // TODO: this should be generic and not bound to funding type.. - match project.funding_type { - FundingType::Brief | FundingType::Proposal => { - // - // Handle refunds on native chain, there is no need to deal with xcm here. - // Todo: Batch call using pallet-utility? - for (acc_id, contribution) in project.contributions.iter() { - let refund_amount = - locked_milestone_percentage.mul_floor(contribution.value); - ::MultiCurrency::transfer( - project.currency_id, - &project_account_id, - acc_id, - refund_amount, - )?; - } - } - // Must a grant be treasury funded? - FundingType::Grant(_) => { - let mut refund_amount: BalanceOf = Zero::zero(); - // Sum the contributions and send a single xcm. - for (_acc_id, contribution) in project.contributions.iter() { - let per_contributor = - locked_milestone_percentage.mul_floor(contribution.value); - refund_amount = refund_amount.saturating_add(per_contributor); - } - ::RefundHandler::send_refund_message_to_treasury( - project_account_id, - refund_amount, - project.currency_id, - project.funding_type, - )?; - } - } - - Projects::::remove(project_key); - ::DepositHandler::return_deposit(project.deposit_id)?; - Self::deposit_event(Event::NoConfidenceRoundFinalised(who, project_key)); - } else { - return Err(Error::::VoteThresholdNotMet.into()); + ) -> Result, DispatchError> { + let mut milestone_key: u32 = 0; + let mut milestones: BoundedBTreeMilestones = BoundedBTreeMap::new(); + for proposed_milestone in proposed_milestones { + let milestone = Milestone::new( + project_key, + milestone_key, + proposed_milestone.percentage_to_unlock, + ); + + milestones + .try_insert(milestone_key, milestone) + .map_err(|_| Error::::TooManyMilestones)?; + milestone_key = milestone_key.saturating_add(1); } - Ok(().into()) + Ok(milestones) } pub(crate) fn try_auto_finalise_milestone_voting( @@ -489,3 +328,43 @@ impl Pallet { Ok(()) } } + +impl DisputeHooks for Pallet { + fn on_dispute_complete( + project_key: ProjectKey, + specifics: Vec, + dispute_result: pallet_disputes::pallet::DisputeResult, + ) -> Weight { + ProjectsInDispute::::remove(project_key); + Projects::::mutate(project_key, |maybe_project| { + match maybe_project { + Some(project) => { + match dispute_result { + DisputeResult::Success => { + for milestone_key in specifics.iter() { + if let Some(milestone) = project.milestones.get_mut(milestone_key) { + // Shouldnt be needed but nice to have this check. + // Will prevent someone calling both refund and withdraw on the same milestone. + if milestone.transfer_status.is_none() { + milestone.can_refund = true; + } + } + } + } + DisputeResult::Failure => { + // I Guess do nothing.. ProjectsInDispute gets cleared regardless allowing further disputes. + } + }; + } + // Looks like the project was deleted somehow during the dispute. + // The only way this is possible is through a refund or final withdraw. + // Not a massive issue as either way the project has been finalised. + // Just ignore and return weight. + None => {} + } + }); + // ProjectsInDispute::remove + // Projects::mutate + T::DbWeight::get().reads_writes(2, 2) + } +} diff --git a/pallets/proposals/src/lib.rs b/pallets/proposals/src/lib.rs index 922628f7..55c74c99 100644 --- a/pallets/proposals/src/lib.rs +++ b/pallets/proposals/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, EncodeLike}; -use common_types::{CurrencyId, FundingType}; +use common_types::CurrencyId; use frame_support::{ pallet_prelude::*, storage::bounded_btree_map::BoundedBTreeMap, traits::EnsureOrigin, PalletId, }; @@ -9,17 +9,23 @@ use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, MultiReservableCurrency}; pub use pallet::*; use pallet_deposits::traits::DepositHandler; +use pallet_disputes::traits::DisputeRaiser; + use scale_info::TypeInfo; use sp_arithmetic::per_things::Percent; use sp_core::H256; -use sp_runtime::traits::{AccountIdConversion, Saturating, Zero}; +use sp_runtime::traits::{AccountIdConversion, One, Saturating, Zero}; use sp_std::{collections::btree_map::*, convert::TryInto, prelude::*}; +use xcm::latest::MultiLocation; pub mod traits; -use traits::{IntoProposal, RefundHandler}; +use traits::{ExternalRefundHandler, IntoProposal}; + +#[cfg(test)] +mod tests; #[cfg(test)] -mod mock; +pub mod mock; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -27,9 +33,6 @@ mod benchmarking; #[cfg(any(feature = "runtime-benchmarks", test))] mod test_utils; -#[cfg(test)] -pub(crate) mod tests; - pub mod weights; pub use weights::*; @@ -52,10 +55,16 @@ pub type StorageItemOf = <::DepositHandler as DepositHandler, AccountIdOf>>::StorageItem; pub type DepositIdOf = <::DepositHandler as DepositHandler, AccountIdOf>>::DepositId; +pub type MaxJuryOf = <::JurySelector as pallet_fellowship::traits::SelectJury< + AccountIdOf, +>>::JurySize; // These are the bounded types which are suitable for handling user input due to their restriction of vector length. -type BoundedBTreeMilestones = - BoundedBTreeMap::MaxMilestonesPerProject>; +type BoundedBTreeMilestones = BoundedBTreeMap< + MilestoneKey, + Milestone>, + ::MaxMilestonesPerProject, +>; pub type BoundedProposedMilestones = BoundedVec::MaxMilestonesPerProject>; pub type AgreementHash = H256; @@ -77,33 +86,50 @@ pub mod pallet { { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The weights generated using the cli. + type WeightInfo: WeightInfoT; + /// The pallet_id used to generate sub accounts for each project fund pot. type PalletId: Get; - type AuthorityOrigin: EnsureOrigin; + /// The currency type. type MultiCurrency: MultiReservableCurrency, CurrencyId = CurrencyId>; - type WeightInfo: WeightInfoT; - type MaxWithdrawalExpiration: Get>; - /// The amount of time given, up to point of decision, when a vote of no confidence is held. - type NoConfidenceTimeLimit: Get>; + /// Defines the length that a milestone can be voted on. + type MilestoneVotingWindow: Get>; /// The minimum percentage of votes, inclusive, that is required for a vote to pass. type PercentRequiredForVoteToPass: Get; /// Maximum number of contributors per project. type MaximumContributorsPerProject: Get; - /// Defines the length that a milestone can be voted on. - type MilestoneVotingWindow: Get>; - /// The type responisble for handling refunds. - type RefundHandler: traits::RefundHandler, BalanceOf, CurrencyId>; + /// Maximum milestones allowed in a project. type MaxMilestonesPerProject: Get; + /// Maximum project a user can submit, make sure its pretty big. type MaxProjectsPerAccount: Get; - /// Imbue fee in percent 0-99 - type ImbueFee: Get; /// The maximum projects to be dealt with per block. Must be small as is dealt with in the hooks. type ExpiringProjectRoundsPerBlock: Get; + /// Imbue fee in percent 0-99 + type ImbueFee: Get; + /// The account the imbue fee goes to. + type ImbueFeeAccount: Get>; + /// The type responisble for handling refunds. + type ExternalRefundHandler: traits::ExternalRefundHandler< + AccountIdOf, + BalanceOf, + CurrencyId, + >; /// The type responsible for storage deposits. type DepositHandler: DepositHandler, AccountIdOf>; /// The type that will be used to calculate the deposit of a project. type ProjectStorageItem: Get>; - /// The minimum percentage of votes, inclusive, that is required for a vote of no confidence to pass/finalize. - type PercentRequiredForVoteNoConfidenceToPass: Get; + /// The trait that handler the raising of a dispute. + type DisputeRaiser: DisputeRaiser< + AccountIdOf, + DisputeKey = ProjectKey, + SpecificId = MilestoneKey, + MaxSpecifics = Self::MaxMilestonesPerProject, + MaxJurySize = MaxJuryOf, + >; + /// The jury selector type which is defining the max jury size. + type JurySelector: pallet_fellowship::traits::SelectJury>; + /// The origin responsible for setting the address responsible for minting tokens. + type AssetSignerOrigin: EnsureOrigin; } const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); @@ -112,6 +138,7 @@ pub mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); + /// Stores the projects of the pallet. #[pallet::storage] #[pallet::getter(fn projects)] pub type Projects = StorageMap<_, Identity, ProjectKey, Project, OptionQuery>; @@ -131,6 +158,7 @@ pub mod pallet { ValueQuery, >; + /// Stores the individuals votes on a given milestone key #[pallet::storage] pub type IndividualVoteStore = StorageMap<_, Blake2_128Concat, ProjectKey, ImmutableIndividualVotes, OptionQuery>; @@ -145,6 +173,7 @@ pub mod pallet { ValueQuery, >; + /// Stores the completed project by a given initiator. #[pallet::storage] #[pallet::getter(fn completed_projects)] pub type CompletedProjects = StorageMap< @@ -155,12 +184,6 @@ pub mod pallet { ValueQuery, >; - /// This holds the votes when a no confidence round is raised. - #[pallet::storage] - #[pallet::getter(fn no_confidence_votes)] - pub(super) type NoConfidenceVotes = - StorageMap<_, Identity, ProjectKey, Vote>, OptionQuery>; - #[pallet::storage] #[pallet::getter(fn project_count)] pub type ProjectCount = StorageValue<_, ProjectKey, ValueQuery>; @@ -187,6 +210,15 @@ pub mod pallet { ValueQuery, >; + #[pallet::storage] + pub type ProjectsInDispute = StorageMap< + _, + Blake2_128Concat, + ProjectKey, + BoundedVec::MaxMilestonesPerProject>, + ValueQuery, + >; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -217,14 +249,13 @@ pub mod pallet { ), /// A milestone has been approved. MilestoneApproved(T::AccountId, ProjectKey, MilestoneKey, BlockNumberFor), - /// You have created a vote of no confidence. - NoConfidenceRoundCreated(T::AccountId, ProjectKey), - /// You have voted upon a round of no confidence. - NoConfidenceRoundVotedUpon(T::AccountId, ProjectKey), - /// You have finalised a vote of no confidence. - NoConfidenceRoundFinalised(T::AccountId, ProjectKey), /// This milestone has been rejected. MilestoneRejected(ProjectKey, MilestoneKey), + /// A project has been refunded either partially or completely. + ProjectRefunded { + project_key: ProjectKey, + total_amount: BalanceOf, + }, /// Foreign Asset Signer Changed ForeignAssetSignerChanged(T::AccountId), /// Foreign Asset Signer Changed @@ -284,10 +315,28 @@ pub mod pallet { TooManyMilestones, /// There are too many projects for a given account TooManyProjects, + /// Not enough funds in project account to distribute fees. + NotEnoughFundsForFees, + /// Conversion failed due to an error while funding the Project. + ProjectFundingFailed, + /// Conversion failed due to an error in milestone conversion (probably a bound has been abused). + MilestoneConversionFailed, + /// This project has too many refund locations. + TooManyRefundLocations, + /// This project has too many jury members. + TooManyJuryMembers, /// There are too many milestone votes, this generally shouldnt be hit. TooManyMilestoneVotes, /// An internal error, a collection of votes for a milestone has been lost.s IndividualVoteNotFound, + /// Only a contributor can raise a dispute. + OnlyContributorsCanRaiseDispute, + /// One of these milestones is already in a dispute. + MilestonesAlreadyInDispute, + /// You cannot raise a dispute on an approved milestone. + CannotRaiseDisputeOnApprovedMilestone, + /// Only a contributor can initiate a refund. + OnlyContributorsCanInitiateRefund, /// Only the ForeignAssetSigner can mint tokens RequireForeignAssetSigner, } @@ -371,33 +420,139 @@ pub mod pallet { Self::new_withdrawal(who, project_key) } - /// In case of contributors losing confidence in the initiator a "Vote of no confidence" can be called. - /// This will start a round which each contributor can vote on. - /// The round will last as long as set in the Config. - #[pallet::call_index(12)] - #[pallet::weight(::WeightInfo::raise_vote_of_no_confidence())] - pub fn raise_vote_of_no_confidence( + /// Raise a dispute using the handle DisputeRaiser in the Config. + #[pallet::call_index(14)] + #[pallet::weight(::WeightInfo::raise_dispute())] + pub fn raise_dispute( origin: OriginFor, project_key: ProjectKey, + milestone_keys: BoundedVec, ) -> DispatchResult { let who = ensure_signed(origin)?; - Self::raise_no_confidence_round(who, project_key) + let project = Projects::::get(project_key).ok_or(Error::::ProjectDoesNotExist)?; + ensure!( + milestone_keys + .iter() + .all(|ms_key| project.milestones.contains_key(ms_key)), + Error::::MilestoneDoesNotExist + ); + ensure!( + project.contributions.contains_key(&who), + Error::::OnlyContributorsCanRaiseDispute + ); + ensure!( + !ProjectsInDispute::::contains_key(project_key), + Error::::MilestonesAlreadyInDispute + ); + ensure!( + !project.milestones.iter().any(|(milestone_key, milestone)| { + milestone_keys.contains(milestone_key) && milestone.is_approved + }), + Error::::CannotRaiseDisputeOnApprovedMilestone + ); + + ::DisputeRaiser::raise_dispute( + project_key, + who, + project.jury, + milestone_keys.clone(), + )?; + ProjectsInDispute::::insert(project_key, milestone_keys); + + Ok(()) } - /// pallet-disputes? - /// Vote on an already existing "Vote of no condidence" round. - /// is_yay is FOR the project's continuation. - /// so is_yay == false == against the project from continuing. - /// This autofinalises like in the milestone voting. - #[pallet::call_index(13)] - #[pallet::weight(::WeightInfo::vote_on_no_confidence_round())] - pub fn vote_on_no_confidence_round( - origin: OriginFor, - project_key: ProjectKey, - is_yay: bool, - ) -> DispatchResult { + /// Attempt a refund of milestones. + /// Will only refund milestones that have can_refund set to true. + #[pallet::call_index(15)] + #[pallet::weight(::WeightInfo::refund())] + pub fn refund(origin: OriginFor, project_key: ProjectKey) -> DispatchResult { let who = ensure_signed(origin)?; - Self::add_vote_no_confidence(who, project_key, is_yay) + let project = Projects::::get(project_key).ok_or(Error::::ProjectDoesNotExist)?; + ensure!( + project.contributions.contains_key(&who), + Error::::OnlyContributorsCanInitiateRefund + ); + let project_account = Self::project_account_id(project_key); + + Projects::::try_mutate_exists(project_key, |maybe_project| { + if let Some(project) = maybe_project { + let mut total_to_refund_including_fee: BalanceOf = Zero::zero(); + + for (_ms_key, ms) in project.milestones.iter_mut() { + if ms.can_refund && ms.transfer_status.is_none() { + let milestone_amount = + ms.percentage_to_unlock.mul_floor(project.raised_funds); + total_to_refund_including_fee = + total_to_refund_including_fee.saturating_add(milestone_amount); + ms.transfer_status = Some(TransferStatus::Refunded { + on: frame_system::Pallet::::block_number(), + }); + } + } + + // Just so we dont multiply by zero. + ensure!( + total_to_refund_including_fee != Zero::zero(), + Error::::NoAvailableFundsToWithdraw + ); + + let fee = + ::ImbueFee::get().mul_floor(total_to_refund_including_fee); + // Take the fee and send to ImbueFeeAccount + T::MultiCurrency::transfer( + project.currency_id, + &project_account, + &::ImbueFeeAccount::get(), + fee, + )?; + + let total_to_refund = total_to_refund_including_fee.saturating_sub(fee); + + for (refund_location, percent_share) in &project.refund_locations { + let per_refund = percent_share.mul_floor(total_to_refund); + match refund_location { + Locality::Local(acc) => { + T::MultiCurrency::transfer( + project.currency_id, + &project_account, + acc, + per_refund, + )?; + } + Locality::Foreign(multilocation) => { + T::ExternalRefundHandler::send_refund_message_to_treasury( + // TODO: change this to reference so that we dont have to clone.... + project_account.clone(), + per_refund, + project.currency_id, + *multilocation, + )?; + } + } + } + project.refunded_funds = project + .refunded_funds + .saturating_add(total_to_refund_including_fee); + if project + .refunded_funds + .saturating_add(project.withdrawn_funds) + == project.raised_funds + { + *maybe_project = None; + } + + Self::deposit_event(Event::::ProjectRefunded { + project_key, + total_amount: total_to_refund_including_fee, + }); + Ok::<(), DispatchError>(()) + } else { + Ok::<(), DispatchError>(()) + } + })?; + + Ok(()) } /// Sets the given AccountId (`new`) as the new Foreign asset signer @@ -405,13 +560,13 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - #[pallet::call_index(14)] - #[pallet::weight(::WeightInfo::vote_on_no_confidence_round())] + #[pallet::call_index(16)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 2))] pub fn set_foreign_asset_signer( origin: OriginFor, new: AccountIdOf, ) -> DispatchResult { - T::AuthorityOrigin::ensure_origin(origin)?; + T::AssetSignerOrigin::ensure_origin(origin)?; ForeignCurrencySigner::::put(&new); Self::deposit_event(Event::ForeignAssetSignerChanged(new)); Ok(()) @@ -421,8 +576,8 @@ pub mod pallet { /// /// The dispatch origin for this call must be the pre defined foreign asset signer. /// - #[pallet::call_index(15)] - #[pallet::weight(::WeightInfo::vote_on_no_confidence_round())] + #[pallet::call_index(17)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 2))] pub fn mint_offchain_assets( origin: OriginFor, beneficiary: AccountIdOf, @@ -445,20 +600,29 @@ pub mod pallet { Ok(()) } } + impl IntoProposal, BalanceOf, BlockNumberFor> for crate::Pallet where Project: EncodeLike>, { + type MaximumContributorsPerProject = T::MaximumContributorsPerProject; + type MaxMilestonesPerProject = T::MaxMilestonesPerProject; + type MaxJuryMembers = MaxJuryOf; /// The caller is used to take the storage deposit from. /// With briefs and grants the caller is the beneficiary, so the fee will come from them. fn convert_to_proposal( currency_id: CurrencyId, - contributions: BTreeMap, Contribution, BlockNumberFor>>, - brief_hash: H256, + contributions: ContributionsFor, + agreement_hash: H256, benificiary: AccountIdOf, - proposed_milestones: Vec, - funding_type: FundingType, + proposed_milestones: BoundedVec, + refund_locations: BoundedVec< + (Locality>, Percent), + Self::MaximumContributorsPerProject, + >, + jury: BoundedVec, Self::MaxJuryMembers>, + on_creation_funding: FundingPath, ) -> Result<(), DispatchError> { let project_key = crate::ProjectCount::::get().saturating_add(1); @@ -469,86 +633,63 @@ pub mod pallet { CurrencyId::Native, )?; + let project_account_id = crate::Pallet::::project_account_id(project_key); + // todo: Error handling here can be improved. + let _is_funded = Self::fund_project( + &on_creation_funding, + &contributions, + &project_account_id, + currency_id, + ) + .map_err(|_| Error::::ProjectFundingFailed)?; + + let converted_milestones = + Self::try_convert_to_milestones(proposed_milestones.clone(), project_key) + .map_err(|_| Error::::MilestoneConversionFailed)?; let sum_of_contributions = contributions .values() .fold(Default::default(), |acc: BalanceOf, x| { acc.saturating_add(x.value) }); - let project_account_id = crate::Pallet::::project_account_id(project_key); - - match funding_type { - FundingType::Proposal | FundingType::Brief => { - for (acc, cont) in contributions.iter() { - let project_account_id = - crate::Pallet::::project_account_id(project_key); - <::MultiCurrency as MultiReservableCurrency< - AccountIdOf, - >>::unreserve(currency_id, acc, cont.value); - ::MultiCurrency::transfer( - currency_id, - acc, - &project_account_id, - cont.value, - )?; - } - } - FundingType::Grant(_) => {} - } - - // TODO: this milestone key has no relation to the milestones coming in except the order they come in. - // This could be a bug somewhere. - let mut milestone_key: u32 = 0; - let mut milestones: BoundedBTreeMilestones = BoundedBTreeMap::new(); - let mut bounded_milestone_keys: BoundedVec = - BoundedVec::new(); - - for milestone in proposed_milestones { - let milestone = Milestone { - project_key, - milestone_key, - percentage_to_unlock: milestone.percentage_to_unlock, - is_approved: false, - }; - milestones - .try_insert(milestone_key, milestone) - .map_err(|_| Error::::TooManyMilestones)?; - - bounded_milestone_keys - .try_push(milestone_key) - .map_err(|_| Error::::TooManyMilestones)?; - - milestone_key = milestone_key.saturating_add(1); - } - - let individual_votes = ImmutableIndividualVotes::new(bounded_milestone_keys); - IndividualVoteStore::::insert(project_key, individual_votes); - - let bounded_contributions: ContributionsFor = contributions + let bounded_milestone_keys = proposed_milestones + .iter() + .enumerate() + .map(|(i, _ms)| i as u32) + .collect::>() .try_into() - .map_err(|_| Error::::TooManyContributions)?; + .map_err(|_| Error::::TooManyMilestones)?; let project: Project = Project { - milestones, - contributions: bounded_contributions, + agreement_hash, + milestones: converted_milestones, + contributions, currency_id, - withdrawn_funds: 0u32.into(), + withdrawn_funds: Zero::zero(), raised_funds: sum_of_contributions, initiator: benificiary.clone(), created_on: frame_system::Pallet::::block_number(), cancelled: false, - agreement_hash: brief_hash, - funding_type, deposit_id, + refund_locations: refund_locations + .try_into() + .map_err(|_| Error::::TooManyRefundLocations)?, + jury: jury + .try_into() + .map_err(|_| Error::::TooManyJuryMembers)?, + on_creation_funding, + refunded_funds: Zero::zero(), }; - Projects::::insert(project_key, project); + let individual_votes = ImmutableIndividualVotes::new(bounded_milestone_keys); + IndividualVoteStore::::insert(project_key, individual_votes); - ProjectCount::::mutate(|c| *c = c.saturating_add(1)); + Projects::::insert(project_key, project); + ProjectCount::::put(project_key); Self::deposit_event(Event::ProjectCreated( benificiary, - brief_hash, + agreement_hash, project_key, sum_of_contributions, currency_id, @@ -556,6 +697,50 @@ pub mod pallet { )); Ok(()) } + + /// Convert a set of contributions into their respective refund locations. + /// Only for local contributions. + fn convert_contributions_to_refund_locations( + contributions: &ContributionsFor, + ) -> BoundedVec<(Locality>, Percent), T::MaximumContributorsPerProject> + { + let sum_of_contributions = contributions + .values() + .fold(Default::default(), |acc: BalanceOf, x| { + acc.saturating_add(x.value) + }); + + let mut sum_of_percents: Percent = Zero::zero(); + let mut ret: BoundedVec< + (Locality>, Percent), + T::MaximumContributorsPerProject, + > = contributions + .iter() + .map(|c| { + let percent = Percent::from_rational(c.1.value, sum_of_contributions); + sum_of_percents = sum_of_percents.saturating_add(percent); + // Since these are local we can use MultiLocation::Default; + (Locality::from_local(c.0.clone()), percent) + }) + .collect::>, Percent)>>() + .try_into() + .expect("Both input and output are bound by the same quantifier; qed"); + + // TEST THIS + if sum_of_percents != One::one() { + // We are missing a part of the fund so take the remainder and use the pallet_id as the return address. + //(as is used throughout the rest of the pallet for fees) + let diff = ::one().saturating_sub(sum_of_percents); + // TODO: IF THE CONTRIBUTION BOUND IS MAX ALREADY THEN WE CANNOT PUSH THE DUST ACCOUNT ON + // FAIL SILENTLY AND CLEAN UP ON FINAL WITHDRAW INSTEAD. + let _ = ret.try_push(( + Locality::from_local(::ImbueFeeAccount::get()), + diff, + )); + } + + ret + } } } @@ -576,12 +761,34 @@ pub struct ProposedMilestone { /// The contribution users made to a project project. /// TODO: move these to a common repo (common_types will do) /// TODO: add ipfs hash like in the grants pallet and + +// TODO: MIGRATION FOR MILESTONES +//can_refund #[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] -pub struct Milestone { +pub struct Milestone { pub project_key: ProjectKey, pub milestone_key: MilestoneKey, pub percentage_to_unlock: Percent, pub is_approved: bool, + pub can_refund: bool, + pub transfer_status: Option>, +} + +impl Milestone { + fn new( + project_key: ProjectKey, + milestone_key: MilestoneKey, + percentage_to_unlock: Percent, + ) -> Self { + Self { + project_key, + milestone_key, + percentage_to_unlock, + is_approved: false, + can_refund: false, + transfer_status: None, + } + } } /// The vote struct is used to @@ -592,31 +799,64 @@ pub struct Vote { is_approved: bool, } -impl> Default for Vote { +impl Default for Vote { fn default() -> Self { Self { - yay: Balance::from(Zero::zero()), - nay: Balance::from(Zero::zero()), + yay: Zero::zero(), + nay: Zero::zero(), is_approved: false, } } } +// TODO MILESTONE MIGRATIONS /// The struct which contain milestones that can be submitted. #[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Project { pub agreement_hash: H256, pub milestones: BoundedBTreeMilestones, + /// The contributions to a project, also known as milestone approvers. TODO: discuss name change. pub contributions: ContributionsFor, + /// The currency id of the Project's funds. pub currency_id: common_types::CurrencyId, + /// The amount of funds already withdrawn from the project. pub withdrawn_funds: BalanceOf, + /// The amount of money actually raised on instantiation of the Project. pub raised_funds: BalanceOf, + /// The initiator of the project, also known as the beneficiary: TODO: discuss name change. pub initiator: AccountIdOf, + /// The blocknumber the Project was created on pub created_on: BlockNumberFor, + /// is the project cancelled TODO: make an issue this is from legacy. pub cancelled: bool, - pub funding_type: FundingType, + /// The deposit_id is reponsible for returning deposits held in pallet-deposits. pub deposit_id: DepositIdOf, + /// Where do the refunds end up and what percent they get. + pub refund_locations: + BoundedVec<(Locality>, Percent), T::MaximumContributorsPerProject>, + /// Who should deal with disputes. + pub jury: BoundedVec, MaxJuryOf>, + /// When is the project funded and how is it taken. + pub on_creation_funding: FundingPath, + /// The amount of funds refunded. + pub refunded_funds: BalanceOf, +} + +/// For deriving the location of an account. +#[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] +pub enum Locality { + Local(AccountId), + Foreign(MultiLocation), +} + +impl Locality { + fn _from_multilocation(m: MultiLocation) -> Self { + Self::Foreign(m) + } + fn from_local(l: AccountId) -> Self { + Self::Local(l) + } } /// The contribution users made to a proposal project. @@ -635,6 +875,26 @@ pub struct Whitelist { max_cap: Balance, } +/// Defines how a project is funded on its instantiation. +#[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen, Default)] +pub enum FundingPath { + // TODO: Possibly wise to change this to actually define where the reserves are coming from. + // This allows us to break the notion of a "contributor" finally and worry only about the "approvers". + /// Take from the reserved amounts of the contributors account. + #[default] + TakeFromReserved, + /// Take nothing from the contributors and await funding from some outside source. + WaitForFunding, +} + +/// Defines how the funds were taken out of a specific milestone. +/// Contians the block number for possible further investigation. +#[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] +pub enum TransferStatus { + Refunded { on: BlockNumber }, + Withdrawn { on: BlockNumber }, +} + /// Stores the btree for each individual vote. #[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] @@ -646,7 +906,7 @@ pub trait WeightInfoT { fn submit_milestone() -> Weight; fn vote_on_milestone() -> Weight; fn withdraw() -> Weight; - fn raise_vote_of_no_confidence() -> Weight; - fn vote_on_no_confidence_round() -> Weight; fn on_initialize() -> Weight; + fn raise_dispute() -> Weight; + fn refund() -> Weight; } diff --git a/pallets/proposals/src/migration.rs b/pallets/proposals/src/migration.rs index 8d2012af..1e04bb71 100644 --- a/pallets/proposals/src/migration.rs +++ b/pallets/proposals/src/migration.rs @@ -1,9 +1,11 @@ use crate::*; use frame_support::traits::OnRuntimeUpgrade; use frame_support::*; - use frame_system::pallet_prelude::BlockNumberFor; + +use common_types::{TreasuryOrigin, TreasuryOriginConverter}; pub use pallet::*; +use pallet_fellowship::traits::SelectJury; pub type TimestampOf = ::Moment; @@ -167,7 +169,7 @@ mod v2 { pub approved_for_funding: bool, pub funding_threshold_met: bool, pub cancelled: bool, - pub funding_type: FundingType, + pub funding_type: v5::FundingType, } #[derive(Clone, Debug, Encode, Decode, TypeInfo)] @@ -210,7 +212,7 @@ mod v2 { funding_threshold_met: project.funding_threshold_met, cancelled: project.cancelled, raised_funds: project.raised_funds, - funding_type: FundingType::Proposal, + funding_type: v5::FundingType::Proposal, }; Some(migrated_project) }); @@ -233,7 +235,7 @@ pub mod v3 { #[derive(Encode, Decode, Clone)] pub struct ProjectV3 { pub agreement_hash: H256, - pub milestones: BTreeMap, + pub milestones: BTreeMap, pub contributions: BTreeMap>, pub currency_id: common_types::CurrencyId, pub withdrawn_funds: Balance, @@ -241,7 +243,7 @@ pub mod v3 { pub initiator: AccountId, pub created_on: BlockNumber, pub cancelled: bool, - pub funding_type: FundingType, + pub funding_type: v5::FundingType, } pub fn migrate_contribution_and_project( @@ -251,7 +253,7 @@ pub mod v3 { let mut migrated_contributions = BTreeMap::new(); let mut migrated_milestones = BTreeMap::new(); - Projects::::translate(|_project_key, project: v2::ProjectV2Of| { + v6::Projects::::translate(|_project_key, project: v2::ProjectV2Of| { project.contributions.iter().for_each(|(key, cont)| { *weight += T::DbWeight::get().reads_writes(1, 1); migrated_contributions.insert( @@ -266,7 +268,7 @@ pub mod v3 { *weight += T::DbWeight::get().reads_writes(1, 1); migrated_milestones.insert( *key, - Milestone { + v6::V6Milestone { project_key: milestone.project_key, milestone_key: milestone.milestone_key, percentage_to_unlock: Percent::from_percent( @@ -276,14 +278,14 @@ pub mod v3 { }, ); }); - let bounded_milestone: Result, _> = + let bounded_milestone: Result, _> = migrated_milestones.clone().try_into(); let bounded_contributions: Result, _> = migrated_contributions.clone().try_into(); if let Ok(ms) = bounded_milestone { if let Ok(cont) = bounded_contributions { *weight += T::DbWeight::get().reads_writes(1, 1); - let migrated_project: Project = Project { + let migrated_project: v6::ProjectV6 = v6::ProjectV6 { milestones: ms, contributions: cont, currency_id: project.currency_id, @@ -293,7 +295,7 @@ pub mod v3 { agreement_hash: Default::default(), cancelled: project.cancelled, raised_funds: project.raised_funds, - funding_type: FundingType::Proposal, + funding_type: v5::FundingType::Proposal, // A deposit_id of u32::MAX is ignored. deposit_id: u32::MAX.into(), }; @@ -499,6 +501,15 @@ pub mod v5 { V4, } + #[derive( + Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen, + )] + pub enum FundingType { + Proposal, + Brief, + Grant(common_types::TreasuryOrigin), + } + /// 1: Custom StorageVersion is removed, macro StorageVersion is used: https://github.com/ImbueNetwork/imbue/issues/178 pub struct MigrateToV5(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV5 { @@ -557,6 +568,37 @@ pub mod v5 { pub mod v6 { use super::*; + pub type V6BoundedBTreeMilestones = + BoundedBTreeMap::MaxMilestonesPerProject>; + + #[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] + pub struct V6Milestone { + pub project_key: ProjectKey, + pub milestone_key: MilestoneKey, + pub percentage_to_unlock: Percent, + pub is_approved: bool, + } + + #[derive(Encode, Decode, PartialEq, Eq, Clone, Debug, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] + pub struct ProjectV6 { + pub agreement_hash: H256, + pub milestones: V6BoundedBTreeMilestones, + pub contributions: ContributionsFor, + pub currency_id: common_types::CurrencyId, + pub withdrawn_funds: BalanceOf, + pub raised_funds: BalanceOf, + pub initiator: AccountIdOf, + pub created_on: BlockNumberFor, + pub cancelled: bool, + pub funding_type: v5::FundingType, + pub deposit_id: DepositIdOf, + } + + #[storage_alias] + pub type Projects = + StorageMap, Identity, ProjectKey, ProjectV6, OptionQuery>; + #[storage_alias] pub(super) type MilestoneVotes = StorageDoubleMap< Pallet, @@ -572,7 +614,7 @@ pub mod v6 { // only migrate the voting rounds awaiting the migration to remove no confidence rounds. // User votes is now handled by IndividualVoteStore:: fn migrate_user_has_voted(weight: &mut Weight) { - Projects::::iter().for_each(|(project_key, project)| { + v6::Projects::::iter().for_each(|(project_key, project)| { *weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); project.milestones.keys().for_each(|milestone_key| { *weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); @@ -678,6 +720,141 @@ pub mod v6 { } } +pub mod v7 { + use super::*; + + pub struct MigrateToV7(T); + + impl OnRuntimeUpgrade for MigrateToV7 + where + AccountIdOf: Into<[u8; 32]>, + { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::warn!( target: "pallet-proposals", "Running pre_upgrade()"); + let current = as GetStorageVersion>::current_storage_version(); + let onchain = as GetStorageVersion>::on_chain_storage_version(); + + ensure!( + ::MaxJuryMembers::get() < u8::MAX as u32, + "Max jury members must be smaller than u8" + ); + + ensure!( + current == 7 && onchain == 6, + "Current version must be set to v7 and onchain to v6" + ); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads_writes(1, 1); + log::warn!("****** STARTING MIGRATION *****"); + + let current = as GetStorageVersion>::current_storage_version(); + let onchain = as GetStorageVersion>::on_chain_storage_version(); + if current == 7 && onchain == 6 { + migrate_new_fields::(&mut weight); + current.put::>(); + log::warn!("v7 has been successfully applied"); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); + } else { + log::warn!("Skipping v7 due to mismatched version, this be removed from Executive"); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + + log::warn!("****** ENDING MIGRATION *****"); + weight + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + log::warn!( target: "pallet-proposals", "Running post_upgrade()"); + + ensure!( + Pallet::::current_storage_version() == 7, + "Storage version should be v7 after the migration" + ); + + Ok(()) + } + } + + fn migrate_new_fields(weight: &mut Weight) + where + AccountIdOf: Into<[u8; 32]>, + { + v6::Projects::::drain().for_each(|(key, project)|{ + *weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + let on_creation_funding = match project.funding_type { + v5::FundingType::Proposal => crate::FundingPath::TakeFromReserved, + v5::FundingType::Brief => crate::FundingPath::TakeFromReserved, + v5::FundingType::Grant(_) => crate::FundingPath::WaitForFunding, + }; + + let jury = >>::select_jury(); + + let refund_locations: BoundedVec<(Locality>, Percent), T::MaximumContributorsPerProject> = match project.funding_type { + v5::FundingType::Proposal => crate::Pallet::::convert_contributions_to_refund_locations(&project.contributions), + v5::FundingType::Brief => crate::Pallet::::convert_contributions_to_refund_locations(&project.contributions), + + v5::FundingType::Grant(treasury_origin) => { + let multilocation = match treasury_origin { + TreasuryOrigin::Kusama => { + ::get_multi_location(&treasury_origin).expect("known good.") + },TreasuryOrigin::Imbue => { + ::get_multi_location(&treasury_origin).expect("known good.") + },TreasuryOrigin::Karura => { + ::get_multi_location(&TreasuryOrigin::Imbue).expect("known good.") + }}; + vec![(Locality::Foreign(multilocation), Percent::from_parts(100))].try_into().expect("1 is lower than bound if it isnt then the system is broken anyway; qed") + }, + }; + + let mut new_milestones: BoundedBTreeMilestones = BoundedBTreeMap::new(); + project.milestones.iter().for_each(|(_ms_key, ms): (&MilestoneKey, &v6::V6Milestone)| { + // assume that if its approved then its been withdrawn. + let mut transfer_status: Option>> = None; + if ms.is_approved { + transfer_status = Some(TransferStatus::Withdrawn{on: frame_system::Pallet::::block_number()}); + } + + let new_ms = crate::Milestone { + project_key: ms.project_key, + milestone_key: ms.milestone_key, + percentage_to_unlock: ms.percentage_to_unlock, + is_approved: ms.is_approved, + can_refund: false, + transfer_status + }; + // This only fails if the bound has been reduced and the old milestones were at max which is unlikely. + new_milestones.try_insert(ms.milestone_key, new_ms).expect("If this fails in try_runtime we have an issue. Dont reduce bound of milestones."); + }); + + let migrated_project = crate::Project { + agreement_hash: project.agreement_hash, + milestones: new_milestones, + contributions: project.contributions, + currency_id: project.currency_id, + withdrawn_funds: project.withdrawn_funds, + raised_funds: project.raised_funds, + initiator: project.initiator, + created_on: project.created_on, + cancelled: project.cancelled, + deposit_id: project.deposit_id, + refund_locations, + jury, + on_creation_funding, + refunded_funds: Zero::zero(), + }; + + *weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + crate::Projects::::insert(key, migrated_project); + }); + } +} + #[cfg(test)] mod test { use super::*; @@ -688,7 +865,7 @@ mod test { #[test] fn migrate_v0_to_v1() { - let contribution_value = 1_000_000_u64; + let contribution_value = 1_000_000_u128; build_test_externality().execute_with(|| { let project_key = 1; @@ -774,7 +951,7 @@ mod test { fn migrate_v1_to_v2() { build_test_externality().execute_with(|| { let project_key = 1; - let contribution_value = 1_000_000_u64; + let contribution_value = 1_000_000_u128; let mut contributions: BTreeMap< AccountIdOf, Contribution, TimestampOf>, @@ -826,7 +1003,7 @@ mod test { ); assert_eq!(H256::default(), migrated_project.agreement_hash); - assert_eq!(FundingType::Proposal, migrated_project.funding_type); + assert_eq!(v5::FundingType::Proposal, migrated_project.funding_type); }) } @@ -891,7 +1068,7 @@ mod test { approved_for_funding: false, funding_threshold_met: false, cancelled: false, - funding_type: FundingType::Brief, + funding_type: v5::FundingType::Brief, }; v2::Projects::::insert(0, &project); v3::UserVotes::::insert((ALICE, 10u32, 10u32, v3::RoundType::VotingRound), true); @@ -900,8 +1077,8 @@ mod test { true, ); let v = Vote { - yay: 100_000u64, - nay: 50_000u64, + yay: 100_000u128, + nay: 50_000u128, is_approved: false, }; v3::OldMilestoneVotes::::insert((10, 10), v); @@ -917,7 +1094,7 @@ mod test { let _w = v3::migrate_all::(); - let project_apres = crate::Projects::::get(0).unwrap(); + let project_apres = v6::Projects::::get(0).unwrap(); // #1, 2, 7 & 8 assert_eq!(project.agreement_hash, project_apres.agreement_hash); assert_eq!( @@ -977,7 +1154,8 @@ mod test { let cont = get_contributions::(vec![BOB, DAVE], 100_000); let prop_milestones = get_milestones(10); let project_key = - create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .expect("project wasnt created!"); let milestone_key: MilestoneKey = 0; let expiry_block: BlockNumber = 10; let rounds_expiring: BoundedProjectKeysPerBlock = diff --git a/pallets/proposals/src/mock.rs b/pallets/proposals/src/mock.rs index 3f2e7db4..db32a2ab 100644 --- a/pallets/proposals/src/mock.rs +++ b/pallets/proposals/src/mock.rs @@ -2,7 +2,6 @@ use crate as pallet_proposals; use frame_support::{ parameter_types, traits::{ConstU32, Nothing}, - weights::{ConstantMultiplier, IdentityFee}, PalletId, }; @@ -27,7 +26,7 @@ type Block = frame_system::mocking::MockBlock; pub type BlockNumber = u64; pub type Amount = i128; -pub type Balance = u64; +pub type Balance = u128; pub type Moment = u64; pub type AccountId = u128; @@ -53,7 +52,6 @@ frame_support::construct_runtime!( Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Tokens: orml_tokens::{Pallet, Storage, Event}, Currencies: orml_currencies::{Pallet, Call, Storage}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, Proposals: pallet_proposals::{Pallet, Call, Storage, Event}, IdentityPallet: pallet_identity::{Pallet, Call, Storage, Event}, } @@ -84,19 +82,6 @@ impl orml_tokens::Config for Test { type ReserveIdentifier = [u8; 8]; } -parameter_types! { - pub const TransactionByteFee: u64 = 1; - pub const OperationalFeeMultiplier: u8 = 5; -} -impl pallet_transaction_payment::Config for Test { - type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter; - type WeightToFee = IdentityFee; - type LengthToFee = ConstantMultiplier; - type FeeMultiplierUpdate = (); - type OperationalFeeMultiplier = OperationalFeeMultiplier; -} - parameter_types! { pub const BlockHashCount: u64 = 250; } @@ -134,14 +119,14 @@ parameter_types! { } parameter_types! { - pub const ExistentialDeposit: u64 = 5; + pub const ExistentialDeposit: Balance = 5; pub const MaxReserves: u32 = 50; } impl pallet_balances::Config for Test { type RuntimeEvent = RuntimeEvent; type AccountStore = System; - type Balance = u64; + type Balance = Balance; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type MaxLocks = (); @@ -165,9 +150,7 @@ impl pallet_timestamp::Config for Test { } parameter_types! { - pub const TwoWeekBlockUnit: u32 = 100800u32; pub const ProposalsPalletId: PalletId = PalletId(*b"imbgrant"); - pub NoConfidenceTimeLimit: BlockNumber = 100800u32.into(); pub PercentRequiredForVoteToPass: Percent = Percent::from_percent(75u8); pub MaximumContributorsPerProject: u32 = 50; pub RefundsPerBlock: u8 = 2; @@ -178,35 +161,35 @@ parameter_types! { pub ExpiringProjectRoundsPerBlock: u32 = 10; pub ProjectStorageItem: StorageItems = StorageItems::Project; pub MaxProjectsPerAccount: u16 = 50; - pub PercentRequiredForVoteNoConfidenceToPass: Percent = Percent::from_percent(75u8); + pub MaxJuryMembers: u32 = 100; + pub ImbueFeeAccount: AccountId = TREASURY; } impl pallet_proposals::Config for Test { type RuntimeEvent = RuntimeEvent; type PalletId = ProposalsPalletId; - type AuthorityOrigin = EnsureRoot; type MultiCurrency = Tokens; - type WeightInfo = crate::WeightInfo; - // Adding 2 weeks as th expiration time - type MaxWithdrawalExpiration = TwoWeekBlockUnit; - type NoConfidenceTimeLimit = NoConfidenceTimeLimit; + type WeightInfo = pallet_proposals::WeightInfo; type PercentRequiredForVoteToPass = PercentRequiredForVoteToPass; type MaximumContributorsPerProject = MaximumContributorsPerProject; type MilestoneVotingWindow = MilestoneVotingWindow; - type RefundHandler = pallet_proposals::traits::MockRefundHandler; + type ExternalRefundHandler = pallet_proposals::traits::MockRefundHandler; type MaxMilestonesPerProject = MaxMilestonesPerProject; type ImbueFee = ImbueFee; + type ImbueFeeAccount = ImbueFeeAccount; type ExpiringProjectRoundsPerBlock = ExpiringProjectRoundsPerBlock; + type DepositHandler = MockDepositHandler; type ProjectStorageItem = ProjectStorageItem; - type DepositHandler = MockDepositHandler; type MaxProjectsPerAccount = MaxProjectsPerAccount; - type PercentRequiredForVoteNoConfidenceToPass = PercentRequiredForVoteNoConfidenceToPass; + type DisputeRaiser = MockDisputeRaiser; + type JurySelector = MockJurySelector; + type AssetSignerOrigin = EnsureRoot; } parameter_types! { - pub const BasicDeposit: u64 = 10; - pub const FieldDeposit: u64 = 10; - pub const SubAccountDeposit: u64 = 10; + pub const BasicDeposit: Balance = 10; + pub const FieldDeposit: Balance = 10; + pub const SubAccountDeposit: Balance = 10; pub const MaxSubAccounts: u32 = 2; pub const MaxAdditionalFields: u32 = 2; pub const MaxRegistrars: u32 = 20; @@ -227,6 +210,14 @@ impl pallet_identity::Config for Test { type WeightInfo = (); } +parameter_types! { + pub MaxCandidatesPerShortlist: u32 = 100; + pub ShortlistPeriod: BlockNumber = 100; + pub MembershipDeposit: Balance = 50_000_000; + pub SlashAccount: AccountId = TREASURY; + pub DepositCurrencyId: CurrencyId = CurrencyId::Native; +} + parameter_types! { pub const UnitWeightCost: u64 = 10; pub const MaxInstructions: u32 = 100; @@ -235,7 +226,7 @@ pub static ALICE: AccountId = 125; pub static BOB: AccountId = 126; pub static CHARLIE: AccountId = 127; pub static DAVE: AccountId = 128; -pub static STEVE: AccountId = 254; +pub static TREASURY: AccountId = 222; pub static JOHN: AccountId = 255; pub(crate) fn build_test_externality() -> sp_io::TestExternalities { @@ -245,14 +236,14 @@ pub(crate) fn build_test_externality() -> sp_io::TestExternalities { let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { - let initial_balance = 100_000_000u64; + let initial_balance = 100_000_000_000u128; System::set_block_number(1); let _ = Tokens::deposit(CurrencyId::Native, &ALICE, initial_balance); let _ = Tokens::deposit(CurrencyId::Native, &BOB, initial_balance); let _ = Tokens::deposit(CurrencyId::Native, &CHARLIE, initial_balance); let _ = Tokens::deposit(CurrencyId::Native, &DAVE, initial_balance); let _ = Tokens::deposit(CurrencyId::Native, &JOHN, initial_balance); - let _ = Tokens::deposit(CurrencyId::Native, &STEVE, initial_balance); + let _ = Tokens::deposit(CurrencyId::Native, &TREASURY, initial_balance); }); ext } @@ -262,14 +253,12 @@ pub enum StorageItems { Project, } -pub struct MockDepositHandler(T); -impl DepositHandler, crate::AccountIdOf> - for MockDepositHandler -{ +pub struct MockDepositHandler; +impl DepositHandler for MockDepositHandler { type DepositId = u64; type StorageItem = StorageItems; fn take_deposit( - _who: crate::AccountIdOf, + _who: AccountId, _storage_item: Self::StorageItem, _currency_id: CurrencyId, ) -> Result { @@ -282,3 +271,27 @@ impl DepositHandler, crate::AccountIdOf Ok(()) } } + +pub struct MockDisputeRaiser; +impl DisputeRaiser for MockDisputeRaiser { + type DisputeKey = ProjectKey; + type SpecificId = MilestoneKey; + type MaxJurySize = MaxJuryMembers; + type MaxSpecifics = MaxMilestonesPerProject; + fn raise_dispute( + _dispute_key: Self::DisputeKey, + _raised_by: AccountId, + _jury: BoundedVec, + _specific_ids: BoundedVec, + ) -> Result<(), DispatchError> { + Ok(()) + } +} + +pub struct MockJurySelector; +impl pallet_fellowship::traits::SelectJury for MockJurySelector { + type JurySize = MaxJuryMembers; + fn select_jury() -> BoundedVec { + BoundedVec::new() + } +} diff --git a/pallets/proposals/src/test_utils.rs b/pallets/proposals/src/test_utils.rs index 2d9da68b..4362be81 100644 --- a/pallets/proposals/src/test_utils.rs +++ b/pallets/proposals/src/test_utils.rs @@ -1,18 +1,32 @@ use crate::*; -use common_types::{CurrencyId, FundingType}; -#[cfg(feature = "runtime-benchmarks")] -use frame_benchmarking::account; -use frame_support::assert_ok; +use common_types::CurrencyId; +use frame_support::{assert_ok, BoundedVec}; use frame_system::EventRecord; -use orml_traits::MultiCurrency; -use pallet_deposits::traits::DepositHandler; +use orml_traits::{MultiCurrency, MultiReservableCurrency}; + +use pallet_disputes::traits::DisputeHooks; use sp_arithmetic::per_things::Percent; use sp_core::{Get, H256}; -use sp_runtime::SaturatedConversion; -use sp_runtime::Saturating; + +use sp_runtime::{DispatchError, SaturatedConversion}; +use sp_std::convert::TryInto; + +#[cfg(feature = "runtime-benchmarks")] +use frame_benchmarking::account; #[cfg(feature = "runtime-benchmarks")] use sp_std::vec::Vec; -use sp_std::{collections::btree_map::BTreeMap, convert::TryInto}; + +// pub fn run_to_block(n: BlockNumber) { +// while System::block_number() < n { +// Tokens::on_finalize(System::block_number()); +// System::on_finalize(System::block_number()); +// Proposals::on_finalize(System::block_number()); +// System::set_block_number(System::block_number() + 1); +// Tokens::on_initialize(System::block_number()); +// System::on_initialize(System::block_number()); +// Proposals::on_initialize(System::block_number()); +// } +// } pub fn get_contributions( accounts: Vec>, @@ -44,76 +58,63 @@ pub fn get_max_milestones() -> Vec { get_milestones(::MaxMilestonesPerProject::get() as u8) } -/// Create a project for test purposes, this will not test the paths coming into this pallet via -/// the IntoProposal trait. -pub fn create_project( +// Using the FundingPath::TakeFromReserved create a project for testing funded milestones +// This will be called in the majority of test cases. +// IntoProposal assumes that funds have been reserved before calling it. +pub fn create_and_fund_project( beneficiary: AccountIdOf, contributions: ContributionsFor, proposed_milestones: Vec, currency_id: CurrencyId, -) -> ProjectKey { - let deposit_id = ::DepositHandler::take_deposit( - beneficiary.clone(), - ::ProjectStorageItem::get(), - CurrencyId::Native, - ) - .expect("this should work"); +) -> Result { + contributions.iter().for_each(|(acc, c)| { + ::MultiCurrency::reserve(currency_id, acc, c.value).unwrap(); + }); let agreement_hash: H256 = Default::default(); - - let project_key = crate::ProjectCount::::get().saturating_add(1); - - let mut raised_funds: BalanceOf = 0u32.into(); - let project_account_id = crate::Pallet::::project_account_id(project_key); - - for (account, contribution) in contributions.iter() { - let amount = contribution.value; - assert_ok!(::MultiCurrency::transfer( - currency_id, - account, - &project_account_id, - amount - )); - raised_funds = raised_funds.saturating_add(amount); - } - - let mut milestone_key: u32 = 0; - let mut milestones: BTreeMap = BTreeMap::new(); - let mut bounded_milestone_keys: BoundedVec = - BoundedVec::new(); - - for ms in proposed_milestones { - let milestone = Milestone { - project_key, - milestone_key, - percentage_to_unlock: ms.percentage_to_unlock, - is_approved: false, - }; - milestones.insert(milestone_key, milestone); - let _ = bounded_milestone_keys.try_push(milestone_key); - milestone_key = milestone_key.saturating_add(1); - } - - let individual_votes = ImmutableIndividualVotes::new(bounded_milestone_keys); - IndividualVoteStore::::insert(project_key, individual_votes); - - let project = Project { - milestones: milestones.try_into().expect("too many milestones"), - contributions, + let refund_locations = as IntoProposal< + AccountIdOf, + BalanceOf, + BlockNumberFor, + >>::convert_contributions_to_refund_locations(&contributions); + + // Reserve the assets from the contributors used. + as IntoProposal, BalanceOf, BlockNumberFor>>::convert_to_proposal( currency_id, - withdrawn_funds: 0u32.into(), - raised_funds, - initiator: beneficiary, - created_on: frame_system::Pallet::::block_number(), - cancelled: false, + contributions, agreement_hash, - funding_type: FundingType::Brief, - deposit_id, - }; - - crate::Projects::::insert(project_key, project); - crate::ProjectCount::::put(project_key); + beneficiary, + proposed_milestones.try_into().map_err(|_|Error::::TooManyMilestones)?, + refund_locations, + BoundedVec::new(), + FundingPath::TakeFromReserved, + )?; + + Ok(ProjectCount::::get()) +} - project_key +// For testing grants and errors pre funding +// TODO: tests for these! +pub fn _create_project_awaiting_funding( + beneficiary: AccountIdOf, + contributions: ContributionsFor, + proposed_milestones: Vec, + currency_id: CurrencyId, + treasury_account: MultiLocation, +) -> Result { + let agreement_hash: H256 = Default::default(); + // Reserve the assets from the contributors used. + as IntoProposal, BalanceOf, BlockNumberFor>>::convert_to_proposal( + currency_id, + contributions, + agreement_hash, + beneficiary, + proposed_milestones.try_into().map_err(|_|Error::::TooManyMilestones)?, + vec![(Locality::Foreign(treasury_account), Percent::from_parts(100u8))].try_into().map_err(|_|Error::::TooManyRefundLocations)?, + BoundedVec::new(), + FundingPath::WaitForFunding, + )?; + + Ok(ProjectCount::::get()) } #[cfg(feature = "runtime-benchmarks")] @@ -123,14 +124,25 @@ pub fn create_funded_user( balance_factor: u128, ) -> T::AccountId { let user = account(seed, n, 0); - assert_ok!(::AccountId, - >>::deposit( - CurrencyId::Native, &user, balance_factor.saturated_into() - )); + assert_ok!( + >>::deposit( + CurrencyId::Native, + &user, + balance_factor.saturated_into() + ) + ); user } +/// Manually call the hook OnDisputeCompleteWith a predefined result for testing> +pub fn complete_dispute( + project_key: ProjectKey, + milestone_keys: Vec, + result: pallet_disputes::DisputeResult, +) -> crate::Weight { + >::on_dispute_complete(project_key, milestone_keys, result) +} + pub fn assert_last_event(generic_event: ::RuntimeEvent) { let events = frame_system::Pallet::::events(); let system_event: ::RuntimeEvent = generic_event.into(); diff --git a/pallets/proposals/src/tests/disputes.rs b/pallets/proposals/src/tests/disputes.rs new file mode 100644 index 00000000..3106528e --- /dev/null +++ b/pallets/proposals/src/tests/disputes.rs @@ -0,0 +1,521 @@ +use crate::{mock::*, *}; +use common_types::CurrencyId; +use frame_support::{assert_noop, assert_ok}; +use pallet_disputes::DisputeResult; +use test_utils::*; + +#[test] +fn raise_dispute_not_contributor() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + + assert_noop!( + Proposals::raise_dispute(RuntimeOrigin::signed(JOHN), project_key, milestone_keys), + Error::::OnlyContributorsCanRaiseDispute + ); + }) +} + +#[test] +fn raise_dispute_project_doesnt_exist() { + build_test_externality().execute_with(|| { + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(JOHN), + 0, + vec![0u32].try_into().unwrap() + ), + Error::::ProjectDoesNotExist + ); + }) +} + +#[test] +fn raise_dispute_milestone_already_in_dispute() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys + )); + for (i, _index) in milestones.iter().enumerate() { + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(CHARLIE), + project_key, + vec![i as u32].try_into().unwrap() + ), + Error::::MilestonesAlreadyInDispute + ); + } + }) +} + +#[test] +fn raise_dispute_invalid_milestone_key() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = + create_and_fund_project::(ALICE, contributions, milestones, CurrencyId::Native) + .unwrap(); + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + vec![11u32].try_into().unwrap() + ), + Error::::MilestoneDoesNotExist + ); + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + vec![12u32].try_into().unwrap() + ), + Error::::MilestoneDoesNotExist + ); + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + vec![1u32, 11u32].try_into().unwrap() + ), + Error::::MilestoneDoesNotExist + ); + }) +} + +#[test] +fn raise_dispute_cant_raise_on_approved_milestone() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = + create_and_fund_project::(ALICE, contributions, milestones, CurrencyId::Native) + .unwrap(); + let submitted_milestone_key = 0u32; + + assert_ok!(Proposals::submit_milestone( + RuntimeOrigin::signed(ALICE), + project_key, + submitted_milestone_key + )); + assert_ok!(Proposals::vote_on_milestone( + RuntimeOrigin::signed(BOB), + project_key, + submitted_milestone_key, + true + )); + assert_ok!(Proposals::vote_on_milestone( + RuntimeOrigin::signed(CHARLIE), + project_key, + submitted_milestone_key, + true + )); + // Milestone should be approved. + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + vec![submitted_milestone_key].try_into().unwrap() + ), + Error::::CannotRaiseDisputeOnApprovedMilestone + ); + assert_noop!( + Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + vec![submitted_milestone_key, 2u32].try_into().unwrap() + ), + Error::::CannotRaiseDisputeOnApprovedMilestone + ); + }) +} + +#[test] +fn on_dispute_complete_success_removes_dispute_status() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + assert!(!ProjectsInDispute::::contains_key(project_key)); + }) +} + +#[test] +fn on_dispute_complete_failure_removes_dispute_status() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Failure, + ); + assert!(!ProjectsInDispute::::contains_key(project_key)); + }) +} + +#[test] +fn dispute_success_does_not_cancel_project() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + + let project = Projects::::get(project_key).unwrap(); + assert!(!project.cancelled); + }) +} + +#[test] +fn dispute_success_approves_milestone_for_refund_but_only_ones_specified() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (1u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + }) +} + +#[test] +fn raise_dispute_allows_milestone_voting() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let submitted_milestone_key = 0; + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + + assert_ok!(Proposals::submit_milestone( + RuntimeOrigin::signed(ALICE), + project_key, + submitted_milestone_key + )); + assert_ok!(Proposals::vote_on_milestone( + RuntimeOrigin::signed(BOB), + project_key, + submitted_milestone_key, + true + )); + let dispute_milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + dispute_milestone_keys + )); + + assert_ok!(Proposals::vote_on_milestone( + RuntimeOrigin::signed(CHARLIE), + project_key, + submitted_milestone_key, + true + )); + }) +} + +#[test] +fn raise_dispute_allows_milestone_voting_on_non_disputed_milestones() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let submitted_milestone_keys = [0, 1]; + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + + assert_ok!(Proposals::submit_milestone( + RuntimeOrigin::signed(ALICE), + project_key, + submitted_milestone_keys[0] + )); + + let dispute_milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (2u32..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + dispute_milestone_keys + )); + + assert_ok!(Proposals::submit_milestone( + RuntimeOrigin::signed(ALICE), + project_key, + submitted_milestone_keys[1] + )); + + assert_ok!(Proposals::vote_on_milestone( + RuntimeOrigin::signed(CHARLIE), + project_key, + submitted_milestone_keys[0], + true + )); + assert_ok!(Proposals::vote_on_milestone( + RuntimeOrigin::signed(BOB), + project_key, + submitted_milestone_keys[1], + true + )); + }) +} + +#[test] +fn raise_dispute_allows_submission() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let milestone_key = 0; + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + + let dispute_milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + dispute_milestone_keys + )); + assert_ok!(Proposals::submit_milestone( + RuntimeOrigin::signed(ALICE), + project_key, + milestone_key + )); + }) +} + +#[test] +fn failed_dispute_tests() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let _milestone_key = 0; + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + + let dispute_milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + dispute_milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + dispute_milestone_keys.into_inner(), + DisputeResult::Failure, + ); + + // just gonna assert that the milestones arnt approved for refund. + let project_after_refund = Projects::::get(project_key).unwrap(); + for i in 0u32..10 { + let milestone = project_after_refund.milestones.get(&i).unwrap(); + assert!(!milestone.can_refund); + assert!(milestone.transfer_status.is_none()); + } + }) +} + +#[test] +fn assert_can_recall_dispute_after_success() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = + create_and_fund_project::(ALICE, contributions, milestones, CurrencyId::Native) + .unwrap(); + // Only call the dispute on part. + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..5_u32).collect::>().try_into().unwrap(); + + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (5u32..10_u32).collect::>().try_into().unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys + )); + }) +} + +#[test] +fn assert_can_recall_dispute_after_failure() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = + create_and_fund_project::(ALICE, contributions, milestones, CurrencyId::Native) + .unwrap(); + // Only call the dispute on part. + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..5_u32).collect::>().try_into().unwrap(); + + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Failure, + ); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (5u32..10_u32).collect::>().try_into().unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys + )); + }) +} diff --git a/pallets/proposals/src/tests/mod.rs b/pallets/proposals/src/tests/mod.rs index 0e42d240..0eeec162 100644 --- a/pallets/proposals/src/tests/mod.rs +++ b/pallets/proposals/src/tests/mod.rs @@ -1,2 +1,4 @@ +pub mod disputes; pub mod immutable_votes; pub mod pallet; +pub mod refunds; diff --git a/pallets/proposals/src/tests/pallet.rs b/pallets/proposals/src/tests/pallet.rs index 79eb19e1..ce730cec 100644 --- a/pallets/proposals/src/tests/pallet.rs +++ b/pallets/proposals/src/tests/pallet.rs @@ -6,21 +6,13 @@ use test_utils::*; pub fn run_to_block(n: BlockNumber) { while System::block_number() < n { - IdentityPallet::on_finalize(System::block_number()); - Proposals::on_finalize(System::block_number()); - TransactionPayment::on_finalize(System::block_number()); - Currencies::on_finalize(System::block_number()); Tokens::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); System::on_finalize(System::block_number()); + Proposals::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); Tokens::on_initialize(System::block_number()); - Currencies::on_initialize(System::block_number()); - TransactionPayment::on_initialize(System::block_number()); + System::on_initialize(System::block_number()); Proposals::on_initialize(System::block_number()); - IdentityPallet::on_initialize(System::block_number()); } } @@ -29,7 +21,9 @@ fn submit_milestone_milestone_doesnt_exist() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); assert_noop!( Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, 11), Error::::MilestoneDoesNotExist @@ -52,7 +46,9 @@ fn submit_milestone_not_initiator() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); assert_noop!( Proposals::submit_milestone(RuntimeOrigin::signed(BOB), project_key, 1), Error::::UserIsNotInitiator @@ -72,12 +68,13 @@ fn submit_milestones_too_many_this_block() { let prop_milestones = get_milestones(10); (0..=max).for_each(|i| { - let project_key = create_project::( + let project_key = create_and_fund_project::( ALICE, cont.clone(), prop_milestones.clone(), CurrencyId::Native, - ); + ) + .unwrap(); if i != max { assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -99,7 +96,9 @@ fn submit_milestone_creates_non_bias_vote() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), project_key, @@ -118,7 +117,9 @@ fn submit_milestone_can_resubmit_during_voting_round() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -162,7 +163,9 @@ fn submit_milestone_can_submit_again_after_failed_vote() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), project_key, @@ -184,7 +187,9 @@ fn submit_milestone_cannot_submit_again_after_success_vote() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -219,7 +224,9 @@ fn ensure_milestone_vote_data_is_cleaned_after_autofinalisation_for() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -284,7 +291,9 @@ fn ensure_milestone_vote_data_is_cleaned_after_autofinalisation_against() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -348,7 +357,9 @@ fn users_can_submit_multiple_milestones_and_vote_independantly() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key_0 = 0; let milestone_key_1 = 1; assert_ok!(Proposals::submit_milestone( @@ -379,14 +390,14 @@ fn users_can_submit_multiple_milestones_and_vote_independantly() { .get(&milestone_key_0) .expect("vote 0 should exist"); - assert!(vote_0.yay == 100_000u64); - assert!(vote_0.nay == 0u64); + assert!(vote_0.yay == 100_000u128); + assert!(vote_0.nay == 0u128); let vote_1 = total_votes .get(&milestone_key_1) .expect("vote 1 should exist"); - assert!(vote_1.yay == 100_000u64); - assert!(vote_1.nay == 0u64); + assert!(vote_1.yay == 100_000u128); + assert!(vote_1.nay == 0u128); }); } @@ -405,7 +416,9 @@ fn vote_on_milestone_before_round_starts_fails() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_noop!( Proposals::vote_on_milestone( @@ -424,7 +437,9 @@ fn vote_on_milestone_after_round_end_fails() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; let expiring_block = frame_system::Pallet::::block_number() + ::MilestoneVotingWindow::get(); @@ -451,7 +466,9 @@ fn vote_on_milestone_where_voting_round_is_active_but_not_the_correct_milestone( build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), project_key, @@ -469,7 +486,9 @@ fn if_double_submission_and_one_finalises_voting_on_the_second_can_vote() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let expiring_block = frame_system::Pallet::::block_number() + ::MilestoneVotingWindow::get(); assert_ok!(Proposals::submit_milestone( @@ -498,7 +517,9 @@ fn vote_on_milestone_not_contributor() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -522,7 +543,9 @@ fn vote_on_milestone_actually_adds_to_vote() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -537,8 +560,8 @@ fn vote_on_milestone_actually_adds_to_vote() { )); let total_votes = MilestoneVotes::::get(project_key); let vote = total_votes.get(&milestone_key).expect("vote should exist"); - assert!(vote.yay == 100_000u64); - assert!(vote.nay == 0u64); + assert!(vote.yay == 100_000u128); + assert!(vote.nay == 0u128); assert_ok!(Proposals::vote_on_milestone( RuntimeOrigin::signed(CHARLIE), project_key, @@ -548,8 +571,8 @@ fn vote_on_milestone_actually_adds_to_vote() { let total_votes = MilestoneVotes::::get(project_key); let vote = total_votes.get(&milestone_key).expect("vote should exist"); - assert!(vote.yay == 100_000u64); - assert!(vote.nay == 100_000u64); + assert!(vote.yay == 100_000u128); + assert!(vote.nay == 100_000u128); }); } @@ -558,7 +581,9 @@ fn vote_on_milestone_autofinalises_on_all_voted_and_fail() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE, DAVE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -595,7 +620,9 @@ fn withdraw_not_initiator() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB, CHARLIE], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_ok!(Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), @@ -632,7 +659,10 @@ fn withdraw_only_transfers_approved_milestones() { let per_contribution = 100_000; let cont = get_contributions::(vec![BOB, CHARLIE], per_contribution); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); + let project_account = crate::Pallet::::project_account_id(project_key); let milestone_key = 0; let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) @@ -651,7 +681,6 @@ fn withdraw_only_transfers_approved_milestones() { true, ) .unwrap(); - let alice_before = ::MultiCurrency::free_balance(CurrencyId::Native, &ALICE); assert_ok!(Proposals::withdraw( @@ -662,14 +691,11 @@ fn withdraw_only_transfers_approved_milestones() { let expected_fee = ::ImbueFee::get().mul_floor(per_contribution * 2 / 10); // total_contribution / number of milestones - fee let alice_expected_balance = - alice_before + ((per_contribution * 2 / 10) as u64) - expected_fee as u64; + alice_before + ((per_contribution * 2 / 10) as u128) - expected_fee as u128; assert_eq!( alice_after, alice_expected_balance, "Alice account is not the expected balance" ); - - let project_account = crate::Pallet::::project_account_id(project_key); - assert_eq!( ::MultiCurrency::free_balance(CurrencyId::Native, &project_account), 180_000, @@ -683,7 +709,9 @@ fn withdraw_removes_project_after_all_funds_taken() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(1); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) @@ -713,7 +741,9 @@ fn store_project_info_after_project_is_completed() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(1); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) @@ -746,12 +776,13 @@ fn store_too_many_projects_for_account() { let prop_milestones = get_milestones(1); let milestone_key = 0; (0..=max).for_each(|i| { - let project_key = create_project::( + let project_key = create_and_fund_project::( ALICE, cont.clone(), prop_milestones.clone(), CurrencyId::Native, - ); + ) + .unwrap(); let _ = Proposals::submit_milestone( RuntimeOrigin::signed(ALICE), project_key, @@ -786,9 +817,14 @@ fn withdraw_takes_imbue_fee() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; - let pallet_account = crate::Pallet::::account_id(); + let fee_account: AccountId = ::ImbueFeeAccount::get(); + let fee_account_balance_before = + ::MultiCurrency::free_balance(CurrencyId::Native, &fee_account); + let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) .unwrap(); @@ -804,8 +840,11 @@ fn withdraw_takes_imbue_fee() { project_key )); let expected_fee = ::ImbueFee::get().mul_floor(10_000); + let fee_account_balance_after = + ::MultiCurrency::free_balance(CurrencyId::Native, &fee_account); + assert_eq!( - ::MultiCurrency::free_balance(CurrencyId::Native, &pallet_account), + fee_account_balance_after - fee_account_balance_before, expected_fee, "fee hasnt been taken out of project as expected." ); @@ -817,7 +856,9 @@ fn withdraw_cannot_double_withdraw() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) @@ -845,7 +886,9 @@ fn withdraw_once_times_with_double_submissions() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, 0).unwrap(); let _ = Proposals::vote_on_milestone(RuntimeOrigin::signed(BOB), project_key, 0, true).unwrap(); @@ -875,7 +918,9 @@ fn withdraw_twice_with_intermitent_submission() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); // The first submission and withdraw let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, 0).unwrap(); @@ -927,7 +972,9 @@ fn withdraw_with_variable_percentage() { percentage_to_unlock: Percent::from_percent(30u8), }, ]; - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, 0).unwrap(); let _ = Proposals::vote_on_milestone(RuntimeOrigin::signed(BOB), project_key, 0, true).unwrap(); @@ -952,7 +999,9 @@ fn withdraw_fails_before_approval() { build_test_externality().execute_with(|| { let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) + .unwrap(); let milestone_key = 0; assert_noop!( Proposals::withdraw(RuntimeOrigin::signed(ALICE), project_key), @@ -969,254 +1018,24 @@ fn withdraw_fails_before_approval() { } #[test] -fn raise_no_confidence_round_already_started() { +fn withdraw_assert_milestone_state_change() { build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); + let cont = get_contributions::(vec![BOB], 100_000); let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - let milestone_key = 0; - - let _ = - Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) + let project_key = + create_and_fund_project::(ALICE, cont, prop_milestones, CurrencyId::Native) .unwrap(); - let _ = Proposals::vote_on_milestone( - RuntimeOrigin::signed(BOB), - project_key, - milestone_key, - true, - ) - .unwrap(); - assert_ok!(Proposals::raise_vote_of_no_confidence( - RuntimeOrigin::signed(BOB), - project_key - )); - assert_noop!( - Proposals::raise_vote_of_no_confidence(RuntimeOrigin::signed(BOB), project_key), - Error::::RoundStarted - ); - }); -} - -#[test] -fn raise_no_confidence_round_not_contributor() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - assert_noop!( - Proposals::raise_vote_of_no_confidence(RuntimeOrigin::signed(CHARLIE), project_key), - Error::::OnlyContributorsCanVote - ); - }); -} - -#[test] -fn raise_no_confidence_round_no_project() { - build_test_externality().execute_with(|| { + let milestone_key = 0; assert_noop!( - Proposals::raise_vote_of_no_confidence(RuntimeOrigin::signed(CHARLIE), 20), - Error::::ProjectDoesNotExist + Proposals::withdraw(RuntimeOrigin::signed(ALICE), project_key), + Error::::NoAvailableFundsToWithdraw ); - }); -} - -#[test] -fn raise_no_confidence_round_puts_initial_vote_is_isnay() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - let milestone_key = 0; - let _ = Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) .unwrap(); - let _ = Proposals::vote_on_milestone( - RuntimeOrigin::signed(BOB), - project_key, - milestone_key, - true, - ) - .unwrap(); - - assert_ok!(Proposals::raise_vote_of_no_confidence( - RuntimeOrigin::signed(BOB), - project_key - )); - - let vote = NoConfidenceVotes::::get(project_key).expect("vote should exist"); - assert_eq!( - vote.nay, 100_000, - "Bobs vote does not equal expected amount." - ); - - let has_voted = UserHasVoted::::get((project_key, RoundType::VoteOfNoConfidence, 0)); - assert!( - has_voted.values().len() == 1usize, - "The btree should only have a single value, the caller of the round." - ); - assert!( - has_voted.contains_key(&BOB), - "Bob called the round so should be recorded as voted." - ); - }); -} - -#[test] -fn vote_on_no_confidence_round_not_in_round() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - - assert_noop!( - Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(CHARLIE), - project_key, - true - ), - Error::::ProjectNotInRound - ); - }); -} - -#[test] -fn vote_on_no_confidence_round_not_contributor() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - - assert_ok!(Proposals::raise_vote_of_no_confidence( - RuntimeOrigin::signed(BOB), - project_key - )); assert_noop!( - Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(CHARLIE), - project_key, - true - ), - Error::::OnlyContributorsCanVote - ); - }); -} - -#[test] -fn vote_on_no_confidence_round_already_voted() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - - assert_ok!(Proposals::raise_vote_of_no_confidence( - RuntimeOrigin::signed(BOB), - project_key - )); - assert_ok!(Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(DAVE), - project_key, - true - )); - assert_noop!( - Proposals::vote_on_no_confidence_round(RuntimeOrigin::signed(DAVE), project_key, true), - Error::::VotesAreImmutable - ); - }); -} - -#[test] -fn vote_on_no_confidence_mutates_vote() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - - assert_ok!(Proposals::raise_vote_of_no_confidence( - RuntimeOrigin::signed(BOB), - project_key - )); - assert_ok!(Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(DAVE), - project_key, - true - )); - let vote = NoConfidenceVotes::::get(project_key).expect("vote should exist"); - assert_eq!( - vote.nay, 100_000, - "Total vote should equal half contributions here." - ); - assert_eq!( - vote.yay, 100_000, - "Total vote should equal half contributions here." - ); - - let has_voted = UserHasVoted::::get((project_key, RoundType::VoteOfNoConfidence, 0)); - assert!( - has_voted.values().len() == 2usize, - "The btree should only have a single value, the caller of the round." - ); - assert!( - has_voted.contains_key(&BOB) && has_voted.contains_key(&DAVE), - "Bob and charlie have voted." - ); - }); -} - -#[test] -fn auto_finalizing_vote_on_no_confidence_when_threshold_is_met() { - build_test_externality().execute_with(|| { - let cont = get_contributions::(vec![BOB, DAVE, CHARLIE, ALICE], 100_000); - let prop_milestones = get_milestones(10); - let project_key = create_project::(ALICE, cont, prop_milestones, CurrencyId::Native); - assert_ok!(Proposals::raise_vote_of_no_confidence( - RuntimeOrigin::signed(BOB), - project_key - )); - assert_ok!(Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(DAVE), - project_key, - true - )); - assert_ok!(Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(CHARLIE), - project_key, - false - )); - assert_ok!(Proposals::vote_on_no_confidence_round( - RuntimeOrigin::signed(ALICE), - project_key, - false - )); - let vote = NoConfidenceVotes::::get(project_key).expect("vote should exist"); - assert_eq!( - vote.nay, 300_000, - "Total vote should equal half contributions here." - ); - assert_eq!( - vote.yay, 100000, - "Total vote should equal half contributions here." - ); - - let has_voted = UserHasVoted::::get((project_key, RoundType::VoteOfNoConfidence, 0)); - assert!( - has_voted.values().len() == 4usize, - "Not all the votes has been recorded" - ); - assert!( - has_voted.contains_key(&BOB) - || has_voted.contains_key(&DAVE) - || has_voted.contains_key(&ALICE) - || has_voted.contains_key(&CHARLIE), - "Bob,Alice,Dave charlie have voted." - ); - assert_last_event::( - Event::::NoConfidenceRoundFinalised(ALICE, project_key).into(), - ); - assert_eq!(Projects::::get(project_key), None); - assert_eq!( - Rounds::::get((project_key, 0), RoundType::VoteOfNoConfidence), - None + Proposals::withdraw(RuntimeOrigin::signed(ALICE), project_key), + Error::::NoAvailableFundsToWithdraw ); }); } @@ -1255,7 +1074,3 @@ fn close_voting_round_works() { .is_empty()); }) } - -// todo: finalise voteof no confidence tests. -// ^^ is connected to making the pallet generic over funding type. -// Todo: assert the last event of each extrinsic/ diff --git a/pallets/proposals/src/tests/refunds.rs b/pallets/proposals/src/tests/refunds.rs new file mode 100644 index 00000000..3b739f25 --- /dev/null +++ b/pallets/proposals/src/tests/refunds.rs @@ -0,0 +1,389 @@ +use crate::{mock::*, *}; +use frame_support::{assert_noop, assert_ok}; +use pallet_disputes::DisputeResult; +use test_utils::*; + +#[test] +fn you_can_actually_refund_after_dispute_success() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + // All milestones should be good for refund + + assert_ok!(Proposals::refund(RuntimeOrigin::signed(BOB), project_key)); + }) +} + +#[test] +fn refund_assert_milestone_state_change() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = + create_and_fund_project::(ALICE, contributions, milestones, CurrencyId::Native) + .unwrap(); + // Only dispute some keys so that we can + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..5_u32).collect::>().try_into().unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + // All milestones should be good for refund + + assert_ok!(Proposals::refund(RuntimeOrigin::signed(BOB), project_key)); + let project_after_refund = Projects::::get(project_key).unwrap(); + assert_eq!(project_after_refund.refunded_funds, 500_000); + for i in 0u32..10 { + let milestone = project_after_refund.milestones.get(&i).unwrap(); + if i < 5 { + assert!(milestone.can_refund); + assert_eq!( + milestone.transfer_status, + Some(TransferStatus::Refunded { + on: frame_system::Pallet::::block_number() + }) + ); + } else { + assert!(!milestone.can_refund); + assert!(milestone.transfer_status.is_none()); + } + } + }) +} + +#[test] +fn refund_not_contributor() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + assert_noop!( + Proposals::refund(RuntimeOrigin::signed(DAVE), project_key), + Error::::OnlyContributorsCanInitiateRefund + ); + }) +} + +#[test] +fn refund_deletes_project_when_all_funds_are_refunded() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB, CHARLIE], 1_000_000u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + // All milestones should be good for refund + + Proposals::refund(RuntimeOrigin::signed(BOB), project_key).unwrap(); + assert!(!Projects::::contains_key(project_key)); + }) +} + +// The case where a project is in a dispute, and the dispute passes however, a milestone has also been approved and withdrawn +// before the refund has been called. +// Without the proper checks there will be a kind of double spend. +#[test] +fn withdraw_then_refund_no_double_spend() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB], 1_000_000u128); + let milestones = get_milestones(10); + let milestone_key = 0; + let alice_before_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &ALICE); + let bob_before_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &ALICE); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + let _ = Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone(), + ); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + let _ = + Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) + .unwrap(); + + let _ = Proposals::vote_on_milestone( + RuntimeOrigin::signed(BOB), + project_key, + milestone_key, + true, + ) + .unwrap(); + // Milestone is approved, withdraw. + assert_ok!(Proposals::withdraw( + RuntimeOrigin::signed(ALICE), + project_key + )); + let project_after_withdraw = Projects::::get(project_key).unwrap(); + let alice_after_withdraw = + ::MultiCurrency::free_balance(CurrencyId::Native, &ALICE); + // Assert that alice has recieved the withdraw. + assert!(alice_after_withdraw > alice_before_creation); + let refund_fee = ::ImbueFee::get().mul_floor( + project_after_withdraw.raised_funds - project_after_withdraw.withdrawn_funds, + ); + // Leaves us with 9 milestones left which we will refund. + assert_ok!(Proposals::refund(RuntimeOrigin::signed(BOB), project_key)); + let bob_after_refund = + ::MultiCurrency::free_balance(CurrencyId::Native, &BOB); + assert_eq!( + bob_after_refund, + (bob_before_creation - project_after_withdraw.withdrawn_funds - refund_fee), + "bobs shizzle aint what it should be." + ); + }) +} + +// The reverse case of withdraw_then_refund_no_double_spend +// essentially if a milestone is refunded one cannot withdraw an approved milestone as its already gone. +#[test] +fn refund_then_withdraw_no_double_spend() { + build_test_externality().execute_with(|| { + let contributions = get_contributions::(vec![BOB], 1_000_000u128); + let milestones = get_milestones(10); + let milestone_key = 0; + let _alice_before_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &ALICE); + let _bob_before_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &ALICE); + let project_key = + create_and_fund_project::(ALICE, contributions, milestones, CurrencyId::Native) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = + (0u32..5_u32).collect::>().try_into().unwrap(); + let _ = Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone(), + ); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + let _ = + Proposals::submit_milestone(RuntimeOrigin::signed(ALICE), project_key, milestone_key) + .unwrap(); + + let _ = Proposals::vote_on_milestone( + RuntimeOrigin::signed(BOB), + project_key, + milestone_key, + true, + ) + .unwrap(); + assert_ok!(Proposals::refund(RuntimeOrigin::signed(BOB), project_key)); + assert_noop!( + Proposals::withdraw(RuntimeOrigin::signed(ALICE), project_key), + Error::::NoAvailableFundsToWithdraw + ); + }) +} + +#[test] +fn refund_check_refund_amount() { + build_test_externality().execute_with(|| { + let bob_pre_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &BOB); + let charlie_pre_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &CHARLIE); + let per_contribution = 100000u128; + let contributions = get_contributions::(vec![BOB, CHARLIE], per_contribution as u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + // All milestones should be good for refund + + assert_ok!(Proposals::refund(RuntimeOrigin::signed(BOB), project_key)); + let bob_post_refund = + ::MultiCurrency::free_balance(CurrencyId::Native, &BOB); + let charlie_post_refund = + ::MultiCurrency::free_balance(CurrencyId::Native, &CHARLIE); + let per_fee = ::ImbueFee::get().mul_floor(per_contribution); + assert_eq!( + bob_pre_creation - per_fee, + bob_post_refund, + "bobo didnt get his money back!!" + ); + assert_eq!( + charlie_pre_creation - per_fee, + charlie_post_refund, + "charlie didnt get his money back!!" + ); + }) +} + +#[test] +fn refund_takes_imbue_fee() { + build_test_externality().execute_with(|| { + let bob_pre_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &BOB); + let charlie_pre_creation = + ::MultiCurrency::free_balance(CurrencyId::Native, &CHARLIE); + let fee_account_pre_creation = ::MultiCurrency::free_balance( + CurrencyId::Native, + &::ImbueFeeAccount::get(), + ); + let per_contribution = 500000u128; + + let contributions = get_contributions::(vec![BOB, CHARLIE], per_contribution as u128); + let milestones = get_milestones(10); + let project_key = create_and_fund_project::( + ALICE, + contributions, + milestones.clone(), + CurrencyId::Native, + ) + .unwrap(); + let milestone_keys: BoundedVec::MaxMilestonesPerProject> = (0u32 + ..milestones.len() as u32) + .collect::>() + .try_into() + .unwrap(); + assert_ok!(Proposals::raise_dispute( + RuntimeOrigin::signed(BOB), + project_key, + milestone_keys.clone() + )); + let _ = complete_dispute::( + project_key, + milestone_keys.into_inner(), + DisputeResult::Success, + ); + // All milestones should be good for refund + + assert_ok!(Proposals::refund(RuntimeOrigin::signed(BOB), project_key)); + + let bob_post_refund = + ::MultiCurrency::free_balance(CurrencyId::Native, &BOB); + let charlie_post_refund = + ::MultiCurrency::free_balance(CurrencyId::Native, &CHARLIE); + let fee_account_post_creation = ::MultiCurrency::free_balance( + CurrencyId::Native, + &::ImbueFeeAccount::get(), + ); + let per_fee = ::ImbueFee::get().mul_floor(per_contribution); + + // Assert that the fee has been taken from each and transferred to ImbueFeeAccount. + assert_eq!( + bob_pre_creation - bob_post_refund, + per_fee, + "bobs fee hasnt been taken out correctly." + ); + assert_eq!( + charlie_pre_creation - charlie_post_refund, + per_fee, + "charlies fee hasnt been taken out correctly." + ); + assert_eq!( + fee_account_post_creation - fee_account_pre_creation, + per_fee * 2, + "total fee hasnt added correctly." + ); + }) +} diff --git a/pallets/proposals/src/todo b/pallets/proposals/src/todo new file mode 100644 index 00000000..23cbd917 --- /dev/null +++ b/pallets/proposals/src/todo @@ -0,0 +1,29 @@ +raise-dispute extrinsic. - done +tests for above implementation. - done?? check code cov +benchmark for raise dispute extrinsic -done +ALLOW VOTING ON SUBMITTED MILESTONE - done +make sure when raising a dispute you cannot raise it on approved milestones. - done +OnDisputeCompleteHooks. +tests for above implementation. - done check code cov +update change withdraw to uses can_withdraw - done +benchmark for hooks to return weight. - done didnt need, we can do it directly in the method. +removal of old no confidence logic. - done + +fix all tests - done +get jury in each pallet (briefs, grants, crowdfunding.) +update the function call in each pallet (jury, TreasuryOrigin, ) +code cov check + + +initiate refund extrinsic - done +benchmark refund - done +tests! - done +add total_refunded to withdraw extrinsic - done +tests for these - done + +Migration for removing NoConfidenceLogic +Migration removing NoConfidenceRound, + + + + diff --git a/pallets/proposals/src/traits.rs b/pallets/proposals/src/traits.rs index 0a7fb7ea..1b9b2426 100644 --- a/pallets/proposals/src/traits.rs +++ b/pallets/proposals/src/traits.rs @@ -1,87 +1,76 @@ -use crate::{AccountIdOf, BalanceOf, BlockNumberFor, Contribution, ProposedMilestone}; -use common_types::{CurrencyId, FundingType, TreasuryOrigin, TreasuryOriginConverter}; -use frame_support::{pallet_prelude::DispatchError, transactional, PalletId}; +use crate::{AccountIdOf, BalanceOf, Contribution, FundingPath, Locality, ProposedMilestone}; +use common_types::CurrencyId; +use frame_support::{pallet_prelude::*, transactional, BoundedBTreeMap}; use orml_traits::XcmTransfer; -use orml_xtokens::Error; -use sp_std::vec::Vec; - +use sp_arithmetic::{traits::AtLeast32BitUnsigned, Percent}; use sp_core::H256; -use sp_runtime::traits::AccountIdConversion; -use sp_std::collections::btree_map::BTreeMap; + use xcm::latest::{MultiLocation, WeightLimit}; -pub trait IntoProposal { - /// Convert a set of milestones into a proposal, the bounty must be fully funded before calling this. - /// If an Ok is returned the brief pallet will delete the brief from storage as its been converted. - /// (if using crate) This function should bypass the usual checks when creating a proposal and - /// instantiate everything carefully. - // TODO: Generic over currencyId: https://github.com/ImbueNetwork/imbue/issues/135 +pub trait IntoProposal { + type MaximumContributorsPerProject: Get; + type MaxMilestonesPerProject: Get; + type MaxJuryMembers: Get; + + /// Convert the properties of a project into a project. + /// This is the main method when wanting to use pallet_proposals and is how one configures a project. fn convert_to_proposal( currency_id: CurrencyId, - current_contribution: BTreeMap>, + current_contribution: BoundedBTreeMap< + AccountId, + Contribution, + Self::MaximumContributorsPerProject, + >, brief_hash: H256, benificiary: AccountId, - milestones: Vec, - funding_type: FundingType, + milestones: BoundedVec, + refund_locations: BoundedVec< + (Locality, Percent), + Self::MaximumContributorsPerProject, + >, + jury: BoundedVec, + on_creation_funding: FundingPath, ) -> Result<(), DispatchError>; + + /// Use when the contributors are the refund locations. + fn convert_contributions_to_refund_locations( + contributions: &BoundedBTreeMap< + AccountId, + Contribution, + Self::MaximumContributorsPerProject, + >, + ) -> BoundedVec<(Locality, Percent), Self::MaximumContributorsPerProject>; } -pub trait RefundHandler { +pub trait ExternalRefundHandler { /// Send a message to some destination chain asking to do some reserve asset transfer. - /// The multilocation is defined by the FundingType. - /// see FundingType and TreasuryOrigin. fn send_refund_message_to_treasury( from: AccountId, amount: Balance, currency: CurrencyId, - funding_type: FundingType, + treasury_origin: MultiLocation, ) -> Result<(), DispatchError>; - fn get_treasury_account_id(treasury_origin: TreasuryOrigin) - -> Result; } -// Some implementations used in Imbue of the traits above. - -// For test purposes -impl IntoProposal, BalanceOf, BlockNumberFor> for T { - fn convert_to_proposal( - _currency_id: CurrencyId, - _contributions: BTreeMap, Contribution, BlockNumberFor>>, - _brief_hash: H256, - _benificiary: AccountIdOf, - _proposed_milestones: Vec, - _funding_type: FundingType, - ) -> Result<(), DispatchError> { - Ok(()) - } -} - -#[cfg(feature = "std")] pub struct MockRefundHandler(T); -#[cfg(feature = "std")] -impl RefundHandler, BalanceOf, CurrencyId> +impl ExternalRefundHandler, BalanceOf, CurrencyId> for MockRefundHandler { fn send_refund_message_to_treasury( _from: AccountIdOf, _amount: BalanceOf, _currency: CurrencyId, - _funding_type: FundingType, + _multilocation: MultiLocation, ) -> Result<(), DispatchError> { Ok(()) } - fn get_treasury_account_id( - _treasury_account: TreasuryOrigin, - ) -> Result, DispatchError> { - Ok(PalletId(*b"py/trsry").into_account_truncating()) - } } pub struct XcmRefundHandler(T, U); -impl RefundHandler, T::Balance, CurrencyId> for XcmRefundHandler +impl ExternalRefundHandler, T::Balance, CurrencyId> for XcmRefundHandler where - [u8; 32]: From<::AccountId>, + [u8; 32]: From>, T: orml_xtokens::Config, U: XcmTransfer, { @@ -91,34 +80,10 @@ where from: T::AccountId, amount: T::Balance, currency: CurrencyId, - funding_type: FundingType, + location: MultiLocation, ) -> Result<(), DispatchError> { - match funding_type { - FundingType::Grant(treasury_origin) => { - let beneficiary: AccountIdOf = Self::get_treasury_account_id(treasury_origin)?; - let location: MultiLocation = treasury_origin - .get_multi_location(beneficiary) - .map_err(|_| Error::::InvalidDest)?; - - // TODO: dest weight limit. or specify a fee. - let _ = U::transfer(from, currency, amount, location, WeightLimit::Unlimited)?; - Ok(()) - } - _ => Err(Error::::InvalidDest.into()), - } - } - fn get_treasury_account_id( - treasury_origin: TreasuryOrigin, - ) -> Result, DispatchError> { - match treasury_origin { - TreasuryOrigin::Kusama => { - // TODO: make this dynamic so its always correct. - Ok(PalletId(*b"py/trsry").into_account_truncating()) - } - _ => { - // At the moment just supporting kusama but allow this instead of a panic - Ok(PalletId(*b"py/trsry").into_account_truncating()) - } - } + // TODO: dest weight limit. or specify a fee. + let _ = U::transfer(from, currency, amount, location, WeightLimit::Unlimited)?; + Ok(()) } } diff --git a/pallets/proposals/src/weights.rs b/pallets/proposals/src/weights.rs index 91bfc50f..db9727fe 100644 --- a/pallets/proposals/src/weights.rs +++ b/pallets/proposals/src/weights.rs @@ -2,13 +2,13 @@ //! Autogenerated weights for `pallet_proposals` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `Justs-MacBook-Pro.local`, CPU: `` +//! HOSTNAME: `user`, CPU: `12th Gen Intel(R) Core(TM) i9-12900H` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("local")`, DB CACHE: 1024 // Executed Command: -// ./target/release/imbue +// ./target/debug/imbue // benchmark // pallet // --chain @@ -22,13 +22,11 @@ // --extrinsic // * // --output -// ./pallets/proposals/src/weights.rs +// weights.rs // --steps // 50 // --repeat // 20 -// --heap-pages -// 4096 #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -42,7 +40,7 @@ use core::marker::PhantomData; pub struct WeightInfo(PhantomData); impl crate::WeightInfoT for WeightInfo { /// Storage: `ImbueProposals::Projects` (r:1 w:0) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(2862), added: 5337, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::RoundsExpiring` (r:1 w:1) /// Proof: `ImbueProposals::RoundsExpiring` (`max_values`: None, `max_size`: Some(111), added: 2586, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::IndividualVoteStore` (r:1 w:1) @@ -53,16 +51,16 @@ impl crate::WeightInfoT for WeightInfo { /// Proof: `ImbueProposals::Rounds` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn submit_milestone() -> Weight { // Proof Size summary in bytes: - // Measured: `496` - // Estimated: `20036` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(31_000_000, 0) - .saturating_add(Weight::from_parts(0, 20036)) + // Measured: `568` + // Estimated: `39815` + // Minimum execution time: 345_914_000 picoseconds. + Weight::from_parts(354_103_000, 0) + .saturating_add(Weight::from_parts(0, 39815)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `ImbueProposals::Projects` (r:1 w:1) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(2862), added: 5337, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::Rounds` (r:1 w:1) /// Proof: `ImbueProposals::Rounds` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::IndividualVoteStore` (r:1 w:1) @@ -73,16 +71,16 @@ impl crate::WeightInfoT for WeightInfo { /// Proof: `ImbueProposals::RoundsExpiring` (`max_values`: None, `max_size`: Some(111), added: 2586, mode: `MaxEncodedLen`) fn vote_on_milestone() -> Weight { // Proof Size summary in bytes: - // Measured: `639` - // Estimated: `20036` - // Minimum execution time: 43_000_000 picoseconds. - Weight::from_parts(46_000_000, 0) - .saturating_add(Weight::from_parts(0, 20036)) + // Measured: `711` + // Estimated: `39815` + // Minimum execution time: 460_847_000 picoseconds. + Weight::from_parts(472_559_000, 0) + .saturating_add(Weight::from_parts(0, 39815)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `ImbueProposals::Projects` (r:1 w:1) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(2862), added: 5337, mode: `MaxEncodedLen`) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:3 w:3) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Deposits::CurrentDeposits` (r:1 w:1) @@ -91,52 +89,14 @@ impl crate::WeightInfoT for WeightInfo { /// Proof: `ImbueProposals::CompletedProjects` (`max_values`: None, `max_size`: Some(262184), added: 264659, mode: `MaxEncodedLen`) fn withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `997` + // Measured: `1120` // Estimated: `265649` - // Minimum execution time: 130_000_000 picoseconds. - Weight::from_parts(144_000_000, 0) + // Minimum execution time: 1_638_213_000 picoseconds. + Weight::from_parts(1_677_055_000, 0) .saturating_add(Weight::from_parts(0, 265649)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(6)) } - /// Storage: `ImbueProposals::Projects` (r:1 w:0) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(2862), added: 5337, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::NoConfidenceVotes` (r:1 w:1) - /// Proof: `ImbueProposals::NoConfidenceVotes` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::RoundsExpiring` (r:1 w:1) - /// Proof: `ImbueProposals::RoundsExpiring` (`max_values`: None, `max_size`: Some(111), added: 2586, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::UserHasVoted` (r:1 w:1) - /// Proof: `ImbueProposals::UserHasVoted` (`max_values`: None, `max_size`: Some(1667), added: 4142, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::Rounds` (r:0 w:1) - /// Proof: `ImbueProposals::Rounds` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - fn raise_vote_of_no_confidence() -> Weight { - // Proof Size summary in bytes: - // Measured: `475` - // Estimated: `6327` - // Minimum execution time: 24_000_000 picoseconds. - Weight::from_parts(26_000_000, 0) - .saturating_add(Weight::from_parts(0, 6327)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// Storage: `ImbueProposals::Rounds` (r:1 w:0) - /// Proof: `ImbueProposals::Rounds` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::Projects` (r:1 w:0) - /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(2862), added: 5337, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::NoConfidenceVotes` (r:1 w:1) - /// Proof: `ImbueProposals::NoConfidenceVotes` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`) - /// Storage: `ImbueProposals::UserHasVoted` (r:1 w:1) - /// Proof: `ImbueProposals::UserHasVoted` (`max_values`: None, `max_size`: Some(1667), added: 4142, mode: `MaxEncodedLen`) - fn vote_on_no_confidence_round() -> Weight { - // Proof Size summary in bytes: - // Measured: `700` - // Estimated: `6327` - // Minimum execution time: 24_000_000 picoseconds. - Weight::from_parts(25_000_000, 0) - .saturating_add(Weight::from_parts(0, 6327)) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } /// Storage: `ImbueProposals::RoundsExpiring` (r:1 w:1) /// Proof: `ImbueProposals::RoundsExpiring` (`max_values`: None, `max_size`: Some(111), added: 2586, mode: `MaxEncodedLen`) /// Storage: `ImbueProposals::MilestoneVotes` (r:1 w:1) @@ -149,10 +109,42 @@ impl crate::WeightInfoT for WeightInfo { // Proof Size summary in bytes: // Measured: `97` // Estimated: `20036` - // Minimum execution time: 10_000_000 picoseconds. - Weight::from_parts(11_000_000, 0) + // Minimum execution time: 147_964_000 picoseconds. + Weight::from_parts(151_147_000, 0) .saturating_add(Weight::from_parts(0, 20036)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `ImbueProposals::Projects` (r:1 w:0) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) + /// Storage: `ImbueProposals::ProjectsInDispute` (r:1 w:1) + /// Proof: `ImbueProposals::ProjectsInDispute` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::Disputes` (r:1 w:1) + /// Proof: `ImbueDisputes::Disputes` (`max_values`: None, `max_size`: Some(6602), added: 9077, mode: `MaxEncodedLen`) + /// Storage: `ImbueDisputes::DisputesFinaliseOn` (r:1 w:1) + /// Proof: `ImbueDisputes::DisputesFinaliseOn` (`max_values`: None, `max_size`: Some(221), added: 2696, mode: `MaxEncodedLen`) + fn raise_dispute() -> Weight { + // Proof Size summary in bytes: + // Measured: `4797` + // Estimated: `39815` + // Minimum execution time: 346_461_000 picoseconds. + Weight::from_parts(356_015_000, 0) + .saturating_add(Weight::from_parts(0, 39815)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `ImbueProposals::Projects` (r:1 w:1) + /// Proof: `ImbueProposals::Projects` (`max_values`: None, `max_size`: Some(36350), added: 38825, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:52 w:52) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `11381` + // Estimated: `136346` + // Minimum execution time: 23_947_016_000 picoseconds. + Weight::from_parts(24_080_686_000, 0) + .saturating_add(Weight::from_parts(0, 136346)) + .saturating_add(T::DbWeight::get().reads(53)) + .saturating_add(T::DbWeight::get().writes(53)) + } } diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index 3e6baf27..9aee4526 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -26,8 +26,6 @@ sp-std = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release ## Polkadot SDK FRAME Dependencies frame-support = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } frame-system = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } -frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false, optional = true } -frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false, optional = true } pallet-authorship = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } pallet-xcm = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false } @@ -46,6 +44,7 @@ common-types = { path = "../../libs/common-types", default-features = false } [dev-dependencies] hex-literal = "0.3.1" hex = "0.4.3" +xcm = { package = "staging-xcm", git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0"} [build-dependencies] substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0" } @@ -57,9 +56,7 @@ std = [ "common-traits/std", "common-types/std", "cumulus-primitives-core/std", - "frame-benchmarking?/std", "frame-support/std", - "frame-system-benchmarking?/std", "frame-system/std", "orml-traits/std", "pallet-authorship/std", @@ -85,7 +82,6 @@ try-runtime = [ ] runtime-benchmarks = [ 'frame-support/runtime-benchmarks', - "frame-system/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "pallet-balances/runtime-benchmarks", ] diff --git a/runtime/imbue-kusama/Cargo.toml b/runtime/imbue-kusama/Cargo.toml index 0da7e545..7af2a092 100644 --- a/runtime/imbue-kusama/Cargo.toml +++ b/runtime/imbue-kusama/Cargo.toml @@ -102,6 +102,7 @@ pallet-deposits = {path = '../../pallets/deposits', default-features = false } pallet-fellowship = {path = '../../pallets/fellowship', default-features = false } pallet-grants = {path = '../../pallets/grants', default-features = false } pallet-proposals = { path = '../../pallets/proposals', default-features = false } +pallet-disputes = {path = '../../pallets/disputes', default-features = false } pallet-proposals-rpc-runtime-api = { path = "../../pallets/proposals/rpc/runtime-api", default-features = false } [dev-dependencies] @@ -151,6 +152,7 @@ std = [ "pallet-collective/std", "pallet-democracy/std", "pallet-deposits/std", + "pallet-disputes/std", "pallet-fellowship/std", "pallet-grants/std", "pallet-identity/std", @@ -234,6 +236,7 @@ try-runtime = [ 'pallet-deposits/try-runtime', 'pallet-fellowship/try-runtime', 'pallet-grants/try-runtime', + 'pallet-disputes/try-runtime', 'pallet-identity/try-runtime', 'pallet-membership/try-runtime', 'pallet-multisig/try-runtime', @@ -285,6 +288,7 @@ runtime-benchmarks = [ 'xcm-builder/runtime-benchmarks', "pallet-deposits/runtime-benchmarks", "pallet-fellowship/runtime-benchmarks", + 'pallet-disputes/runtime-benchmarks', ] # A feature that should be enabled when the runtime should be build for on-chain diff --git a/runtime/imbue-kusama/src/lib.rs b/runtime/imbue-kusama/src/lib.rs index 863eabfa..8e364359 100644 --- a/runtime/imbue-kusama/src/lib.rs +++ b/runtime/imbue-kusama/src/lib.rs @@ -181,6 +181,7 @@ pub mod migrations { use super::*; /// Unreleased migrations. Add new ones here: pub type Unreleased = ( + pallet_proposals::migration::v7::MigrateToV7, pallet_balances::migration::MigrateToTrackInactive, pallet_collator_selection::migration::v1::MigrateToV1, pallet_xcm::migration::v1::VersionUncheckedMigrateToV1, @@ -802,8 +803,6 @@ impl pallet_treasury::Config for Runtime { parameter_types! { pub const ProposalsPalletId: PalletId = PalletId(*b"imbgrant"); pub const MaxProjectsPerRound: u32 = 256; - pub const MaxWithdrawalExpiration: BlockNumber = 180 * DAYS; - pub const NoConfidenceTimeLimit: BlockNumber = 14 * DAYS; pub const PercentRequiredForVoteToPass: Percent = Percent::from_percent(75u8); pub const MaximumContributorsPerProject: u32 = 50; pub const IsIdentityRequired: bool = false; @@ -813,28 +812,27 @@ parameter_types! { pub const ProjectStorageItem: StorageDepositItems = StorageDepositItems::Project; pub const MaxMilestonesPerProject: u32 = 10; pub const MaxProjectsPerAccount: u16 = u16::MAX; - pub PercentRequiredForVoteNoConfidenceToPass: Percent = Percent::from_percent(75u8); } impl pallet_proposals::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PalletId = ProposalsPalletId; type MultiCurrency = Currencies; - type AuthorityOrigin = AdminOrigin; - type MaxWithdrawalExpiration = MaxWithdrawalExpiration; - type NoConfidenceTimeLimit = NoConfidenceTimeLimit; type PercentRequiredForVoteToPass = PercentRequiredForVoteToPass; type MaximumContributorsPerProject = MaximumContributorsPerProject; type WeightInfo = pallet_proposals::weights::WeightInfo; type MilestoneVotingWindow = MilestoneVotingWindow; - type RefundHandler = pallet_proposals::traits::XcmRefundHandler; + type ExternalRefundHandler = pallet_proposals::traits::XcmRefundHandler; type MaxMilestonesPerProject = MaxMilestonesPerProject; type ImbueFee = ImbueFee; type ExpiringProjectRoundsPerBlock = ExpiringProjectRoundsPerBlock; type ProjectStorageItem = ProjectStorageItem; type DepositHandler = Deposits; type MaxProjectsPerAccount = MaxProjectsPerAccount; - type PercentRequiredForVoteNoConfidenceToPass = PercentRequiredForVoteNoConfidenceToPass; + type JurySelector = PointerBasedJurySelector; + type ImbueFeeAccount = TreasuryAccount; + type DisputeRaiser = pallet_disputes::Pallet; + type AssetSignerOrigin = EnsureRoot; } parameter_types! { @@ -862,13 +860,13 @@ parameter_types! { impl pallet_briefs::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RMultiCurrency = Currencies; - type AuthorityOrigin = EnsureRoot; type IntoProposal = pallet_proposals::Pallet; type MaxBriefOwners = MaxBriefOwners; type MaxMilestonesPerBrief = MaxMilestonesPerProject; type WeightInfo = pallet_briefs::weights::WeightInfo; type BriefStorageItem = BriefStorageItem; type DepositHandler = Deposits; + type JurySelector = PointerBasedJurySelector; } parameter_types! { @@ -920,6 +918,27 @@ impl pallet_deposits::Config for Runtime { type DepositSlashAccount = TreasuryAccount; } +parameter_types! { + pub MaxJurySize: u32 = 100; + pub MaxDisputesPerBlock: u32 = 50; + pub VotingTimeLimit: BlockNumber = DAYS * 14; +} + +impl pallet_disputes::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type DisputeKey = pallet_proposals::ProjectKey; + type SpecificId = pallet_proposals::MilestoneKey; + type MaxJurySize = MaxJurySize; + // TODO: this syntax for each max milestones per brief grant and projet. + // it binds automatically. + type MaxSpecifics = ::MaxMilestonesPerProject; + type MaxDisputesPerBlock = MaxDisputesPerBlock; + type VotingTimeLimit = VotingTimeLimit; + type ForceOrigin = EnsureRootOr; + type DisputeHooks = pallet_proposals::Pallet; + type WeightInfo = pallet_disputes::weights::WeightInfo; +} + construct_runtime! { pub enum Runtime { @@ -974,6 +993,7 @@ construct_runtime! { ImbueGrants: pallet_grants::{Pallet, Call, Storage, Event} = 102, Deposits: pallet_deposits::{Pallet, Storage, Event} = 103, ImbueFellowship: pallet_fellowship::{Pallet, Call, Storage, Event} = 104, + ImbueDisputes: pallet_disputes::{Pallet, Call, Storage, Event} = 105, } } @@ -1012,6 +1032,7 @@ mod benches { [pallet_briefs, ImbueBriefs] [pallet_grants, ImbueGrants] [pallet_fellowship, ImbueFellowship] + [pallet_disputes, ImbueDisputes] ); } @@ -1074,6 +1095,7 @@ impl_runtime_apis! { fn initialize_block(header: &::Header) { Executive::initialize_block(header) } + } impl sp_api::Metadata for Runtime { @@ -1281,3 +1303,38 @@ cumulus_pallet_parachain_system::register_validate_block! { BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, CheckInherents = CheckInherents, } + +type AccountIdOf = ::AccountId; +pub struct PointerBasedJurySelector(T); +impl pallet_fellowship::traits::SelectJury> + for PointerBasedJurySelector +{ + type JurySize = MaxJurySize; + fn select_jury() -> frame_support::BoundedVec, Self::JurySize> { + let mut out: frame_support::BoundedVec, Self::JurySize> = + frame_support::BoundedVec::new(); + let amount = Self::JurySize::get(); + let keys = pallet_fellowship::Roles::::iter_keys().collect::>>(); + let keys_len = keys.len(); + let pointer = pallet_fellowship::JuryPointer::::get(); + let pointer_with_bound = pointer.saturating_add(amount.into()); + for i in pointer..pointer_with_bound { + if keys_len > 0 { + let index = i as usize % keys_len; + // shouldnt be an issue due to mod. + if index < keys.len() { + let key = &keys[index]; + // Here weve gone in a circle! break. + if out.contains(&key) { + break; + } + if let Err(_) = out.try_push(key.clone()) { + break; + } + } + } + } + pallet_fellowship::JuryPointer::::put(pointer_with_bound); + out + } +} diff --git a/runtime/integration-tests/Cargo.toml b/runtime/integration-tests/Cargo.toml index 5c45fb10..a02aaf77 100644 --- a/runtime/integration-tests/Cargo.toml +++ b/runtime/integration-tests/Cargo.toml @@ -88,7 +88,6 @@ std = [ "sp-io/std", ] runtime-benchmarks = [ - "default", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", @@ -103,6 +102,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "kusama-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", "common-runtime/runtime-benchmarks" ] try-runtime = [ diff --git a/runtime/integration-tests/src/xcm_transfers.rs b/runtime/integration-tests/src/xcm_transfers.rs index c6f14e7b..b6151e8a 100644 --- a/runtime/integration-tests/src/xcm_transfers.rs +++ b/runtime/integration-tests/src/xcm_transfers.rs @@ -10,9 +10,10 @@ // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -use frame_support::assert_ok; use frame_support::dispatch::RawOrigin; +use frame_support::{assert_ok, PalletId}; +use sp_runtime::traits::AccountIdConversion; use xcm_emulator::{bx, Chain, TestExt}; use xcm::latest::{Junction, Junction::*, Junctions::*, MultiLocation, NetworkId, WeightLimit}; @@ -24,20 +25,21 @@ use crate::kusama_test_net::{ Development, DevelopmentReceiver, DevelopmentSender, Kusama, KusamaReceiver, KusamaSender, Sibling, SiblingReceiver, }; -use crate::setup::{ksm_amount, mgx_amount, native_amount, PARA_ID_DEVELOPMENT, PARA_ID_SIBLING}; +use crate::setup::{ + ksm_amount, mgx_amount, native_amount, AccountId, PARA_ID_DEVELOPMENT, PARA_ID_SIBLING, +}; use common_runtime::Balance; -use common_types::{CurrencyId, FundingType, TreasuryOrigin}; +use common_types::{CurrencyId, TreasuryOrigin, TreasuryOriginConverter}; use imbue_kusama_runtime::{OrmlTokens, Runtime as R, RuntimeOrigin, XTokens}; use orml_traits::MultiCurrency; -use pallet_proposals::traits::RefundHandler; +use pallet_proposals::traits::ExternalRefundHandler; #[test] fn transfer_treasury_to_parachain_grant_escrow_address() { let transfer_amount: Balance = ksm_amount(1); let treasury_origin = TreasuryOrigin::Kusama; - let kusama_treasury_address = - ::RefundHandler::get_treasury_account_id(treasury_origin) - .unwrap(); + let kusama_treasury_address: AccountId = PalletId(*b"py/trsry").into_account_truncating(); + Development::execute_with(|| { assert_eq!( OrmlTokens::free_balance(CurrencyId::KSM, &DevelopmentReceiver::get()), @@ -132,9 +134,7 @@ fn transfer_ksm_to_relay_chain() { #[test] fn test_xcm_refund_handler_to_kusama() { let treasury_origin = TreasuryOrigin::Kusama; - let kusama_treasury_address = - ::RefundHandler::get_treasury_account_id(treasury_origin) - .unwrap(); + let kusama_treasury_address: AccountId = PalletId(*b"py/trsry").into_account_truncating(); let _kusama_treasury_balance_before = Kusama::account_data_of(kusama_treasury_address.clone()).free; let transfer_amount: Balance = ksm_amount(10); @@ -163,11 +163,14 @@ fn test_xcm_refund_handler_to_kusama() { let ksm_balance = OrmlTokens::free_balance(CurrencyId::KSM, &DevelopmentReceiver::get()); assert!(ksm_balance > 0); assert_ok!( - ::RefundHandler::send_refund_message_to_treasury( + ::ExternalRefundHandler::send_refund_message_to_treasury( DevelopmentReceiver::get(), ksm_balance, CurrencyId::KSM, - FundingType::Grant(TreasuryOrigin::Kusama) + ::get_multi_location( + &TreasuryOrigin::Kusama + ) + .unwrap() ) ); });