From 3cd49b3e6453f5c9510da412158f8c5537b15cda Mon Sep 17 00:00:00 2001 From: ndkazu Date: Tue, 24 Dec 2024 18:21:17 +0900 Subject: [PATCH 01/22] new start --- Cargo.toml | 2 + substrate/bin/node/runtime/src/lib.rs | 49 +++++++ substrate/frame/opf/Cargo.toml | 90 ++++++++++++ substrate/frame/opf/src/lib.rs | 146 +++++++++++++++++++ substrate/frame/opf/src/types.rs | 201 ++++++++++++++++++++++++++ umbrella/Cargo.toml | 11 +- 6 files changed, 498 insertions(+), 1 deletion(-) create mode 100644 substrate/frame/opf/Cargo.toml create mode 100644 substrate/frame/opf/src/lib.rs create mode 100644 substrate/frame/opf/src/types.rs diff --git a/Cargo.toml b/Cargo.toml index 64a11a340d10..de3e59b5d8f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -390,6 +390,7 @@ members = [ "substrate/frame/nomination-pools/test-transfer-stake", "substrate/frame/offences", "substrate/frame/offences/benchmarking", + "substrate/frame/opf", "substrate/frame/paged-list", "substrate/frame/paged-list/fuzzer", "substrate/frame/parameters", @@ -963,6 +964,7 @@ pallet-nomination-pools-benchmarking = { path = "substrate/frame/nomination-pool pallet-nomination-pools-runtime-api = { path = "substrate/frame/nomination-pools/runtime-api", default-features = false } pallet-offences = { path = "substrate/frame/offences", default-features = false } pallet-offences-benchmarking = { path = "substrate/frame/offences/benchmarking", default-features = false } +pallet-opf = { path = "substrate/frame/opf", default-features = false } pallet-paged-list = { path = "substrate/frame/paged-list", default-features = false } pallet-parachain-template = { path = "templates/parachain/pallets/template", default-features = false } pallet-parameters = { path = "substrate/frame/parameters", default-features = false } diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf..0eaa636f8efd 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1588,6 +1588,51 @@ impl pallet_offences::Config for Runtime { type OnOffenceHandler = Staking; } + +parameter_types! { + // Id of the treasury + pub const PotId: PalletId = PalletId(*b"py/potid"); + pub const VotingPeriod:BlockNumber = 30 * DAYS; + pub const ClaimingPeriod: BlockNumber = 7 * DAYS; + pub const VoteValidityPeriod: BlockNumber = 7 * DAYS; + pub const MaxProjects:u32 = 50; + + +} +impl pallet_opf::Config for Runtime { + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + + type NativeBalance = Balances; + + /// Pot PalletId + type PotId = PotId; + + /// Maximum number of whitelisted projects + type MaxProjects = MaxProjects; + + /// Time period in which people can vote. + /// After the period has ended, the votes are counted (STOP THE COUNT) + /// and then the funds are distributed into Spends. + type VotingPeriod = VotingPeriod; + + /// Time for claiming a Spend. + /// After the period has passed, a spend is thrown away + /// and the funds are available again for distribution in the pot. + type ClaimingPeriod = ClaimingPeriod; + + /// Period after which all the votes are resetted. + type VoteValidityPeriod = VoteValidityPeriod; + + type BlockNumberProvider = System; + + type Preimages = Preimage; + + type Scheduler = Scheduler; + + type WeightInfo = (); //pallet_opf::weights::SubstrateWeight; +} + impl pallet_authority_discovery::Config for Runtime { type MaxAuthorities = MaxAuthorities; } @@ -2633,6 +2678,9 @@ mod runtime { #[runtime::pallet_index(81)] pub type VerifySignature = pallet_verify_signature::Pallet; + + #[runtime::pallet_index(82)] + pub type Opf = pallet_opf::Pallet; } impl TryFrom for pallet_revive::Call { @@ -2893,6 +2941,7 @@ mod benches { [pallet_example_mbm, PalletExampleMbms] [pallet_asset_conversion_ops, AssetConversionMigration] [pallet_verify_signature, VerifySignature] + [pallet_opf, Opf] ); } diff --git a/substrate/frame/opf/Cargo.toml b/substrate/frame/opf/Cargo.toml new file mode 100644 index 000000000000..08bfa9c4543c --- /dev/null +++ b/substrate/frame/opf/Cargo.toml @@ -0,0 +1,90 @@ +[package] +authors.workspace = true +description = "Optimist Project Funding - pallet allowing users to nominate projects to be funded, by locking their DOTS." +edition.workspace = true +homepage = "https://substrate.io" +license = "Apache-2.0" +name = "pallet-opf" +readme = "README.md" +repository.workspace = true +version = "0.1.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = false } +frame-benchmarking = { optional = true, workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +log = { workspace = true } +pallet-conviction-voting = { workspace = true, default-features = false } +scale-info = { features = [ + "derive", +], workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } + +[dev-dependencies] +pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-preimage = { workspace = true, default-features = true } +pallet-scheduler = { workspace = true, default-features = true } +pallet-sudo = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-transaction-payment/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-assets/std", + "pallet-balances/std", + "pallet-conviction-voting/std", + "pallet-preimage/std", + "pallet-scheduler/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "pallet-conviction-voting/try-runtime", + "pallet-preimage/try-runtime", + "pallet-scheduler/try-runtime", + "pallet-sudo/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs new file mode 100644 index 000000000000..86ad8ab08d6e --- /dev/null +++ b/substrate/frame/opf/src/lib.rs @@ -0,0 +1,146 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; +mod functions; +mod types; +pub use pallet_scheduler as Schedule; +pub use types::*; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; +pub mod weights; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeCall: Parameter + + Dispatchable + + From> + + IsType<::RuntimeCall> + + From>; + + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Type to access the Balances Pallet. + type NativeBalance: fungible::Inspect + + fungible::Mutate + + fungible::hold::Inspect + + fungible::hold::Mutate + + fungible::freeze::Inspect + + fungible::freeze::Mutate; + + /// Provider for the block number. + type BlockNumberProvider: BlockNumberProvider>; + + /// Treasury account Id + #[pallet::constant] + type PotId: Get; + + /// The preimage provider. + type Preimages: QueryPreimage + StorePreimage; + + /// The Scheduler. + type Scheduler: ScheduleAnon< + BlockNumberFor, + CallOf, + PalletsOriginOf, + Hasher = Self::Hashing, + > + ScheduleNamed< + BlockNumberFor, + CallOf, + PalletsOriginOf, + Hasher = Self::Hashing, + >; + + /// Time period in which people can vote. + /// After the period has ended, the votes are counted (STOP THE COUNT) + /// and then the funds are distributed into Spends. + #[pallet::constant] + type VotingPeriod: Get>; + + /// Maximum number projects that can be accepted by this pallet + #[pallet::constant] + type MaxProjects: Get; + + /// Time for claiming a Spend. + /// After the period has passed, a spend is thrown away + /// and the funds are available again for distribution in the pot. + #[pallet::constant] + type ClaimingPeriod: Get>; + + /// Period after which all the votes are resetted. + #[pallet::constant] + type VoteValidityPeriod: Get>; + + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// Spends that still have to be claimed. + #[pallet::storage] + pub(super) type Spends = + CountedStorageMap<_, Twox64Concat, ProjectId, SpendInfo, OptionQuery>; + + /// List of whitelisted projects to be rewarded + #[pallet::storage] + pub type Projects = + StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Reward successfully claimed + RewardClaimed { when: BlockNumberFor, amount: BalanceOf, project_id: ProjectId }, + + /// A Spend was created + SpendCreated { when: BlockNumberFor, amount: BalanceOf, project_id: ProjectId }, + + /// Not yet in the claiming period + NotClaimingPeriod { project_id: ProjectId, claiming_period: BlockNumberFor }, + + /// Payment will be enacted for corresponding project + WillBeEnacted { project_id: ProjectId }, + } + + #[pallet::error] + pub enum Error { + /// Not enough Funds in the Pot + InsufficientPotReserves, + /// The funds transfer operation failed + TransferFailed, + /// Spend or Spend index does not exists + InexistentSpend, + /// No valid Account_id found + NoValidAccount, + /// No project available for funding + NoProjectAvailable, + /// The Funds transfer failed + FailedSpendOperation, + /// Still not in claiming period + NotClaimingPeriod, + /// Funds locking failed + FundsReserveFailed, + /// An invalid result was returned + InvalidResult, + } + + /*#[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + Self::begin_block(n) + } + }*/ +} \ No newline at end of file diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs new file mode 100644 index 000000000000..912440417c7a --- /dev/null +++ b/substrate/frame/opf/src/types.rs @@ -0,0 +1,201 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types & Imports for Distribution pallet. + +pub use super::*; + +pub use frame_support::{ + pallet_prelude::*, + traits::{ + fungible, + fungible::{Inspect, Mutate, MutateHold}, + fungibles, + schedule::{ + v3::{Anon as ScheduleAnon, Named as ScheduleNamed}, + DispatchTime, MaybeHashed, + }, + tokens::{Precision, Preservation}, + Bounded, DefensiveOption, EnsureOrigin, LockIdentifier, OriginTrait, QueryPreimage, + StorePreimage, + }, + transactional, PalletId, Serialize, +}; +pub use frame_system::{pallet_prelude::*, RawOrigin}; +pub use scale_info::prelude::vec::Vec; +pub use sp_runtime::traits::{ + AccountIdConversion, BlockNumberProvider, Convert, Dispatchable, Saturating, StaticLookup, Zero, +}; +pub use sp_std::boxed::Box; +pub use weights::WeightInfo; + +pub type BalanceOf = <::NativeBalance as fungible::Inspect< + ::AccountId, +>>::Balance; +pub type AccountIdOf = ::AccountId; +/// A reward index. +pub type SpendIndex = u32; +pub type CallOf = ::RuntimeCall; +pub type BoundedCallOf = Bounded, ::Hashing>; +pub type ProjectId = AccountIdOf; +pub type PalletsOriginOf = + <::RuntimeOrigin as OriginTrait>::PalletsOrigin; +pub const DISTRIBUTION_ID: LockIdentifier = *b"distribu"; +ub type RoundIndex = u32; +pub type VoterId = AccountIdOf; + +/// The state of the payment claim. +#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo, Default)] +pub enum SpendState { + /// Unclaimed + #[default] + Unclaimed, + /// Claimed & Paid. + Completed, + /// Claimed but Failed. + Failed, +} + +//Processed Reward status +#[derive(Encode, Decode, Clone, PartialEq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct SpendInfo { + /// The asset amount of the spend. + pub amount: BalanceOf, + /// The block number from which the spend can be claimed(24h after SpendStatus Creation). + pub valid_from: BlockNumberFor, + /// Corresponding project id + pub whitelisted_project: AccountIdOf, + /// Has it been claimed? + pub claimed: bool, + /// Claim Expiration block + pub expire: BlockNumberFor, +} + +impl SpendInfo { + pub fn new(whitelisted: &ProjectInfo) -> Self { + let amount = whitelisted.amount; + let whitelisted_project = whitelisted.project_id.clone(); + let claimed = false; + let valid_from = + >::block_number(); + let expire = valid_from.clone().saturating_add(T::ClaimingPeriod::get()) + + let spend = SpendInfo { amount, valid_from, whitelisted_project, claimed, expire }; + + //Add it to the Spends storage + Spends::::insert(whitelisted.project_id.clone(), spend.clone()); + + spend + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct ProjectInfo { + /// AcountId that will receive the payment. + pub project_id: ProjectId, + + /// Block at which the project was submitted for reward distribution + pub submission_block: BlockNumberFor, + + /// Amount to be lock & pay for this project + pub amount: BalanceOf, +} + +#[derive(Encode, Decode, Clone, PartialEq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct VoteInfo { + /// The amount of stake/slash placed on this vote. + pub amount: BalanceOf, + + /// Round at which the vote was casted + pub round: VotingRoundInfo, + + /// Whether the vote is "fund" / "not fund" + pub is_fund: bool, + + pub conviction: Conviction, + + pub funds_unlock_block: BlockNumberFor, +} + +// If no conviction, user's funds are released at the end of the voting round +impl VoteInfo { + pub fn funds_unlock(&mut self) { + let conviction_coeff = >::from(self.conviction); + let funds_unlock_block = self + .round + .round_ending_block + .saturating_add(T::VoteLockingPeriod::get().saturating_mul(conviction_coeff.into())); + self.funds_unlock_block = funds_unlock_block; + } +} + +impl Default for VoteInfo { + // Dummy vote infos used to handle errors + fn default() -> Self { + // get round number + let round = VotingRounds::::get(0).expect("Round 0 exists"); + let amount = Zero::zero(); + let is_fund = false; + let conviction = Conviction::None; + let funds_unlock_block = round.round_ending_block; + VoteInfo { amount, round, is_fund, conviction, funds_unlock_block } + } +} + +/// Voting rounds are periodically created inside a hook on_initialize (use poll in the future) +#[derive(Encode, Decode, Clone, PartialEq, MaxEncodedLen, RuntimeDebug, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct VotingRoundInfo { + pub round_number: u32, + pub round_starting_block: BlockNumberFor, + pub round_ending_block: BlockNumberFor, + pub total_positive_votes_amount: BalanceOf, + pub total_negative_votes_amount: BalanceOf, +} + +impl VotingRoundInfo { + pub fn new() -> Self { + let round_starting_block = T::BlockNumberProvider::current_block_number(); + let round_ending_block = round_starting_block + .clone() + .checked_add(&T::VotingPeriod::get()) + .expect("Invalid Result"); + let round_number = VotingRoundNumber::::get(); + let new_number = round_number.checked_add(1).expect("Invalid Result"); + VotingRoundNumber::::put(new_number); + let total_positive_votes_amount = BalanceOf::::zero(); + let total_negative_votes_amount = BalanceOf::::zero(); + + Pallet::::deposit_event(Event::::VotingRoundStarted { + when: round_starting_block, + round_number, + }); + + let round_infos = VotingRoundInfo { + round_number, + round_starting_block, + round_ending_block, + total_positive_votes_amount, + total_negative_votes_amount, + }; + VotingRounds::::insert(round_number, round_infos.clone()); + round_infos + } +} \ No newline at end of file diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index f36d39d63f6a..221bd2bd00e2 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -112,6 +112,7 @@ std = [ "pallet-nomination-pools?/std", "pallet-offences-benchmarking?/std", "pallet-offences?/std", + "pallet-opf?/std", "pallet-paged-list?/std", "pallet-parameters?/std", "pallet-preimage?/std", @@ -300,6 +301,7 @@ runtime-benchmarks = [ "pallet-nomination-pools?/runtime-benchmarks", "pallet-offences-benchmarking?/runtime-benchmarks", "pallet-offences?/runtime-benchmarks", + "pallet-opf?/runtime-benchmarks", "pallet-paged-list?/runtime-benchmarks", "pallet-parameters?/runtime-benchmarks", "pallet-preimage?/runtime-benchmarks", @@ -434,6 +436,7 @@ try-runtime = [ "pallet-node-authorization?/try-runtime", "pallet-nomination-pools?/try-runtime", "pallet-offences?/try-runtime", + "pallet-opf?/try-runtime", "pallet-paged-list?/try-runtime", "pallet-parameters?/try-runtime", "pallet-preimage?/try-runtime", @@ -495,6 +498,7 @@ serde = [ "pallet-democracy?/serde", "pallet-message-queue?/serde", "pallet-offences?/serde", + "pallet-opf?/serde", "pallet-parameters?/serde", "pallet-referenda?/serde", "pallet-remark?/serde", @@ -541,7 +545,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-opf", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -1148,6 +1152,11 @@ default-features = false optional = true path = "../substrate/frame/offences/benchmarking" +[dependencies.pallet-opf] +default-features = false +optional = true +path = "../substrate/frame/opf" + [dependencies.pallet-paged-list] default-features = false optional = true From 859e6b3ebfd4b54959fcd2185336c157252c07f5 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Tue, 24 Dec 2024 21:22:28 +0900 Subject: [PATCH 02/22] New OPF pallet in work... --- Cargo.lock | 27 +++++++++++++++ substrate/frame/opf/Cargo.toml | 5 +++ substrate/frame/opf/src/lib.rs | 57 ++++++++++++++++++++++++-------- substrate/frame/opf/src/types.rs | 9 +++-- umbrella/src/lib.rs | 5 +++ 5 files changed, 84 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6151ed33c5b6..126e18d6a742 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14530,6 +14530,32 @@ dependencies = [ "sp-staking 36.0.0", ] +[[package]] +name = "pallet-opf" +version = "0.1.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "pallet-assets 29.1.0", + "pallet-balances 28.0.0", + "pallet-conviction-voting 28.0.0", + "pallet-preimage 28.0.0", + "pallet-scheduler 29.0.0", + "pallet-sudo 28.0.0", + "pallet-timestamp 27.0.0", + "pallet-transaction-payment 28.0.0", + "pallet-transaction-payment-rpc-runtime-api 28.0.0", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + [[package]] name = "pallet-paged-list" version = "0.6.0" @@ -18749,6 +18775,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api 23.0.0", "pallet-offences 27.0.0", "pallet-offences-benchmarking 28.0.0", + "pallet-opf", "pallet-paged-list 0.6.0", "pallet-parameters 0.1.0", "pallet-preimage 28.0.0", diff --git a/substrate/frame/opf/Cargo.toml b/substrate/frame/opf/Cargo.toml index 08bfa9c4543c..db8d32613361 100644 --- a/substrate/frame/opf/Cargo.toml +++ b/substrate/frame/opf/Cargo.toml @@ -22,12 +22,15 @@ frame-support = { workspace = true, default-features = false } frame-system = { workspace = true, default-features = false } log = { workspace = true } pallet-conviction-voting = { workspace = true, default-features = false } +pallet-scheduler = { workspace = true, default-features = false } scale-info = { features = [ "derive", ], workspace = true, default-features = false } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true, default-features = false } sp-io = { workspace = true, default-features = false } sp-runtime = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = true } [dev-dependencies] pallet-assets = { workspace = true, default-features = true } @@ -53,6 +56,8 @@ runtime-benchmarks = [ "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", + "scale-info/std", + "serde", "sp-runtime/runtime-benchmarks", ] std = [ diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 86ad8ab08d6e..e81ee5514feb 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -1,24 +1,16 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; -mod functions; mod types; pub use pallet_scheduler as Schedule; pub use types::*; -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod tests; -pub mod weights; #[frame_support::pallet] pub mod pallet { use super::*; + use frame_system::WeightInfo; #[pallet::pallet] pub struct Pallet(_); @@ -35,11 +27,7 @@ pub mod pallet { /// Type to access the Balances Pallet. type NativeBalance: fungible::Inspect - + fungible::Mutate - + fungible::hold::Inspect - + fungible::hold::Mutate - + fungible::freeze::Inspect - + fungible::freeze::Mutate; + + fungible::Mutate; /// Provider for the block number. type BlockNumberProvider: BlockNumberProvider>; @@ -89,6 +77,15 @@ pub mod pallet { type WeightInfo: WeightInfo; } + /// Number of Voting Rounds executed so far + #[pallet::storage] + pub type VotingRoundNumber = StorageValue<_, u32, ValueQuery>; + + /// Returns Infos about a Voting Round agains the Voting Round index + #[pallet::storage] + pub type VotingRounds = + StorageMap<_, Twox64Concat, RoundIndex, VotingRoundInfo, OptionQuery>; + /// Spends that still have to be claimed. #[pallet::storage] pub(super) type Spends = @@ -113,6 +110,38 @@ pub mod pallet { /// Payment will be enacted for corresponding project WillBeEnacted { project_id: ProjectId }, + + /// Reward successfully assigned + RewardsAssigned { when: BlockNumberFor }, + + /// User's vote successfully submitted + VoteCasted { who: VoterId, when: BlockNumberFor, project_id: ProjectId }, + + /// User's vote successfully removed + VoteRemoved { who: VoterId, when: BlockNumberFor, project_id: ProjectId }, + + /// Project removed from whitelisted projects list + ProjectUnlisted { when: BlockNumberFor, project_id: ProjectId }, + + /// Project Funding Accepted by voters + ProjectFundingAccepted { + project_id: ProjectId, + when: BlockNumberFor, + round_number: u32, + amount: BalanceOf, + }, + + /// Project Funding rejected by voters + ProjectFundingRejected { when: BlockNumberFor, project_id: ProjectId }, + + /// A new voting round started + VotingRoundStarted { when: BlockNumberFor, round_number: u32 }, + + /// The users voting period ended. Reward calculation will start. + VoteActionLocked { when: BlockNumberFor, round_number: u32 }, + + /// The voting round ended + VotingRoundEnded { when: BlockNumberFor, round_number: u32 }, } #[pallet::error] diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 912440417c7a..2594d902861d 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -35,13 +35,13 @@ pub use frame_support::{ }, transactional, PalletId, Serialize, }; +pub use pallet_conviction_voting::Conviction; pub use frame_system::{pallet_prelude::*, RawOrigin}; pub use scale_info::prelude::vec::Vec; pub use sp_runtime::traits::{ AccountIdConversion, BlockNumberProvider, Convert, Dispatchable, Saturating, StaticLookup, Zero, }; pub use sp_std::boxed::Box; -pub use weights::WeightInfo; pub type BalanceOf = <::NativeBalance as fungible::Inspect< ::AccountId, @@ -55,7 +55,7 @@ pub type ProjectId = AccountIdOf; pub type PalletsOriginOf = <::RuntimeOrigin as OriginTrait>::PalletsOrigin; pub const DISTRIBUTION_ID: LockIdentifier = *b"distribu"; -ub type RoundIndex = u32; +pub type RoundIndex = u32; pub type VoterId = AccountIdOf; /// The state of the payment claim. @@ -93,7 +93,7 @@ impl SpendInfo { let claimed = false; let valid_from = >::block_number(); - let expire = valid_from.clone().saturating_add(T::ClaimingPeriod::get()) + let expire = valid_from.clone().saturating_add(T::ClaimingPeriod::get()); let spend = SpendInfo { amount, valid_from, whitelisted_project, claimed, expire }; @@ -140,8 +140,7 @@ impl VoteInfo { let conviction_coeff = >::from(self.conviction); let funds_unlock_block = self .round - .round_ending_block - .saturating_add(T::VoteLockingPeriod::get().saturating_mul(conviction_coeff.into())); + .round_ending_block; self.funds_unlock_block = funds_unlock_block; } } diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 7b3c869588f0..7bb7cf3211c3 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -543,6 +543,11 @@ pub use pallet_offences; #[cfg(feature = "pallet-offences-benchmarking")] pub use pallet_offences_benchmarking; +/// Optimist Project Funding - pallet allowing users to nominate projects to be funded, by +/// locking their DOTS. +#[cfg(feature = "pallet-opf")] +pub use pallet_opf; + /// FRAME pallet that provides a paged list data structure. #[cfg(feature = "pallet-paged-list")] pub use pallet_paged_list; From 788b33f7ecffde9ff7e9f159553ca977fa80781d Mon Sep 17 00:00:00 2001 From: ndkazu Date: Thu, 26 Dec 2024 11:49:19 +0900 Subject: [PATCH 03/22] Added main extrinsics --- substrate/frame/opf/src/function.rs | 0 substrate/frame/opf/src/lib.rs | 45 +++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 substrate/frame/opf/src/function.rs diff --git a/substrate/frame/opf/src/function.rs b/substrate/frame/opf/src/function.rs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index e81ee5514feb..bd0ff45832f6 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -7,7 +7,7 @@ pub use types::*; -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; use frame_system::WeightInfo; @@ -68,7 +68,7 @@ pub mod pallet { #[pallet::constant] type ClaimingPeriod: Get>; - /// Period after which all the votes are resetted. + /// Period after which all the votes are reset. #[pallet::constant] type VoteValidityPeriod: Get>; @@ -172,4 +172,45 @@ pub mod pallet { Self::begin_block(n) } }*/ + + #[pallet::call] + impl Pallet { + + #[pallet::call_index(0)] + #[transactional] + pub fn register_project(origin: OriginFor) -> DispatchResult{ + Ok(()) + } + + #[pallet::call_index(1)] + #[transactional] + pub fn unregister_project(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(2)] + #[transactional] + pub fn vote(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(3)] + #[transactional] + pub fn remove_vote(origin: OriginFor) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(4)] + #[transactional] + pub fn claim_reward_for(origin: OriginFor, project_id: ProjectId) -> DispatchResult { + Ok(()) + } + + #[pallet::call_index(5)] + #[transactional] + pub fn execute_claim(origin: OriginFor, project_id: ProjectId) -> DispatchResult { + Ok(()) + } + + } } \ No newline at end of file From 0f6bd3d0a91d4ce2b34a8c51633d95174a814ff9 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 11:20:45 +0900 Subject: [PATCH 04/22] extrinsic #0 added --- substrate/frame/opf/src/function.rs | 0 substrate/frame/opf/src/functions.rs | 32 +++++++++++++ substrate/frame/opf/src/lib.rs | 70 +++++++++++++++++++--------- substrate/frame/opf/src/types.rs | 15 +++--- 4 files changed, 88 insertions(+), 29 deletions(-) delete mode 100644 substrate/frame/opf/src/function.rs create mode 100644 substrate/frame/opf/src/functions.rs diff --git a/substrate/frame/opf/src/function.rs b/substrate/frame/opf/src/function.rs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs new file mode 100644 index 000000000000..542d73242bd1 --- /dev/null +++ b/substrate/frame/opf/src/functions.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! Helper functions for OPF pallet. + + +pub use super::*; +impl Pallet { + + // Helper function for project registration + pub fn register_new(project_id: ProjectId, amount: BalanceOf) -> DispatchResult{ + let submission_block = T::BlockNumberProvider::current_block_number(); + let project_infos: ProjectInfo = ProjectInfo { project_id, submission_block, amount}; + let mut bounded = Projects::get(); + let _ = bounded.try_push(project_infos); + Projects::put(bounded); + Ok(()) + } +} \ No newline at end of file diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index bd0ff45832f6..96e3e4014402 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -1,12 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; mod types; +mod functions; pub use pallet_scheduler as Schedule; pub use types::*; - - #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; @@ -30,7 +46,7 @@ pub mod pallet { + fungible::Mutate; /// Provider for the block number. - type BlockNumberProvider: BlockNumberProvider>; + type BlockNumberProvider: BlockNumberProvider; /// Treasury account Id #[pallet::constant] @@ -41,12 +57,12 @@ pub mod pallet { /// The Scheduler. type Scheduler: ScheduleAnon< - BlockNumberFor, + ProvidedBlockNumberFor, CallOf, PalletsOriginOf, Hasher = Self::Hashing, > + ScheduleNamed< - BlockNumberFor, + ProvidedBlockNumberFor, CallOf, PalletsOriginOf, Hasher = Self::Hashing, @@ -56,7 +72,7 @@ pub mod pallet { /// After the period has ended, the votes are counted (STOP THE COUNT) /// and then the funds are distributed into Spends. #[pallet::constant] - type VotingPeriod: Get>; + type VotingPeriod: Get>; /// Maximum number projects that can be accepted by this pallet #[pallet::constant] @@ -66,11 +82,11 @@ pub mod pallet { /// After the period has passed, a spend is thrown away /// and the funds are available again for distribution in the pot. #[pallet::constant] - type ClaimingPeriod: Get>; + type ClaimingPeriod: Get>; /// Period after which all the votes are reset. #[pallet::constant] - type VoteValidityPeriod: Get>; + type VoteValidityPeriod: Get>; /// Weight information for extrinsics in this pallet. @@ -100,48 +116,51 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Reward successfully claimed - RewardClaimed { when: BlockNumberFor, amount: BalanceOf, project_id: ProjectId }, + RewardClaimed { when: ProvidedBlockNumberFor, amount: BalanceOf, project_id: ProjectId }, /// A Spend was created - SpendCreated { when: BlockNumberFor, amount: BalanceOf, project_id: ProjectId }, + SpendCreated { when: ProvidedBlockNumberFor, amount: BalanceOf, project_id: ProjectId }, /// Not yet in the claiming period - NotClaimingPeriod { project_id: ProjectId, claiming_period: BlockNumberFor }, + NotClaimingPeriod { project_id: ProjectId, claiming_period: ProvidedBlockNumberFor }, /// Payment will be enacted for corresponding project WillBeEnacted { project_id: ProjectId }, /// Reward successfully assigned - RewardsAssigned { when: BlockNumberFor }, + RewardsAssigned { when: ProvidedBlockNumberFor }, /// User's vote successfully submitted - VoteCasted { who: VoterId, when: BlockNumberFor, project_id: ProjectId }, + VoteCasted { who: VoterId, when: ProvidedBlockNumberFor, project_id: ProjectId }, /// User's vote successfully removed - VoteRemoved { who: VoterId, when: BlockNumberFor, project_id: ProjectId }, + VoteRemoved { who: VoterId, when: ProvidedBlockNumberFor, project_id: ProjectId }, + + /// Project added to whitelisted projects list + Projectlisted { when:ProvidedBlockNumberFor, project_id: ProjectId }, /// Project removed from whitelisted projects list - ProjectUnlisted { when: BlockNumberFor, project_id: ProjectId }, + ProjectUnlisted { when:ProvidedBlockNumberFor, project_id: ProjectId }, /// Project Funding Accepted by voters ProjectFundingAccepted { project_id: ProjectId, - when: BlockNumberFor, + when:ProvidedBlockNumberFor, round_number: u32, amount: BalanceOf, }, /// Project Funding rejected by voters - ProjectFundingRejected { when: BlockNumberFor, project_id: ProjectId }, + ProjectFundingRejected { when:ProvidedBlockNumberFor, project_id: ProjectId }, /// A new voting round started - VotingRoundStarted { when: BlockNumberFor, round_number: u32 }, + VotingRoundStarted { when:ProvidedBlockNumberFor, round_number: u32 }, /// The users voting period ended. Reward calculation will start. - VoteActionLocked { when: BlockNumberFor, round_number: u32 }, + VoteActionLocked { when:ProvidedBlockNumberFor, round_number: u32 }, /// The voting round ended - VotingRoundEnded { when: BlockNumberFor, round_number: u32 }, + VotingRoundEnded { when:ProvidedBlockNumberFor, round_number: u32 }, } #[pallet::error] @@ -168,7 +187,7 @@ pub mod pallet { /*#[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(n: BlockNumberFor) -> Weight { + fn on_initialize(n:ProvidedBlockNumberFor) -> Weight { Self::begin_block(n) } }*/ @@ -178,7 +197,14 @@ pub mod pallet { #[pallet::call_index(0)] #[transactional] - pub fn register_project(origin: OriginFor) -> DispatchResult{ + pub fn register_project(origin: OriginFor, project_id: ProjectId, amount: BalanceOf) -> DispatchResult{ + let _caller = ensure_signed(origin)?; + let when = T::BlockNumberProvider::current_block_number(); + Self::register_new(project_id.clone(), amount)?; + Self::deposit_event(Event::Projectlisted { + when, + project_id, + }); Ok(()) } diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 2594d902861d..dc3b929d87a6 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -57,6 +57,7 @@ pub type PalletsOriginOf = pub const DISTRIBUTION_ID: LockIdentifier = *b"distribu"; pub type RoundIndex = u32; pub type VoterId = AccountIdOf; +pub type ProvidedBlockNumberFor = <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; /// The state of the payment claim. #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo, Default)] @@ -77,13 +78,13 @@ pub struct SpendInfo { /// The asset amount of the spend. pub amount: BalanceOf, /// The block number from which the spend can be claimed(24h after SpendStatus Creation). - pub valid_from: BlockNumberFor, + pub valid_from:ProvidedBlockNumberFor, /// Corresponding project id pub whitelisted_project: AccountIdOf, /// Has it been claimed? pub claimed: bool, /// Claim Expiration block - pub expire: BlockNumberFor, + pub expire:ProvidedBlockNumberFor, } impl SpendInfo { @@ -92,7 +93,7 @@ impl SpendInfo { let whitelisted_project = whitelisted.project_id.clone(); let claimed = false; let valid_from = - >::block_number(); + T::BlockNumberProvider::current_block_number(); let expire = valid_from.clone().saturating_add(T::ClaimingPeriod::get()); let spend = SpendInfo { amount, valid_from, whitelisted_project, claimed, expire }; @@ -111,7 +112,7 @@ pub struct ProjectInfo { pub project_id: ProjectId, /// Block at which the project was submitted for reward distribution - pub submission_block: BlockNumberFor, + pub submission_block:ProvidedBlockNumberFor, /// Amount to be lock & pay for this project pub amount: BalanceOf, @@ -131,7 +132,7 @@ pub struct VoteInfo { pub conviction: Conviction, - pub funds_unlock_block: BlockNumberFor, + pub funds_unlock_block:ProvidedBlockNumberFor, } // If no conviction, user's funds are released at the end of the voting round @@ -163,8 +164,8 @@ impl Default for VoteInfo { #[scale_info(skip_type_params(T))] pub struct VotingRoundInfo { pub round_number: u32, - pub round_starting_block: BlockNumberFor, - pub round_ending_block: BlockNumberFor, + pub round_starting_block:ProvidedBlockNumberFor, + pub round_ending_block:ProvidedBlockNumberFor, pub total_positive_votes_amount: BalanceOf, pub total_negative_votes_amount: BalanceOf, } From 54701b7da09e71d6e97165d035f2003d19b66e22 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 12:43:03 +0900 Subject: [PATCH 05/22] Added some helpers and extrinsic_#1 --- substrate/frame/opf/src/functions.rs | 122 +++++++++++++++++++++++++-- substrate/frame/opf/src/lib.rs | 34 +++++++- substrate/frame/opf/src/types.rs | 4 +- 3 files changed, 150 insertions(+), 10 deletions(-) diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 542d73242bd1..1c409e224daf 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -20,13 +20,125 @@ pub use super::*; impl Pallet { + pub fn pot_account() -> AccountIdOf { + // Get Pot account + T::PotId::get().into_account_truncating() + } + + /// Funds transfer from the Pot to a project account + pub fn spend(amount: BalanceOf, beneficiary: AccountIdOf) -> DispatchResult { + // Get Pot account + let pot_account: AccountIdOf = Self::pot_account(); + + //Operate the transfer + T::NativeBalance::transfer(&pot_account, &beneficiary, amount, Preservation::Preserve)?; + + Ok(()) + } + + /// Series of checks on the Pot, to ensure that we have enough funds + /// before executing a Spend --> used in tests. + pub fn pot_check(spend: BalanceOf) -> DispatchResult { + // Get Pot account + let pot_account = Self::pot_account(); + + // Check that the Pot as enough funds for the transfer + let balance = T::NativeBalance::balance(&pot_account); + let minimum_balance = T::NativeBalance::minimum_balance(); + let remaining_balance = balance.saturating_sub(spend); + + ensure!(remaining_balance > minimum_balance, Error::::InsufficientPotReserves); + ensure!(balance > spend, Error::::InsufficientPotReserves); + Ok(()) + } + // Helper function for project registration - pub fn register_new(project_id: ProjectId, amount: BalanceOf) -> DispatchResult{ + pub fn register_new(project_id: ProjectId) -> DispatchResult{ let submission_block = T::BlockNumberProvider::current_block_number(); - let project_infos: ProjectInfo = ProjectInfo { project_id, submission_block, amount}; - let mut bounded = Projects::get(); - let _ = bounded.try_push(project_infos); - Projects::put(bounded); + let mut bounded: BoundedVec, T::MaxProjects> = WhiteListedProjectAccounts::::get(); + let _ = bounded.try_push(project_id); + WhiteListedProjectAccounts::::put(bounded); Ok(()) } + + // Voting Period checks + pub fn period_check() -> DispatchResult { + // Get current voting round & check if we are in voting period or not + let current_round_index = VotingRoundNumber::::get().saturating_sub(1); + let round = VotingRounds::::get(current_round_index).ok_or(Error::::NoRoundFound)?; + let now = T::BlockNumberProvider::current_block_number(); + ensure!(now < round.round_ending_block, Error::::VotingRoundOver); + Ok(()) + } + + pub fn unlist_project(project_id: ProjectId) -> DispatchResult { + WhiteListedProjectAccounts::::mutate(|value| { + let mut val = value.clone(); + val.retain(|x| *x != project_id); + *value = val; + }); + + Ok(()) + } + + // The total reward to be distributed is a portion or inflation, determined in another pallet + // Reward calculation is executed within the Voting period + pub fn calculate_rewards(total_reward: BalanceOf) -> DispatchResult { + let projects = WhiteListedProjectAccounts::::get(); + let round_number = VotingRoundNumber::::get().saturating_sub(1); + let round = VotingRounds::::get(round_number).ok_or(Error::::NoRoundFound)?; + if projects.clone().len() > 0 as usize { + let total_positive_votes_amount = round.total_positive_votes_amount; + let total_negative_votes_amount = round.total_negative_votes_amount; + + let total_votes_amount = + total_positive_votes_amount.saturating_sub(total_negative_votes_amount); + + // for each project, calculate the percentage of votes, the amount to be distributed, + // and then populate the storage Projects + for project in projects { + if ProjectFunds::::contains_key(&project) { + let funds = ProjectFunds::::get(&project); + let project_positive_reward = funds[0]; + let project_negative_reward = funds[1]; + + let project_reward = + project_positive_reward.saturating_sub(project_negative_reward); + + let project_percentage = + Percent::from_rational(project_reward, total_votes_amount); + let final_amount = project_percentage * total_reward; + + // Send calculated reward for reward distribution + let now = T::BlockNumberProvider::current_block_number(); + let project_info = ProjectInfo { + project_id: project.clone(), + submission_block: now, + amount: final_amount, + }; + + let mut rewarded = Projects::::get(); + rewarded + .try_push(project_info.clone()) + .map_err(|_| Error::::MaximumProjectsNumber)?; + + Projects::::mutate(|value| { + *value = rewarded; + }); + + let when = T::BlockNumberProvider::current_block_number(); + Self::deposit_event(Event::::ProjectFundingAccepted { + project_id: project, + when, + round_number, + amount: project_info.amount, + }) + + } + } + } + + Ok(()) + } + } \ No newline at end of file diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 96e3e4014402..80259c92923a 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -111,6 +111,21 @@ pub mod pallet { #[pallet::storage] pub type Projects = StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; + + /// List of Whitelisted Project registered + #[pallet::storage] + pub type WhiteListedProjectAccounts = + StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; + + /// Returns (positive_funds,negative_funds) of Whitelisted Project accounts + #[pallet::storage] + pub type ProjectFunds = StorageMap< + _, + Twox64Concat, + ProjectId, + BoundedVec, ConstU32<2>>, + ValueQuery, + >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -183,6 +198,14 @@ pub mod pallet { FundsReserveFailed, /// An invalid result was returned InvalidResult, + /// The reward calculation failed due to an internal error + FailedRewardCalculation, + /// Voting round is over + VotingRoundOver, + /// This voting round does not exists + NoRoundFound, + /// Maximum number of projects submission for reward distribution as been reached + MaximumProjectsNumber, } /*#[pallet::hooks] @@ -197,10 +220,10 @@ pub mod pallet { #[pallet::call_index(0)] #[transactional] - pub fn register_project(origin: OriginFor, project_id: ProjectId, amount: BalanceOf) -> DispatchResult{ + pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult{ let _caller = ensure_signed(origin)?; let when = T::BlockNumberProvider::current_block_number(); - Self::register_new(project_id.clone(), amount)?; + Self::register_new(project_id.clone())?; Self::deposit_event(Event::Projectlisted { when, project_id, @@ -210,7 +233,12 @@ pub mod pallet { #[pallet::call_index(1)] #[transactional] - pub fn unregister_project(origin: OriginFor) -> DispatchResult { + pub fn unregister_project(origin: OriginFor, project_id: ProjectId ) -> DispatchResult { + ensure_root(origin)?; + let when = T::BlockNumberProvider::current_block_number(); + Self::unlist_project(project_id.clone())?; + Self::deposit_event(Event::::ProjectUnlisted { when, project_id }); + Ok(()) } diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index dc3b929d87a6..0a93d3594ed5 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -38,9 +38,9 @@ pub use frame_support::{ pub use pallet_conviction_voting::Conviction; pub use frame_system::{pallet_prelude::*, RawOrigin}; pub use scale_info::prelude::vec::Vec; -pub use sp_runtime::traits::{ +pub use sp_runtime::{traits::{ AccountIdConversion, BlockNumberProvider, Convert, Dispatchable, Saturating, StaticLookup, Zero, -}; +}, Percent}; pub use sp_std::boxed::Box; pub type BalanceOf = <::NativeBalance as fungible::Inspect< From e09ff1178afb4347f658d240b0061d5c72204063 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 17:57:06 +0900 Subject: [PATCH 06/22] Remove unneedded storage --- substrate/bin/node/runtime/src/lib.rs | 7 ++- substrate/frame/opf/src/functions.rs | 75 ++++++++++++++++++++++----- substrate/frame/opf/src/lib.rs | 8 ++- substrate/frame/opf/src/types.rs | 6 +-- 4 files changed, 74 insertions(+), 22 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index b6d61636b4ef..f05048fa9a32 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1596,13 +1596,15 @@ parameter_types! { pub const ClaimingPeriod: BlockNumber = 7 * DAYS; pub const VoteValidityPeriod: BlockNumber = 7 * DAYS; pub const MaxProjects:u32 = 50; + + /// This should be calculated as a percentage of inflation. + pub const TemporaryRewards: Balance = 100000 * DOLLARS; } impl pallet_opf::Config for Runtime { type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; - type NativeBalance = Balances; /// Pot PalletId @@ -1626,6 +1628,9 @@ impl pallet_opf::Config for Runtime { type BlockNumberProvider = System; + /// This should be calculated as a percentage of inflation. + type TemporaryRewards = TemporaryRewards; + type Preimages = Preimage; type Scheduler = Scheduler; diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 1c409e224daf..4ae2b4a899ba 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -90,7 +90,7 @@ impl Pallet { if projects.clone().len() > 0 as usize { let total_positive_votes_amount = round.total_positive_votes_amount; let total_negative_votes_amount = round.total_negative_votes_amount; - + let when = T::BlockNumberProvider::current_block_number(); let total_votes_amount = total_positive_votes_amount.saturating_sub(total_negative_votes_amount); @@ -102,7 +102,8 @@ impl Pallet { let project_positive_reward = funds[0]; let project_negative_reward = funds[1]; - let project_reward = + if project_positive_reward > project_negative_reward{ + let project_reward = project_positive_reward.saturating_sub(project_negative_reward); let project_percentage = @@ -110,29 +111,28 @@ impl Pallet { let final_amount = project_percentage * total_reward; // Send calculated reward for reward distribution - let now = T::BlockNumberProvider::current_block_number(); let project_info = ProjectInfo { project_id: project.clone(), - submission_block: now, + submission_block: when, amount: final_amount, }; - let mut rewarded = Projects::::get(); - rewarded - .try_push(project_info.clone()) - .map_err(|_| Error::::MaximumProjectsNumber)?; - - Projects::::mutate(|value| { - *value = rewarded; - }); + // create a spend for project to be rewarded + let new_spend = SpendInfo::::new(&project_info); - let when = T::BlockNumberProvider::current_block_number(); + Self::deposit_event(Event::::ProjectFundingAccepted { project_id: project, when, round_number, amount: project_info.amount, }) + } else{ + Self::deposit_event(Event::::ProjectFundingRejected { + when, + project_id: project, + }) + } } } @@ -141,4 +141,53 @@ impl Pallet { Ok(()) } + // To be executed in a hook, on_initialize + pub fn on_idle_function(now: ProvidedBlockNumberFor, limit: Weight) -> Weight { + let mut meter = WeightMeter::with_limit(limit); + let max_block_weight = T::DbWeight::get().reads_writes(14, 8); + + if meter.try_consume(max_block_weight).is_err() { + return meter.consumed(); + } + let mut round_index = VotingRoundNumber::::get(); + + // No active round? + if round_index == 0 { + // Start the first voting round + let _round0 = VotingRoundInfo::::new(); + round_index = VotingRoundNumber::::get(); + } + + let current_round_index = round_index.saturating_sub(1); + + let round_infos = VotingRounds::::get(current_round_index).expect("InvalidResult"); + let round_ending_block = round_infos.round_ending_block; + + // Conditions for reward distribution preparations are: + // - We are at the end of voting_round period + + if now == round_ending_block { + + // prepare reward distribution + // for now we are using the temporary-constant reward. + let _ = Self::calculate_rewards(T::TemporaryRewards::get()) + .map_err(|_| Error::::FailedRewardCalculation); + + // Create a new round. + let _new_round = VotingRoundInfo::::new(); + // Clear WhiteListedProjectAccounts storage + WhiteListedProjectAccounts::::kill(); + // Clear ProjectFunds storage + ProjectFunds::::drain(); + // Emmit events + Self::deposit_event(Event::::VotingRoundEnded { + when: now, + round_number: round_infos.round_number, + }); + } + + meter.consumed() + } + + } \ No newline at end of file diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 80259c92923a..089443341608 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -88,6 +88,9 @@ pub mod pallet { #[pallet::constant] type VoteValidityPeriod: Get>; + /// Used for Pallet testing only. Represents the Total Reward distributed + type TemporaryRewards: Get>; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; @@ -106,11 +109,6 @@ pub mod pallet { #[pallet::storage] pub(super) type Spends = CountedStorageMap<_, Twox64Concat, ProjectId, SpendInfo, OptionQuery>; - - /// List of whitelisted projects to be rewarded - #[pallet::storage] - pub type Projects = - StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; /// List of Whitelisted Project registered #[pallet::storage] diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 0a93d3594ed5..e6b7a934cb36 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -33,7 +33,7 @@ pub use frame_support::{ Bounded, DefensiveOption, EnsureOrigin, LockIdentifier, OriginTrait, QueryPreimage, StorePreimage, }, - transactional, PalletId, Serialize, + transactional, PalletId, Serialize, weights::WeightMeter, }; pub use pallet_conviction_voting::Conviction; pub use frame_system::{pallet_prelude::*, RawOrigin}; @@ -80,7 +80,7 @@ pub struct SpendInfo { /// The block number from which the spend can be claimed(24h after SpendStatus Creation). pub valid_from:ProvidedBlockNumberFor, /// Corresponding project id - pub whitelisted_project: AccountIdOf, + pub whitelisted_project: ProjectInfo, /// Has it been claimed? pub claimed: bool, /// Claim Expiration block @@ -90,7 +90,7 @@ pub struct SpendInfo { impl SpendInfo { pub fn new(whitelisted: &ProjectInfo) -> Self { let amount = whitelisted.amount; - let whitelisted_project = whitelisted.project_id.clone(); + let whitelisted_project = whitelisted.clone(); let claimed = false; let valid_from = T::BlockNumberProvider::current_block_number(); From a52879a2619800fe81bab49a1c779ff1d02cf519 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 18:15:10 +0900 Subject: [PATCH 07/22] prdoc --- prdoc/pr_6994.prdoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 prdoc/pr_6994.prdoc diff --git a/prdoc/pr_6994.prdoc b/prdoc/pr_6994.prdoc new file mode 100644 index 000000000000..32805f2c145e --- /dev/null +++ b/prdoc/pr_6994.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Optimistic Project Funding" + +doc: + - audience: Runtime Dev + description: | + the PF pallet handles the Optimistic Project Funding. + It allows users to nominate projects (whitelisted in OpenGov) with their DOT. + This mechanism will be funded with a constant stream of DOT taken directly from inflation and distributed to projects based on the proportion of DOT that has nominated them. + +crates: + - name: pallet-opf + bump: patch + - name: frame-support + bump: none + - name: polkadot-sdk + bump: patch + - name: polkadot + bump: none \ No newline at end of file From e023fefabb75db98f5998744e91635f1c6d94fc9 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 18:19:51 +0900 Subject: [PATCH 08/22] cargo fmt --- substrate/frame/opf/src/functions.rs | 62 ++++++++++++------------- substrate/frame/opf/src/lib.rs | 68 +++++++++++++++------------- substrate/frame/opf/src/types.rs | 42 +++++++++-------- 3 files changed, 88 insertions(+), 84 deletions(-) diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 4ae2b4a899ba..c2b046b2224e 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -16,11 +16,9 @@ // limitations under the License. //! Helper functions for OPF pallet. - pub use super::*; impl Pallet { - - pub fn pot_account() -> AccountIdOf { + pub fn pot_account() -> AccountIdOf { // Get Pot account T::PotId::get().into_account_truncating() } @@ -36,7 +34,7 @@ impl Pallet { Ok(()) } - /// Series of checks on the Pot, to ensure that we have enough funds + /// Series of checks on the Pot, to ensure that we have enough funds /// before executing a Spend --> used in tests. pub fn pot_check(spend: BalanceOf) -> DispatchResult { // Get Pot account @@ -52,16 +50,17 @@ impl Pallet { Ok(()) } - // Helper function for project registration - pub fn register_new(project_id: ProjectId) -> DispatchResult{ - let submission_block = T::BlockNumberProvider::current_block_number(); - let mut bounded: BoundedVec, T::MaxProjects> = WhiteListedProjectAccounts::::get(); - let _ = bounded.try_push(project_id); - WhiteListedProjectAccounts::::put(bounded); - Ok(()) - } + // Helper function for project registration + pub fn register_new(project_id: ProjectId) -> DispatchResult { + let submission_block = T::BlockNumberProvider::current_block_number(); + let mut bounded: BoundedVec, T::MaxProjects> = + WhiteListedProjectAccounts::::get(); + let _ = bounded.try_push(project_id); + WhiteListedProjectAccounts::::put(bounded); + Ok(()) + } - // Voting Period checks + // Voting Period checks pub fn period_check() -> DispatchResult { // Get current voting round & check if we are in voting period or not let current_round_index = VotingRoundNumber::::get().saturating_sub(1); @@ -71,18 +70,18 @@ impl Pallet { Ok(()) } - pub fn unlist_project(project_id: ProjectId) -> DispatchResult { + pub fn unlist_project(project_id: ProjectId) -> DispatchResult { WhiteListedProjectAccounts::::mutate(|value| { let mut val = value.clone(); val.retain(|x| *x != project_id); *value = val; }); - + Ok(()) } - // The total reward to be distributed is a portion or inflation, determined in another pallet - // Reward calculation is executed within the Voting period + // The total reward to be distributed is a portion or inflation, determined in another pallet + // Reward calculation is executed within the Voting period pub fn calculate_rewards(total_reward: BalanceOf) -> DispatchResult { let projects = WhiteListedProjectAccounts::::get(); let round_number = VotingRoundNumber::::get().saturating_sub(1); @@ -90,7 +89,7 @@ impl Pallet { if projects.clone().len() > 0 as usize { let total_positive_votes_amount = round.total_positive_votes_amount; let total_negative_votes_amount = round.total_negative_votes_amount; - let when = T::BlockNumberProvider::current_block_number(); + let when = T::BlockNumberProvider::current_block_number(); let total_votes_amount = total_positive_votes_amount.saturating_sub(total_negative_votes_amount); @@ -102,9 +101,9 @@ impl Pallet { let project_positive_reward = funds[0]; let project_negative_reward = funds[1]; - if project_positive_reward > project_negative_reward{ - let project_reward = - project_positive_reward.saturating_sub(project_negative_reward); + if project_positive_reward > project_negative_reward { + let project_reward = + project_positive_reward.saturating_sub(project_negative_reward); let project_percentage = Percent::from_rational(project_reward, total_votes_amount); @@ -117,23 +116,21 @@ impl Pallet { amount: final_amount, }; - // create a spend for project to be rewarded + // create a spend for project to be rewarded let new_spend = SpendInfo::::new(&project_info); - Self::deposit_event(Event::::ProjectFundingAccepted { project_id: project, when, round_number, amount: project_info.amount, }) - } else{ + } else { Self::deposit_event(Event::::ProjectFundingRejected { when, - project_id: project, + project_id: project, }) - } - + } } } } @@ -141,7 +138,7 @@ impl Pallet { Ok(()) } - // To be executed in a hook, on_initialize + // To be executed in a hook, on_initialize pub fn on_idle_function(now: ProvidedBlockNumberFor, limit: Weight) -> Weight { let mut meter = WeightMeter::with_limit(limit); let max_block_weight = T::DbWeight::get().reads_writes(14, 8); @@ -158,6 +155,8 @@ impl Pallet { round_index = VotingRoundNumber::::get(); } + // ToDo: Check Spends and remove those which are not valid anymore + let current_round_index = round_index.saturating_sub(1); let round_infos = VotingRounds::::get(current_round_index).expect("InvalidResult"); @@ -167,13 +166,12 @@ impl Pallet { // - We are at the end of voting_round period if now == round_ending_block { - // prepare reward distribution // for now we are using the temporary-constant reward. let _ = Self::calculate_rewards(T::TemporaryRewards::get()) .map_err(|_| Error::::FailedRewardCalculation); - // Create a new round. + // Create a new round. let _new_round = VotingRoundInfo::::new(); // Clear WhiteListedProjectAccounts storage WhiteListedProjectAccounts::::kill(); @@ -188,6 +186,4 @@ impl Pallet { meter.consumed() } - - -} \ No newline at end of file +} diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 089443341608..f5edb25ff0af 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -18,8 +18,8 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; -mod types; mod functions; +mod types; pub use pallet_scheduler as Schedule; pub use types::*; @@ -42,8 +42,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Type to access the Balances Pallet. - type NativeBalance: fungible::Inspect - + fungible::Mutate; + type NativeBalance: fungible::Inspect + fungible::Mutate; /// Provider for the block number. type BlockNumberProvider: BlockNumberProvider; @@ -57,20 +56,20 @@ pub mod pallet { /// The Scheduler. type Scheduler: ScheduleAnon< - ProvidedBlockNumberFor, + ProvidedBlockNumberFor, CallOf, PalletsOriginOf, Hasher = Self::Hashing, > + ScheduleNamed< - ProvidedBlockNumberFor, + ProvidedBlockNumberFor, CallOf, PalletsOriginOf, Hasher = Self::Hashing, >; - /// Time period in which people can vote. - /// After the period has ended, the votes are counted (STOP THE COUNT) - /// and then the funds are distributed into Spends. + /// Time period in which people can vote. + /// After the period has ended, the votes are counted (STOP THE COUNT) + /// and then the funds are distributed into Spends. #[pallet::constant] type VotingPeriod: Get>; @@ -78,9 +77,9 @@ pub mod pallet { #[pallet::constant] type MaxProjects: Get; - /// Time for claiming a Spend. - /// After the period has passed, a spend is thrown away - /// and the funds are available again for distribution in the pot. + /// Time for claiming a Spend. + /// After the period has passed, a spend is thrown away + /// and the funds are available again for distribution in the pot. #[pallet::constant] type ClaimingPeriod: Get>; @@ -91,7 +90,6 @@ pub mod pallet { /// Used for Pallet testing only. Represents the Total Reward distributed type TemporaryRewards: Get>; - /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -109,12 +107,12 @@ pub mod pallet { #[pallet::storage] pub(super) type Spends = CountedStorageMap<_, Twox64Concat, ProjectId, SpendInfo, OptionQuery>; - + /// List of Whitelisted Project registered #[pallet::storage] pub type WhiteListedProjectAccounts = StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; - + /// Returns (positive_funds,negative_funds) of Whitelisted Project accounts #[pallet::storage] pub type ProjectFunds = StorageMap< @@ -129,10 +127,18 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Reward successfully claimed - RewardClaimed { when: ProvidedBlockNumberFor, amount: BalanceOf, project_id: ProjectId }, + RewardClaimed { + when: ProvidedBlockNumberFor, + amount: BalanceOf, + project_id: ProjectId, + }, /// A Spend was created - SpendCreated { when: ProvidedBlockNumberFor, amount: BalanceOf, project_id: ProjectId }, + SpendCreated { + when: ProvidedBlockNumberFor, + amount: BalanceOf, + project_id: ProjectId, + }, /// Not yet in the claiming period NotClaimingPeriod { project_id: ProjectId, claiming_period: ProvidedBlockNumberFor }, @@ -150,30 +156,30 @@ pub mod pallet { VoteRemoved { who: VoterId, when: ProvidedBlockNumberFor, project_id: ProjectId }, /// Project added to whitelisted projects list - Projectlisted { when:ProvidedBlockNumberFor, project_id: ProjectId }, + Projectlisted { when: ProvidedBlockNumberFor, project_id: ProjectId }, /// Project removed from whitelisted projects list - ProjectUnlisted { when:ProvidedBlockNumberFor, project_id: ProjectId }, + ProjectUnlisted { when: ProvidedBlockNumberFor, project_id: ProjectId }, /// Project Funding Accepted by voters ProjectFundingAccepted { project_id: ProjectId, - when:ProvidedBlockNumberFor, + when: ProvidedBlockNumberFor, round_number: u32, amount: BalanceOf, }, /// Project Funding rejected by voters - ProjectFundingRejected { when:ProvidedBlockNumberFor, project_id: ProjectId }, + ProjectFundingRejected { when: ProvidedBlockNumberFor, project_id: ProjectId }, /// A new voting round started - VotingRoundStarted { when:ProvidedBlockNumberFor, round_number: u32 }, + VotingRoundStarted { when: ProvidedBlockNumberFor, round_number: u32 }, /// The users voting period ended. Reward calculation will start. - VoteActionLocked { when:ProvidedBlockNumberFor, round_number: u32 }, + VoteActionLocked { when: ProvidedBlockNumberFor, round_number: u32 }, /// The voting round ended - VotingRoundEnded { when:ProvidedBlockNumberFor, round_number: u32 }, + VotingRoundEnded { when: ProvidedBlockNumberFor, round_number: u32 }, } #[pallet::error] @@ -215,23 +221,22 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::call_index(0)] #[transactional] - pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult{ + pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { let _caller = ensure_signed(origin)?; let when = T::BlockNumberProvider::current_block_number(); Self::register_new(project_id.clone())?; - Self::deposit_event(Event::Projectlisted { - when, - project_id, - }); + Self::deposit_event(Event::Projectlisted { when, project_id }); Ok(()) } #[pallet::call_index(1)] #[transactional] - pub fn unregister_project(origin: OriginFor, project_id: ProjectId ) -> DispatchResult { + pub fn unregister_project( + origin: OriginFor, + project_id: ProjectId, + ) -> DispatchResult { ensure_root(origin)?; let when = T::BlockNumberProvider::current_block_number(); Self::unlist_project(project_id.clone())?; @@ -263,6 +268,5 @@ pub mod pallet { pub fn execute_claim(origin: OriginFor, project_id: ProjectId) -> DispatchResult { Ok(()) } - } -} \ No newline at end of file +} diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index e6b7a934cb36..4e0c518c8b83 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -33,14 +33,20 @@ pub use frame_support::{ Bounded, DefensiveOption, EnsureOrigin, LockIdentifier, OriginTrait, QueryPreimage, StorePreimage, }, - transactional, PalletId, Serialize, weights::WeightMeter, + transactional, + weights::WeightMeter, + PalletId, Serialize, }; -pub use pallet_conviction_voting::Conviction; pub use frame_system::{pallet_prelude::*, RawOrigin}; +pub use pallet_conviction_voting::Conviction; pub use scale_info::prelude::vec::Vec; -pub use sp_runtime::{traits::{ - AccountIdConversion, BlockNumberProvider, Convert, Dispatchable, Saturating, StaticLookup, Zero, -}, Percent}; +pub use sp_runtime::{ + traits::{ + AccountIdConversion, BlockNumberProvider, Convert, Dispatchable, Saturating, StaticLookup, + Zero, + }, + Percent, +}; pub use sp_std::boxed::Box; pub type BalanceOf = <::NativeBalance as fungible::Inspect< @@ -57,7 +63,8 @@ pub type PalletsOriginOf = pub const DISTRIBUTION_ID: LockIdentifier = *b"distribu"; pub type RoundIndex = u32; pub type VoterId = AccountIdOf; -pub type ProvidedBlockNumberFor = <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; +pub type ProvidedBlockNumberFor = + <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; /// The state of the payment claim. #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo, Default)] @@ -78,13 +85,13 @@ pub struct SpendInfo { /// The asset amount of the spend. pub amount: BalanceOf, /// The block number from which the spend can be claimed(24h after SpendStatus Creation). - pub valid_from:ProvidedBlockNumberFor, + pub valid_from: ProvidedBlockNumberFor, /// Corresponding project id pub whitelisted_project: ProjectInfo, /// Has it been claimed? pub claimed: bool, /// Claim Expiration block - pub expire:ProvidedBlockNumberFor, + pub expire: ProvidedBlockNumberFor, } impl SpendInfo { @@ -92,9 +99,8 @@ impl SpendInfo { let amount = whitelisted.amount; let whitelisted_project = whitelisted.clone(); let claimed = false; - let valid_from = - T::BlockNumberProvider::current_block_number(); - let expire = valid_from.clone().saturating_add(T::ClaimingPeriod::get()); + let valid_from = T::BlockNumberProvider::current_block_number(); + let expire = valid_from.saturating_add(T::ClaimingPeriod::get()); let spend = SpendInfo { amount, valid_from, whitelisted_project, claimed, expire }; @@ -112,7 +118,7 @@ pub struct ProjectInfo { pub project_id: ProjectId, /// Block at which the project was submitted for reward distribution - pub submission_block:ProvidedBlockNumberFor, + pub submission_block: ProvidedBlockNumberFor, /// Amount to be lock & pay for this project pub amount: BalanceOf, @@ -132,16 +138,14 @@ pub struct VoteInfo { pub conviction: Conviction, - pub funds_unlock_block:ProvidedBlockNumberFor, + pub funds_unlock_block: ProvidedBlockNumberFor, } // If no conviction, user's funds are released at the end of the voting round impl VoteInfo { pub fn funds_unlock(&mut self) { let conviction_coeff = >::from(self.conviction); - let funds_unlock_block = self - .round - .round_ending_block; + let funds_unlock_block = self.round.round_ending_block; self.funds_unlock_block = funds_unlock_block; } } @@ -164,8 +168,8 @@ impl Default for VoteInfo { #[scale_info(skip_type_params(T))] pub struct VotingRoundInfo { pub round_number: u32, - pub round_starting_block:ProvidedBlockNumberFor, - pub round_ending_block:ProvidedBlockNumberFor, + pub round_starting_block: ProvidedBlockNumberFor, + pub round_ending_block: ProvidedBlockNumberFor, pub total_positive_votes_amount: BalanceOf, pub total_negative_votes_amount: BalanceOf, } @@ -198,4 +202,4 @@ impl VotingRoundInfo { VotingRounds::::insert(round_number, round_infos.clone()); round_infos } -} \ No newline at end of file +} From c5104d7b2f30a863bc5fa36e49d680a438455272 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 20:14:42 +0900 Subject: [PATCH 09/22] Claim_reward conditions --- substrate/frame/opf/src/functions.rs | 5 +-- substrate/frame/opf/src/lib.rs | 54 +++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index c2b046b2224e..1abff4fc4410 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -52,7 +52,6 @@ impl Pallet { // Helper function for project registration pub fn register_new(project_id: ProjectId) -> DispatchResult { - let submission_block = T::BlockNumberProvider::current_block_number(); let mut bounded: BoundedVec, T::MaxProjects> = WhiteListedProjectAccounts::::get(); let _ = bounded.try_push(project_id); @@ -117,7 +116,7 @@ impl Pallet { }; // create a spend for project to be rewarded - let new_spend = SpendInfo::::new(&project_info); + let _ = SpendInfo::::new(&project_info); Self::deposit_event(Event::::ProjectFundingAccepted { project_id: project, @@ -155,8 +154,6 @@ impl Pallet { round_index = VotingRoundNumber::::get(); } - // ToDo: Check Spends and remove those which are not valid anymore - let current_round_index = round_index.saturating_sub(1); let round_infos = VotingRounds::::get(current_round_index).expect("InvalidResult"); diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index f5edb25ff0af..14d0dd875989 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -169,6 +169,9 @@ pub mod pallet { amount: BalanceOf, }, + /// Reward claim has expired + ExpiredClaim { expired_when: ProvidedBlockNumberFor, project_id: ProjectId }, + /// Project Funding rejected by voters ProjectFundingRejected { when: ProvidedBlockNumberFor, project_id: ProjectId }, @@ -257,16 +260,57 @@ pub mod pallet { Ok(()) } + /// OPF Reward Claim logic + /// + /// ## Dispatch Origin + /// + /// Must be signed + /// + /// ## Details + /// + /// From this extrinsic any user can claim a reward for a nominated/whitelisted project. + /// + /// ### Parameters + /// - `project_id`: The account that will receive the reward. + /// + /// ### Errors + /// - [`Error::::InexistentSpend`]:Spend or Spend index does not exists + /// - [`Error::::NoValidAccount`]: No valid Account_id found + /// - [`Error::::NotClaimingPeriod`]: Still not in claiming period + /// + /// ## Events + /// Emits [`Event::::RewardClaimed`] if successful for a positive approval. #[pallet::call_index(4)] #[transactional] pub fn claim_reward_for(origin: OriginFor, project_id: ProjectId) -> DispatchResult { + let _caller = ensure_signed(origin)?; + let now = T::BlockNumberProvider::current_block_number(); + let info = Spends::::get(&project_id).ok_or(Error::::InexistentSpend)?; + match now { + _ if now >= info.expire => { + Spends::::remove(&project_id); + Self::deposit_event(Event::ExpiredClaim { + expired_when: info.expire, + project_id, + }); + Ok(()) + }, + _ if now >= info.expire => { + // transfer the funds + Self::spend(info.amount, project_id.clone())?; + + Self::deposit_event(Event::RewardClaimed { + when: now, + amount: info.amount, + project_id, + }); Ok(()) - } + } + _ => Err(DispatchError::Other("Not Claiming Period")) + } - #[pallet::call_index(5)] - #[transactional] - pub fn execute_claim(origin: OriginFor, project_id: ProjectId) -> DispatchResult { - Ok(()) } + + } } From f440a8ee78f0024bedb79a68e1a326120c1fce83 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Mon, 30 Dec 2024 20:16:47 +0900 Subject: [PATCH 10/22] cargo fmt --- substrate/frame/opf/src/lib.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 14d0dd875989..147259bd9135 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -289,28 +289,25 @@ pub mod pallet { match now { _ if now >= info.expire => { Spends::::remove(&project_id); - Self::deposit_event(Event::ExpiredClaim { - expired_when: info.expire, - project_id, - }); - Ok(()) + Self::deposit_event(Event::ExpiredClaim { + expired_when: info.expire, + project_id, + }); + Ok(()) }, _ if now >= info.expire => { // transfer the funds - Self::spend(info.amount, project_id.clone())?; - - Self::deposit_event(Event::RewardClaimed { - when: now, - amount: info.amount, - project_id, - }); - Ok(()) - } - _ => Err(DispatchError::Other("Not Claiming Period")) + Self::spend(info.amount, project_id.clone())?; + + Self::deposit_event(Event::RewardClaimed { + when: now, + amount: info.amount, + project_id, + }); + Ok(()) + }, + _ => Err(DispatchError::Other("Not Claiming Period")), } - } - - } } From 27f910771fad245548b945f435eacfa996b7c25e Mon Sep 17 00:00:00 2001 From: ndkazu Date: Tue, 31 Dec 2024 10:43:29 +0900 Subject: [PATCH 11/22] Hook on_idle added --- substrate/frame/opf/src/functions.rs | 3 ++- substrate/frame/opf/src/lib.rs | 10 +++++----- substrate/frame/opf/src/types.rs | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 1abff4fc4410..0d11d9c124a8 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -138,7 +138,8 @@ impl Pallet { } // To be executed in a hook, on_initialize - pub fn on_idle_function(now: ProvidedBlockNumberFor, limit: Weight) -> Weight { + pub fn on_idle_function(limit: Weight) -> Weight { + let now = T::BlockNumberProvider::current_block_number(); let mut meter = WeightMeter::with_limit(limit); let max_block_weight = T::DbWeight::get().reads_writes(14, 8); diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 147259bd9135..c835162c7ab9 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -215,12 +215,12 @@ pub mod pallet { MaximumProjectsNumber, } - /*#[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n:ProvidedBlockNumberFor) -> Weight { - Self::begin_block(n) + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(_n: SystemBlockNumberFor, remaining_weight: Weight) -> Weight { + Self::on_idle_function(remaining_weight) } - }*/ + } #[pallet::call] impl Pallet { diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 4e0c518c8b83..99ea557ed0ac 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -65,6 +65,7 @@ pub type RoundIndex = u32; pub type VoterId = AccountIdOf; pub type ProvidedBlockNumberFor = <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; +pub use frame_system::pallet_prelude::BlockNumberFor as SystemBlockNumberFor; /// The state of the payment claim. #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo, Default)] From 57f751dd4b6e3c55d24e642932fdbfb1a909feeb Mon Sep 17 00:00:00 2001 From: ndkazu Date: Wed, 1 Jan 2025 12:39:14 +0900 Subject: [PATCH 12/22] Adding some errors --- substrate/frame/opf/src/functions.rs | 8 +++++- substrate/frame/opf/src/lib.rs | 40 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 0d11d9c124a8..ca01204907cd 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -54,7 +54,9 @@ impl Pallet { pub fn register_new(project_id: ProjectId) -> DispatchResult { let mut bounded: BoundedVec, T::MaxProjects> = WhiteListedProjectAccounts::::get(); - let _ = bounded.try_push(project_id); + let vec = bounded.clone().to_vec(); + ensure!(!vec.contains(&project_id), Error::::SubmittedProjectId); + let _ = bounded.try_push(project_id).map_err(|_| Error::::MaximumProjectsNumber); WhiteListedProjectAccounts::::put(bounded); Ok(()) } @@ -70,6 +72,10 @@ impl Pallet { } pub fn unlist_project(project_id: ProjectId) -> DispatchResult { + let mut bounded: BoundedVec, T::MaxProjects> = + WhiteListedProjectAccounts::::get(); + let vec = bounded.clone().to_vec(); + ensure!(vec.contains(&project_id), Error::::NoProjectAvailable); WhiteListedProjectAccounts::::mutate(|value| { let mut val = value.clone(); val.retain(|x| *x != project_id); diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index c835162c7ab9..2edb1f04245a 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -213,6 +213,8 @@ pub mod pallet { NoRoundFound, /// Maximum number of projects submission for reward distribution as been reached MaximumProjectsNumber, + /// Another project has already been submitted under the same project_id + SubmittedProjectId, } #[pallet::hooks] @@ -224,6 +226,26 @@ pub mod pallet { #[pallet::call] impl Pallet { + + /// OPF Projects registration + /// + /// ## Dispatch Origin + /// + /// Must be signed + /// + /// ## Details + /// + /// From this extrinsic any user can register project. + /// + /// ### Parameters + /// - `project_id`: The account that will receive the reward. + /// + /// ### Errors + /// - [`Error::::SubmittedProjectId`]: Project already submitted under this project_id + /// - [`Error::::MaximumProjectsNumber`]: Maximum number of project subscriptions reached + /// + /// ## Events + /// Emits [`Event::::Projectlisted`]. #[pallet::call_index(0)] #[transactional] pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { @@ -234,6 +256,24 @@ pub mod pallet { Ok(()) } + /// OPF Projects de-listing + /// + /// ## Dispatch Origin + /// + /// Must be signed + /// + /// ## Details + /// + /// From this extrinsic only Root can de-list a project. + /// + /// ### Parameters + /// - `project_id`: The account that will receive the reward. + /// + /// ### Errors + /// - [`Error::::NoProjectAvailable`]: No project found under this project_id + /// + /// ## Events + /// Emits [`Event::::ProjectUnlisted`]. #[pallet::call_index(1)] #[transactional] pub fn unregister_project( From b639990632a09c3c1e1ffcc1f84cccf35f0063de Mon Sep 17 00:00:00 2001 From: ndkazu Date: Wed, 1 Jan 2025 22:26:27 +0900 Subject: [PATCH 13/22] Re-factored the code --- substrate/frame/opf/src/functions.rs | 30 ++---- substrate/frame/opf/src/lib.rs | 11 ++- substrate/frame/opf/src/mock.rs | 137 +++++++++++++++++++++++++++ substrate/frame/opf/src/tests.rs | 55 +++++++++++ substrate/frame/opf/src/types.rs | 13 +++ 5 files changed, 219 insertions(+), 27 deletions(-) create mode 100644 substrate/frame/opf/src/mock.rs create mode 100644 substrate/frame/opf/src/tests.rs diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index ca01204907cd..8a3b26cb9fce 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -50,17 +50,6 @@ impl Pallet { Ok(()) } - // Helper function for project registration - pub fn register_new(project_id: ProjectId) -> DispatchResult { - let mut bounded: BoundedVec, T::MaxProjects> = - WhiteListedProjectAccounts::::get(); - let vec = bounded.clone().to_vec(); - ensure!(!vec.contains(&project_id), Error::::SubmittedProjectId); - let _ = bounded.try_push(project_id).map_err(|_| Error::::MaximumProjectsNumber); - WhiteListedProjectAccounts::::put(bounded); - Ok(()) - } - // Voting Period checks pub fn period_check() -> DispatchResult { // Get current voting round & check if we are in voting period or not @@ -72,13 +61,9 @@ impl Pallet { } pub fn unlist_project(project_id: ProjectId) -> DispatchResult { - let mut bounded: BoundedVec, T::MaxProjects> = - WhiteListedProjectAccounts::::get(); - let vec = bounded.clone().to_vec(); - ensure!(vec.contains(&project_id), Error::::NoProjectAvailable); WhiteListedProjectAccounts::::mutate(|value| { let mut val = value.clone(); - val.retain(|x| *x != project_id); + val.retain(|x| x.project_id != project_id); *value = val; }); @@ -101,8 +86,9 @@ impl Pallet { // for each project, calculate the percentage of votes, the amount to be distributed, // and then populate the storage Projects for project in projects { - if ProjectFunds::::contains_key(&project) { - let funds = ProjectFunds::::get(&project); + let project_id = &project.project_id; + if ProjectFunds::::contains_key(project_id) { + let funds = ProjectFunds::::get(project_id); let project_positive_reward = funds[0]; let project_negative_reward = funds[1]; @@ -116,7 +102,7 @@ impl Pallet { // Send calculated reward for reward distribution let project_info = ProjectInfo { - project_id: project.clone(), + project_id: project.project_id.clone(), submission_block: when, amount: final_amount, }; @@ -125,7 +111,7 @@ impl Pallet { let _ = SpendInfo::::new(&project_info); Self::deposit_event(Event::::ProjectFundingAccepted { - project_id: project, + project_id: project.project_id, when, round_number, amount: project_info.amount, @@ -133,7 +119,7 @@ impl Pallet { } else { Self::deposit_event(Event::::ProjectFundingRejected { when, - project_id: project, + project_id: project.project_id, }) } } @@ -177,8 +163,6 @@ impl Pallet { // Create a new round. let _new_round = VotingRoundInfo::::new(); - // Clear WhiteListedProjectAccounts storage - WhiteListedProjectAccounts::::kill(); // Clear ProjectFunds storage ProjectFunds::::drain(); // Emmit events diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 2edb1f04245a..7d20b8f2ed1d 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -111,7 +111,11 @@ pub mod pallet { /// List of Whitelisted Project registered #[pallet::storage] pub type WhiteListedProjectAccounts = - StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; + StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; + + /// Whitelisted Projects registration counter + #[pallet::storage] + pub type WhiteListedProjectCounter = StorageValue<_, u32, ValueQuery>; /// Returns (positive_funds,negative_funds) of Whitelisted Project accounts #[pallet::storage] @@ -226,7 +230,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// OPF Projects registration /// /// ## Dispatch Origin @@ -251,7 +254,7 @@ pub mod pallet { pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { let _caller = ensure_signed(origin)?; let when = T::BlockNumberProvider::current_block_number(); - Self::register_new(project_id.clone())?; + ProjectInfo::::new(project_id.clone()); Self::deposit_event(Event::Projectlisted { when, project_id }); Ok(()) } @@ -316,7 +319,7 @@ pub mod pallet { /// ### Errors /// - [`Error::::InexistentSpend`]:Spend or Spend index does not exists /// - [`Error::::NoValidAccount`]: No valid Account_id found - /// - [`Error::::NotClaimingPeriod`]: Still not in claiming period + /// - [`Not Claiming Period`]: Still not in claiming period /// /// ## Events /// Emits [`Event::::RewardClaimed`] if successful for a positive approval. diff --git a/substrate/frame/opf/src/mock.rs b/substrate/frame/opf/src/mock.rs new file mode 100644 index 000000000000..c041669d5e19 --- /dev/null +++ b/substrate/frame/opf/src/mock.rs @@ -0,0 +1,137 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test environment for OPF pallet. +use crate as pallet_opf; +pub use frame_support::{ + derive_impl, parameter_types, + traits::{ConstU32, EqualPrivilegeOnly, OnFinalize, OnInitialize}, + weights::Weight, + PalletId, +}; +pub use sp_runtime::{ + traits::{AccountIdConversion, IdentityLookup}, + BuildStorage, +}; + +pub use frame_system::EnsureRoot; +pub type Block = frame_system::mocking::MockBlock; +pub type Balance = u64; +pub type AccountId = u64; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub struct Test { + System: frame_system, + Balances: pallet_balances, + Preimage: pallet_preimage, + Scheduler: pallet_scheduler, + Opf: pallet_opf, + } +); + +parameter_types! { + pub MaxWeight: Weight = Weight::from_parts(2_000_000_000_000, u64::MAX); +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type AccountData = pallet_balances::AccountData; + type Block = Block; + type Lookup = IdentityLookup; +} + +impl pallet_preimage::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type Consideration = (); +} +impl pallet_scheduler::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaxWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = ConstU32<100>; + type WeightInfo = (); + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = Preimage; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"py/potid"); + pub const MaxProjects:u32 = 50; + pub const TemporaryRewards: Balance = 100_000; + pub const VoteLockingPeriod:u32 = 10; + pub const VotingPeriod:u32 = 30; +} +impl pallet_opf::Config for Test { + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type NativeBalance = Balances; + type PotId = PotId; + type MaxProjects = MaxProjects; + type VotingPeriod = VotingPeriod; + type ClaimingPeriod = VotingPeriod; + type VoteValidityPeriod = VotingPeriod; + type BlockNumberProvider = System; + type TemporaryRewards = TemporaryRewards; + type Preimages = Preimage; + type Scheduler = Scheduler; + type WeightInfo = (); +} + +//Define some accounts and use them +pub const ALICE: AccountId = 10; +pub const BOB: AccountId = 11; +pub const DAVE: AccountId = 12; +pub const EVE: AccountId = 13; +pub const BSX: Balance = 100_000_000_000; + +pub fn expect_events(e: Vec) { + e.into_iter().for_each(frame_system::Pallet::::assert_has_event); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let pot_account = PotId::get().into_account_truncating(); + + pallet_balances::GenesisConfig:: { + balances: vec![ + (ALICE, 200_000 * BSX), + (BOB, 200_000 * BSX), + (DAVE, 150_000 * BSX), + (EVE, 150_000 * BSX), + (pot_account, 150_000_000 * BSX), + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} \ No newline at end of file diff --git a/substrate/frame/opf/src/tests.rs b/substrate/frame/opf/src/tests.rs new file mode 100644 index 000000000000..03b20b10ab83 --- /dev/null +++ b/substrate/frame/opf/src/tests.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for OPF pallet. + +pub use super::*; +use crate::mock::*; +use frame_support::{assert_noop, assert_ok, traits::OnIdle}; + +pub fn next_block() { + System::set_block_number( + ::BlockNumberProvider::current_block_number() + 1, + ); + AllPalletsWithSystem::on_initialize( + ::BlockNumberProvider::current_block_number(), + ); + AllPalletsWithSystem::on_idle( + ::BlockNumberProvider::current_block_number(), + Weight::MAX, + ); +} + +pub fn run_to_block(n: BlockNumberFor) { + while ::BlockNumberProvider::current_block_number() < n { + if ::BlockNumberProvider::current_block_number() > 1 { + AllPalletsWithSystem::on_finalize( + ::BlockNumberProvider::current_block_number(), + ); + } + next_block(); + } +} + +#[test] +fn project_registration_works(){ + new_test_ext().execute_with(|| { + + + + }) +} \ No newline at end of file diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 99ea557ed0ac..3f96af2939d7 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -125,6 +125,19 @@ pub struct ProjectInfo { pub amount: BalanceOf, } +impl ProjectInfo { + pub fn new(project_id: ProjectId) { + let submission_block = T::BlockNumberProvider::current_block_number(); + let amount = Zero::zero(); + let project_info = ProjectInfo { project_id, submission_block, amount }; + WhiteListedProjectAccounts::::mutate(|project| { + let _ = project + .try_push(project_info.clone()) + .map_err(|_| Error::::MaximumProjectsNumber); + }); + } +} + #[derive(Encode, Decode, Clone, PartialEq, MaxEncodedLen, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct VoteInfo { From 757d7a44332061fbad3e668411e8f8e81330ca37 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Wed, 1 Jan 2025 23:13:37 +0900 Subject: [PATCH 14/22] Listing/unlisting reserved to Root --- substrate/frame/opf/src/lib.rs | 15 ++++++++------- substrate/frame/opf/src/tests.rs | 6 ++++-- substrate/frame/opf/src/types.rs | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 7d20b8f2ed1d..7b6c9ce8aac7 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -23,6 +23,12 @@ mod types; pub use pallet_scheduler as Schedule; pub use types::*; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; @@ -113,10 +119,6 @@ pub mod pallet { pub type WhiteListedProjectAccounts = StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; - /// Whitelisted Projects registration counter - #[pallet::storage] - pub type WhiteListedProjectCounter = StorageValue<_, u32, ValueQuery>; - /// Returns (positive_funds,negative_funds) of Whitelisted Project accounts #[pallet::storage] pub type ProjectFunds = StorageMap< @@ -238,13 +240,12 @@ pub mod pallet { /// /// ## Details /// - /// From this extrinsic any user can register project. + /// From this extrinsic only Root can register project. /// /// ### Parameters /// - `project_id`: The account that will receive the reward. /// /// ### Errors - /// - [`Error::::SubmittedProjectId`]: Project already submitted under this project_id /// - [`Error::::MaximumProjectsNumber`]: Maximum number of project subscriptions reached /// /// ## Events @@ -252,7 +253,7 @@ pub mod pallet { #[pallet::call_index(0)] #[transactional] pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { - let _caller = ensure_signed(origin)?; + let _caller = ensure_root(origin)?; let when = T::BlockNumberProvider::current_block_number(); ProjectInfo::::new(project_id.clone()); Self::deposit_event(Event::Projectlisted { when, project_id }); diff --git a/substrate/frame/opf/src/tests.rs b/substrate/frame/opf/src/tests.rs index 03b20b10ab83..74b2472a978d 100644 --- a/substrate/frame/opf/src/tests.rs +++ b/substrate/frame/opf/src/tests.rs @@ -49,7 +49,9 @@ pub fn run_to_block(n: BlockNumberFor) { fn project_registration_works(){ new_test_ext().execute_with(|| { - - + assert_ok!(Opf::register_project(RawOrigin::Root.into(), BOB)); + let project_list = WhiteListedProjectAccounts::::get().to_vec(); + assert_eq!(project_list.len(),1); + }) } \ No newline at end of file diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 3f96af2939d7..f3667600a622 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -158,7 +158,7 @@ pub struct VoteInfo { // If no conviction, user's funds are released at the end of the voting round impl VoteInfo { pub fn funds_unlock(&mut self) { - let conviction_coeff = >::from(self.conviction); + //let conviction_coeff = >::from(self.conviction); let funds_unlock_block = self.round.round_ending_block; self.funds_unlock_block = funds_unlock_block; } From bf0390271ab5be56c472393655cc0c1ee7c74d19 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Thu, 2 Jan 2025 20:35:57 +0900 Subject: [PATCH 15/22] Add pallet-referenda --- Cargo.lock | 1 + substrate/bin/node/runtime/src/lib.rs | 7 +- substrate/frame/opf/Cargo.toml | 4 + substrate/frame/opf/src/lib.rs | 26 +--- substrate/frame/opf/src/mock.rs | 173 ++++++++++++++++++++++++-- substrate/frame/opf/src/tests.rs | 20 ++- 6 files changed, 179 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 126e18d6a742..98b3cda37104 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14542,6 +14542,7 @@ dependencies = [ "pallet-balances 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-preimage 28.0.0", + "pallet-referenda 28.0.0", "pallet-scheduler 29.0.0", "pallet-sudo 28.0.0", "pallet-timestamp 27.0.0", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index f05048fa9a32..847746d3fb5d 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1603,7 +1603,6 @@ parameter_types! { } impl pallet_opf::Config for Runtime { - type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type NativeBalance = Balances; @@ -1630,11 +1629,7 @@ impl pallet_opf::Config for Runtime { /// This should be calculated as a percentage of inflation. type TemporaryRewards = TemporaryRewards; - - type Preimages = Preimage; - - type Scheduler = Scheduler; - + type WeightInfo = (); //pallet_opf::weights::SubstrateWeight; } diff --git a/substrate/frame/opf/Cargo.toml b/substrate/frame/opf/Cargo.toml index db8d32613361..406abdb7b703 100644 --- a/substrate/frame/opf/Cargo.toml +++ b/substrate/frame/opf/Cargo.toml @@ -22,6 +22,7 @@ frame-support = { workspace = true, default-features = false } frame-system = { workspace = true, default-features = false } log = { workspace = true } pallet-conviction-voting = { workspace = true, default-features = false } +pallet-referenda = { workspace = true, default-features = true } pallet-scheduler = { workspace = true, default-features = false } scale-info = { features = [ "derive", @@ -52,6 +53,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", @@ -70,6 +72,7 @@ std = [ "pallet-balances/std", "pallet-conviction-voting/std", "pallet-preimage/std", + "pallet-referenda/std", "pallet-scheduler/std", "pallet-sudo/std", "pallet-timestamp/std", @@ -87,6 +90,7 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-preimage/try-runtime", + "pallet-referenda/try-runtime", "pallet-scheduler/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 7b6c9ce8aac7..e0c8a5c3f091 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -20,7 +20,7 @@ pub use pallet::*; mod functions; mod types; -pub use pallet_scheduler as Schedule; +pub use pallet_referenda as Referenda; pub use types::*; #[cfg(test)] @@ -38,13 +38,7 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeCall: Parameter - + Dispatchable - + From> - + IsType<::RuntimeCall> - + From>; - + pub trait Config: frame_system::Config + Referenda::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Type to access the Balances Pallet. @@ -57,22 +51,6 @@ pub mod pallet { #[pallet::constant] type PotId: Get; - /// The preimage provider. - type Preimages: QueryPreimage + StorePreimage; - - /// The Scheduler. - type Scheduler: ScheduleAnon< - ProvidedBlockNumberFor, - CallOf, - PalletsOriginOf, - Hasher = Self::Hashing, - > + ScheduleNamed< - ProvidedBlockNumberFor, - CallOf, - PalletsOriginOf, - Hasher = Self::Hashing, - >; - /// Time period in which people can vote. /// After the period has ended, the votes are counted (STOP THE COUNT) /// and then the funds are distributed into Spends. diff --git a/substrate/frame/opf/src/mock.rs b/substrate/frame/opf/src/mock.rs index c041669d5e19..8a3070584596 100644 --- a/substrate/frame/opf/src/mock.rs +++ b/substrate/frame/opf/src/mock.rs @@ -17,22 +17,55 @@ //! Test environment for OPF pallet. use crate as pallet_opf; +use codec::{Decode, Encode, MaxEncodedLen}; pub use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, EqualPrivilegeOnly, OnFinalize, OnInitialize}, + derive_impl, ord_parameter_types, + pallet_prelude::TypeInfo, + parameter_types, + traits::{ + ConstU32, ConstU64, EqualPrivilegeOnly, OnFinalize, OnInitialize, OriginTrait, VoteTally, + }, weights::Weight, PalletId, }; +pub use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_referenda::{impl_tracksinfo_get, Curve, TrackInfo, TracksInfo}; pub use sp_runtime::{ traits::{AccountIdConversion, IdentityLookup}, - BuildStorage, + BuildStorage, Perbill, }; - -pub use frame_system::EnsureRoot; pub type Block = frame_system::mocking::MockBlock; pub type Balance = u64; pub type AccountId = u64; +#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, MaxEncodedLen)] +pub struct Tally { + pub ayes: u32, + pub nays: u32, +} + +impl VoteTally for Tally { + fn new(_: Class) -> Self { + Self { ayes: 0, nays: 0 } + } + + fn ayes(&self, _: Class) -> u32 { + self.ayes + } + + fn support(&self, _: Class) -> Perbill { + Perbill::from_percent(self.ayes) + } + + fn approval(&self, _: Class) -> Perbill { + if self.ayes + self.nays > 0 { + Perbill::from_rational(self.ayes, self.ayes + self.nays) + } else { + Perbill::zero() + } + } +} + // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( pub struct Test { @@ -41,6 +74,7 @@ frame_support::construct_runtime!( Preimage: pallet_preimage, Scheduler: pallet_scheduler, Opf: pallet_opf, + Referenda: pallet_referenda, } ); @@ -82,14 +116,135 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub const PotId: PalletId = PalletId(*b"py/potid"); + pub static AlarmInterval: u64 = 1; +} +ord_parameter_types! { + pub const One: u64 = 1; + pub const Two: u64 = 2; + pub const Three: u64 = 3; + pub const Four: u64 = 4; + pub const Five: u64 = 5; + pub const Six: u64 = 6; +} + +pub struct TestTracksInfo; +impl TracksInfo for TestTracksInfo { + type Id = u8; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, TrackInfo)] { + static DATA: [(u8, TrackInfo); 3] = [ + ( + 0u8, + TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 10, + prepare_period: 4, + decision_period: 4, + confirm_period: 2, + min_enactment_period: 4, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }, + ), + ( + 1u8, + TrackInfo { + name: "none", + max_deciding: 3, + decision_deposit: 1, + prepare_period: 2, + decision_period: 2, + confirm_period: 1, + min_enactment_period: 2, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), + }, + }, + ), + ( + 2u8, + TrackInfo { + name: "none", + max_deciding: 3, + decision_deposit: 1, + prepare_period: 2, + decision_period: 2, + confirm_period: 1, + min_enactment_period: 0, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), + }, + }, + ), + ]; + &DATA[..] + } + fn track_for(id: &Self::RuntimeOrigin) -> Result { + if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { + match system_origin { + frame_system::RawOrigin::Root => Ok(0), + frame_system::RawOrigin::None => Ok(1), + frame_system::RawOrigin::Signed(1) => Ok(2), + _ => Err(()), + } + } else { + Err(()) + } + } +} +impl_tracksinfo_get!(TestTracksInfo, u64, u64); + +impl pallet_referenda::Config for Test { + type WeightInfo = (); + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = pallet_balances::Pallet; + type SubmitOrigin = frame_system::EnsureSigned; + type CancelOrigin = EnsureSignedBy; + type KillOrigin = EnsureRoot; + type Slash = (); + type Votes = u32; + type Tally = Tally; + type SubmissionDeposit = ConstU64<2>; + type MaxQueued = ConstU32<3>; + type UndecidingTimeout = ConstU64<20>; + type AlarmInterval = AlarmInterval; + type Tracks = TestTracksInfo; + type Preimages = Preimage; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"py/potid"); pub const MaxProjects:u32 = 50; pub const TemporaryRewards: Balance = 100_000; pub const VoteLockingPeriod:u32 = 10; pub const VotingPeriod:u32 = 30; } impl pallet_opf::Config for Test { - type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type NativeBalance = Balances; type PotId = PotId; @@ -99,8 +254,6 @@ impl pallet_opf::Config for Test { type VoteValidityPeriod = VotingPeriod; type BlockNumberProvider = System; type TemporaryRewards = TemporaryRewards; - type Preimages = Preimage; - type Scheduler = Scheduler; type WeightInfo = (); } @@ -134,4 +287,4 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext -} \ No newline at end of file +} diff --git a/substrate/frame/opf/src/tests.rs b/substrate/frame/opf/src/tests.rs index 74b2472a978d..f49dab30e1fd 100644 --- a/substrate/frame/opf/src/tests.rs +++ b/substrate/frame/opf/src/tests.rs @@ -22,9 +22,7 @@ use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::OnIdle}; pub fn next_block() { - System::set_block_number( - ::BlockNumberProvider::current_block_number() + 1, - ); + System::set_block_number(::BlockNumberProvider::current_block_number() + 1); AllPalletsWithSystem::on_initialize( ::BlockNumberProvider::current_block_number(), ); @@ -46,12 +44,10 @@ pub fn run_to_block(n: BlockNumberFor) { } #[test] -fn project_registration_works(){ - new_test_ext().execute_with(|| { - - assert_ok!(Opf::register_project(RawOrigin::Root.into(), BOB)); - let project_list = WhiteListedProjectAccounts::::get().to_vec(); - assert_eq!(project_list.len(),1); - - }) -} \ No newline at end of file +fn project_registration_works() { + new_test_ext().execute_with(|| { + assert_ok!(Opf::register_project(RawOrigin::Root.into(), BOB)); + let project_list = WhiteListedProjectAccounts::::get().to_vec(); + assert_eq!(project_list.len(), 1); + }) +} From 9ad6f9834d71729964d2897682a662ba2c93a03f Mon Sep 17 00:00:00 2001 From: ndkazu Date: Sat, 4 Jan 2025 22:32:08 +0900 Subject: [PATCH 16/22] Applied some corrections --- substrate/bin/node/runtime/src/lib.rs | 3 +++ substrate/frame/opf/src/functions.rs | 33 ++++++++++++--------------- substrate/frame/opf/src/lib.rs | 33 ++++++++++++++++++--------- substrate/frame/opf/src/mock.rs | 1 + substrate/frame/opf/src/tests.rs | 4 ++-- substrate/frame/opf/src/types.rs | 23 ++++++++----------- 6 files changed, 52 insertions(+), 45 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 847746d3fb5d..61e11a5a2eb8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1609,6 +1609,9 @@ impl pallet_opf::Config for Runtime { /// Pot PalletId type PotId = PotId; + /// A reason for placing a hold on funds. + type RuntimeHoldReason = RuntimeHoldReason; + /// Maximum number of whitelisted projects type MaxProjects = MaxProjects; diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 8a3b26cb9fce..2834208fb052 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -53,7 +53,7 @@ impl Pallet { // Voting Period checks pub fn period_check() -> DispatchResult { // Get current voting round & check if we are in voting period or not - let current_round_index = VotingRoundNumber::::get().saturating_sub(1); + let current_round_index = NextVotingRoundNumber::::get().saturating_sub(1); let round = VotingRounds::::get(current_round_index).ok_or(Error::::NoRoundFound)?; let now = T::BlockNumberProvider::current_block_number(); ensure!(now < round.round_ending_block, Error::::VotingRoundOver); @@ -61,11 +61,7 @@ impl Pallet { } pub fn unlist_project(project_id: ProjectId) -> DispatchResult { - WhiteListedProjectAccounts::::mutate(|value| { - let mut val = value.clone(); - val.retain(|x| x.project_id != project_id); - *value = val; - }); + WhiteListedProjectAccounts::::remove(&project_id); Ok(()) } @@ -73,8 +69,9 @@ impl Pallet { // The total reward to be distributed is a portion or inflation, determined in another pallet // Reward calculation is executed within the Voting period pub fn calculate_rewards(total_reward: BalanceOf) -> DispatchResult { - let projects = WhiteListedProjectAccounts::::get(); - let round_number = VotingRoundNumber::::get().saturating_sub(1); + let projects: Vec> = WhiteListedProjectAccounts::::iter_keys().collect(); + //if projects.is_empty() { return Ok(()) } + let round_number = NextVotingRoundNumber::::get().saturating_sub(1); let round = VotingRounds::::get(round_number).ok_or(Error::::NoRoundFound)?; if projects.clone().len() > 0 as usize { let total_positive_votes_amount = round.total_positive_votes_amount; @@ -85,10 +82,9 @@ impl Pallet { // for each project, calculate the percentage of votes, the amount to be distributed, // and then populate the storage Projects - for project in projects { - let project_id = &project.project_id; - if ProjectFunds::::contains_key(project_id) { - let funds = ProjectFunds::::get(project_id); + for project_id in projects { + if ProjectFunds::::contains_key(&project_id) { + let funds = ProjectFunds::::get(&project_id); let project_positive_reward = funds[0]; let project_negative_reward = funds[1]; @@ -102,7 +98,7 @@ impl Pallet { // Send calculated reward for reward distribution let project_info = ProjectInfo { - project_id: project.project_id.clone(), + project_id: project_id.clone(), submission_block: when, amount: final_amount, }; @@ -111,7 +107,7 @@ impl Pallet { let _ = SpendInfo::::new(&project_info); Self::deposit_event(Event::::ProjectFundingAccepted { - project_id: project.project_id, + project_id: project_id.clone(), when, round_number, amount: project_info.amount, @@ -119,7 +115,7 @@ impl Pallet { } else { Self::deposit_event(Event::::ProjectFundingRejected { when, - project_id: project.project_id, + project_id: project_id.clone(), }) } } @@ -138,13 +134,13 @@ impl Pallet { if meter.try_consume(max_block_weight).is_err() { return meter.consumed(); } - let mut round_index = VotingRoundNumber::::get(); + let mut round_index = NextVotingRoundNumber::::get(); // No active round? if round_index == 0 { // Start the first voting round let _round0 = VotingRoundInfo::::new(); - round_index = VotingRoundNumber::::get(); + round_index = NextVotingRoundNumber::::get(); } let current_round_index = round_index.saturating_sub(1); @@ -154,8 +150,7 @@ impl Pallet { // Conditions for reward distribution preparations are: // - We are at the end of voting_round period - - if now == round_ending_block { + if now >= round_ending_block { // prepare reward distribution // for now we are using the temporary-constant reward. let _ = Self::calculate_rewards(T::TemporaryRewards::get()) diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index e0c8a5c3f091..2a7c6cc21c63 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -42,8 +42,14 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Type to access the Balances Pallet. - type NativeBalance: fungible::Inspect + fungible::Mutate; - + type NativeBalance: fungible::Inspect + + fungible::Mutate + + fungible::hold::Inspect + + fungible::hold::Mutate + + fungible::freeze::Inspect + + fungible::freeze::Mutate; + + type RuntimeHoldReason: From; /// Provider for the block number. type BlockNumberProvider: BlockNumberProvider; @@ -78,9 +84,17 @@ pub mod pallet { type WeightInfo: WeightInfo; } + /// A reason for placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Funds are held for a given buffer time before payment + #[codec(index = 0)] + FundsReserved, + } + /// Number of Voting Rounds executed so far #[pallet::storage] - pub type VotingRoundNumber = StorageValue<_, u32, ValueQuery>; + pub type NextVotingRoundNumber = StorageValue<_, u32, ValueQuery>; /// Returns Infos about a Voting Round agains the Voting Round index #[pallet::storage] @@ -95,7 +109,7 @@ pub mod pallet { /// List of Whitelisted Project registered #[pallet::storage] pub type WhiteListedProjectAccounts = - StorageValue<_, BoundedVec, T::MaxProjects>, ValueQuery>; + CountedStorageMap<_, Twox64Concat, ProjectId, ProjectInfo, OptionQuery>; /// Returns (positive_funds,negative_funds) of Whitelisted Project accounts #[pallet::storage] @@ -229,7 +243,6 @@ pub mod pallet { /// ## Events /// Emits [`Event::::Projectlisted`]. #[pallet::call_index(0)] - #[transactional] pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { let _caller = ensure_root(origin)?; let when = T::BlockNumberProvider::current_block_number(); @@ -308,16 +321,14 @@ pub mod pallet { let _caller = ensure_signed(origin)?; let now = T::BlockNumberProvider::current_block_number(); let info = Spends::::get(&project_id).ok_or(Error::::InexistentSpend)?; - match now { - _ if now >= info.expire => { + if now >= info.expire { Spends::::remove(&project_id); Self::deposit_event(Event::ExpiredClaim { expired_when: info.expire, project_id, }); Ok(()) - }, - _ if now >= info.expire => { + } else if now < info.expire { // transfer the funds Self::spend(info.amount, project_id.clone())?; @@ -327,8 +338,8 @@ pub mod pallet { project_id, }); Ok(()) - }, - _ => Err(DispatchError::Other("Not Claiming Period")), + } else { + Err(DispatchError::Other("Not Claiming Period")) } } } diff --git a/substrate/frame/opf/src/mock.rs b/substrate/frame/opf/src/mock.rs index 8a3070584596..7f68aaa5c6f8 100644 --- a/substrate/frame/opf/src/mock.rs +++ b/substrate/frame/opf/src/mock.rs @@ -248,6 +248,7 @@ impl pallet_opf::Config for Test { type RuntimeEvent = RuntimeEvent; type NativeBalance = Balances; type PotId = PotId; + type RuntimeHoldReason = RuntimeHoldReason; type MaxProjects = MaxProjects; type VotingPeriod = VotingPeriod; type ClaimingPeriod = VotingPeriod; diff --git a/substrate/frame/opf/src/tests.rs b/substrate/frame/opf/src/tests.rs index f49dab30e1fd..ce7975346407 100644 --- a/substrate/frame/opf/src/tests.rs +++ b/substrate/frame/opf/src/tests.rs @@ -47,7 +47,7 @@ pub fn run_to_block(n: BlockNumberFor) { fn project_registration_works() { new_test_ext().execute_with(|| { assert_ok!(Opf::register_project(RawOrigin::Root.into(), BOB)); - let project_list = WhiteListedProjectAccounts::::get().to_vec(); - assert_eq!(project_list.len(), 1); + let project_list = WhiteListedProjectAccounts::::get(BOB); + assert!(project_list.is_some()); }) } diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index f3667600a622..59c394dd589a 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -129,13 +129,10 @@ impl ProjectInfo { pub fn new(project_id: ProjectId) { let submission_block = T::BlockNumberProvider::current_block_number(); let amount = Zero::zero(); - let project_info = ProjectInfo { project_id, submission_block, amount }; - WhiteListedProjectAccounts::::mutate(|project| { - let _ = project - .try_push(project_info.clone()) - .map_err(|_| Error::::MaximumProjectsNumber); - }); + let project_info = ProjectInfo { project_id: project_id.clone(), submission_block, amount }; + WhiteListedProjectAccounts::::insert(project_id, project_info); } + } #[derive(Encode, Decode, Clone, PartialEq, MaxEncodedLen, RuntimeDebug, TypeInfo)] @@ -158,8 +155,11 @@ pub struct VoteInfo { // If no conviction, user's funds are released at the end of the voting round impl VoteInfo { pub fn funds_unlock(&mut self) { - //let conviction_coeff = >::from(self.conviction); - let funds_unlock_block = self.round.round_ending_block; + let conviction_coeff = >::from(self.conviction); + let funds_unlock_block = self + .round + .round_ending_block + .saturating_add(T::VoteValidityPeriod::get().saturating_mul(conviction_coeff.into())); self.funds_unlock_block = funds_unlock_block; } } @@ -193,11 +193,8 @@ impl VotingRoundInfo { let round_starting_block = T::BlockNumberProvider::current_block_number(); let round_ending_block = round_starting_block .clone() - .checked_add(&T::VotingPeriod::get()) - .expect("Invalid Result"); - let round_number = VotingRoundNumber::::get(); - let new_number = round_number.checked_add(1).expect("Invalid Result"); - VotingRoundNumber::::put(new_number); + .saturating_add(T::VotingPeriod::get()); + let round_number = NextVotingRoundNumber::::mutate(|n| { let res = *n; *n = n.saturating_add(1); res }); let total_positive_votes_amount = BalanceOf::::zero(); let total_negative_votes_amount = BalanceOf::::zero(); From 2d39b0cb3245a31052b4310125348c633b7389e5 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Sat, 4 Jan 2025 22:47:02 +0900 Subject: [PATCH 17/22] Custom Origin --- substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/frame/opf/src/lib.rs | 7 +++++-- substrate/frame/opf/src/mock.rs | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 61e11a5a2eb8..c9919bde9b76 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1605,7 +1605,7 @@ parameter_types! { impl pallet_opf::Config for Runtime { type RuntimeEvent = RuntimeEvent; type NativeBalance = Balances; - + type AdminOrigin = EnsureRoot; /// Pot PalletId type PotId = PotId; diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 2a7c6cc21c63..bedf28366473 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -41,6 +41,9 @@ pub mod pallet { pub trait Config: frame_system::Config + Referenda::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The admin origin that can list and un-list whitelisted projects. + type AdminOrigin: EnsureOrigin; + /// Type to access the Balances Pallet. type NativeBalance: fungible::Inspect + fungible::Mutate @@ -244,7 +247,7 @@ pub mod pallet { /// Emits [`Event::::Projectlisted`]. #[pallet::call_index(0)] pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { - let _caller = ensure_root(origin)?; + T::AdminOrigin::ensure_origin_or_root(origin)?; let when = T::BlockNumberProvider::current_block_number(); ProjectInfo::::new(project_id.clone()); Self::deposit_event(Event::Projectlisted { when, project_id }); @@ -275,7 +278,7 @@ pub mod pallet { origin: OriginFor, project_id: ProjectId, ) -> DispatchResult { - ensure_root(origin)?; + T::AdminOrigin::ensure_origin_or_root(origin)?; let when = T::BlockNumberProvider::current_block_number(); Self::unlist_project(project_id.clone())?; Self::deposit_event(Event::::ProjectUnlisted { when, project_id }); diff --git a/substrate/frame/opf/src/mock.rs b/substrate/frame/opf/src/mock.rs index 7f68aaa5c6f8..403e41215e29 100644 --- a/substrate/frame/opf/src/mock.rs +++ b/substrate/frame/opf/src/mock.rs @@ -246,6 +246,7 @@ parameter_types! { } impl pallet_opf::Config for Test { type RuntimeEvent = RuntimeEvent; + type AdminOrigin = frame_system::EnsureRoot; type NativeBalance = Balances; type PotId = PotId; type RuntimeHoldReason = RuntimeHoldReason; From 21a3169cac22d2bef3319fe1e9be8ee8f531cd5c Mon Sep 17 00:00:00 2001 From: ndkazu Date: Sun, 5 Jan 2025 16:44:18 +0900 Subject: [PATCH 18/22] Batch registration --- substrate/frame/opf/src/lib.rs | 53 ++++++++++++++++++++++++++------ substrate/frame/opf/src/types.rs | 2 +- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index bedf28366473..13d497d45235 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -159,6 +159,9 @@ pub mod pallet { /// Project added to whitelisted projects list Projectlisted { when: ProvidedBlockNumberFor, project_id: ProjectId }, + /// Several projects added to whitelisted projects list + Projectslisted { when: ProvidedBlockNumberFor, projects_id: Vec> }, + /// Project removed from whitelisted projects list ProjectUnlisted { when: ProvidedBlockNumberFor, project_id: ProjectId }, @@ -231,14 +234,14 @@ pub mod pallet { /// /// ## Dispatch Origin /// - /// Must be signed + /// Must be AdminOrigin /// /// ## Details /// - /// From this extrinsic only Root can register project. + /// From this extrinsic only AdminOrigin can register project. /// /// ### Parameters - /// - `project_id`: The account that will receive the reward. + /// - `project_id`: The account that might be funded. /// /// ### Errors /// - [`Error::::MaximumProjectsNumber`]: Maximum number of project subscriptions reached @@ -254,6 +257,36 @@ pub mod pallet { Ok(()) } + /// OPF Projects registration + /// + /// ## Dispatch Origin + /// + /// Must be AdminOrigin + /// + /// ## Details + /// + /// From this extrinsic only AdminOrigin can register project. + /// + /// ### Parameters + /// - `projects_id`: The accounts that might be funded. + /// + /// ### Errors + /// - [`Error::::MaximumProjectsNumber`]: Maximum number of project subscriptions reached + /// + /// ## Events + /// Emits [`Event::::Projectslisted`]. + #[pallet::call_index(1)] + pub fn register_projects_batch(origin: OriginFor, projects_id: Vec>) -> DispatchResult { + T::AdminOrigin::ensure_origin_or_root(origin)?; + let when = T::BlockNumberProvider::current_block_number(); + for project_id in &projects_id{ + ProjectInfo::::new(project_id.clone()); + } + + Self::deposit_event(Event::Projectslisted { when, projects_id }); + Ok(()) + } + /// OPF Projects de-listing /// /// ## Dispatch Origin @@ -272,7 +305,7 @@ pub mod pallet { /// /// ## Events /// Emits [`Event::::ProjectUnlisted`]. - #[pallet::call_index(1)] + #[pallet::call_index(2)] #[transactional] pub fn unregister_project( origin: OriginFor, @@ -286,13 +319,13 @@ pub mod pallet { Ok(()) } - #[pallet::call_index(2)] + #[pallet::call_index(3)] #[transactional] pub fn vote(origin: OriginFor) -> DispatchResult { Ok(()) } - #[pallet::call_index(3)] + #[pallet::call_index(4)] #[transactional] pub fn remove_vote(origin: OriginFor) -> DispatchResult { Ok(()) @@ -318,7 +351,7 @@ pub mod pallet { /// /// ## Events /// Emits [`Event::::RewardClaimed`] if successful for a positive approval. - #[pallet::call_index(4)] + #[pallet::call_index(5)] #[transactional] pub fn claim_reward_for(origin: OriginFor, project_id: ProjectId) -> DispatchResult { let _caller = ensure_signed(origin)?; @@ -333,13 +366,13 @@ pub mod pallet { Ok(()) } else if now < info.expire { // transfer the funds - Self::spend(info.amount, project_id.clone())?; - + Self::spend(info.amount, project_id.clone())?; Self::deposit_event(Event::RewardClaimed { when: now, amount: info.amount, - project_id, + project_id: project_id.clone(), }); + Self::unlist_project(project_id)?; Ok(()) } else { Err(DispatchError::Other("Not Claiming Period")) diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 59c394dd589a..4a10d0756d8a 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -121,7 +121,7 @@ pub struct ProjectInfo { /// Block at which the project was submitted for reward distribution pub submission_block: ProvidedBlockNumberFor, - /// Amount to be lock & pay for this project + /// Amount to be locked & payed for this project pub amount: BalanceOf, } From 78454bc75ea32bf91e52c752ec80b36ae9e455fa Mon Sep 17 00:00:00 2001 From: ndkazu Date: Tue, 7 Jan 2025 13:22:25 +0900 Subject: [PATCH 19/22] Correction --- substrate/frame/opf/src/functions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 2834208fb052..36e1c811a2ad 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -70,7 +70,7 @@ impl Pallet { // Reward calculation is executed within the Voting period pub fn calculate_rewards(total_reward: BalanceOf) -> DispatchResult { let projects: Vec> = WhiteListedProjectAccounts::::iter_keys().collect(); - //if projects.is_empty() { return Ok(()) } + if projects.is_empty() { return Ok(()) } let round_number = NextVotingRoundNumber::::get().saturating_sub(1); let round = VotingRounds::::get(round_number).ok_or(Error::::NoRoundFound)?; if projects.clone().len() > 0 as usize { From 367d7bb226e321528df10fd1bb322e68e00c9250 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Tue, 7 Jan 2025 16:57:09 +0900 Subject: [PATCH 20/22] Using pallet democracy instead of pallet referenda --- Cargo.lock | 2 +- substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/frame/opf/Cargo.toml | 8 +- substrate/frame/opf/src/functions.rs | 10 +- substrate/frame/opf/src/lib.rs | 78 ++++++----- substrate/frame/opf/src/mock.rs | 184 +++++++------------------- substrate/frame/opf/src/tests.rs | 8 +- substrate/frame/opf/src/types.rs | 7 +- 8 files changed, 120 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dedd56da42d8..516d06e5d051 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14555,8 +14555,8 @@ dependencies = [ "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-conviction-voting 28.0.0", + "pallet-democracy 28.0.0", "pallet-preimage 28.0.0", - "pallet-referenda 28.0.0", "pallet-scheduler 29.0.0", "pallet-sudo 28.0.0", "pallet-timestamp 27.0.0", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index cdf4f139ce04..80fc5d88047a 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1593,7 +1593,6 @@ impl pallet_offences::Config for Runtime { parameter_types! { // Id of the treasury pub const PotId: PalletId = PalletId(*b"py/potid"); - pub const VotingPeriod:BlockNumber = 30 * DAYS; pub const ClaimingPeriod: BlockNumber = 7 * DAYS; pub const VoteValidityPeriod: BlockNumber = 7 * DAYS; pub const MaxProjects:u32 = 50; @@ -1605,6 +1604,7 @@ parameter_types! { } impl pallet_opf::Config for Runtime { type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type NativeBalance = Balances; type AdminOrigin = EnsureRoot; /// Pot PalletId diff --git a/substrate/frame/opf/Cargo.toml b/substrate/frame/opf/Cargo.toml index 406abdb7b703..d2c7c662d39b 100644 --- a/substrate/frame/opf/Cargo.toml +++ b/substrate/frame/opf/Cargo.toml @@ -22,7 +22,7 @@ frame-support = { workspace = true, default-features = false } frame-system = { workspace = true, default-features = false } log = { workspace = true } pallet-conviction-voting = { workspace = true, default-features = false } -pallet-referenda = { workspace = true, default-features = true } +pallet-democracy = { workspace = true, default-features = true } pallet-scheduler = { workspace = true, default-features = false } scale-info = { features = [ "derive", @@ -52,8 +52,8 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", + "pallet-democracy/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", - "pallet-referenda/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", @@ -71,8 +71,8 @@ std = [ "pallet-assets/std", "pallet-balances/std", "pallet-conviction-voting/std", + "pallet-democracy/std", "pallet-preimage/std", - "pallet-referenda/std", "pallet-scheduler/std", "pallet-sudo/std", "pallet-timestamp/std", @@ -89,8 +89,8 @@ try-runtime = [ "pallet-assets/try-runtime", "pallet-balances/try-runtime", "pallet-conviction-voting/try-runtime", + "pallet-democracy/try-runtime", "pallet-preimage/try-runtime", - "pallet-referenda/try-runtime", "pallet-scheduler/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", diff --git a/substrate/frame/opf/src/functions.rs b/substrate/frame/opf/src/functions.rs index 36e1c811a2ad..863cddad927f 100644 --- a/substrate/frame/opf/src/functions.rs +++ b/substrate/frame/opf/src/functions.rs @@ -150,14 +150,8 @@ impl Pallet { // Conditions for reward distribution preparations are: // - We are at the end of voting_round period - if now >= round_ending_block { - // prepare reward distribution - // for now we are using the temporary-constant reward. - let _ = Self::calculate_rewards(T::TemporaryRewards::get()) - .map_err(|_| Error::::FailedRewardCalculation); - - // Create a new round. - let _new_round = VotingRoundInfo::::new(); + if now > round_ending_block { + // Clear ProjectFunds storage ProjectFunds::::drain(); // Emmit events diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 13d497d45235..5342d66b486d 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -20,7 +20,7 @@ pub use pallet::*; mod functions; mod types; -pub use pallet_referenda as Referenda; +pub use pallet_democracy as Democracy; pub use types::*; #[cfg(test)] @@ -38,9 +38,13 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + Referenda::Config { + pub trait Config: frame_system::Config + Democracy::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - + type RuntimeCall: Convert<::RuntimeCall, ::RuntimeCall> + + Parameter + + UnfilteredDispatchable::RuntimeOrigin> + + From> + + GetDispatchInfo; /// The admin origin that can list and un-list whitelisted projects. type AdminOrigin: EnsureOrigin; @@ -219,6 +223,8 @@ pub mod pallet { MaximumProjectsNumber, /// Another project has already been submitted under the same project_id SubmittedProjectId, + /// Project batch already submitted + BatchAlreadySubmitted } #[pallet::hooks] @@ -230,32 +236,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// OPF Projects registration - /// - /// ## Dispatch Origin - /// - /// Must be AdminOrigin - /// - /// ## Details - /// - /// From this extrinsic only AdminOrigin can register project. - /// - /// ### Parameters - /// - `project_id`: The account that might be funded. - /// - /// ### Errors - /// - [`Error::::MaximumProjectsNumber`]: Maximum number of project subscriptions reached - /// - /// ## Events - /// Emits [`Event::::Projectlisted`]. - #[pallet::call_index(0)] - pub fn register_project(origin: OriginFor, project_id: ProjectId) -> DispatchResult { - T::AdminOrigin::ensure_origin_or_root(origin)?; - let when = T::BlockNumberProvider::current_block_number(); - ProjectInfo::::new(project_id.clone()); - Self::deposit_event(Event::Projectlisted { when, project_id }); - Ok(()) - } /// OPF Projects registration /// @@ -277,10 +257,39 @@ pub mod pallet { /// Emits [`Event::::Projectslisted`]. #[pallet::call_index(1)] pub fn register_projects_batch(origin: OriginFor, projects_id: Vec>) -> DispatchResult { - T::AdminOrigin::ensure_origin_or_root(origin)?; + //T::AdminOrigin::ensure_origin_or_root(origin.clone())?; + let who = T::SubmitOrigin::ensure_origin(origin.clone())?; + // Only 1 batch submission per round + let mut round_index = NextVotingRoundNumber::::get(); + + // No active round? + if round_index == 0 { + // Start the first voting round + let _round0 = VotingRoundInfo::::new(); + round_index = NextVotingRoundNumber::::get(); + } + + let current_round_index = round_index.saturating_sub(1); + + let round_infos = VotingRounds::::get(current_round_index).expect("InvalidResult"); + // Check no Project batch has been submitted yet + ensure!(round_infos.batch_submitted == false, Error::::BatchAlreadySubmitted); + let round_ending_block = round_infos.round_ending_block; + + // If current voting round is over, start a new one let when = T::BlockNumberProvider::current_block_number(); + if when >= round_ending_block { + // Create a new round. + let _new_round = VotingRoundInfo::::new(); + } + for project_id in &projects_id{ ProjectInfo::::new(project_id.clone()); + // Prepare the proposal call + let call0: ::RuntimeCall = crate::Call::::on_registration {project_id: project_id.clone()}.into(); + let call = ::RuntimeCall::convert(call0); + let call_f = T::Preimages::bound(call).unwrap(); + Democracy::Pallet::::propose(origin.clone(), call_f, T::MinimumDeposit::get())?; } Self::deposit_event(Event::Projectslisted { when, projects_id }); @@ -378,5 +387,14 @@ pub mod pallet { Err(DispatchError::Other("Not Claiming Period")) } } + + #[pallet::call_index(6)] + #[transactional] + pub fn on_registration(origin: OriginFor, project_id: ProjectId) -> DispatchResult { + // prepare reward distribution + // for now we are using the temporary-constant reward. + let _ = Self::calculate_rewards(T::TemporaryRewards::get())?; + Ok(()) + } } } diff --git a/substrate/frame/opf/src/mock.rs b/substrate/frame/opf/src/mock.rs index 403e41215e29..e6bbb19cb443 100644 --- a/substrate/frame/opf/src/mock.rs +++ b/substrate/frame/opf/src/mock.rs @@ -18,18 +18,18 @@ //! Test environment for OPF pallet. use crate as pallet_opf; use codec::{Decode, Encode, MaxEncodedLen}; +use crate::Convert; pub use frame_support::{ derive_impl, ord_parameter_types, pallet_prelude::TypeInfo, parameter_types, traits::{ - ConstU32, ConstU64, EqualPrivilegeOnly, OnFinalize, OnInitialize, OriginTrait, VoteTally, + ConstU32, ConstU64, SortedMembers, EqualPrivilegeOnly, OnFinalize, OnInitialize, OriginTrait, VoteTally, }, weights::Weight, PalletId, }; -pub use frame_system::{EnsureRoot, EnsureSignedBy}; -use pallet_referenda::{impl_tracksinfo_get, Curve, TrackInfo, TracksInfo}; +pub use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; pub use sp_runtime::{ traits::{AccountIdConversion, IdentityLookup}, BuildStorage, Perbill, @@ -38,33 +38,6 @@ pub type Block = frame_system::mocking::MockBlock; pub type Balance = u64; pub type AccountId = u64; -#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, MaxEncodedLen)] -pub struct Tally { - pub ayes: u32, - pub nays: u32, -} - -impl VoteTally for Tally { - fn new(_: Class) -> Self { - Self { ayes: 0, nays: 0 } - } - - fn ayes(&self, _: Class) -> u32 { - self.ayes - } - - fn support(&self, _: Class) -> Perbill { - Perbill::from_percent(self.ayes) - } - - fn approval(&self, _: Class) -> Perbill { - if self.ayes + self.nays > 0 { - Perbill::from_rational(self.ayes, self.ayes + self.nays) - } else { - Perbill::zero() - } - } -} // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -74,7 +47,7 @@ frame_support::construct_runtime!( Preimage: pallet_preimage, Scheduler: pallet_scheduler, Opf: pallet_opf, - Referenda: pallet_referenda, + Democracy: pallet_democracy, } ); @@ -116,7 +89,8 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub static AlarmInterval: u64 = 1; + pub static PreimageByteDeposit: u64 = 0; + pub static InstantAllowed: bool = false; } ord_parameter_types! { pub const One: u64 = 1; @@ -127,113 +101,44 @@ ord_parameter_types! { pub const Six: u64 = 6; } -pub struct TestTracksInfo; -impl TracksInfo for TestTracksInfo { - type Id = u8; - type RuntimeOrigin = ::PalletsOrigin; - fn tracks() -> &'static [(Self::Id, TrackInfo)] { - static DATA: [(u8, TrackInfo); 3] = [ - ( - 0u8, - TrackInfo { - name: "root", - max_deciding: 1, - decision_deposit: 10, - prepare_period: 4, - decision_period: 4, - confirm_period: 2, - min_enactment_period: 4, - min_approval: Curve::LinearDecreasing { - length: Perbill::from_percent(100), - floor: Perbill::from_percent(50), - ceil: Perbill::from_percent(100), - }, - min_support: Curve::LinearDecreasing { - length: Perbill::from_percent(100), - floor: Perbill::from_percent(0), - ceil: Perbill::from_percent(100), - }, - }, - ), - ( - 1u8, - TrackInfo { - name: "none", - max_deciding: 3, - decision_deposit: 1, - prepare_period: 2, - decision_period: 2, - confirm_period: 1, - min_enactment_period: 2, - min_approval: Curve::LinearDecreasing { - length: Perbill::from_percent(100), - floor: Perbill::from_percent(95), - ceil: Perbill::from_percent(100), - }, - min_support: Curve::LinearDecreasing { - length: Perbill::from_percent(100), - floor: Perbill::from_percent(90), - ceil: Perbill::from_percent(100), - }, - }, - ), - ( - 2u8, - TrackInfo { - name: "none", - max_deciding: 3, - decision_deposit: 1, - prepare_period: 2, - decision_period: 2, - confirm_period: 1, - min_enactment_period: 0, - min_approval: Curve::LinearDecreasing { - length: Perbill::from_percent(100), - floor: Perbill::from_percent(95), - ceil: Perbill::from_percent(100), - }, - min_support: Curve::LinearDecreasing { - length: Perbill::from_percent(100), - floor: Perbill::from_percent(90), - ceil: Perbill::from_percent(100), - }, - }, - ), - ]; - &DATA[..] - } - fn track_for(id: &Self::RuntimeOrigin) -> Result { - if let Ok(system_origin) = frame_system::RawOrigin::try_from(id.clone()) { - match system_origin { - frame_system::RawOrigin::Root => Ok(0), - frame_system::RawOrigin::None => Ok(1), - frame_system::RawOrigin::Signed(1) => Ok(2), - _ => Err(()), - } - } else { - Err(()) - } +pub struct OneToFive; +impl SortedMembers for OneToFive { + fn sorted_members() -> Vec { + vec![1, 2, 3, 4, 5] } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} } -impl_tracksinfo_get!(TestTracksInfo, u64, u64); -impl pallet_referenda::Config for Test { - type WeightInfo = (); - type RuntimeCall = RuntimeCall; +impl pallet_democracy::Config for Test { type RuntimeEvent = RuntimeEvent; - type Scheduler = Scheduler; type Currency = pallet_balances::Pallet; - type SubmitOrigin = frame_system::EnsureSigned; - type CancelOrigin = EnsureSignedBy; - type KillOrigin = EnsureRoot; + type EnactmentPeriod = ConstU64<2>; + type LaunchPeriod = ConstU64<2>; + type VotingPeriod = ConstU64<2>; + type VoteLockingPeriod = ConstU64<3>; + type FastTrackVotingPeriod = ConstU64<2>; + type MinimumDeposit = ConstU64<1>; + type MaxDeposits = ConstU32<1000>; + type MaxBlacklisted = ConstU32<5>; + type SubmitOrigin = EnsureSigned; + type ExternalOrigin = EnsureSignedBy; + type ExternalMajorityOrigin = EnsureSignedBy; + type ExternalDefaultOrigin = EnsureSignedBy; + type FastTrackOrigin = EnsureSignedBy; + type CancellationOrigin = EnsureSignedBy; + type BlacklistOrigin = EnsureRoot; + type CancelProposalOrigin = EnsureRoot; + type VetoOrigin = EnsureSignedBy; + type CooloffPeriod = ConstU64<2>; type Slash = (); - type Votes = u32; - type Tally = Tally; - type SubmissionDeposit = ConstU64<2>; - type MaxQueued = ConstU32<3>; - type UndecidingTimeout = ConstU64<20>; - type AlarmInterval = AlarmInterval; - type Tracks = TestTracksInfo; + type InstantOrigin = EnsureSignedBy; + type InstantAllowed = InstantAllowed; + type Scheduler = Scheduler; + type MaxVotes = ConstU32<100>; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); + type MaxProposals = ConstU32<100>; type Preimages = Preimage; } @@ -246,6 +151,7 @@ parameter_types! { } impl pallet_opf::Config for Test { type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type AdminOrigin = frame_system::EnsureRoot; type NativeBalance = Balances; type PotId = PotId; @@ -259,6 +165,18 @@ impl pallet_opf::Config for Test { type WeightInfo = (); } +impl Convert for RuntimeCall { + fn convert(call: RuntimeCall) -> RuntimeCall { + let call_encoded: Vec = call.encode(); + let ref_call_encoded = &call_encoded; + if let Ok(call_formatted) = RuntimeCall::decode(&mut &ref_call_encoded[..]){ + call_formatted + } else{ + call + } + } +} + //Define some accounts and use them pub const ALICE: AccountId = 10; pub const BOB: AccountId = 11; diff --git a/substrate/frame/opf/src/tests.rs b/substrate/frame/opf/src/tests.rs index ce7975346407..dcd42430d054 100644 --- a/substrate/frame/opf/src/tests.rs +++ b/substrate/frame/opf/src/tests.rs @@ -32,6 +32,11 @@ pub fn next_block() { ); } +pub fn project_list() -> Vec>{ + vec![ALICE, BOB, DAVE] + +} + pub fn run_to_block(n: BlockNumberFor) { while ::BlockNumberProvider::current_block_number() < n { if ::BlockNumberProvider::current_block_number() > 1 { @@ -46,7 +51,8 @@ pub fn run_to_block(n: BlockNumberFor) { #[test] fn project_registration_works() { new_test_ext().execute_with(|| { - assert_ok!(Opf::register_project(RawOrigin::Root.into(), BOB)); + let batch = project_list(); + assert_ok!(Opf::register_projects_batch(RuntimeOrigin::signed(EVE), batch)); let project_list = WhiteListedProjectAccounts::::get(BOB); assert!(project_list.is_some()); }) diff --git a/substrate/frame/opf/src/types.rs b/substrate/frame/opf/src/types.rs index 4a10d0756d8a..ae6ac1581654 100644 --- a/substrate/frame/opf/src/types.rs +++ b/substrate/frame/opf/src/types.rs @@ -21,6 +21,8 @@ pub use super::*; pub use frame_support::{ pallet_prelude::*, + traits::UnfilteredDispatchable, + dispatch::GetDispatchInfo, traits::{ fungible, fungible::{Inspect, Mutate, MutateHold}, @@ -186,14 +188,16 @@ pub struct VotingRoundInfo { pub round_ending_block: ProvidedBlockNumberFor, pub total_positive_votes_amount: BalanceOf, pub total_negative_votes_amount: BalanceOf, + pub batch_submitted: bool, } impl VotingRoundInfo { pub fn new() -> Self { let round_starting_block = T::BlockNumberProvider::current_block_number(); + let batch_submitted = false; let round_ending_block = round_starting_block .clone() - .saturating_add(T::VotingPeriod::get()); + .saturating_add(::VotingPeriod::get()); let round_number = NextVotingRoundNumber::::mutate(|n| { let res = *n; *n = n.saturating_add(1); res }); let total_positive_votes_amount = BalanceOf::::zero(); let total_negative_votes_amount = BalanceOf::::zero(); @@ -209,6 +213,7 @@ impl VotingRoundInfo { round_ending_block, total_positive_votes_amount, total_negative_votes_amount, + batch_submitted, }; VotingRounds::::insert(round_number, round_infos.clone()); round_infos From d3b9928eb18783120f549975a1a04ed34c5b5f20 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Tue, 7 Jan 2025 17:28:59 +0900 Subject: [PATCH 21/22] Referendums started on project batch submission --- substrate/frame/opf/src/lib.rs | 6 +++++- substrate/frame/opf/src/tests.rs | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index 5342d66b486d..d5701080f36e 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -289,7 +289,11 @@ pub mod pallet { let call0: ::RuntimeCall = crate::Call::::on_registration {project_id: project_id.clone()}.into(); let call = ::RuntimeCall::convert(call0); let call_f = T::Preimages::bound(call).unwrap(); - Democracy::Pallet::::propose(origin.clone(), call_f, T::MinimumDeposit::get())?; + let call_hash = call_f.hash(); + let threshold = Democracy::VoteThreshold::SimpleMajority; + Democracy::Pallet::::propose(origin.clone(), call_f.clone(), T::MinimumDeposit::get())?; + Democracy::Pallet::::internal_start_referendum(call_f,threshold, Zero::zero()); + } Self::deposit_event(Event::Projectslisted { when, projects_id }); diff --git a/substrate/frame/opf/src/tests.rs b/substrate/frame/opf/src/tests.rs index dcd42430d054..18a3b29baad0 100644 --- a/substrate/frame/opf/src/tests.rs +++ b/substrate/frame/opf/src/tests.rs @@ -55,5 +55,8 @@ fn project_registration_works() { assert_ok!(Opf::register_projects_batch(RuntimeOrigin::signed(EVE), batch)); let project_list = WhiteListedProjectAccounts::::get(BOB); assert!(project_list.is_some()); + // we should have 3 referendum started + assert_eq!(pallet_democracy::PublicProps::::get().len(), 3); + assert_eq!(pallet_democracy::ReferendumCount::::get(), 3); }) } From 1e317a52240b59e814731c2bc07e32d359271d05 Mon Sep 17 00:00:00 2001 From: ndkazu Date: Thu, 9 Jan 2025 13:18:37 +0900 Subject: [PATCH 22/22] Remove unused variable --- substrate/frame/opf/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/substrate/frame/opf/src/lib.rs b/substrate/frame/opf/src/lib.rs index d5701080f36e..9858033cb899 100644 --- a/substrate/frame/opf/src/lib.rs +++ b/substrate/frame/opf/src/lib.rs @@ -289,7 +289,6 @@ pub mod pallet { let call0: ::RuntimeCall = crate::Call::::on_registration {project_id: project_id.clone()}.into(); let call = ::RuntimeCall::convert(call0); let call_f = T::Preimages::bound(call).unwrap(); - let call_hash = call_f.hash(); let threshold = Democracy::VoteThreshold::SimpleMajority; Democracy::Pallet::::propose(origin.clone(), call_f.clone(), T::MinimumDeposit::get())?; Democracy::Pallet::::internal_start_referendum(call_f,threshold, Zero::zero());