Skip to content

Commit

Permalink
feat(pallet-smart-contract): allow collective approval to cancel cont…
Browse files Browse the repository at this point in the history
…ract (#886)
  • Loading branch information
renauter authored Nov 14, 2023
1 parent ab138c8 commit 3385509
Show file tree
Hide file tree
Showing 12 changed files with 265 additions and 171 deletions.
16 changes: 16 additions & 0 deletions docs/architecture/0017-rework_cancel_contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 17. Allow collective approval to cancel contracts

Date: 2023-11-06

## Status

Accepted

## Context

See [here](https://github.com/threefoldtech/tfchain/issues/884) for more details.

## Decision

In `pallet-smart-contract`, add `cancel_contract_collective()` extrinsic to allow a collective approval (council or farmers) to cancel a contract.
For this purpose we also add a new entry `CanceledByCollective` in `Cause` enum to better qualify the cancelation cause.
1 change: 1 addition & 0 deletions substrate-node/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Once the motion is closed it is removed from list and the `proposal` extrinsic i

### smartContractModule

* `cancelContract()` (cancels a contract)
* `approveSolutionProvider()` (approves a solution provider)
* `changeBillingFrequency()` (changes the billing frequency)

Expand Down
2 changes: 2 additions & 0 deletions substrate-node/pallets/pallet-smart-contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ substrate-fixed = { git = 'https://github.com/encointer/substrate-fixed.git', re
pallet-balances.workspace = true
frame-support.workspace = true
frame-system.workspace = true
pallet-collective.workspace = true
sp-runtime.workspace = true
sp-std.workspace = true
pallet-timestamp.workspace = true
Expand Down Expand Up @@ -66,6 +67,7 @@ std = [
'scale-info/std',
'frame-benchmarking/std',
'sp-io/std',
"pallet-collective/std",
'frame-try-runtime/std',
'sp-core/std',
'pallet-authorship/std',
Expand Down
66 changes: 33 additions & 33 deletions substrate-node/pallets/pallet-smart-contract/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,48 @@ This module is tightly coupled with the [Tfgrid module](../pallet-tfgrid/readme.

The smart contract module provides functions for:

- Creating / Updating / Canceling a smart contract
- Reporting resource usage for a smart contract
- Creating / Approving solution providers
- Creating / Updating / Canceling a Service Contract
- Setting an extra price for a dedicated ZOS node
- Billing all sorts of contracts based on a pricing policy defined in the [Tfgrid module](../pallet-tfgrid/readme.md) and a TFT price fetched in the [Tft Price Module](../pallet-tft-price/readme.md)..
* Creating / Updating / Canceling a smart contract
* Reporting resource usage for a smart contract
* Creating / Approving solution providers
* Creating / Updating / Canceling a Service Contract
* Setting an extra price for a dedicated ZOS node
* Billing all sorts of contracts based on a pricing policy defined in the [Tfgrid module](../pallet-tfgrid/readme.md) and a TFT price fetched in the [Tft Price Module](../pallet-tft-price/readme.md)..

The billing is triggered by an offchain worker that runs after a block is created. This offchain worker relies on the `aura` key in the keystore to sign the transaction triggers the billing. This means that only valid block creators can sign this transaction. Given this configuration this pallet will only work in an Aura / Grandpa based chains.

For a more in depth view of this module, see [spec](./spec.md).

## Terminology

- [ZOS](https://github.com/threefoldtech/zos): a Zero-OS node that is running on a physical machine.
- Smart Contract: an agreement between a user and a ZOS node that is enforced by the blockchain. This can be any of:
- `NodeContract`: An agreement between a user and a ZOS node for the usage of resources on that node.
- `NameContract`: An agreement between a user and a ZOS node for the usage of a dns name on that node using a gateway.
- `RentContract`: An agreement between a user and a ZOS node for the usage of the entire node.
- `ServiceContract`: An agreement between a user and an external service provider for the usage of a service.
- Solution provider: a provider of a solution, see [solution provider](./solution_provider.md)
* [ZOS](https://github.com/threefoldtech/zos): a Zero-OS node that is running on a physical machine.
* Smart Contract: an agreement between a user and a ZOS node that is enforced by the blockchain. This can be any of:
* `NodeContract`: An agreement between a user and a ZOS node for the usage of resources on that node.
* `NameContract`: An agreement between a user and a ZOS node for the usage of a dns name on that node using a gateway.
* `RentContract`: An agreement between a user and a ZOS node for the usage of the entire node.
* `ServiceContract`: An agreement between a user and an external service provider for the usage of a service.
* Solution provider: a provider of a solution, see [solution provider](./solution_provider.md)

## Interface

Dispatchable functions of this pallet.

- `create_node_contract` - Create a node contract
- `update_node_contract` - Update a node contract
- `create_name_contract` - Create a name contract
- `create_rent_contract` - Create a rent contract
- `cancel_contract`: Cancel a contract (any of the smart contract type)
- `add_nru_reports`: Reports network resource usage from ZOS to the chain
- `report_contract_resources`: Reports a `NodeContract` used resources (nru, cru, mru, sru, ipu) to the chain.
- `create_solution_provider`: Create a solution provider
- `approve_solution_provider`: Approve a solution provider, the origin for this call is a configurable origin.
- `bill_contract_for_block`: Triggers the billing of a contract on this block.
- `service_contract_create`: Create a service contract
- `service_contract_set_metadata`: Set metadata for a service contract
- `service_contract_set_fees`: Set fees for a service contract
- `service_contract_approve`: Approve a service contract
- `service_contract_reject`: Reject a service contract
- `service_contract_cancel`: Cancel a service contract
- `service_contract_bill`: Bill a service contract
- `change_billing_frequency`: Change the billing frequency of all contracts, the origin for this call is a configurable origin.
- `attach_solution_provider_id`: Attach a solution provider id to a contract
- `set_dedicated_node_extra_fee`: Set an extra fee for a dedicated node
* `create_node_contract` - Create a node contract
* `update_node_contract` - Update a node contract
* `create_name_contract` - Create a name contract
* `create_rent_contract` - Create a rent contract
* `cancel_contract`: Cancel a contract (for any of the smart contract type and callable by the node the contract is on or a collective council/farmers approval)
* `add_nru_reports`: Reports network resource usage from ZOS to the chain
* `report_contract_resources`: Reports a `NodeContract` used resources (nru, cru, mru, sru, ipu) to the chain.
* `create_solution_provider`: Create a solution provider
* `approve_solution_provider`: Approve a solution provider, the origin for this call is a configurable origin.
* `bill_contract_for_block`: Triggers the billing of a contract on this block.
* `service_contract_create`: Create a service contract
* `service_contract_set_metadata`: Set metadata for a service contract
* `service_contract_set_fees`: Set fees for a service contract
* `service_contract_approve`: Approve a service contract
* `service_contract_reject`: Reject a service contract
* `service_contract_cancel`: Cancel a service contract
* `service_contract_bill`: Bill a service contract
* `change_billing_frequency`: Change the billing frequency of all contracts, the origin for this call is a configurable origin.
* `attach_solution_provider_id`: Attach a solution provider id to a contract
* `set_dedicated_node_extra_fee`: Set an extra fee for a dedicated node
22 changes: 22 additions & 0 deletions substrate-node/pallets/pallet-smart-contract/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,28 @@ benchmarks! {
);
}

// cancel_contract_collective()
cancel_contract_collective {
let farmer: T::AccountId = account("Alice", 0, 0);
_prepare_farm_with_node::<T>(farmer.clone());

let user: T::AccountId = whitelisted_caller();
_create_twin::<T>(user.clone());
_create_node_contract::<T>(user.clone());
let contract_id = 1;

}: _(RawOrigin::Root, contract_id)
verify {
assert!(SmartContractModule::<T>::contracts(contract_id).is_none());
let node_id = 1;
let twin_id = 2;
assert_last_event::<T>(Event::NodeContractCanceled {
contract_id,
node_id,
twin_id,
}.into());
}

// Calling the `impl_benchmark_test_suite` macro inside the `benchmarks`
// block will generate one #[test] function per benchmark
impl_benchmark_test_suite!(SmartContractModule, crate::mock::new_test_ext(), crate::mock::TestRuntime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,22 @@ impl<T: Config> Pallet<T> {
Error::<T>::TwinNotAuthorizedToCancelContract
);

Self::do_cancel_contract(&mut contract, cause)
}

pub fn _cancel_contract_collective(
contract_id: u64,
cause: types::Cause,
) -> DispatchResultWithPostInfo {
let mut contract = Contracts::<T>::get(contract_id).ok_or(Error::<T>::ContractNotExists)?;

Self::do_cancel_contract(&mut contract, cause)
}

fn do_cancel_contract(
contract: &mut types::Contract<T>,
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,
Expand All @@ -303,7 +319,7 @@ impl<T: Config> Pallet<T> {
);
}

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())
Expand Down
10 changes: 10 additions & 0 deletions substrate-node/pallets/pallet-smart-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,16 @@ pub mod pallet {
let account_id = ensure_signed(origin)?;
Self::_set_dedicated_node_extra_fee(account_id, node_id, extra_fee)
}

#[pallet::call_index(21)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_contract_collective())]
pub fn cancel_contract_collective(
origin: OriginFor<T>,
contract_id: u64,
) -> DispatchResultWithPostInfo {
<T as Config>::RestrictedOrigin::ensure_origin(origin)?;
Self::_cancel_contract_collective(contract_id, types::Cause::CanceledByCollective)
}
}

#[pallet::hooks]
Expand Down
37 changes: 32 additions & 5 deletions substrate-node/pallets/pallet-smart-contract/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use frame_support::{
dispatch::DispatchErrorWithPostInfo,
dispatch::PostDispatchInfo,
parameter_types,
traits::{ConstU32, GenesisBuild},
traits::{ConstU32, EitherOfDiverse, GenesisBuild},
BoundedVec,
};
use frame_system::EnsureRoot;
Expand Down Expand Up @@ -114,6 +114,7 @@ construct_runtime!(
Authorship: pallet_authorship::{Pallet, Storage},
ValidatorSet: substrate_validator_set::{Pallet, Call, Storage, Event<T>, Config<T>},
Session: pallet_session::{Pallet, Call, Storage, Event, Config<T>},
Council: pallet_collective::<Instance1>::{Pallet, Call, Origin<T>, Event<T>, Config<T>},
}
);

Expand Down Expand Up @@ -267,6 +268,11 @@ parameter_types! {

pub(crate) type TestNameContractName = NameContractName<TestRuntime>;

type EnsureRootOrCouncilApproval = EitherOfDiverse<
EnsureRoot<AccountId>,
pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 3, 5>,
>;

use weights;
impl pallet_smart_contract::Config for TestRuntime {
type RuntimeEvent = RuntimeEvent;
Expand All @@ -281,7 +287,7 @@ impl pallet_smart_contract::Config for TestRuntime {
type NodeChanged = NodeChanged;
type MaxNameContractNameLength = MaxNameContractNameLength;
type NameContractName = TestNameContractName;
type RestrictedOrigin = EnsureRoot<Self::AccountId>;
type RestrictedOrigin = EnsureRootOrCouncilApproval;
type MaxDeploymentDataLength = MaxDeploymentDataLength;
type MaxNodeContractPublicIps = MaxNodeContractPublicIPs;
type AuthorityId = pallet_smart_contract::crypto::AuthId;
Expand Down Expand Up @@ -365,7 +371,26 @@ impl pallet_session::Config for TestRuntime {
type WeightInfo = ();
}

type AccountPublic = <MultiSignature as Verify>::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<CouncilCollective> 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<Self::AccountId>;
type WeightInfo = ();
type MaxProposalWeight = ();
}

pub(crate) fn get_name_contract_name(contract_name_input: &[u8]) -> TestNameContractName {
NameContractName::try_from(contract_name_input.to_vec()).expect("Invalid farm input.")
Expand Down Expand Up @@ -445,6 +470,8 @@ where
}
}

type AccountPublic = <MultiSignature as Verify>::Signer;

/// Helper function to generate an account ID from seed
fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
where
Expand Down Expand Up @@ -503,12 +530,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
};
session_genesis.assimilate_storage(&mut storage).unwrap();

let genesis = pallet_tft_price::GenesisConfig::<TestRuntime> {
let price_genesis = pallet_tft_price::GenesisConfig::<TestRuntime> {
min_tft_price: 10,
max_tft_price: 1000,
_data: PhantomData,
};
genesis.assimilate_storage(&mut storage).unwrap();
price_genesis.assimilate_storage(&mut storage).unwrap();

let t = sp_io::TestExternalities::from(storage);

Expand Down
Loading

0 comments on commit 3385509

Please sign in to comment.