Skip to content

Commit

Permalink
Fix treasury spend origin (#3130)
Browse files Browse the repository at this point in the history
* add root as spend origin

* test: add rust tests

* update spender origin

* test: add rust tests

* fix rust code formatting

* add treasury council test

* improve test title

* remove Treasury calls from NormalFilter and update tests

* update typescript tests
  • Loading branch information
RomarQ authored Jan 17, 2025
1 parent bbe2685 commit e167a3a
Show file tree
Hide file tree
Showing 9 changed files with 570 additions and 94 deletions.
16 changes: 4 additions & 12 deletions runtime/moonbase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,10 @@ parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
pub const TreasuryId: PalletId = PalletId(*b"pc/trsry");
pub TreasuryAccount: AccountId = Treasury::account_id();
pub const MaxSpendBalance: crate::Balance = crate::Balance::max_value();
}

type TreasuryRejectOrigin = EitherOfDiverse<
type RootOrTreasuryCouncilOrigin = EitherOfDiverse<
EnsureRoot<AccountId>,
pallet_collective::EnsureProportionMoreThan<AccountId, TreasuryCouncilInstance, 1, 2>,
>;
Expand All @@ -569,19 +570,16 @@ impl pallet_treasury::Config for Runtime {
type PalletId = TreasuryId;
type Currency = Balances;
// More than half of the council is required (or root) to reject a proposal
type RejectOrigin = TreasuryRejectOrigin;
type RejectOrigin = RootOrTreasuryCouncilOrigin;
type RuntimeEvent = RuntimeEvent;
type SpendPeriod = ConstU32<{ 6 * DAYS }>;
type Burn = ();
type BurnDestination = ();
type MaxApprovals = ConstU32<100>;
type WeightInfo = moonbase_weights::pallet_treasury::WeightInfo<Runtime>;
type SpendFunds = ();
#[cfg(not(feature = "runtime-benchmarks"))]
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<Balance>; // Disabled, no spending
#[cfg(feature = "runtime-benchmarks")]
type SpendOrigin =
frame_system::EnsureWithSuccess<EnsureRoot<AccountId>, AccountId, benches::MaxBalance>;
frame_system::EnsureWithSuccess<RootOrTreasuryCouncilOrigin, AccountId, MaxSpendBalance>;
type AssetKind = ();
type Beneficiary = AccountId;
type BeneficiaryLookup = IdentityLookup<AccountId>;
Expand Down Expand Up @@ -1190,12 +1188,6 @@ impl Contains<RuntimeCall> for NormalFilter {
// Note: It is also assumed that EVM calls are only allowed through `Origin::Root` so
// this can be seen as an additional security
RuntimeCall::EVM(_) => false,
RuntimeCall::Treasury(
pallet_treasury::Call::spend { .. }
| pallet_treasury::Call::payout { .. }
| pallet_treasury::Call::check_status { .. }
| pallet_treasury::Call::void_spend { .. },
) => false,
_ => true,
}
}
Expand Down
172 changes: 170 additions & 2 deletions runtime/moonbase/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use frame_support::{
assert_noop, assert_ok,
dispatch::DispatchClass,
traits::{
fungible::Inspect, Currency as CurrencyT, EnsureOrigin, PalletInfo, StorageInfo,
StorageInfoTrait,
fungible::Inspect, Currency as CurrencyT, EnsureOrigin, OnInitialize, PalletInfo,
StorageInfo, StorageInfoTrait,
},
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight},
StorageHasher, Twox128,
Expand All @@ -56,6 +56,7 @@ use moonbase_runtime::{
System,
TransactionPayment,
TransactionPaymentAsGasPrice,
Treasury,
TreasuryCouncilCollective,
XcmTransactor,
FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX,
Expand Down Expand Up @@ -2973,6 +2974,173 @@ fn validate_transaction_fails_on_filtered_call() {
});
}

#[cfg(test)]
mod treasury_tests {
use super::*;
use sp_runtime::traits::Hash;

fn expect_events(events: Vec<RuntimeEvent>) {
let block_events: Vec<RuntimeEvent> =
System::events().into_iter().map(|r| r.event).collect();

dbg!(events.clone());
dbg!(block_events.clone());

assert!(events.iter().all(|evt| block_events.contains(evt)))
}

fn next_block() {
System::reset_events();
System::set_block_number(System::block_number() + 1u32);
System::on_initialize(System::block_number());
Treasury::on_initialize(System::block_number());
}

#[test]
fn test_treasury_spend_local_with_root_origin() {
let initial_treasury_balance = 1_000 * UNIT;
ExtBuilder::default()
.with_balances(vec![
(AccountId::from(ALICE), 2_000 * UNIT),
(Treasury::account_id(), initial_treasury_balance),
])
.build()
.execute_with(|| {
let spend_amount = 100u128 * UNIT;
let spend_beneficiary = AccountId::from(BOB);

next_block();

// Perform treasury spending

assert_ok!(moonbase_runtime::Sudo::sudo(
root_origin(),
Box::new(RuntimeCall::Treasury(pallet_treasury::Call::spend {
amount: spend_amount,
asset_kind: Box::new(()),
beneficiary: Box::new(AccountId::from(BOB)),
valid_from: Some(5u32),
}))
));

let payout_period =
<<Runtime as pallet_treasury::Config>::PayoutPeriod as Get<u32>>::get();
let expected_events = [RuntimeEvent::Treasury(
pallet_treasury::Event::AssetSpendApproved {
index: 0,
asset_kind: (),
amount: spend_amount,
beneficiary: spend_beneficiary,
valid_from: 5u32,
expire_at: payout_period + 5u32,
},
)]
.to_vec();
expect_events(expected_events);

while System::block_number() < 5u32 {
next_block();
}

assert_ok!(Treasury::payout(origin_of(spend_beneficiary), 0));

let expected_events = [
RuntimeEvent::Treasury(pallet_treasury::Event::Paid {
index: 0,
payment_id: (),
}),
RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: Treasury::account_id(),
to: spend_beneficiary,
amount: spend_amount,
}),
]
.to_vec();
expect_events(expected_events);
});
}

#[test]
fn test_treasury_spend_local_with_council_origin() {
let initial_treasury_balance = 1_000 * UNIT;
ExtBuilder::default()
.with_balances(vec![
(AccountId::from(ALICE), 2_000 * UNIT),
(Treasury::account_id(), initial_treasury_balance),
])
.build()
.execute_with(|| {
let spend_amount = 100u128 * UNIT;
let spend_beneficiary = AccountId::from(BOB);

next_block();

// TreasuryCouncilCollective
assert_ok!(TreasuryCouncilCollective::set_members(
root_origin(),
vec![AccountId::from(ALICE)],
Some(AccountId::from(ALICE)),
1
));

next_block();

// Perform treasury spending
let proposal = RuntimeCall::Treasury(pallet_treasury::Call::spend {
amount: spend_amount,
asset_kind: Box::new(()),
beneficiary: Box::new(AccountId::from(BOB)),
valid_from: Some(5u32),
});
assert_ok!(TreasuryCouncilCollective::propose(
origin_of(AccountId::from(ALICE)),
1,
Box::new(proposal.clone()),
1_000
));

let payout_period =
<<Runtime as pallet_treasury::Config>::PayoutPeriod as Get<u32>>::get();
let expected_events = [
RuntimeEvent::Treasury(pallet_treasury::Event::AssetSpendApproved {
index: 0,
asset_kind: (),
amount: spend_amount,
beneficiary: spend_beneficiary,
valid_from: 5u32,
expire_at: payout_period + 5u32,
}),
RuntimeEvent::TreasuryCouncilCollective(pallet_collective::Event::Executed {
proposal_hash: sp_runtime::traits::BlakeTwo256::hash_of(&proposal),
result: Ok(()),
}),
]
.to_vec();
expect_events(expected_events);

while System::block_number() < 5u32 {
next_block();
}

assert_ok!(Treasury::payout(origin_of(spend_beneficiary), 0));

let expected_events = [
RuntimeEvent::Treasury(pallet_treasury::Event::Paid {
index: 0,
payment_id: (),
}),
RuntimeEvent::Balances(pallet_balances::Event::Transfer {
from: Treasury::account_id(),
to: spend_beneficiary,
amount: spend_amount,
}),
]
.to_vec();
expect_events(expected_events);
});
}
}

#[cfg(test)]
mod fee_tests {
use super::*;
Expand Down
16 changes: 4 additions & 12 deletions runtime/moonbeam/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,10 @@ parameter_types! {
pub const ProposalBond: Permill = Permill::from_percent(5);
pub const TreasuryId: PalletId = PalletId(*b"py/trsry");
pub TreasuryAccount: AccountId = Treasury::account_id();
pub const MaxSpendBalance: crate::Balance = crate::Balance::max_value();
}

type TreasuryRejectOrigin = EitherOfDiverse<
type RootOrTreasuryCouncilOrigin = EitherOfDiverse<
EnsureRoot<AccountId>,
pallet_collective::EnsureProportionMoreThan<AccountId, TreasuryCouncilInstance, 1, 2>,
>;
Expand All @@ -557,19 +558,16 @@ impl pallet_treasury::Config for Runtime {
type PalletId = TreasuryId;
type Currency = Balances;
// More than half of the council is required (or root) to reject a proposal
type RejectOrigin = TreasuryRejectOrigin;
type RejectOrigin = RootOrTreasuryCouncilOrigin;
type RuntimeEvent = RuntimeEvent;
type SpendPeriod = ConstU32<{ 6 * DAYS }>;
type Burn = ();
type BurnDestination = ();
type MaxApprovals = ConstU32<100>;
type WeightInfo = moonbeam_weights::pallet_treasury::WeightInfo<Runtime>;
type SpendFunds = ();
#[cfg(not(feature = "runtime-benchmarks"))]
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<Balance>; // Disabled, no spending
#[cfg(feature = "runtime-benchmarks")]
type SpendOrigin =
frame_system::EnsureWithSuccess<EnsureRoot<AccountId>, AccountId, benches::MaxBalance>;
frame_system::EnsureWithSuccess<RootOrTreasuryCouncilOrigin, AccountId, MaxSpendBalance>;
type AssetKind = ();
type Beneficiary = AccountId;
type BeneficiaryLookup = IdentityLookup<AccountId>;
Expand Down Expand Up @@ -1193,12 +1191,6 @@ impl Contains<RuntimeCall> for NormalFilter {
// Note: It is also assumed that EVM calls are only allowed through `Origin::Root` so
// this can be seen as an additional security
RuntimeCall::EVM(_) => false,
RuntimeCall::Treasury(
pallet_treasury::Call::spend { .. }
| pallet_treasury::Call::payout { .. }
| pallet_treasury::Call::check_status { .. }
| pallet_treasury::Call::void_spend { .. },
) => false,
_ => true,
}
}
Expand Down
Loading

0 comments on commit e167a3a

Please sign in to comment.