From 884c8be4c84135bb7967c213c147487649ad0b71 Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Mon, 20 May 2024 11:57:27 +0800 Subject: [PATCH 1/2] poe lib --- Cargo.lock | 29 ++++++------ Cargo.toml | 1 + pallets/poe/Cargo.toml | 58 +++++++++++++++++++++++ pallets/poe/src/lib.rs | 102 +++++++++++++++++++++++++++++++++++++++++ runtime/Cargo.toml | 4 +- runtime/src/lib.rs | 8 ++++ 6 files changed, 188 insertions(+), 14 deletions(-) create mode 100644 pallets/poe/Cargo.toml create mode 100644 pallets/poe/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 73f2f96..1dee80a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4842,6 +4842,20 @@ dependencies = [ "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", ] +[[package]] +name = "pallet-poe" +version = "0.0.1" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-session" version = "28.0.0" @@ -4894,18 +4908,6 @@ dependencies = [ "sp-runtime", ] -[[package]] -name = "pallet-template" -version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" -dependencies = [ - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", -] - [[package]] name = "pallet-timestamp" version = "27.0.0" @@ -7750,8 +7752,9 @@ dependencies = [ "pallet-aura", "pallet-balances", "pallet-grandpa", + "pallet-poe", "pallet-sudo", - "pallet-template 0.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "pallet-template", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", diff --git a/Cargo.toml b/Cargo.toml index 3a500ef..0f718d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ resolver = "2" members = [ "node", "pallets/template", + "pallets/poe", "runtime", ] diff --git a/pallets/poe/Cargo.toml b/pallets/poe/Cargo.toml new file mode 100644 index 0000000..1e321d4 --- /dev/null +++ b/pallets/poe/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "pallet-poe" +description = "FRAME pallet to implement Proof of Existence." +version = "0.0.1" +license = "MIT-0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.10.0", default-features = false, features = [ + "derive", +] } + +# frame deps +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/pallets/poe/src/lib.rs b/pallets/poe/src/lib.rs new file mode 100644 index 0000000..b9fa871 --- /dev/null +++ b/pallets/poe/src/lib.rs @@ -0,0 +1,102 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +/// A module for proof of existence +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The maximum length of claim that can be added. + #[pallet::constant] + type MaxClaimLength: Get; + /// The runtime event + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub type Proofs = StorageMap< + _, + Blake2_128Concat, + BoundedVec, + (T::AccountId, BlockNumberFor), + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ClaimCreated(T::AccountId, BoundedVec), + ClaimRevoked(T::AccountId, BoundedVec), + } + + #[pallet::error] + pub enum Error { + ProofAlreadyExist, + ClaimTooLong, + ClaimNotExist, + NotClaimOwner, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight({0})] + pub fn create_claim(origin: OriginFor, claim: BoundedVec) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + ensure!(!Proofs::::contains_key(&claim), Error::::ProofAlreadyExist); + + Proofs::::insert( + &claim, + (sender.clone(), frame_system::Pallet::::block_number()), + ); + + Self::deposit_event(Event::ClaimCreated(sender, claim)); + + Ok(().into()) + } + + #[pallet::call_index(1)] + #[pallet::weight({0})] + pub fn revoke_claim(origin: OriginFor, claim: BoundedVec) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let (owner, _) = Proofs::::get(&claim).ok_or(Error::::ClaimNotExist)?; + ensure!(owner == sender, Error::::NotClaimOwner); + + Proofs::::remove(&claim); + + Self::deposit_event(Event::ClaimRevoked(sender, claim)); + + Ok(().into()) + } + + #[pallet::call_index(2)] + #[pallet::weight({0})] + pub fn transfer_claim( + origin: OriginFor, + claim: BoundedVec, + dest: T::AccountId, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let (owner, _block_number) = + Proofs::::get(&claim).ok_or(Error::::ClaimNotExist)?; + ensure!(owner == sender, Error::::NotClaimOwner); + + Proofs::::insert(&claim, (dest, frame_system::Pallet::::block_number())); + + Ok(().into()) + } + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 947406a..b52fa02 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -73,7 +73,8 @@ frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", t frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false, optional = true } # The pallet in this template. -pallet-template = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-template = { default-features = false, path = "../pallets/template" } +pallet-poe = { default-features = false, path = "../pallets/poe" } [build-dependencies] substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", optional = true } @@ -98,6 +99,7 @@ std = [ "pallet-grandpa/std", "pallet-sudo/std", "pallet-template/std", + "pallet-poe/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d9e0e6d..8f99b7a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -252,6 +252,11 @@ impl pallet_template::Config for Runtime { type WeightInfo = pallet_template::weights::SubstrateWeight; } +impl pallet_poe::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MaxClaimLength = ConstU32<3>; +} + // Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] mod runtime { @@ -293,6 +298,9 @@ mod runtime { // Include the custom logic from the pallet-template in the runtime. #[runtime::pallet_index(7)] pub type TemplateModule = pallet_template; + + #[runtime::pallet_index(8)] + pub type PoeModule = pallet_poe; } /// The address format for describing accounts. From dfd79180698896d00e36c62bd560f0c944847f2f Mon Sep 17 00:00:00 2001 From: kaichaosun Date: Mon, 20 May 2024 12:09:09 +0800 Subject: [PATCH 2/2] poe test --- pallets/poe/src/lib.rs | 6 +++ pallets/poe/src/mock.rs | 61 ++++++++++++++++++++++ pallets/poe/src/tests.rs | 107 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 pallets/poe/src/mock.rs create mode 100644 pallets/poe/src/tests.rs diff --git a/pallets/poe/src/lib.rs b/pallets/poe/src/lib.rs index b9fa871..4b75268 100644 --- a/pallets/poe/src/lib.rs +++ b/pallets/poe/src/lib.rs @@ -3,6 +3,12 @@ /// A module for proof of existence pub use pallet::*; +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + #[frame_support::pallet] pub mod pallet { use super::*; diff --git a/pallets/poe/src/mock.rs b/pallets/poe/src/mock.rs new file mode 100644 index 0000000..852d889 --- /dev/null +++ b/pallets/poe/src/mock.rs @@ -0,0 +1,61 @@ +use crate as pallet_poe; +use frame_support::{ + derive_impl, + traits::{ConstU16, ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + PoeModule: pallet_poe, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_poe::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MaxClaimLength = ConstU32<10>; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() +} diff --git a/pallets/poe/src/tests.rs b/pallets/poe/src/tests.rs new file mode 100644 index 0000000..3afdb78 --- /dev/null +++ b/pallets/poe/src/tests.rs @@ -0,0 +1,107 @@ +use super::*; +use crate::{mock::*, Error}; +use frame_support::{assert_noop, assert_ok, BoundedVec, pallet_prelude::Get}; + +#[test] +fn create_claim_works() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + assert_ok!(PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone())); + + assert_eq!( + Proofs::::get(&claim), + Some((1, frame_system::Pallet::::block_number())) + ); + assert_eq!(<::MaxClaimLength as Get>::get(), 10); + }) +} + +#[test] +fn create_claim_failed_when_claim_already_exist() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + let _ = PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone()); + + assert_noop!( + PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone()), + Error::::ProofAlreadyExist + ); + }) +} + +#[test] +fn revoke_claim_works() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + let _ = PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone()); + + assert_ok!(PoeModule::revoke_claim(RuntimeOrigin::signed(1), claim.clone())); + }) +} + +#[test] +fn revoke_claim_failed_when_claim_is_not_exist() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + + assert_noop!( + PoeModule::revoke_claim(RuntimeOrigin::signed(1), claim.clone()), + Error::::ClaimNotExist + ); + }) +} + +#[test] +fn revoke_claim_failed_with_wrong_owner() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + let _ = PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone()); + + assert_noop!( + PoeModule::revoke_claim(RuntimeOrigin::signed(2), claim.clone()), + Error::::NotClaimOwner + ); + }) +} + +#[test] +fn transfer_claim_works() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + let _ = PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone()); + + assert_ok!(PoeModule::transfer_claim(RuntimeOrigin::signed(1), claim.clone(), 2)); + + let bounded_claim = + BoundedVec::::MaxClaimLength>::try_from(claim.clone()).unwrap(); + assert_eq!( + Proofs::::get(&bounded_claim), + Some((2, frame_system::Pallet::::block_number())) + ); + }) +} + +#[test] +fn transfer_claim_failed_when_claim_is_not_exist() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + + assert_noop!( + PoeModule::transfer_claim(RuntimeOrigin::signed(1), claim.clone(), 2), + Error::::ClaimNotExist + ); + }) +} + +#[test] +fn transfer_claim_failed_with_wrong_owner() { + new_test_ext().execute_with(|| { + let claim = BoundedVec::try_from(vec![0, 1]).unwrap(); + let _ = PoeModule::create_claim(RuntimeOrigin::signed(1), claim.clone()); + + assert_noop!( + PoeModule::transfer_claim(RuntimeOrigin::signed(2), claim.clone(), 3), + Error::::NotClaimOwner + ); + }) +}