feat: add collective precompile (#204)

## Add Collective Precompile

Adds the pallet collective precompile to `mainnet`, `stagenet`, and
`testnet` according to Moonbeam's configuration.

### Changes:
- Added `pallet-evm-precompile-collective` dependency to workspace
- Configured collective precompile at address `2064` using
`TreasuryCouncilInstanc`
- Configured collective precompile at address `2068` using
`TechnicalCommitteeInstance`

The precompile provides EVM access to collective governance
functionality including proposal execution, voting, and membership
management.

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
This commit is contained in:
Gonza Montiel 2025-10-08 23:16:34 +02:00 committed by GitHub
parent 65e245a82e
commit f7d441d9e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 2234 additions and 500 deletions

1007
operator/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -35,9 +35,10 @@ pallet-datahaven-native-transfer = { path = "./pallets/datahaven-native-transfer
pallet-evm-precompile-balances-erc20 = { path = "./precompiles/erc20-balances", default-features = false }
pallet-evm-precompile-batch = { path = "./precompiles/batch", default-features = false }
pallet-evm-precompile-call-permit = { path = "./precompiles/call-permit", default-features = false }
pallet-evm-precompile-collective = { path = "./precompiles/collective", default-features = false }
pallet-evm-precompile-identity = { path = "./precompiles/identity", default-features = false }
pallet-evm-precompile-proxy = { path = "./precompiles/proxy", default-features = false }
pallet-evm-precompile-registry = { path = "./precompiles/precompile-registry", default-features = false }
pallet-evm-precompile-identity = { path = "./precompiles/identity", default-features = false }
# Crates.io (wasm)
alloy-core = { version = "0.8.15", default-features = false }

View file

@ -0,0 +1,50 @@
[package]
name = "pallet-evm-precompile-collective"
authors = { workspace = true }
description = "A Precompile wrapping the collective pallet."
edition = "2021"
version = "0.1.0"
[dependencies]
# Substrate
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-collective = { workspace = true }
parity-scale-codec = { workspace = true, features = ["max-encoded-len"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
# Frontier
evm = { workspace = true, features = ["with-codec"] }
fp-evm = { workspace = true }
pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] }
precompile-utils = { workspace = true }
[dev-dependencies]
#similar-asserts = { workspace = true }
pallet-balances = { workspace = true, features = ["std"] }
pallet-timestamp = { workspace = true, features = ["std"] }
pallet-treasury = { workspace = true, features = ["std"] }
parity-scale-codec = { workspace = true, features = ["max-encoded-len"] }
precompile-utils = { workspace = true, features = ["std", "testing"] }
scale-info = { workspace = true, features = ["derive", "std"] }
sp-runtime = { workspace = true, features = ["std"] }
[features]
default = ["std"]
std = [
"fp-evm/std",
"frame-support/std",
"frame-system/std",
"pallet-collective/std",
"pallet-evm/std",
"parity-scale-codec/std",
"precompile-utils/std",
"sp-core/std",
"sp-io/std",
"sp-std/std",
]
runtime-benchmarks = []

View file

@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;
/// @dev The Collective Council contract's address.
address constant COLLECTIVE_COUNCIL_ADDRESS = 0x000000000000000000000000000000000000080e;
/// @dev The Collective Technical Committee contract's address.
address constant COLLECTIVE_TECHNICAL_ADDRESS = 0x000000000000000000000000000000000000080F;
/// @dev The Collective Treasury Council contract's address.
address constant COLLECTIVE_TREASURY_ADDRESS = 0x0000000000000000000000000000000000000810;
/// @dev The Collective Council contract's instance.
Collective constant COLLECTIVE_COUNCIL_CONTRACT = Collective(
COLLECTIVE_COUNCIL_ADDRESS
);
/// @dev The Collective Technical Committee contract's instance.
Collective constant COLLECTIVE_TECHNICAL_CONTRACT = Collective(
COLLECTIVE_TECHNICAL_ADDRESS
);
/// @dev The Collective Treasury Council contract's instance.
Collective constant COLLECTIVE_TREASURY_CONTRACT = Collective(
COLLECTIVE_TREASURY_ADDRESS
);
/// @title Collective precompile
/// Allows to interact with Substrate pallet_collective from the EVM.
/// Addresses:
/// - 0x000000000000000000000000000000000000080e: Council
/// - 0x000000000000000000000000000000000000080f: Technical Committee
/// - 0x0000000000000000000000000000000000000810: Treasury Council.
interface Collective {
/// @dev Execute a proposal as a single member of the collective.
/// The sender must be a member of the collective.
/// This will NOT revert if the Substrate proposal is dispatched but fails !
///
/// @param proposal SCALE-encoded Substrate call.
///
/// @custom:selector 09c5eabe
function execute(bytes memory proposal) external;
/// @dev Make a proposal for a call.
/// The sender must be a member of the collective.
/// If the threshold is less than 2 then the proposal will be dispatched
/// directly from the group of one member of the collective.
///
/// @param threshold Amount of members required to dispatch the proposal.
/// @param proposal SCALE-encoded Substrate call.
/// @return index Index of the new proposal. Meaningless if threshold < 2
///
/// @custom:selector c57f3260
function propose(uint32 threshold, bytes memory proposal)
external
returns (uint32 index);
/// @dev Vote for a proposal.
/// The sender must be a member of the collective.
///
/// @param proposalHash Hash of the proposal to vote for. Ensure the caller knows what they're
/// voting in case of front-running or reorgs.
/// @param proposalIndex Index of the proposal (returned by propose).
/// @param approve The vote itself, is the caller approving or not the proposal.
///
/// @custom:selector 73e37688
function vote(
bytes32 proposalHash,
uint32 proposalIndex,
bool approve
) external;
/// @dev Close a proposal.
/// Can be called by anyone once there is enough votes.
/// Reverts if called at a non appropriate time.
///
/// @param proposalHash Hash of the proposal to close.
/// @param proposalIndex Index of the proposal.
/// @param proposalWeightBound Maximum amount of Substrate weight the proposal can use.
/// This call will revert if the proposal call would use more.
/// @param lengthBound Must be a value higher or equal to the length of the SCALE-encoded
/// proposal in bytes.
/// @return executed Was the proposal executed or removed?
///
/// @custom:selector 638d9d47
function close(
bytes32 proposalHash,
uint32 proposalIndex,
uint64 proposalWeightBound,
uint32 lengthBound
) external returns (bool executed);
/// @dev Compute the hash of a proposal.
///
/// @param proposal SCALE-encoded Substrate call.
/// @return proposalHash Hash of the proposal.
///
/// @custom:selector fc379417
function proposalHash(bytes memory proposal)
external
view
returns (bytes32 proposalHash);
/// @dev Get the hashes of active proposals.
///
/// @return proposalsHash Hashes of active proposals.
///
/// @custom:selector 55ef20e6
function proposals() external view returns (bytes32[] memory proposalsHash);
/// @dev Get the list of members.
///
/// @return members List of members.
///
/// @custom:selector bdd4d18d
function members() external view returns (address[] memory members);
/// @dev Check if the given account is a member of the collective.
///
/// @param account Account to check membership.
///
/// @custom:selector a230c524
function isMember(address account) external view returns (bool);
/// @dev Get the prime account if there is one.
///
/// @return prime Prime account of 0x00..00 if None.
///
/// @custom:selector c7ee005e
function prime() external view returns (address prime);
event Executed(bytes32 indexed proposalHash);
event Proposed(
address indexed who,
uint32 indexed proposalIndex,
bytes32 indexed proposalHash,
uint32 threshold
);
event Voted(address indexed who, bytes32 indexed proposalHash, bool voted);
event Closed(bytes32 indexed proposalHash);
}

View file

@ -0,0 +1,366 @@
// Copyright 2019-2025 PureStake Inc.
// This file is part of Moonbeam.
// Moonbeam 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.
// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.
//! Precompile to interact with pallet_collective instances.
#![cfg_attr(not(feature = "std"), no_std)]
use core::marker::PhantomData;
use fp_evm::Log;
use frame_support::{
dispatch::{GetDispatchInfo, Pays, PostDispatchInfo},
sp_runtime::traits::Hash,
traits::ConstU32,
weights::Weight,
};
use pallet_evm::AddressMapping;
use parity_scale_codec::DecodeLimit as _;
use precompile_utils::prelude::*;
use sp_core::{Decode, Get, H160, H256};
use sp_runtime::traits::Dispatchable;
use sp_std::{boxed::Box, vec::Vec};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
/// System account size in bytes = Pallet_Name_Hash (16) + Storage_name_hash (16) +
/// Blake2_128Concat (16) + AccountId (20) + AccountInfo (4 + 12 + AccountData (4* 16)) = 148
pub const SYSTEM_ACCOUNT_SIZE: u64 = 148;
/// Proposal max proof size in bytes. See:
/// moonbeam/blob/dd3e2b69f847dd74f6116b965fe7e2d97c3c7eb5/primitives/xcm/src/ethereum_xcm.rs#L27-L33
pub const PROPOSAL_MAX_PROOF_SIZE: u64 = 256 * 1024;
/// Solidity selector of the Executed log.
pub const SELECTOR_LOG_EXECUTED: [u8; 32] = keccak256!("Executed(bytes32)");
/// Solidity selector of the Proposed log.
pub const SELECTOR_LOG_PROPOSED: [u8; 32] = keccak256!("Proposed(address,uint32,bytes32,uint32)");
/// Solidity selector of the Voted log.
pub const SELECTOR_LOG_VOTED: [u8; 32] = keccak256!("Voted(address,bytes32,bool)");
/// Solidity selector of the Closed log.
pub const SELECTOR_LOG_CLOSED: [u8; 32] = keccak256!("Closed(bytes32)");
pub fn log_executed(address: impl Into<H160>, hash: H256) -> Log {
log2(address.into(), SELECTOR_LOG_EXECUTED, hash, Vec::new())
}
pub fn log_proposed(
address: impl Into<H160>,
who: impl Into<H160>,
index: u32,
hash: H256,
threshold: u32,
) -> Log {
log4(
address.into(),
SELECTOR_LOG_PROPOSED,
who.into(),
H256::from_slice(&solidity::encode_arguments(index)),
hash,
solidity::encode_arguments(threshold),
)
}
pub fn log_voted(address: impl Into<H160>, who: impl Into<H160>, hash: H256, voted: bool) -> Log {
log3(
address.into(),
SELECTOR_LOG_VOTED,
who.into(),
hash,
solidity::encode_arguments(voted),
)
}
pub fn log_closed(address: impl Into<H160>, hash: H256) -> Log {
log2(address.into(), SELECTOR_LOG_CLOSED, hash, Vec::new())
}
type GetProposalLimit = ConstU32<{ 2u32.pow(16) }>;
type DecodeLimit = ConstU32<8>;
pub struct CollectivePrecompile<Runtime, Instance: 'static>(PhantomData<(Runtime, Instance)>);
#[precompile_utils::precompile]
impl<Runtime, Instance> CollectivePrecompile<Runtime, Instance>
where
Instance: 'static,
Runtime: pallet_collective::Config<Instance> + pallet_evm::Config,
Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo + Decode,
Runtime::RuntimeCall: From<pallet_collective::Call<Runtime, Instance>>,
<Runtime as pallet_collective::Config<Instance>>::Proposal: From<Runtime::RuntimeCall>,
<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
Runtime::AccountId: Into<H160>,
H256: From<<Runtime as frame_system::Config>::Hash>
+ Into<<Runtime as frame_system::Config>::Hash>,
<Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
{
#[precompile::public("execute(bytes)")]
fn execute(
handle: &mut impl PrecompileHandle,
proposal: BoundedBytes<GetProposalLimit>,
) -> EvmResult {
let proposal: Vec<_> = proposal.into();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
let log = log_executed(handle.context().address, proposal_hash);
handle.record_log_costs(&[&log])?;
let proposal_length: u32 = proposal.len().try_into().map_err(|_| {
RevertReason::value_is_too_large("uint32")
.in_field("length")
.in_field("proposal")
})?;
let proposal =
Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*proposal)
.map_err(|_| {
RevertReason::custom("Failed to decode proposal").in_field("proposal")
})?
.into();
let proposal = Box::new(proposal);
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_collective::Call::<Runtime, Instance>::execute {
proposal,
length_bound: proposal_length,
},
SYSTEM_ACCOUNT_SIZE,
)?;
log.record(handle)?;
Ok(())
}
#[precompile::public("propose(uint32,bytes)")]
fn propose(
handle: &mut impl PrecompileHandle,
threshold: u32,
proposal: BoundedBytes<GetProposalLimit>,
) -> EvmResult<u32> {
// ProposalCount
handle.record_db_read::<Runtime>(4)?;
let proposal: Vec<_> = proposal.into();
let proposal_length: u32 = proposal.len().try_into().map_err(|_| {
RevertReason::value_is_too_large("uint32")
.in_field("length")
.in_field("proposal")
})?;
let proposal_index = pallet_collective::ProposalCount::<Runtime, Instance>::get();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
// In pallet_collective a threshold < 2 means the proposal has been
// executed directly.
let log = if threshold < 2 {
log_executed(handle.context().address, proposal_hash)
} else {
log_proposed(
handle.context().address,
handle.context().caller,
proposal_index,
proposal_hash,
threshold,
)
};
handle.record_log_costs(&[&log])?;
let proposal =
Runtime::RuntimeCall::decode_with_depth_limit(DecodeLimit::get(), &mut &*proposal)
.map_err(|_| {
RevertReason::custom("Failed to decode proposal").in_field("proposal")
})?
.into();
let proposal = Box::new(proposal);
{
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_collective::Call::<Runtime, Instance>::propose {
threshold,
proposal,
length_bound: proposal_length,
},
SYSTEM_ACCOUNT_SIZE,
)?;
}
log.record(handle)?;
Ok(proposal_index)
}
#[precompile::public("vote(bytes32,uint32,bool)")]
fn vote(
handle: &mut impl PrecompileHandle,
proposal_hash: H256,
proposal_index: u32,
approve: bool,
) -> EvmResult {
// TODO: Since we cannot access ayes/nays of a proposal we cannot
// include it in the EVM events to mirror Substrate events.
let log = log_voted(
handle.context().address,
handle.context().caller,
proposal_hash,
approve,
);
handle.record_log_costs(&[&log])?;
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_collective::Call::<Runtime, Instance>::vote {
proposal: proposal_hash.into(),
index: proposal_index,
approve,
},
SYSTEM_ACCOUNT_SIZE,
)?;
log.record(handle)?;
Ok(())
}
#[precompile::public("close(bytes32,uint32,uint64,uint32)")]
fn close(
handle: &mut impl PrecompileHandle,
proposal_hash: H256,
proposal_index: u32,
proposal_weight_bound: u64,
length_bound: u32,
) -> EvmResult<bool> {
// Because the actual log cannot be built before dispatch, we manually
// record it first (`executed` and `closed` have the same cost).
handle.record_log_costs_manual(2, 0)?;
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
let post_dispatch_info = RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_collective::Call::<Runtime, Instance>::close {
proposal_hash: proposal_hash.into(),
index: proposal_index,
proposal_weight_bound: Weight::from_parts(
proposal_weight_bound,
PROPOSAL_MAX_PROOF_SIZE,
),
length_bound,
},
SYSTEM_ACCOUNT_SIZE,
)?;
// We can know if the proposal was executed or not based on the `pays_fee` in
// `PostDispatchInfo`.
let (executed, log) = match post_dispatch_info.pays_fee {
Pays::Yes => (true, log_executed(handle.context().address, proposal_hash)),
Pays::No => (false, log_closed(handle.context().address, proposal_hash)),
};
log.record(handle)?;
Ok(executed)
}
#[precompile::public("proposalHash(bytes)")]
#[precompile::view]
fn proposal_hash(
_handle: &mut impl PrecompileHandle,
proposal: BoundedBytes<GetProposalLimit>,
) -> EvmResult<H256> {
let proposal: Vec<_> = proposal.into();
let hash = hash::<Runtime>(&proposal);
Ok(hash)
}
#[precompile::public("proposals()")]
#[precompile::view]
fn proposals(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<H256>> {
// Proposals: BoundedVec(32 * MaxProposals)
handle.record_db_read::<Runtime>(
32 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
)?;
let proposals = pallet_collective::Proposals::<Runtime, Instance>::get();
let proposals: Vec<_> = proposals.into_iter().map(|hash| hash.into()).collect();
Ok(proposals)
}
#[precompile::public("members()")]
#[precompile::view]
fn members(handle: &mut impl PrecompileHandle) -> EvmResult<Vec<Address>> {
// Members: Vec(20 * MaxMembers)
handle.record_db_read::<Runtime>(
20 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
)?;
let members = pallet_collective::Members::<Runtime, Instance>::get();
let members: Vec<_> = members.into_iter().map(|id| Address(id.into())).collect();
Ok(members)
}
#[precompile::public("isMember(address)")]
#[precompile::view]
fn is_member(handle: &mut impl PrecompileHandle, account: Address) -> EvmResult<bool> {
// Members: Vec(20 * MaxMembers)
handle.record_db_read::<Runtime>(
20 * (<Runtime as pallet_collective::Config<Instance>>::MaxProposals::get() as usize),
)?;
let account = Runtime::AddressMapping::into_account_id(account.into());
let is_member = pallet_collective::Pallet::<Runtime, Instance>::is_member(&account);
Ok(is_member)
}
#[precompile::public("prime()")]
#[precompile::view]
fn prime(handle: &mut impl PrecompileHandle) -> EvmResult<Address> {
// Prime
handle.record_db_read::<Runtime>(20)?;
let prime = pallet_collective::Prime::<Runtime, Instance>::get()
.map(|prime| prime.into())
.unwrap_or(H160::zero());
Ok(Address(prime))
}
}
pub fn hash<Runtime>(data: &[u8]) -> H256
where
Runtime: frame_system::Config,
H256: From<<Runtime as frame_system::Config>::Hash>,
{
<Runtime as frame_system::Config>::Hashing::hash(data).into()
}

View file

@ -0,0 +1,384 @@
// Copyright 2019-2025 PureStake Inc.
// This file is part of Moonbeam.
// Moonbeam 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.
// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.
//! Test utilities
use super::*;
use frame_support::traits::tokens::{PayFromAccount, UnityAssetBalanceConversion};
use frame_support::{
construct_runtime, parameter_types,
traits::{ConstU128, Everything, MapSuccess, OnFinalize, OnInitialize},
PalletId,
};
use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot};
use pallet_evm::{
EnsureAddressNever, EnsureAddressRoot, FrameSystemAccountProvider, SubstrateBlockHashMapping,
};
use precompile_utils::{
precompile_set::*,
testing::{Bob, Charlie, MockAccount},
};
use sp_core::{H256, U256};
use sp_io;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup, Replace},
BuildStorage, Permill,
};
#[cfg(feature = "runtime-benchmarks")]
use pallet_treasury::ArgumentsFactory;
pub type AccountId = MockAccount;
pub type Balance = u128;
pub type BlockNumber = BlockNumberFor<Runtime>;
type Block = frame_system::mocking::MockBlockU32<Runtime>;
// Configure a mock runtime to test the pallet.
construct_runtime!(
pub enum Runtime {
System: frame_system,
Balances: pallet_balances,
Evm: pallet_evm,
Timestamp: pallet_timestamp,
Treasury: pallet_treasury,
CouncilCollective:
pallet_collective::<Instance1>,
}
);
parameter_types! {
pub const BlockHashCount: u32 = 250;
pub const SS58Prefix: u8 = 42;
}
impl frame_system::Config for Runtime {
type BaseCallFilter = Everything;
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeTask = RuntimeTask;
type Nonce = u64;
type Block = Block;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type BlockWeights = ();
type BlockLength = ();
type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
type SingleBlockMigrations = ();
type MultiBlockMigrator = ();
type PreInherents = ();
type PostInherents = ();
type PostTransactions = ();
type ExtensionsWeightInfo = ();
}
parameter_types! {
pub const ExistentialDeposit: u128 = 0;
}
impl pallet_balances::Config for Runtime {
type MaxReserves = ();
type ReserveIdentifier = ();
type MaxLocks = ();
type Balance = Balance;
type RuntimeEvent = RuntimeEvent;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type RuntimeHoldReason = ();
type FreezeIdentifier = ();
type MaxFreezes = ();
type RuntimeFreezeReason = ();
type DoneSlashHandler = ();
}
pub type Precompiles<R> = PrecompileSetBuilder<
R,
(PrecompileAt<AddressU64<1>, CollectivePrecompile<R, pallet_collective::Instance1>>,),
>;
pub type PCall = CollectivePrecompileCall<Runtime, pallet_collective::Instance1>;
const MAX_POV_SIZE: u64 = 5 * 1024 * 1024;
/// Block storage limit in bytes. Set to 40 KB.
const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024;
parameter_types! {
pub BlockGasLimit: U256 = U256::from(u64::MAX);
pub PrecompilesValue: Precompiles<Runtime> = Precompiles::new();
pub const WeightPerGas: Weight = Weight::from_parts(1, 0);
pub GasLimitPovSizeRatio: u64 = {
let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
block_gas_limit.saturating_div(MAX_POV_SIZE)
};
pub GasLimitStorageGrowthRatio : u64 = {
let block_gas_limit = BlockGasLimit::get().min(u64::MAX.into()).low_u64();
block_gas_limit.saturating_div(BLOCK_STORAGE_LIMIT)
};
}
impl pallet_evm::Config for Runtime {
type FeeCalculator = ();
type GasWeightMapping = pallet_evm::FixedGasWeightMapping<Self>;
type WeightPerGas = WeightPerGas;
type CallOrigin = EnsureAddressRoot<AccountId>;
type WithdrawOrigin = EnsureAddressNever<AccountId>;
type AddressMapping = AccountId;
type Currency = Balances;
type RuntimeEvent = RuntimeEvent;
type Runner = pallet_evm::runner::stack::Runner<Self>;
type PrecompilesType = Precompiles<Self>;
type PrecompilesValue = PrecompilesValue;
type ChainId = ();
type OnChargeTransaction = ();
type BlockGasLimit = BlockGasLimit;
type BlockHashMapping = SubstrateBlockHashMapping<Self>;
type FindAuthor = ();
type OnCreate = ();
type GasLimitPovSizeRatio = GasLimitPovSizeRatio;
type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio;
type Timestamp = Timestamp;
type WeightInfo = pallet_evm::weights::SubstrateWeight<Runtime>;
type AccountProvider = FrameSystemAccountProvider<Runtime>;
}
parameter_types! {
pub const MinimumPeriod: u64 = 5;
}
impl pallet_timestamp::Config for Runtime {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = MinimumPeriod;
type WeightInfo = ();
}
parameter_types! {
pub const LaunchPeriod: BlockNumber = 10;
pub const VotingPeriod: BlockNumber = 10;
pub const VoteLockingPeriod: BlockNumber = 10;
pub const FastTrackVotingPeriod: BlockNumber = 5;
pub const EnactmentPeriod: BlockNumber = 10;
pub const CooloffPeriod: BlockNumber = 10;
pub const MinimumDeposit: Balance = 10;
pub const MaxVotes: u32 = 10;
pub const MaxProposals: u32 = 10;
pub const PreimageByteDeposit: Balance = 10;
pub const InstantAllowed: bool = false;
}
parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
pub const TreasuryId: PalletId = PalletId(*b"pc/trsry");
pub TreasuryAccount: AccountId = Treasury::account_id();
}
#[cfg(feature = "runtime-benchmarks")]
pub struct BenchmarkHelper;
#[cfg(feature = "runtime-benchmarks")]
impl ArgumentsFactory<(), AccountId> for BenchmarkHelper {
fn create_asset_kind(_seed: u32) -> () {
()
}
fn create_beneficiary(seed: [u8; 32]) -> AccountId {
AccountId::from(H160::from(H256::from(seed)))
}
}
impl pallet_treasury::Config for Runtime {
type PalletId = TreasuryId;
type Currency = Balances;
type RejectOrigin = frame_support::traits::NeverEnsureOrigin<Balance>;
type RuntimeEvent = RuntimeEvent;
// If spending proposal rejected, transfer proposer bond to treasury
type SpendPeriod = ConstU32<1>;
type Burn = ();
type BurnDestination = ();
type MaxApprovals = ConstU32<100>;
type WeightInfo = pallet_treasury::weights::SubstrateWeight<Runtime>;
type SpendFunds = ();
type SpendOrigin = MapSuccess<
pallet_collective::EnsureProportionMoreThan<AccountId, pallet_collective::Instance1, 1, 2>,
Replace<ConstU128<1000>>,
>;
type AssetKind = ();
type Beneficiary = AccountId;
type BeneficiaryLookup = IdentityLookup<AccountId>;
type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
type BalanceConverter = UnityAssetBalanceConversion;
type PayoutPeriod = ConstU32<0>;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = BenchmarkHelper;
type BlockNumberProvider = System;
}
parameter_types! {
pub MaxProposalWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000_000);
}
impl pallet_collective::Config<pallet_collective::Instance1> for Runtime {
type RuntimeOrigin = RuntimeOrigin;
type RuntimeEvent = RuntimeEvent;
type Proposal = RuntimeCall;
/// The maximum amount of time (in blocks) for council members to vote on motions.
/// Motions may end in fewer blocks if enough votes are cast to determine the result.
type MotionDuration = ConstU32<2>;
/// The maximum number of Proposlas that can be open in the council at once.
type MaxProposals = ConstU32<100>;
/// The maximum number of council members.
type MaxMembers = ConstU32<100>;
type DefaultVote = pallet_collective::MoreThanMajorityThenPrimeDefaultVote;
type WeightInfo = pallet_collective::weights::SubstrateWeight<Runtime>;
type SetMembersOrigin = frame_system::EnsureRoot<AccountId>;
type MaxProposalWeight = MaxProposalWeight;
type KillOrigin = EnsureRoot<AccountId>;
type DisapproveOrigin = EnsureRoot<AccountId>;
type Consideration = ();
}
/// Build test externalities, prepopulated with data for testing democracy precompiles
pub(crate) struct ExtBuilder {
/// Endowed accounts with balances
balances: Vec<(AccountId, Balance)>,
/// Collective members
collective: Vec<AccountId>,
}
impl Default for ExtBuilder {
fn default() -> ExtBuilder {
ExtBuilder {
balances: vec![],
collective: vec![Bob.into(), Charlie.into()],
}
}
}
impl ExtBuilder {
/// Fund some accounts before starting the test
#[allow(unused)]
pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
self.balances = balances;
self
}
/// Set members of the collective
#[allow(unused)]
pub(crate) fn with_collective(mut self, collective: Vec<AccountId>) -> Self {
self.collective = collective;
self
}
/// Build the test externalities for use in tests
#[allow(unused)]
pub(crate) fn build(self) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Runtime>::default()
.build_storage()
.expect("Frame system builds valid default genesis config");
pallet_balances::GenesisConfig::<Runtime> {
balances: self.balances.clone(),
}
.assimilate_storage(&mut t)
.expect("Pallet balances storage can be assimilated");
pallet_collective::GenesisConfig::<Runtime, pallet_collective::Instance1> {
members: self.collective.clone(),
phantom: Default::default(),
}
.assimilate_storage(&mut t)
.expect("Pallet collective storage can be assimilated");
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}
}
#[allow(unused)]
pub(crate) fn roll_to(n: BlockNumber) {
// We skip timestamp's on_finalize because it requires that the timestamp inherent be set
// We may be able to simulate this by poking its storage directly, but I don't see any value
// added from doing that.
while System::block_number() < n {
Treasury::on_finalize(System::block_number());
// Times tamp::on_finalize(System::block_number());
Evm::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
Evm::on_initialize(System::block_number());
Timestamp::on_initialize(System::block_number());
Treasury::on_initialize(System::block_number());
}
}
pub(crate) fn events() -> Vec<RuntimeEvent> {
System::events()
.into_iter()
.map(|r| r.event)
.collect::<Vec<_>>()
}
#[macro_export]
macro_rules! assert_tail_eq {
($tail:expr, $arr:expr) => {
if $tail.len() != 0 {
// 0-length always passes
if $tail.len() > $arr.len() {
similar_asserts::assert_eq!($tail, $arr); // will fail
}
let len_diff = $arr.len() - $tail.len();
similar_asserts::assert_eq!($tail, $arr[len_diff..]);
}
};
}
/// Panics if an event is not found in the system log of events
#[macro_export]
macro_rules! assert_event_emitted {
($event:expr) => {
match &$event {
e => {
assert!(
crate::mock::events().iter().find(|x| *x == e).is_some(),
"Event {:?} was not found in events: \n {:#?}",
e,
crate::mock::events()
);
}
}
};
}

View file

@ -0,0 +1,685 @@
// Copyright 2019-2025 PureStake Inc.
// This file is part of Moonbeam.
// Moonbeam 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.
// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.
use crate::{
assert_event_emitted, hash, log_closed, log_executed, log_proposed, log_voted,
mock::{ExtBuilder, PCall, Precompiles, PrecompilesValue, Runtime, RuntimeOrigin},
};
use frame_support::{assert_ok, instances::Instance1};
use parity_scale_codec::Encode;
use precompile_utils::{solidity::codec::Address, testing::*};
use sp_core::{H160, H256};
use sp_runtime::DispatchError;
fn precompiles() -> Precompiles<Runtime> {
PrecompilesValue::get()
}
#[test]
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
check_precompile_implements_solidity_interfaces(&["Collective.sol"], PCall::supports_selector)
}
#[test]
fn selector_less_than_four_bytes() {
ExtBuilder::default().build().execute_with(|| {
// This selector is only three bytes long when four are required.
precompiles()
.prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8])
.execute_reverts(|output| output == b"Tried to read selector out of bounds");
});
}
#[test]
fn no_selector_exists_but_length_is_right() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(Alice, Precompile1, vec![1u8, 2u8, 3u8, 4u8])
.execute_reverts(|output| output == b"Unknown selector");
});
}
#[test]
fn selectors() {
assert!(PCall::execute_selectors().contains(&0x09c5eabe));
assert!(PCall::propose_selectors().contains(&0xc57f3260));
assert!(PCall::vote_selectors().contains(&0x73e37688));
assert!(PCall::close_selectors().contains(&0x638d9d47));
assert!(PCall::proposal_hash_selectors().contains(&0xfc379417));
assert!(PCall::proposals_selectors().contains(&0x55ef20e6));
assert!(PCall::members_selectors().contains(&0xbdd4d18d));
assert!(PCall::is_member_selectors().contains(&0xa230c524));
assert!(PCall::prime_selectors().contains(&0xc7ee005e));
}
#[test]
fn modifiers() {
ExtBuilder::default()
.with_balances(vec![(Alice.into(), 1000)])
.build()
.execute_with(|| {
let mut tester = PrecompilesModifierTester::new(precompiles(), Alice, Precompile1);
tester.test_default_modifier(PCall::execute_selectors());
tester.test_default_modifier(PCall::propose_selectors());
tester.test_default_modifier(PCall::vote_selectors());
tester.test_default_modifier(PCall::close_selectors());
tester.test_view_modifier(PCall::proposal_hash_selectors());
tester.test_view_modifier(PCall::proposals_selectors());
tester.test_view_modifier(PCall::members_selectors());
tester.test_view_modifier(PCall::is_member_selectors());
tester.test_view_modifier(PCall::prime_selectors());
});
}
#[test]
fn non_member_cannot_propose() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
precompiles()
.prepare_test(
Alice,
Precompile1,
PCall::propose {
threshold: 1,
proposal: proposal.into(),
},
)
.expect_no_logs()
.execute_reverts(|output| output.ends_with(b"NotMember\") })"));
});
}
#[test]
fn non_member_cannot_vote() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(
Alice,
Precompile1,
PCall::vote {
proposal_hash: H256::zero(),
proposal_index: 1,
approve: false,
},
)
.expect_no_logs()
.execute_reverts(|output| output.ends_with(b"NotMember\") })"));
});
}
#[test]
fn non_member_cannot_execute() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
precompiles()
.prepare_test(
Alice,
Precompile1,
PCall::execute {
proposal: proposal.into(),
},
)
.expect_no_logs()
.execute_reverts(|output| output.ends_with(b"NotMember\") })"));
});
}
#[test]
fn cannot_vote_for_unknown_proposal() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::vote {
proposal_hash: H256::zero(),
proposal_index: 1,
approve: false,
},
)
.expect_no_logs()
.execute_reverts(|output| output.ends_with(b"ProposalMissing\") })"));
});
}
#[test]
fn cannot_close_unknown_proposal() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::close {
proposal_hash: H256::zero(),
proposal_index: 1,
proposal_weight_bound: 0,
length_bound: 0,
},
)
.expect_no_logs()
.execute_reverts(|output| output.ends_with(b"ProposalMissing\") })"));
});
}
#[test]
fn member_can_make_instant_proposal() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
// Proposal is executed. The proposal call will itself fail but it
// still counts as a success according to pallet_collective.
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 1,
proposal: proposal.into(),
},
)
.expect_log(log_executed(Precompile1, proposal_hash))
.execute_returns(0u32);
assert_event_emitted!(pallet_collective::Event::Executed {
proposal_hash,
result: Err(DispatchError::BadOrigin)
}
.into());
});
}
#[test]
fn member_can_make_delayed_proposal() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
.execute_returns(0u32);
assert_event_emitted!(pallet_collective::Event::Proposed {
account: Bob.into(),
proposal_index: 0,
proposal_hash,
threshold: 2,
}
.into());
});
}
#[test]
fn member_can_vote_on_proposal() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
.execute_returns(0u32);
precompiles()
.prepare_test(
Charlie,
Precompile1,
PCall::vote {
proposal_hash,
proposal_index: 0,
approve: true,
},
)
.expect_log(log_voted(Precompile1, Charlie, proposal_hash, true))
.execute_returns(());
assert_event_emitted!(pallet_collective::Event::Voted {
account: Charlie.into(),
proposal_hash,
voted: true,
yes: 1,
no: 0,
}
.into());
});
}
#[test]
fn cannot_close_if_not_enough_votes() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
let length_bound = proposal.len() as u32;
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
.execute_returns(0u32);
precompiles()
.prepare_test(
Alice,
Precompile1,
PCall::close {
proposal_hash,
proposal_index: 0,
proposal_weight_bound: 10_000_000,
length_bound,
},
)
.expect_no_logs()
.execute_reverts(|output| output.ends_with(b"TooEarly\") })"));
});
}
#[test]
fn can_close_execute_if_enough_votes() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
let length_bound = proposal.len() as u32;
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
.execute_returns(0u32);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::vote {
proposal_hash,
proposal_index: 0,
approve: true,
},
)
.expect_log(log_voted(Precompile1, Bob, proposal_hash, true))
.execute_returns(());
precompiles()
.prepare_test(
Charlie,
Precompile1,
PCall::vote {
proposal_hash,
proposal_index: 0,
approve: true,
},
)
.expect_log(log_voted(Precompile1, Charlie, proposal_hash, true))
.execute_returns(());
precompiles()
.prepare_test(
Alice,
Precompile1,
PCall::close {
proposal_hash,
proposal_index: 0,
proposal_weight_bound: 200_000_000,
length_bound,
},
)
.expect_log(log_executed(Precompile1, proposal_hash))
.execute_returns(true);
assert_event_emitted!(pallet_collective::Event::Closed {
proposal_hash,
yes: 2,
no: 0,
}
.into());
assert_event_emitted!(pallet_collective::Event::Approved { proposal_hash }.into());
assert_event_emitted!(pallet_collective::Event::Executed {
proposal_hash,
result: Ok(())
}
.into());
assert_event_emitted!(pallet_treasury::Event::SpendApproved {
proposal_index: 0,
amount: 1,
beneficiary: Alice.into(),
}
.into());
});
}
#[test]
fn can_close_refuse_if_enough_votes() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
let length_bound = proposal.len() as u32;
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
.execute_returns(0u32);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::vote {
proposal_hash,
proposal_index: 0,
approve: false,
},
)
.expect_log(log_voted(Precompile1, Bob, proposal_hash, false))
.execute_returns(());
precompiles()
.prepare_test(
Charlie,
Precompile1,
PCall::vote {
proposal_hash,
proposal_index: 0,
approve: false,
},
)
.expect_log(log_voted(Precompile1, Charlie, proposal_hash, false))
.execute_returns(());
precompiles()
.prepare_test(
Alice,
Precompile1,
PCall::close {
proposal_hash,
proposal_index: 0,
proposal_weight_bound: 100_000_000,
length_bound,
},
)
.expect_log(log_closed(Precompile1, proposal_hash))
.execute_returns(false);
assert_event_emitted!(pallet_collective::Event::Closed {
proposal_hash,
yes: 0,
no: 2,
}
.into());
assert_event_emitted!(pallet_collective::Event::Disapproved { proposal_hash }.into());
});
}
#[test]
fn multiple_propose_increase_index() {
ExtBuilder::default().build().execute_with(|| {
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 1,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 0, proposal_hash, 2))
.execute_returns(0u32);
let proposal = pallet_treasury::Call::<Runtime>::spend_local {
amount: 2,
beneficiary: Alice.into(),
};
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
let proposal_hash: H256 = hash::<Runtime>(&proposal);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::propose {
threshold: 2,
proposal: proposal.into(),
},
)
.expect_log(log_proposed(Precompile1, Bob, 1, proposal_hash, 2))
.execute_returns(1u32);
});
}
#[test]
fn view_members() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(Bob, Precompile1, PCall::members {})
.expect_no_logs()
.execute_returns(vec![Address(Bob.into()), Address(Charlie.into())]);
});
}
#[test]
fn view_no_prime() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(Bob, Precompile1, PCall::prime {})
.expect_no_logs()
.execute_returns(Address(H160::zero()));
});
}
#[test]
fn view_some_prime() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(pallet_collective::Pallet::<
Runtime,
pallet_collective::Instance1,
>::set_members(
RuntimeOrigin::root(),
vec![Alice.into(), Bob.into()],
Some(Alice.into()),
2
));
precompiles()
.prepare_test(Bob, Precompile1, PCall::prime {})
.expect_no_logs()
.execute_returns(Address(Alice.into()));
});
}
#[test]
fn view_is_member() {
ExtBuilder::default().build().execute_with(|| {
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::is_member {
account: Address(Bob.into()),
},
)
.expect_no_logs()
.execute_returns(true);
precompiles()
.prepare_test(
Bob,
Precompile1,
PCall::is_member {
account: Address(Alice.into()),
},
)
.expect_no_logs()
.execute_returns(false);
});
}
mod bounded_proposal_decode {
use super::*;
use crate::GetProposalLimit;
use precompile_utils::prelude::BoundedBytes;
fn scenario<F>(nesting: usize, call: F)
where
F: FnOnce(BoundedBytes<GetProposalLimit>) -> PCall,
{
ExtBuilder::default().build().execute_with(|| {
// Some random call.
let mut proposal = pallet_collective::Call::<Runtime, Instance1>::set_members {
new_members: Vec::new(),
prime: None,
old_count: 0,
};
// Nest it.
for _ in 0..nesting {
proposal = pallet_collective::Call::<Runtime, Instance1>::propose {
threshold: 10,
proposal: Box::new(proposal.into()),
length_bound: 1,
};
}
let proposal: <Runtime as frame_system::Config>::RuntimeCall = proposal.into();
let proposal = proposal.encode();
precompiles()
.prepare_test(Alice, Precompile1, call(proposal.into()))
.expect_no_logs()
.execute_reverts(|output| {
if nesting < 8 {
output.ends_with(b"NotMember\") })")
} else {
output == b"proposal: Failed to decode proposal"
}
});
});
}
#[test]
fn proposal_above_bound() {
scenario(8, |proposal| PCall::propose {
threshold: 1,
proposal,
});
}
#[test]
fn proposal_below_bound() {
scenario(7, |proposal| PCall::propose {
threshold: 1,
proposal,
});
}
#[test]
fn execute_above_bound() {
scenario(8, |proposal| PCall::execute { proposal });
}
#[test]
fn execute_below_bound() {
scenario(7, |proposal| PCall::execute { proposal });
}
}

View file

@ -126,9 +126,10 @@ strum_macros = { workspace = true }
pallet-evm-precompile-balances-erc20 = { workspace = true }
pallet-evm-precompile-batch = { workspace = true }
pallet-evm-precompile-call-permit = { workspace = true }
pallet-evm-precompile-collective = { workspace = true }
pallet-evm-precompile-identity = { workspace = true }
pallet-evm-precompile-proxy = { workspace = true }
pallet-evm-precompile-registry = { workspace = true }
pallet-evm-precompile-identity = { workspace = true }
# StorageHub
pallet-bucket-nfts = { workspace = true }
@ -199,9 +200,10 @@ std = [
"pallet-evm-precompile-balances-erc20/std",
"pallet-evm-precompile-batch/std",
"pallet-evm-precompile-call-permit/std",
"pallet-evm-precompile-collective/std",
"pallet-evm-precompile-identity/std",
"pallet-evm-precompile-proxy/std",
"pallet-evm-precompile-registry/std",
"pallet-evm-precompile-identity/std",
"pallet-external-validators/std",
"pallet-external-validators-rewards/std",
"pallet-external-validators-rewards-runtime-api/std",

View file

@ -739,17 +739,11 @@ impl frame_support::traits::InstanceFilter<RuntimeCall> for ProxyType {
}
/// Helper function to identify governance precompiles (copied from Moonbeam)
fn is_governance_precompile(_precompile_name: &PrecompileName) -> bool {
// TODO: Uncomment when DataHaven implements these governance precompiles
// matches!(
// precompile_name,
// PrecompileName::ConvictionVotingPrecompile
// | PrecompileName::PreimagePrecompile
// | PrecompileName::ReferendaPrecompile
// | PrecompileName::OpenTechCommitteeInstance
// | PrecompileName::TreasuryCouncilInstance
// )
false // Temporarily disabled until governance precompiles are added
fn is_governance_precompile(precompile_name: &PrecompileName) -> bool {
matches!(
precompile_name,
PrecompileName::TechnicalCommitteeInstance | PrecompileName::TreasuryCouncilInstance
)
}
impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {

View file

@ -15,11 +15,13 @@
// along with DataHaven. If not, see <http://www.gnu.org/licenses/>.
use crate::configs::MaxAdditionalFields;
use crate::governance::councils::{TechnicalCommitteeInstance, TreasuryCouncilInstance};
use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadata};
use pallet_evm_precompile_batch::BatchPrecompile;
use pallet_evm_precompile_blake2::Blake2F;
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_call_permit::CallPermitPrecompile;
use pallet_evm_precompile_collective::CollectivePrecompile;
use pallet_evm_precompile_file_system::FileSystemPrecompile;
use pallet_evm_precompile_identity::IdentityPrecompile;
use pallet_evm_precompile_modexp::Modexp;
@ -100,6 +102,16 @@ type DataHavenPrecompilesAt<R> = (
CallableByPrecompile<OnlyFrom<AddressU64<2056>>>,
),
>,
PrecompileAt<
AddressU64<2064>,
CollectivePrecompile<R, TreasuryCouncilInstance>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2068>,
CollectivePrecompile<R, TechnicalCommitteeInstance>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2069>,
PrecompileRegistry<R>,

View file

@ -126,9 +126,10 @@ xcm-executor = { workspace = true }
pallet-evm-precompile-balances-erc20 = { workspace = true }
pallet-evm-precompile-batch = { workspace = true }
pallet-evm-precompile-call-permit = { workspace = true }
pallet-evm-precompile-collective = { workspace = true }
pallet-evm-precompile-identity = { workspace = true }
pallet-evm-precompile-proxy = { workspace = true }
pallet-evm-precompile-registry = { workspace = true }
pallet-evm-precompile-identity = { workspace = true }
# StorageHub
pallet-bucket-nfts = { workspace = true }
@ -199,9 +200,10 @@ std = [
"pallet-evm-precompile-balances-erc20/std",
"pallet-evm-precompile-batch/std",
"pallet-evm-precompile-call-permit/std",
"pallet-evm-precompile-collective/std",
"pallet-evm-precompile-identity/std",
"pallet-evm-precompile-proxy/std",
"pallet-evm-precompile-registry/std",
"pallet-evm-precompile-identity/std",
"pallet-external-validators/std",
"pallet-external-validators-rewards/std",
"pallet-external-validators-rewards-runtime-api/std",

View file

@ -738,17 +738,11 @@ impl frame_support::traits::InstanceFilter<RuntimeCall> for ProxyType {
}
/// Helper function to identify governance precompiles (copied from Moonbeam)
fn is_governance_precompile(_precompile_name: &PrecompileName) -> bool {
// TODO: Uncomment when DataHaven implements these governance precompiles
// matches!(
// precompile_name,
// PrecompileName::ConvictionVotingPrecompile
// | PrecompileName::PreimagePrecompile
// | PrecompileName::ReferendaPrecompile
// | PrecompileName::OpenTechCommitteeInstance
// | PrecompileName::TreasuryCouncilInstance
// )
false // Temporarily disabled until governance precompiles are added
fn is_governance_precompile(precompile_name: &PrecompileName) -> bool {
matches!(
precompile_name,
PrecompileName::TechnicalCommitteeInstance | PrecompileName::TreasuryCouncilInstance
)
}
impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {

View file

@ -15,11 +15,13 @@
// along with DataHaven. If not, see <http://www.gnu.org/licenses/>.
use crate::configs::MaxAdditionalFields;
use crate::governance::councils::{TechnicalCommitteeInstance, TreasuryCouncilInstance};
use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadata};
use pallet_evm_precompile_batch::BatchPrecompile;
use pallet_evm_precompile_blake2::Blake2F;
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_call_permit::CallPermitPrecompile;
use pallet_evm_precompile_collective::CollectivePrecompile;
use pallet_evm_precompile_file_system::FileSystemPrecompile;
use pallet_evm_precompile_identity::IdentityPrecompile;
use pallet_evm_precompile_modexp::Modexp;
@ -100,6 +102,16 @@ type DataHavenPrecompilesAt<R> = (
CallableByPrecompile<OnlyFrom<AddressU64<2056>>>,
),
>,
PrecompileAt<
AddressU64<2064>,
CollectivePrecompile<R, TreasuryCouncilInstance>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2068>,
CollectivePrecompile<R, TechnicalCommitteeInstance>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2069>,
PrecompileRegistry<R>,

View file

@ -126,9 +126,10 @@ strum_macros = { workspace = true }
pallet-evm-precompile-balances-erc20 = { workspace = true }
pallet-evm-precompile-batch = { workspace = true }
pallet-evm-precompile-call-permit = { workspace = true }
pallet-evm-precompile-collective = { workspace = true }
pallet-evm-precompile-identity = { workspace = true }
pallet-evm-precompile-proxy = { workspace = true }
pallet-evm-precompile-registry = { workspace = true }
pallet-evm-precompile-identity = { workspace = true }
# StorageHub
pallet-bucket-nfts = { workspace = true }
@ -199,9 +200,10 @@ std = [
"pallet-evm-precompile-balances-erc20/std",
"pallet-evm-precompile-batch/std",
"pallet-evm-precompile-call-permit/std",
"pallet-evm-precompile-collective/std",
"pallet-evm-precompile-identity/std",
"pallet-evm-precompile-proxy/std",
"pallet-evm-precompile-registry/std",
"pallet-evm-precompile-identity/std",
"pallet-evm-precompile-file-system/std",
"pallet-grandpa/std",
"pallet-identity/std",

View file

@ -738,17 +738,11 @@ impl frame_support::traits::InstanceFilter<RuntimeCall> for ProxyType {
}
/// Helper function to identify governance precompiles (copied from Moonbeam)
fn is_governance_precompile(_precompile_name: &PrecompileName) -> bool {
// TODO: Uncomment when DataHaven implements these governance precompiles
// matches!(
// precompile_name,
// PrecompileName::ConvictionVotingPrecompile
// | PrecompileName::PreimagePrecompile
// | PrecompileName::ReferendaPrecompile
// | PrecompileName::OpenTechCommitteeInstance
// | PrecompileName::TreasuryCouncilInstance
// )
false // Temporarily disabled until governance precompiles are added
fn is_governance_precompile(precompile_name: &PrecompileName) -> bool {
matches!(
precompile_name,
PrecompileName::TechnicalCommitteeInstance | PrecompileName::TreasuryCouncilInstance
)
}
impl pallet_evm_precompile_proxy::EvmProxyCallFilter for ProxyType {

View file

@ -15,11 +15,13 @@
// along with DataHaven. If not, see <http://www.gnu.org/licenses/>.
use crate::configs::MaxAdditionalFields;
use crate::governance::councils::{TechnicalCommitteeInstance, TreasuryCouncilInstance};
use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadata};
use pallet_evm_precompile_batch::BatchPrecompile;
use pallet_evm_precompile_blake2::Blake2F;
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_call_permit::CallPermitPrecompile;
use pallet_evm_precompile_collective::CollectivePrecompile;
use pallet_evm_precompile_file_system::FileSystemPrecompile;
use pallet_evm_precompile_identity::IdentityPrecompile;
use pallet_evm_precompile_modexp::Modexp;
@ -100,6 +102,16 @@ type DataHavenPrecompilesAt<R> = (
CallableByPrecompile<OnlyFrom<AddressU64<2056>>>,
),
>,
PrecompileAt<
AddressU64<2064>,
CollectivePrecompile<R, TreasuryCouncilInstance>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2068>,
CollectivePrecompile<R, TechnicalCommitteeInstance>,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2069>,
PrecompileRegistry<R>,