From 8fc1e25ef12a1b71abe5e61fc596e684ade172e0 Mon Sep 17 00:00:00 2001 From: renauter Date: Mon, 6 Nov 2023 15:43:58 -0300 Subject: [PATCH] feat: collective approval or single council member can also cancel contract --- clients/tfchain-client-go/utils.go | 2 +- substrate-node/Cargo.lock | 2 + .../pallets/pallet-smart-contract/Cargo.toml | 4 ++ .../src/grid_contract.rs | 44 ++++++++++++++----- .../pallets/pallet-smart-contract/src/lib.rs | 6 +-- .../pallets/pallet-smart-contract/src/mock.rs | 38 +++++++++++++++- .../pallet-smart-contract/src/tests.rs | 2 +- .../pallet-smart-contract/src/types.rs | 3 ++ 8 files changed, 85 insertions(+), 16 deletions(-) diff --git a/clients/tfchain-client-go/utils.go b/clients/tfchain-client-go/utils.go index 1ec6c3356..a878aa9f0 100644 --- a/clients/tfchain-client-go/utils.go +++ b/clients/tfchain-client-go/utils.go @@ -84,7 +84,7 @@ var smartContractModuleErrors = []string{ "FailedToFreeIPs", "ContractNotExists", "TwinNotAuthorizedToUpdateContract", - "TwinNotAuthorizedToCancelContract", + "NotAuthorizedToCancelContract", "NodeNotAuthorizedToDeployContract", "NodeNotAuthorizedToComputeReport", "PricingPolicyNotExists", diff --git a/substrate-node/Cargo.lock b/substrate-node/Cargo.lock index 2f624b2cf..bd518c753 100644 --- a/substrate-node/Cargo.lock +++ b/substrate-node/Cargo.lock @@ -4835,6 +4835,8 @@ dependencies = [ "log", "pallet-authorship", "pallet-balances", + "pallet-collective", + "pallet-membership", "pallet-session", "pallet-tfgrid", "pallet-tft-price", diff --git a/substrate-node/pallets/pallet-smart-contract/Cargo.toml b/substrate-node/pallets/pallet-smart-contract/Cargo.toml index f9eace51c..f0852e889 100644 --- a/substrate-node/pallets/pallet-smart-contract/Cargo.toml +++ b/substrate-node/pallets/pallet-smart-contract/Cargo.toml @@ -31,6 +31,8 @@ substrate-fixed = { git = 'https://github.com/encointer/substrate-fixed.git', re pallet-balances.workspace = true frame-support.workspace = true frame-system.workspace = true +pallet-membership.workspace = true +pallet-collective.workspace = true sp-runtime.workspace = true sp-std.workspace = true pallet-timestamp.workspace = true @@ -66,6 +68,8 @@ std = [ 'scale-info/std', 'frame-benchmarking/std', 'sp-io/std', + "pallet-membership/std", + "pallet-collective/std", 'frame-try-runtime/std', 'sp-core/std', 'pallet-authorship/std', diff --git a/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs b/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs index ada8dcaa0..7a64b310a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/grid_contract.rs @@ -3,8 +3,10 @@ use frame_support::{ dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Pays}, ensure, pallet_prelude::TypeInfo, + traits::EnsureOrigin, BoundedVec, RuntimeDebugNoBound, }; +use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use pallet_tfgrid::pallet::{InterfaceOf, LocationOf, SerialNumberOf, TfgridNode}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use sp_std::{marker::PhantomData, vec, vec::Vec}; @@ -277,19 +279,34 @@ impl Pallet { Ok(().into()) } - pub fn _cancel_contract( - account_id: T::AccountId, - contract_id: u64, - cause: types::Cause, - ) -> DispatchResultWithPostInfo { + pub fn _cancel_contract(origin: OriginFor, contract_id: u64) -> DispatchResultWithPostInfo { let mut contract = Contracts::::get(contract_id).ok_or(Error::::ContractNotExists)?; + + // Allow collective approval (council or farmers) to cancel contract + if ::RestrictedOrigin::ensure_origin(origin.clone()).is_ok() { + return Self::_do_cancel_contract(&mut contract, types::Cause::CanceledByCollective); + } + + // Allow single council member cancel contract + let account_id = ensure_signed(origin)?; + if Self::is_council_member(account_id.clone()) { + return Self::_do_cancel_contract(&mut contract, types::Cause::CanceledByCouncilMember); + } + + // Allow node the contract is on to cancel contract let twin = pallet_tfgrid::Twins::::get(contract.twin_id).ok_or(Error::::TwinNotExists)?; - ensure!( - twin.account_id == account_id, - Error::::TwinNotAuthorizedToCancelContract - ); + if twin.account_id == account_id { + return Self::_do_cancel_contract(&mut contract, types::Cause::CanceledByNode); + } + Err(Error::::NotAuthorizedToCancelContract.into()) + } + + pub fn _do_cancel_contract( + contract: &mut types::Contract, + cause: types::Cause, + ) -> DispatchResultWithPostInfo { // If it's a rent contract and it still has active workloads, don't allow cancellation. if matches!( &contract.contract_type, @@ -303,7 +320,7 @@ impl Pallet { ); } - Self::update_contract_state(&mut contract, &types::ContractState::Deleted(cause))?; + Self::update_contract_state(contract, &types::ContractState::Deleted(cause))?; Self::bill_contract(contract.contract_id)?; Ok(().into()) @@ -651,6 +668,13 @@ impl Pallet { Ok(().into()) } + + fn is_council_member(who: T::AccountId) -> bool { + let council_members = + pallet_membership::Pallet::::members(); + + council_members.contains(&who) + } } impl PublicIpModifier for Pallet { diff --git a/substrate-node/pallets/pallet-smart-contract/src/lib.rs b/substrate-node/pallets/pallet-smart-contract/src/lib.rs index 1207ff61d..f4432f46a 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/lib.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/lib.rs @@ -206,6 +206,7 @@ pub mod pallet { + pallet_tft_price::Config + pallet_authorship::Config + pallet_session::Config + + pallet_membership::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; type Currency: LockableCurrency; @@ -343,7 +344,7 @@ pub mod pallet { FailedToFreeIPs, ContractNotExists, TwinNotAuthorizedToUpdateContract, - TwinNotAuthorizedToCancelContract, + NotAuthorizedToCancelContract, NodeNotAuthorizedToDeployContract, NodeNotAuthorizedToComputeReport, PricingPolicyNotExists, @@ -451,8 +452,7 @@ pub mod pallet { origin: OriginFor, contract_id: u64, ) -> DispatchResultWithPostInfo { - let account_id = ensure_signed(origin)?; - Self::_cancel_contract(account_id, contract_id, types::Cause::CanceledByUser) + Self::_cancel_contract(origin, contract_id) } #[pallet::call_index(4)] diff --git a/substrate-node/pallets/pallet-smart-contract/src/mock.rs b/substrate-node/pallets/pallet-smart-contract/src/mock.rs index c786d6487..ce09d5648 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/mock.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/mock.rs @@ -114,6 +114,8 @@ construct_runtime!( Authorship: pallet_authorship::{Pallet, Storage}, ValidatorSet: substrate_validator_set::{Pallet, Call, Storage, Event, Config}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + Council: pallet_collective::::{Pallet, Call, Origin, Event, Config}, + Membership: pallet_membership::::{Pallet, Call, Storage, Event}, } ); @@ -365,7 +367,39 @@ impl pallet_session::Config for TestRuntime { type WeightInfo = (); } -type AccountPublic = ::Signer; +pub type BlockNumber = u32; +parameter_types! { + pub const CouncilMotionDuration: BlockNumber = 4; + pub const CouncilMaxProposals: u32 = 100; + pub const CouncilMaxMembers: u32 = 100; +} + +pub type CouncilCollective = pallet_collective::Instance1; +impl pallet_collective::Config for TestRuntime { + type RuntimeOrigin = RuntimeOrigin; + type Proposal = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type MotionDuration = CouncilMotionDuration; + type MaxProposals = CouncilMaxProposals; + type MaxMembers = CouncilMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type SetMembersOrigin = EnsureRoot; + type WeightInfo = (); + type MaxProposalWeight = (); +} + +impl pallet_membership::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRoot; + type RemoveOrigin = EnsureRoot; + type SwapOrigin = EnsureRoot; + type ResetOrigin = EnsureRoot; + type PrimeOrigin = EnsureRoot; + type MembershipInitialized = Council; + type MembershipChanged = (); + type MaxMembers = CouncilMaxMembers; + type WeightInfo = pallet_membership::weights::SubstrateWeight; +} pub(crate) fn get_name_contract_name(contract_name_input: &[u8]) -> TestNameContractName { NameContractName::try_from(contract_name_input.to_vec()).expect("Invalid farm input.") @@ -445,6 +479,8 @@ where } } +type AccountPublic = ::Signer; + /// Helper function to generate an account ID from seed fn get_account_id_from_seed(seed: &str) -> AccountId where diff --git a/substrate-node/pallets/pallet-smart-contract/src/tests.rs b/substrate-node/pallets/pallet-smart-contract/src/tests.rs index a9b3b94d9..6c9a452e7 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/tests.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/tests.rs @@ -469,7 +469,7 @@ fn test_cancel_node_contract_wrong_twins_fails() { assert_noop!( SmartContractModule::cancel_contract(RuntimeOrigin::signed(bob()), contract_id), - Error::::TwinNotAuthorizedToCancelContract + Error::::NotAuthorizedToCancelContract ); }); } diff --git a/substrate-node/pallets/pallet-smart-contract/src/types.rs b/substrate-node/pallets/pallet-smart-contract/src/types.rs index ba554a121..c3ba886b9 100644 --- a/substrate-node/pallets/pallet-smart-contract/src/types.rs +++ b/substrate-node/pallets/pallet-smart-contract/src/types.rs @@ -134,7 +134,10 @@ pub enum ContractState { #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] pub enum Cause { + CanceledByNode, CanceledByUser, + CanceledByCouncilMember, + CanceledByCollective, OutOfFunds, }