diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9debb5fb8..c440a51d9 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -21,7 +21,7 @@ jobs: - name: Install latest nightly uses: actions-rs/toolchain@v1 with: - toolchain: nightly-2022-12-26 + toolchain: nightly-2023-07-16 override: true components: rustfmt @@ -109,30 +109,3 @@ jobs: - name: Check run: cargo check --release - dkg-liveness: - concurrency: - group: dkg-liveness-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Install toolchain - id: toolchain - uses: actions-rs/toolchain@master - with: - profile: minimal - toolchain: nightly - target: wasm32-unknown-unknown - - - name: Rust Cache - uses: Swatinem/rust-cache@v1.3.0 - - - name: Install Protobuf - run: sudo apt-get install protobuf-compiler - - - name: Build - run: cargo build --release -p tangle-standalone --features integration-tests - - - name: DKG Liveness - run: bash ./scripts/ci-dkg-test.sh diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 7b38af5bc..32c86f783 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -31,9 +31,6 @@ jobs: - binary: tangle-standalone features: relayer image_name: tangle-standalone-relayer - - binary: tangle-standalone - features: light-client - image_name: tangle-standalone-light-client permissions: contents: read packages: write diff --git a/.github/workflows/publish-release-binary.yml b/.github/workflows/publish-release-binary.yml index 73398a7b3..018057ea7 100644 --- a/.github/workflows/publish-release-binary.yml +++ b/.github/workflows/publish-release-binary.yml @@ -61,16 +61,4 @@ jobs: file: target/release/tangle-standalone asset_name: tangle-standalone-relayer-linux-amd64 tag: ${{ github.ref }} - overwrite: true - - - name: Build binary with light-client - run: cargo build --release -p tangle-standalone --locked --features light-client - - - name: Upload light-client binaries to release - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: target/release/tangle-standalone - asset_name: tangle-standalone-light-client-linux-amd64 - tag: ${{ github.ref }} overwrite: true \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 50fda8189..08953c963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8815,6 +8815,7 @@ dependencies = [ "num_enum 0.5.11", "pallet-balances", "pallet-dkg-metadata", + "pallet-dkg-proposals", "pallet-evm", "pallet-session", "pallet-staking", @@ -9007,6 +9008,26 @@ dependencies = [ "sp-std 8.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", ] +[[package]] +name = "pallet-roles" +version = "0.5.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal 0.3.4", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde", + "serde_json", + "sp-core 21.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", + "sp-io 23.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", + "sp-runtime 24.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", + "sp-std 8.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", + "tangle-primitives", +] + [[package]] name = "pallet-scheduler" version = "4.0.0-dev" @@ -15343,7 +15364,10 @@ version = "0.5.0" dependencies = [ "cumulus-primitives-core", "frame-support", + "parity-scale-codec", + "scale-info", "smallvec 1.11.0", + "sp-arithmetic 16.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "sp-core 21.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", "sp-runtime 24.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v1.0.0)", ] diff --git a/Cargo.toml b/Cargo.toml index 79728142f..60d215d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "standalone/runtime/evm_tracer", "pallets/claims", "pallets/transaction-pause", + "pallets/roles", "precompiles/utils", "precompiles/utils/macro", "precompiles/utils/tests-external", @@ -83,6 +84,7 @@ tangle-runtime = { package = "tangle-standalone-runtime", path = "standalone/run # Tangle Dependencies pallet-ecdsa-claims = { path = "pallets/claims", default-features = false } +pallet-roles = { path = "pallets/roles", default-features = false } # Orml dependencies orml-currencies = { git = "https://github.com/open-web3-stack/open-runtime-module-library.git", branch = "polkadot-v1.0.0", default-features = false } @@ -121,6 +123,8 @@ sc-consensus-grandpa = { git = "https://github.com/paritytech/substrate", branch sc-network-sync = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false } sp-trie = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false } +sp-rpc = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +sp-weights = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false } frame-election-provider-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0", default-features = false } diff --git a/pallets/roles/Cargo.toml b/pallets/roles/Cargo.toml new file mode 100644 index 000000000..a7741be0c --- /dev/null +++ b/pallets/roles/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "pallet-roles" +version = { workspace = true } +authors = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +homepage = { workspace = true } +repository = { workspace = true } + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true, features = ["derive", "max-encoded-len"] } +scale-info = { workspace = true } +serde = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +tangle-primitives = {workspace = true, default-features = false } +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +hex-literal = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } +serde_json = { workspace = true } + +[features] +default = ["std"] +std = [ + "serde/std", + "parity-scale-codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "pallet-balances/std", + "tangle-primitives/std" +] + +try-runtime = ["frame-support/try-runtime"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "pallet-balances/runtime-benchmarks" +] diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs new file mode 100644 index 000000000..4637d972d --- /dev/null +++ b/pallets/roles/src/impls.rs @@ -0,0 +1,137 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use super::*; +use frame_support::{pallet_prelude::DispatchResult, traits::WithdrawReasons}; +use sp_runtime::Saturating; +use tangle_primitives::{roles::RoleType, traits::roles::RolesHandler}; + +/// Implements RolesHandler for the pallet. +impl RolesHandler for Pallet { + /// Validates if the given address has the given role. + /// + /// # Parameters + /// - `address`: The account ID of the validator. + /// - `role`: The key representing the type of job. + /// + /// # Returns + /// Returns `true` if the validator is permitted to work with this job type, otherwise `false`. + fn validate_role(address: T::AccountId, role: RoleType) -> bool { + let assigned_role = AccountRolesMapping::::get(address); + match assigned_role { + Some(r) => + if r == role { + return true + }, + None => return false, + } + + false + } + + /// Slash validator stake for the reported offence. The function should be a best effort + /// slashing, slash upto max possible by the offence type. + /// + /// # Parameters + /// - `address`: The account ID of the validator. + /// - `offence`: The offence reported against the validator + /// + /// # Returns + /// DispatchResult emitting `Slashed` event if validator is slashed + fn slash_validator( + address: T::AccountId, + _offence: tangle_primitives::jobs::ValidatorOffence, + ) -> sp_runtime::DispatchResult { + // TODO: implement calculation of slash amount. + let slash_amount = 1000u64; + Self::do_slash(address, slash_amount.into())?; + Ok(()) + } +} + +/// Functions for the pallet. +impl Pallet { + /// Get the total amount of the balance that is locked for the given stash. + /// + /// # Parameters + /// - `stash`: The stash account ID. + /// + /// # Returns + /// The total amount of the balance that can be slashed. + pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { + // Weight note: consider making the stake accessible through stash. + Self::ledger(&stash).map(|l| l.total_locked).unwrap_or_default() + } + + /// Slash the given amount from the stash account. + /// + /// # Parameters + /// - `address`: The stash account ID. + /// - `slash_amount`: The amount to be slashed. + pub(crate) fn do_slash( + address: T::AccountId, + slash_amount: T::CurrencyBalance, + ) -> sp_runtime::DispatchResult { + let mut ledger = Self::ledger(&address).ok_or(Error::::InvalidStashController)?; + let (_imbalance, _missing) = T::Currency::slash(&address, slash_amount.into()); + ledger.total_locked = ledger.total_locked.saturating_sub(slash_amount.into()); + Self::update_ledger(&address, &ledger); + Self::deposit_event(Event::Slashed { account: address, amount: slash_amount }); + Ok(()) + } + + /// Update the ledger for the given stash account. + /// + /// # Parameters + /// - `staker`: The stash account ID. + /// - `ledger`: The new ledger. + /// + /// # Note + /// This function will set a lock on the stash account. + pub(crate) fn update_ledger(staker: &T::AccountId, ledger: &RoleStakingLedger) { + T::Currency::set_lock( + ROLES_STAKING_ID, + &ledger.stash, + ledger.total_locked, + WithdrawReasons::all(), + ); + >::insert(staker, ledger); + } + + /// Kill the stash account and remove all related information. + pub(crate) fn kill_stash(stash: &T::AccountId) -> DispatchResult { + >::remove(&stash); + Ok(()) + } + + /// Unbond the stash account. + /// + /// # Parameters + /// - `ledger`: The ledger of the stash account. + /// + /// # Note + /// This function will remove the lock on the stash account. + pub(super) fn unbond(ledger: &RoleStakingLedger) -> DispatchResult { + let stash = ledger.stash.clone(); + if ledger.total_locked > T::Currency::minimum_balance() { + // Remove the lock. + T::Currency::remove_lock(ROLES_STAKING_ID, &stash); + // Kill the stash and related information. + Self::kill_stash(&stash)?; + } + Ok(()) + } +} diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs new file mode 100644 index 000000000..5db090c4b --- /dev/null +++ b/pallets/roles/src/lib.rs @@ -0,0 +1,260 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +//! Pallet to process claims from Ethereum addresses. +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::all)] + +use codec::MaxEncodedLen; +use frame_support::{ + ensure, + traits::{Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced}, + CloneNoBound, EqNoBound, PalletId, PartialEqNoBound, RuntimeDebugNoBound, +}; +pub use pallet::*; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::{codec, traits::Zero}; +use sp_std::{convert::TryInto, prelude::*, vec}; +use tangle_primitives::{roles::RoleType, traits::roles::RolesHandler}; +mod impls; +#[cfg(test)] +pub(crate) mod mock; +#[cfg(test)] +mod tests; +mod weights; +pub use weights::WeightInfo; + +/// The ledger of a (bonded) stash. +#[derive( + PartialEqNoBound, + EqNoBound, + CloneNoBound, + Encode, + Decode, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct RoleStakingLedger { + /// The stash account whose balance is actually locked and at stake. + pub stash: T::AccountId, + /// The total amount of the stash's balance that we are currently accounting for. + /// It's just `active` plus all the `unlocking` balances. + #[codec(compact)] + pub total_locked: BalanceOf, +} + +impl RoleStakingLedger { + /// Initializes the default object using the given `validator`. + pub fn default_from(stash: T::AccountId) -> Self { + Self { stash, total_locked: Zero::zero() } + } + + /// Returns `true` if the stash account has no funds at all. + pub fn is_empty(&self) -> bool { + self.total_locked.is_zero() + } +} + +pub type CurrencyOf = ::Currency; +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; + +const ROLES_STAKING_ID: LockIdentifier = *b"rstaking"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + type Currency: LockableCurrency< + Self::AccountId, + Moment = BlockNumberFor, + Balance = Self::CurrencyBalance, + >; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `From`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + From + + TypeInfo + + MaxEncodedLen; + + /// Handler for the unbalanced reduction when slashing a staker. + type Slash: OnUnbalanced>; + + type WeightInfo: WeightInfo; + + #[pallet::constant] + type PalletId: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// Role assigned to the validator. + RoleAssigned { account: T::AccountId, role: RoleType }, + /// Removed validator from role. + RoleRemoved { account: T::AccountId, role: RoleType }, + /// Funds bonded to become a validator. + Bonded { account: T::AccountId, amount: BalanceOf }, + /// Funds unbonded to stop being a validator. + Unbonded { account: T::AccountId, amount: BalanceOf }, + /// Slashed validator. + Slashed { account: T::AccountId, amount: BalanceOf }, + } + + #[pallet::error] + pub enum Error { + /// Role has already been assigned to provided validator. + RoleAlreadyAssigned, + /// No role assigned to provided validator. + RoleNotAssigned, + /// Insufficient bond to become a validator. + InsufficientBond, + /// Stash controller account already added to Ledger + AlreadyPaired, + /// Stash controller account not found in ledger + InvalidStashController, + } + + /// Map from all "controller" accounts to the info regarding the staking. + #[pallet::storage] + #[pallet::getter(fn ledger)] + pub type Ledger = + StorageMap<_, Blake2_128Concat, T::AccountId, RoleStakingLedger>; + + #[pallet::storage] + #[pallet::getter(fn account_role)] + /// Mapping of resource to bridge index + pub type AccountRolesMapping = StorageMap<_, Blake2_256, T::AccountId, RoleType>; + + /// The minimum active bond to become and maintain the role. + #[pallet::storage] + #[pallet::getter(fn min_active_bond)] + pub(super) type MinActiveBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Assigns a role to the validator. + /// + /// # Parameters + /// + /// - `origin`: Origin of the transaction. + /// - `bond_value`: Amount of funds to bond. + /// - `role`: Role to assign to the validator. + /// + /// This function will return error if + /// - Role is already assigned to the validator. + /// - Min active bond is not met. + #[pallet::call] + impl Pallet { + #[pallet::weight({0})] + #[pallet::call_index(0)] + pub fn assign_role( + origin: OriginFor, + #[pallet::compact] bond_value: BalanceOf, + role: RoleType, + ) -> DispatchResult { + let stash_account = ensure_signed(origin)?; + // Check if role is already assigned. + ensure!( + !AccountRolesMapping::::contains_key(&stash_account), + Error::::RoleAlreadyAssigned + ); + + // Check if stash account is already paired. + ensure!(!>::contains_key(&stash_account), Error::::AlreadyPaired); + + // Check if min active bond is met. + let min_active_bond = MinActiveBond::::get(); + ensure!(bond_value > min_active_bond.into(), Error::::InsufficientBond); + + // Bond with stash account. + let stash_balance = T::Currency::free_balance(&stash_account); + let value = bond_value.min(stash_balance); + + // Update ledger. + let item = RoleStakingLedger { stash: stash_account.clone(), total_locked: value }; + Self::update_ledger(&stash_account, &item); + + Self::deposit_event(Event::::Bonded { + account: stash_account.clone(), + amount: value, + }); + + // Add role mapping for the stash account. + AccountRolesMapping::::insert(&stash_account, role); + Self::deposit_event(Event::::RoleAssigned { account: stash_account.clone(), role }); + Ok(()) + } + + /// Removes the role from the validator. + /// + /// # Parameters + /// + /// - `origin`: Origin of the transaction. + /// - `role`: Role to remove from the validator. + /// + /// This function will return error if + /// - Role is not assigned to the validator. + #[pallet::weight({0})] + #[pallet::call_index(1)] + pub fn clear_role(origin: OriginFor, role: RoleType) -> DispatchResult { + let stash_account = ensure_signed(origin)?; + // check if role is assigned. + ensure!( + Self::validate_role(stash_account.clone(), role.clone()), + Error::::RoleNotAssigned + ); + // TODO: Call jobs manager to remove the services. + // On successful removal of services, remove the role from the mapping. + // Issue link for reference : https://github.com/webb-tools/tangle/issues/292 + + // Unbound locked funds. + let ledger = Self::ledger(&stash_account).ok_or(Error::::InvalidStashController)?; + Self::unbond(&ledger)?; + Self::deposit_event(Event::::Unbonded { + account: ledger.stash, + amount: ledger.total_locked, + }); + + // Remove role from the mapping. + AccountRolesMapping::::remove(&stash_account); + Self::deposit_event(Event::::RoleRemoved { account: stash_account, role }); + + Ok(()) + } + } +} diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs new file mode 100644 index 000000000..f4cc2c38c --- /dev/null +++ b/pallets/roles/src/mock.rs @@ -0,0 +1,129 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . +#![cfg(test)] + +use super::*; +use crate as pallet_roles; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU128, ConstU32, ConstU64, Everything}, +}; + +use sp_core::H256; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; +pub type AccountId = u128; +pub type Balance = u128; + +impl frame_system::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Block = Block; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type BlockWeights = (); + type BlockLength = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BaseCallFilter = Everything; + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = (); + type WeightInfo = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = (); + type FreezeIdentifier = (); + type MaxFreezes = (); +} + +parameter_types! { + pub const RolesPalletId: PalletId = PalletId(*b"py/roles"); +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type CurrencyBalance = ::Balance; + type Slash = (); + type PalletId = RolesPalletId; + type WeightInfo = (); +} + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime + { + System: frame_system, + Balances: pallet_balances, + Roles: pallet_roles, + } +); + +pub struct ExtBuilder; + +impl Default for ExtBuilder { + fn default() -> Self { + ExtBuilder + } +} + +// Checks events against the latest. A contiguous set of events must be +// provided. They must include the most recent RuntimeEvent, but do not have to include +// every past RuntimeEvent. +pub fn assert_events(mut expected: Vec) { + let mut actual: Vec = System::events().iter().map(|e| e.event.clone()).collect(); + + expected.reverse(); + for evt in expected { + let next = actual.pop().expect("RuntimeEvent expected"); + assert_eq!(next, evt, "Events don't match (actual,expected)"); + } +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + // We use default for brevity, but you can configure as desired if needed. + pallet_balances::GenesisConfig:: { balances: vec![(10, 10000), (20, 10000)] } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs new file mode 100644 index 000000000..38abccadd --- /dev/null +++ b/pallets/roles/src/tests.rs @@ -0,0 +1,92 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . +#![cfg(test)] +use super::*; +use frame_support::assert_ok; +use mock::*; +use tangle_primitives::jobs::ValidatorOffence; + +#[test] +fn test_assign_role() { + new_test_ext().execute_with(|| { + // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(10), 5000, RoleType::Tss)); + + assert_events(vec![ + RuntimeEvent::Roles(crate::Event::Bonded { account: 10, amount: 5000 }), + RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 10, role: RoleType::Tss }), + ]); + + // Lets verify role assigned to account. + assert_eq!(Roles::account_role(10), Some(RoleType::Tss)); + // Verify ledger mapping + assert_eq!(Roles::ledger(10), Some(RoleStakingLedger { stash: 10, total_locked: 5000 })); + // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should + // have 5000 tokens usable. + assert_eq!(Balances::usable_balance(10), 5000); + }); +} + +#[test] +fn test_clear_role() { + new_test_ext().execute_with(|| { + // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(10), 5000, RoleType::Tss)); + // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should + // have 5000 tokens usable. + assert_eq!(Balances::usable_balance(10), 5000); + + // Now lets clear the role + assert_ok!(Roles::clear_role(RuntimeOrigin::signed(10), RoleType::Tss)); + + assert_events(vec![ + RuntimeEvent::Roles(crate::Event::Unbonded { account: 10, amount: 5000 }), + RuntimeEvent::Roles(crate::Event::RoleRemoved { account: 10, role: RoleType::Tss }), + ]); + + // Role should be removed from account role mappings. + assert_eq!(Roles::account_role(10), None); + + // Ledger should be removed from ledger mappings. + assert_eq!(Roles::ledger(10), None); + + // Verify total usable balance of the account. Since we have cleared the role, we should + // have 10000 tokens usable. + assert_eq!(Balances::usable_balance(10), 10000); + }); +} + +// test slashing +#[test] +fn test_slash_validator() { + new_test_ext().execute_with(|| { + // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens + assert_ok!(Roles::assign_role(RuntimeOrigin::signed(10), 5000, RoleType::Tss)); + // Verify total usable balance of the account. Since we have bonded 5000 tokens, we should + // have 5000 tokens usable. + assert_eq!(Balances::usable_balance(10), 5000); + + // Now lets slash the account for being Inactive. + assert_ok!(Roles::slash_validator(10, ValidatorOffence::Inactivity)); + + assert_events(vec![RuntimeEvent::Roles(crate::Event::Slashed { + account: 10, + amount: 1000, + })]); + // should be updated in ledger + assert_eq!(Roles::ledger(10), Some(RoleStakingLedger { stash: 10, total_locked: 4000 })); + }); +} diff --git a/pallets/roles/src/weights.rs b/pallets/roles/src/weights.rs new file mode 100644 index 000000000..bcb3f0e67 --- /dev/null +++ b/pallets/roles/src/weights.rs @@ -0,0 +1,68 @@ +// This file is part of Tangle. +// Copyright (C) 2022-2023 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +//! Autogenerated weights for module_transaction_pause +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-08-16, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/tangle-standalone +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=module_transaction_pause +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for roles pallet. +pub trait WeightInfo { + fn assign_role() -> Weight; + fn clear_role() -> Weight; +} + +/// Weights for roles pallet. +pub struct TestWeightInfo(PhantomData); +impl WeightInfo for TestWeightInfo { + fn assign_role() -> Weight { + Weight::from_parts(0, 0) + } + fn clear_role() -> Weight { + Weight::from_parts(0, 0) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn assign_role() -> Weight { + Weight::from_parts(0, 0) + } + fn clear_role() -> Weight { + Weight::from_parts(0, 0) + } +} \ No newline at end of file diff --git a/precompiles/staking/Cargo.toml b/precompiles/staking/Cargo.toml index d4a19e4d1..650955a16 100644 --- a/precompiles/staking/Cargo.toml +++ b/precompiles/staking/Cargo.toml @@ -39,6 +39,7 @@ precompile-utils = { workspace = true, features = [ "std", "testing" ] } # Substrate pallet-balances = { workspace = true, features = [ "std" ] } +pallet-dkg-proposals = { workspace = true, features = [ "std" ] } pallet-timestamp = { workspace = true, features = [ "std" ] } scale-info = { workspace = true, features = [ "derive", "std" ] } sp-runtime = { workspace = true, features = [ "std" ] } diff --git a/precompiles/staking/src/mock.rs b/precompiles/staking/src/mock.rs index f8e513c09..18277772a 100644 --- a/precompiles/staking/src/mock.rs +++ b/precompiles/staking/src/mock.rs @@ -220,6 +220,7 @@ impl pallet_dkg_metadata::Config for Runtime { type OnDKGPublicKeyChangeHandler = (); type OffChainAuthId = dkg_runtime_primitives::offchain::crypto::OffchainAuthId; type NextSessionRotation = pallet_session::PeriodicSessions; + type DKGAuthorityToMerkleLeaf = pallet_dkg_proposals::DKGEcdsaToEthereumAddress; type ForceOrigin = frame_system::EnsureRoot; type KeygenJailSentence = Period; type SigningJailSentence = Period; diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 3e6849f7d..ff749c7db 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -12,7 +12,10 @@ frame-support = { workspace = true } smallvec = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +sp-arithmetic = { workspace = true } cumulus-primitives-core = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = {workspace = true} [features] default = ["std"] diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 54c739388..d80756acf 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -29,6 +29,7 @@ use sp_runtime::{ pub mod types; pub use types::*; +pub mod traits; /// Tangle parachain time-related pub mod time { diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs new file mode 100644 index 000000000..ad3e620aa --- /dev/null +++ b/primitives/src/traits.rs @@ -0,0 +1 @@ +pub mod roles; diff --git a/primitives/src/traits/roles.rs b/primitives/src/traits/roles.rs new file mode 100644 index 000000000..13db8613b --- /dev/null +++ b/primitives/src/traits/roles.rs @@ -0,0 +1,46 @@ +// This file is part of Webb. +// Copyright (C) 2022 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use crate::{jobs::ValidatorOffence, roles::RoleType}; +use sp_runtime::DispatchResult; + +/// A trait that handles roles associated with job types. +pub trait RolesHandler { + /// Returns true if the validator is permitted to work with this job type. + /// + /// # Parameters + /// + /// - `address`: The account ID of the validator. + /// - `role_type`: The key representing the type of job. + /// + /// # Returns + /// + /// Returns `true` if the validator is permitted to work with this job type, otherwise `false`. + fn validate_role(address: AccountId, role_type: RoleType) -> bool; + + /// Slash validator stake for the reported offence. The function should be a best effort + /// slashing, slash upto max possible by the offence type. + /// + /// # Parameters + /// + /// - `address`: The account ID of the validator. + /// - `offence`: The offence reported against the validator + /// + /// # Returns + /// + /// Returns Ok() if the address is a validator and was slashed + fn slash_validator(address: AccountId, offence: ValidatorOffence) -> DispatchResult; +} diff --git a/primitives/src/types.rs b/primitives/src/types.rs index 8569e906a..996209737 100644 --- a/primitives/src/types.rs +++ b/primitives/src/types.rs @@ -15,7 +15,8 @@ use sp_runtime::AccountId32; // limitations under the License. // use super::*; - +pub mod jobs; +pub mod roles; /// Reputation type pub type Reputation = u128; /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. diff --git a/primitives/src/types/jobs.rs b/primitives/src/types/jobs.rs new file mode 100644 index 000000000..d293ea9e9 --- /dev/null +++ b/primitives/src/types/jobs.rs @@ -0,0 +1,25 @@ +// This file is part of Webb. +// Copyright (C) 2022 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use frame_support::{pallet_prelude::*, RuntimeDebug}; +/// Represents different types of validator offences. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo, Clone)] +pub enum ValidatorOffence { + /// The validator has been inactive. + Inactivity, + /// The validator has committed duplicate signing. + Equivocation, +} diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs new file mode 100644 index 000000000..9f687ac83 --- /dev/null +++ b/primitives/src/types/roles.rs @@ -0,0 +1,36 @@ +// This file is part of Webb. +// Copyright (C) 2022 Webb Technologies Inc. +// +// Tangle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Tangle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Tangle. If not, see . + +use frame_support::pallet_prelude::*; + +/// Role type to be used in the system. +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +pub enum RoleType { + Tss, + ZkSaas, +} + +impl RoleType { + /// Checks if the role type is a TSS role. + pub fn is_tss(self) -> bool { + self == RoleType::Tss + } + + /// Checks if the role type is a Zk-Saas role. + pub fn is_zksaas(self) -> bool { + self == RoleType::ZkSaas + } +}