mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
## Polkadot upgrade 2503 In this PR, we upgrade form Polkadot SDK 2412 to Polkadot SDK 2503. In order to upgrade the SDK we need to upgrade some dependencies : StorageHub and frontier simultaneously. ## What changes ### Trivial changes * https://github.com/paritytech/polkadot-sdk/pull/7634 -> The new trait is required in all the pallets using scale encoding. * https://github.com/paritytech/polkadot-sdk/pull/7043 -> Remove deprecated `sp-std` and replace with `alloc` or `core`. * https://github.com/paritytech/polkadot-sdk/pull/6140 -> Accurate weight reclaim with frame_system::WeightReclaim ### Breaking changes * https://github.com/paritytech/polkadot-sdk/pull/2072 -> There is a change in `pallet-referenda`. Now, the tracks are retrieved as a list of `Track`s. Also, the names of the tracks might have some trailing null values (`\0`). This means display representation of the tracks' names must be sanitized. * https://github.com/paritytech/polkadot-sdk/pull/5723 -> adds the ability for these pallets to specify their source of the block number. This is useful when these pallets are migrated from the relay chain to a parachain and vice versa. (Not entirely sure it is breaking as it is being marked as backward compatible). * https://github.com/paritytech/polkadot-sdk/pull/6338 -> Update Referenda to Support Block Number Provider ### Notable changes * https://github.com/paritytech/polkadot-sdk/pull/5703 -> Not changes required in the codebase but impact fastSync mode. Should improve testing. * https://github.com/paritytech/polkadot-sdk/pull/5842 -> Removes `libp2p` types in authority-discovery, and replace them with network backend agnostic types from `sc-network-types`. The `sc-network` interface is therefore updated accordingly. ## What changes in Frontier * https://github.com/polkadot-evm/frontier/pull/1693 -> Add support for EIP 7702 which has been enable with Pectra. This EIP add a new field `AuthorizationList` in Ethereum transaction. Changes example : ```rust #[test] fn validate_transaction_fails_on_filtered_call() { ... pallet_evm::Call::<Runtime>::call { source: H160::default(), target: H160::default(), input: Vec::new(), value: sp_core::U256::zero(), gas_limit: 21000, max_fee_per_gas: sp_core::U256::zero(), max_priority_fee_per_gas: Some(sp_core::U256::zero()), nonce: None, access_list: Vec::new(), authorization_list: Vec::new(), } .into(), ``` ## ⚠️ Breaking Changes ⚠️ * Upgrade to Stirage hub v0.5.1 * Support for Ethereum latest upgrade (txs now have the `authoriation_list` for support EIP 7702) --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
550 lines
22 KiB
Rust
550 lines
22 KiB
Rust
// Copyright 2025 DataHaven
|
|
// This file is part of DataHaven.
|
|
|
|
// DataHaven 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.
|
|
|
|
// DataHaven 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 DataHaven. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//! Moonbase Runtime Integration Tests
|
|
|
|
mod common;
|
|
use common::*;
|
|
|
|
use datahaven_runtime_common::Balance;
|
|
use datahaven_testnet_runtime::{
|
|
configs::{
|
|
runtime_params::dynamic_params::runtime_config::FeesTreasuryProportion,
|
|
TransactionPaymentAsGasPrice,
|
|
},
|
|
currency::*,
|
|
AccountId, Balances, ExistentialDeposit, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin,
|
|
System, Treasury, TreasuryCouncil,
|
|
};
|
|
use fp_evm::FeeCalculator;
|
|
use frame_support::{
|
|
assert_ok,
|
|
traits::{Currency as CurrencyT, Get},
|
|
};
|
|
use sp_core::{H160, U256};
|
|
use sp_runtime::traits::{Dispatchable, Hash as HashT};
|
|
const BASE_FEE_GENESIS: u128 = 10 * MILLIHAVE / 4;
|
|
|
|
/// Helper function to get existential deposit (specific to this test file)
|
|
fn existential_deposit() -> Balance {
|
|
ExistentialDeposit::get()
|
|
}
|
|
|
|
#[test]
|
|
fn author_does_receive_priority_fee() {
|
|
ExtBuilder::default()
|
|
.with_balances(vec![(
|
|
AccountId::from(BOB),
|
|
(1 * HAVE) + (21_000 * (500 * MILLIHAVE)),
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// Set Charlie (validator index 0) as the block author
|
|
set_block_author_by_index(0);
|
|
let author = get_validator_by_index(0);
|
|
|
|
// Currently the default impl of the evm uses `deposit_into_existing`.
|
|
// If we were to use this implementation, and for an author to receive eventual tips,
|
|
// the account needs to be somehow initialized, otherwise the deposit would fail.
|
|
Balances::make_free_balance_be(&author, 100 * HAVE);
|
|
|
|
// EVM transfer.
|
|
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::<Runtime>::call {
|
|
source: H160::from(BOB),
|
|
target: H160::from(ALICE),
|
|
input: Vec::new(),
|
|
value: (1 * HAVE).into(),
|
|
gas_limit: 21_000u64,
|
|
max_fee_per_gas: U256::from(300 * MILLIHAVE),
|
|
max_priority_fee_per_gas: Some(U256::from(200 * MILLIHAVE)),
|
|
nonce: Some(U256::from(0)),
|
|
access_list: Vec::new(),
|
|
authorization_list: Vec::new(),
|
|
})
|
|
.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::root()));
|
|
|
|
let priority_fee = 200 * MILLIHAVE * 21_000;
|
|
// Author free balance increased by priority fee.
|
|
assert_eq!(Balances::free_balance(author), 100 * HAVE + priority_fee,);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn total_issuance_after_evm_transaction_with_priority_fee() {
|
|
ExtBuilder::default()
|
|
.with_balances(vec![
|
|
(
|
|
AccountId::from(BOB),
|
|
(1 * HAVE) + (21_000 * (2 * BASE_FEE_GENESIS) + existential_deposit()),
|
|
),
|
|
(AccountId::from(ALICE), existential_deposit()),
|
|
(
|
|
<pallet_treasury::TreasuryAccountId<Runtime> as sp_core::TypedGet>::get(),
|
|
existential_deposit(),
|
|
),
|
|
])
|
|
.build()
|
|
.execute_with(|| {
|
|
let issuance_before = <Runtime as pallet_evm::Config>::Currency::total_issuance();
|
|
|
|
// Set Charlie (validator index 0) as the block author
|
|
set_block_author_by_index(0);
|
|
let _author = get_validator_by_index(0);
|
|
|
|
// EVM transfer.
|
|
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::<Runtime>::call {
|
|
source: H160::from(BOB),
|
|
target: H160::from(ALICE),
|
|
input: Vec::new(),
|
|
value: (1 * HAVE).into(),
|
|
gas_limit: 21_000u64,
|
|
max_fee_per_gas: U256::from(2 * BASE_FEE_GENESIS),
|
|
max_priority_fee_per_gas: Some(U256::from(BASE_FEE_GENESIS)),
|
|
nonce: Some(U256::from(0)),
|
|
access_list: Vec::new(),
|
|
authorization_list: Vec::new(),
|
|
})
|
|
.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::root()));
|
|
|
|
let issuance_after = <Runtime as pallet_evm::Config>::Currency::total_issuance();
|
|
|
|
// Get the actual base fee that was charged by querying the fee calculator
|
|
// This represents the real network base fee, not the genesis constant
|
|
let (actual_base_fee_u256, _) = TransactionPaymentAsGasPrice::min_gas_price();
|
|
let actual_base_fee: Balance = actual_base_fee_u256.as_u128();
|
|
|
|
// Calculate expected treasury and burn amounts based on the actual base fee
|
|
let gas_used = 21_000u128; // Standard transfer gas
|
|
let total_base_fee_charged = actual_base_fee * gas_used;
|
|
|
|
let treasury_proportion = FeesTreasuryProportion::get();
|
|
let expected_treasury_amount = treasury_proportion.mul_floor(total_base_fee_charged);
|
|
let expected_burnt_amount = total_base_fee_charged - expected_treasury_amount;
|
|
|
|
// Verify total issuance was reduced by the burnt amount
|
|
assert_eq!(
|
|
issuance_after,
|
|
issuance_before - expected_burnt_amount,
|
|
"Total issuance should decrease by burnt base fee amount"
|
|
);
|
|
|
|
// Verify treasury received the expected amount
|
|
// Treasury pot starts at 0 and should equal exactly the treasury fee amount
|
|
let treasury_final_balance = datahaven_testnet_runtime::Treasury::pot();
|
|
assert_eq!(
|
|
treasury_final_balance,
|
|
expected_treasury_amount,
|
|
"Treasury should receive {}% of base fee: expected {}, got {}",
|
|
treasury_proportion.deconstruct() as f64 / 10_000_000.0 * 100.0,
|
|
expected_treasury_amount,
|
|
treasury_final_balance
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn total_issuance_after_evm_transaction_without_priority_fee() {
|
|
use fp_evm::FeeCalculator;
|
|
ExtBuilder::default()
|
|
.with_balances(vec![
|
|
(
|
|
AccountId::from(BOB),
|
|
(1 * HAVE) + (21_000 * (2 * BASE_FEE_GENESIS)),
|
|
),
|
|
(AccountId::from(ALICE), existential_deposit()),
|
|
(
|
|
<pallet_treasury::TreasuryAccountId<Runtime> as sp_core::TypedGet>::get(),
|
|
existential_deposit(),
|
|
),
|
|
])
|
|
.build()
|
|
.execute_with(|| {
|
|
// Set block author for fee allocation
|
|
set_block_author_by_index(0);
|
|
let issuance_before = <Runtime as pallet_evm::Config>::Currency::total_issuance();
|
|
// EVM transfer.
|
|
assert_ok!(RuntimeCall::EVM(pallet_evm::Call::<Runtime>::call {
|
|
source: H160::from(BOB),
|
|
target: H160::from(ALICE),
|
|
input: Vec::new(),
|
|
value: (1 * HAVE).into(),
|
|
gas_limit: 21_000u64,
|
|
max_fee_per_gas: U256::from(BASE_FEE_GENESIS),
|
|
max_priority_fee_per_gas: None,
|
|
nonce: Some(U256::from(0)),
|
|
access_list: Vec::new(),
|
|
authorization_list: Vec::new(),
|
|
})
|
|
.dispatch(<Runtime as frame_system::Config>::RuntimeOrigin::root()));
|
|
|
|
let issuance_after = <Runtime as pallet_evm::Config>::Currency::total_issuance();
|
|
|
|
// Get the actual base fee that was charged by querying the fee calculator
|
|
// This represents the real network base fee, not the genesis constant
|
|
let (actual_base_fee_u256, _) = TransactionPaymentAsGasPrice::min_gas_price();
|
|
let actual_base_fee: Balance = actual_base_fee_u256.as_u128();
|
|
|
|
// Calculate expected treasury and burn amounts based on the actual base fee
|
|
let gas_used = 21_000u128; // Standard transfer gas
|
|
let total_base_fee_charged = actual_base_fee * gas_used;
|
|
|
|
let treasury_proportion = FeesTreasuryProportion::get();
|
|
let expected_treasury_amount = treasury_proportion.mul_floor(total_base_fee_charged);
|
|
let expected_burnt_amount = total_base_fee_charged - expected_treasury_amount;
|
|
|
|
// Verify total issuance was reduced by the burnt amount
|
|
assert_eq!(
|
|
issuance_after,
|
|
issuance_before - expected_burnt_amount,
|
|
"Total issuance should decrease by burnt base fee amount"
|
|
);
|
|
|
|
// Verify treasury received the expected amount
|
|
// Treasury pot starts at 0 and should equal exactly the treasury fee amount
|
|
let treasury_final_balance = datahaven_testnet_runtime::Treasury::pot();
|
|
assert_eq!(
|
|
treasury_final_balance,
|
|
expected_treasury_amount,
|
|
"Treasury should receive {}% of base fee: expected {}, got {}",
|
|
treasury_proportion.deconstruct() as f64 / 10_000_000.0 * 100.0,
|
|
expected_treasury_amount,
|
|
treasury_final_balance
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn deal_with_fees_handles_tip() {
|
|
use datahaven_runtime_common::deal_with_fees::DealWithSubstrateFeesAndTip;
|
|
use frame_support::traits::OnUnbalanced;
|
|
|
|
ExtBuilder::default()
|
|
.with_balances(vec![
|
|
// Set up minimal balances for treasury to avoid existential deposit issues
|
|
(
|
|
datahaven_testnet_runtime::Treasury::account_id(),
|
|
existential_deposit(),
|
|
),
|
|
(get_validator_by_index(0), existential_deposit()),
|
|
])
|
|
.build()
|
|
.execute_with(|| {
|
|
// Set block author for fee allocation
|
|
set_block_author_by_index(0);
|
|
|
|
// This test validates the functionality of the `DealWithSubstrateFeesAndTip` trait implementation
|
|
// in the DataHaven runtime. It verifies that:
|
|
// - The correct proportion of the fee is sent to the treasury.
|
|
// - The remaining fee is burned (removed from the total supply).
|
|
// - The entire tip is sent to the block author.
|
|
|
|
// The test details:
|
|
// 1. Simulate issuing a `fee` of 100 and a `tip` of 1000.
|
|
// 2. Confirm the initial total supply is 1,100 (equal to the sum of the issued fee and tip).
|
|
// 3. Confirm the treasury's balance is initially 0.
|
|
// 4. Execute the `DealWithSubstrateFeesAndTip::on_unbalanceds` function with the `fee` and `tip`.
|
|
// 5. Validate that the treasury's balance has increased by 20% of the fee (based on FeesTreasuryProportion).
|
|
// 6. Validate that 80% of the fee is burned, and the total supply decreases accordingly.
|
|
// 7. Validate that the entire tip (100%) is sent to the block author (collator).
|
|
|
|
// Step 1: Issue the fee and tip amounts.
|
|
let fee =
|
|
<pallet_balances::Pallet<Runtime> as frame_support::traits::fungible::Balanced<
|
|
AccountId,
|
|
>>::issue(100);
|
|
let tip =
|
|
<pallet_balances::Pallet<Runtime> as frame_support::traits::fungible::Balanced<
|
|
AccountId,
|
|
>>::issue(1000);
|
|
|
|
// Step 2: Validate the initial supply and balances.
|
|
let total_supply_before = Balances::total_issuance();
|
|
let block_author = get_validator_by_index(0);
|
|
let block_author_balance_before = Balances::free_balance(&block_author);
|
|
|
|
// Total supply should be: issued fee (100) + issued tip (1000) + 2 existential deposits
|
|
let expected_supply = 1_100 + (2 * existential_deposit());
|
|
assert_eq!(total_supply_before, expected_supply);
|
|
assert_eq!(
|
|
Balances::free_balance(&datahaven_testnet_runtime::Treasury::account_id()),
|
|
existential_deposit()
|
|
);
|
|
|
|
// Step 3: Execute the fees handling logic.
|
|
DealWithSubstrateFeesAndTip::<Runtime, FeesTreasuryProportion>::on_unbalanceds(
|
|
vec![fee, tip].into_iter(),
|
|
);
|
|
|
|
// Step 4: Compute the split between treasury and burned fees based on FeesTreasuryProportion (20%).
|
|
let treasury_proportion = FeesTreasuryProportion::get();
|
|
|
|
let treasury_fee_part: Balance = treasury_proportion.mul_floor(100);
|
|
let burnt_fee_part: Balance = 100 - treasury_fee_part;
|
|
|
|
// Step 5: Validate the treasury received 20% of the fee.
|
|
assert_eq!(
|
|
Balances::free_balance(&datahaven_testnet_runtime::Treasury::account_id()),
|
|
existential_deposit() + treasury_fee_part,
|
|
);
|
|
|
|
// Step 6: Verify that 80% of the fee was burned (removed from the total supply).
|
|
let total_supply_after = Balances::total_issuance();
|
|
assert_eq!(total_supply_before - total_supply_after, burnt_fee_part,);
|
|
|
|
// Step 7: Validate that the block author (collator) received 100% of the tip.
|
|
let block_author_balance_after = Balances::free_balance(&block_author);
|
|
assert_eq!(
|
|
block_author_balance_after - block_author_balance_before,
|
|
1000,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn fees_burned_when_block_author_not_set() {
|
|
use datahaven_runtime_common::deal_with_fees::DealWithSubstrateFeesAndTip;
|
|
use frame_support::traits::OnUnbalanced;
|
|
|
|
ExtBuilder::default()
|
|
.with_balances(vec![(
|
|
datahaven_testnet_runtime::Treasury::account_id(),
|
|
existential_deposit(),
|
|
)])
|
|
.build()
|
|
.execute_with(|| {
|
|
// Explicitly do NOT set block author - this is the key difference from the test above
|
|
|
|
let fee =
|
|
<pallet_balances::Pallet<Runtime> as frame_support::traits::fungible::Balanced<
|
|
AccountId,
|
|
>>::issue(100);
|
|
let tip =
|
|
<pallet_balances::Pallet<Runtime> as frame_support::traits::fungible::Balanced<
|
|
AccountId,
|
|
>>::issue(1000);
|
|
|
|
let total_supply_before = Balances::total_issuance();
|
|
|
|
// Expected supply: issued fee (100) + issued tip (1000) + existential deposit
|
|
let expected_supply = 1_100 + existential_deposit();
|
|
assert_eq!(total_supply_before, expected_supply);
|
|
|
|
DealWithSubstrateFeesAndTip::<Runtime, FeesTreasuryProportion>::on_unbalanceds(
|
|
vec![fee, tip].into_iter(),
|
|
);
|
|
|
|
let treasury_proportion = FeesTreasuryProportion::get();
|
|
let treasury_fee_part: Balance = treasury_proportion.mul_floor(100);
|
|
let burnt_fee_part: Balance = 100 - treasury_fee_part;
|
|
|
|
// Verify treasury received its portion of the fee
|
|
assert_eq!(
|
|
Balances::free_balance(&datahaven_testnet_runtime::Treasury::account_id()),
|
|
existential_deposit() + treasury_fee_part,
|
|
);
|
|
|
|
// When block author is not set and ExistentialDeposit is 0,
|
|
// the tip goes to the default account (zero account) instead of being burned
|
|
let total_supply_after = Balances::total_issuance();
|
|
let expected_burned = burnt_fee_part; // Only the fee portion is burned
|
|
assert_eq!(
|
|
total_supply_before - total_supply_after,
|
|
expected_burned,
|
|
"Only fee portion should be burned when block author is not set"
|
|
);
|
|
|
|
// With ExistentialDeposit = 0, the default account can now hold the tip
|
|
let default_account: AccountId = Default::default();
|
|
assert_eq!(
|
|
Balances::free_balance(&default_account),
|
|
1000,
|
|
"Default account should receive the tip when ExistentialDeposit is 0"
|
|
);
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod treasury_tests {
|
|
use super::*;
|
|
use frame_support::traits::Hooks;
|
|
|
|
/// Helper function to create an origin for an account
|
|
fn origin_of(account_id: AccountId) -> RuntimeOrigin {
|
|
RuntimeOrigin::signed(account_id)
|
|
}
|
|
|
|
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());
|
|
datahaven_testnet_runtime::Treasury::on_initialize(System::block_number());
|
|
}
|
|
|
|
#[test]
|
|
fn test_treasury_spend_local_with_root_origin() {
|
|
let initial_treasury_balance = 1_000 * HAVE;
|
|
ExtBuilder::default()
|
|
.with_balances(vec![
|
|
(AccountId::from(ALICE), 2_000 * HAVE),
|
|
(Treasury::account_id(), initial_treasury_balance),
|
|
])
|
|
.build()
|
|
.execute_with(|| {
|
|
let spend_amount = 100u128 * HAVE;
|
|
let spend_beneficiary = AccountId::from(BOB);
|
|
|
|
next_block();
|
|
|
|
// Perform treasury spending
|
|
let valid_from = System::block_number() + 5u32;
|
|
assert_ok!(datahaven_testnet_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(valid_from),
|
|
}))
|
|
));
|
|
|
|
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,
|
|
expire_at: payout_period + valid_from,
|
|
},
|
|
)]
|
|
.to_vec();
|
|
expect_events(expected_events);
|
|
|
|
while System::block_number() < valid_from {
|
|
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 * HAVE;
|
|
ExtBuilder::default()
|
|
.with_balances(vec![
|
|
(AccountId::from(ALICE), 2_000 * HAVE),
|
|
(Treasury::account_id(), initial_treasury_balance),
|
|
])
|
|
.build()
|
|
.execute_with(|| {
|
|
let spend_amount = 100u128 * HAVE;
|
|
let spend_beneficiary = AccountId::from(BOB);
|
|
|
|
next_block();
|
|
|
|
// TreasuryCouncilCollective
|
|
assert_ok!(TreasuryCouncil::set_members(
|
|
root_origin(),
|
|
vec![AccountId::from(ALICE)],
|
|
Some(AccountId::from(ALICE)),
|
|
1
|
|
));
|
|
|
|
next_block();
|
|
|
|
// Perform treasury spending
|
|
let valid_from = System::block_number() + 5u32;
|
|
let proposal = RuntimeCall::Treasury(pallet_treasury::Call::spend {
|
|
amount: spend_amount,
|
|
asset_kind: Box::new(()),
|
|
beneficiary: Box::new(AccountId::from(BOB)),
|
|
valid_from: Some(valid_from),
|
|
});
|
|
assert_ok!(TreasuryCouncil::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,
|
|
expire_at: payout_period + valid_from,
|
|
}),
|
|
RuntimeEvent::TreasuryCouncil(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() < valid_from {
|
|
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);
|
|
});
|
|
}
|
|
}
|