diff --git a/Cargo.lock b/Cargo.lock
index fe5d6edba..7b90e58b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2858,6 +2858,7 @@ dependencies = [
"pallet-timestamp",
"pallet-transaction-payment",
"pallet-transaction-payment-rpc-runtime-api",
+ "pallet-treasury",
"pallet-tx-pause",
"pallet-utility",
"pallet-xcm",
@@ -3990,6 +3991,7 @@ dependencies = [
"pallet-timestamp",
"pallet-transaction-payment",
"pallet-transaction-payment-rpc-runtime-api",
+ "pallet-treasury",
"pallet-tx-pause",
"pallet-utility",
"parachain-info",
diff --git a/Cargo.toml b/Cargo.toml
index 59060db29..afb4f9e2e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -98,6 +98,7 @@ pallet-staking = { git = "https://github.com/moondance-labs/polkadot-sdk", branc
pallet-sudo = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false }
pallet-timestamp = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false }
pallet-transaction-payment = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false }
+pallet-treasury = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false }
pallet-tx-pause = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false }
pallet-utility = { git = "https://github.com/moondance-labs/polkadot-sdk", branch = "tanssi-polkadot-v1.3.0", default-features = false }
parity-scale-codec = { version = "3.0.0", default-features = false, features = [ "derive", "max-encoded-len" ] }
diff --git a/node/src/chain_spec/dancebox.rs b/node/src/chain_spec/dancebox.rs
index fe48d3fb2..d5a6c6f28 100644
--- a/node/src/chain_spec/dancebox.rs
+++ b/node/src/chain_spec/dancebox.rs
@@ -291,6 +291,7 @@ fn testnet_genesis(
polkadot_xcm: PolkadotXcmConfig::default(),
transaction_payment: Default::default(),
tx_pause: Default::default(),
+ treasury: Default::default(),
}
}
diff --git a/node/src/chain_spec/flashbox.rs b/node/src/chain_spec/flashbox.rs
index 5a5b95b0f..70821c27e 100644
--- a/node/src/chain_spec/flashbox.rs
+++ b/node/src/chain_spec/flashbox.rs
@@ -289,6 +289,7 @@ fn testnet_genesis(
},
transaction_payment: Default::default(),
tx_pause: Default::default(),
+ treasury: Default::default(),
}
}
diff --git a/pallets/collator-assignment/src/tests.rs b/pallets/collator-assignment/src/tests.rs
index ce444c442..1e248ebf1 100644
--- a/pallets/collator-assignment/src/tests.rs
+++ b/pallets/collator-assignment/src/tests.rs
@@ -14,9 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see
-use dp_collator_assignment::AssignedCollators;
use {
crate::{mock::*, CollatorContainerChain, Event, PendingCollatorContainerChain},
+ dp_collator_assignment::AssignedCollators,
std::collections::BTreeMap,
};
diff --git a/pallets/collator-assignment/src/tests/assign_full.rs b/pallets/collator-assignment/src/tests/assign_full.rs
index 2ac36e2f3..593a552a5 100644
--- a/pallets/collator-assignment/src/tests/assign_full.rs
+++ b/pallets/collator-assignment/src/tests/assign_full.rs
@@ -14,9 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see
-use crate::assignment::AssignmentError;
use {
- crate::{assignment::Assignment, tests::Test},
+ crate::{
+ assignment::{Assignment, AssignmentError},
+ tests::Test,
+ },
sp_std::collections::btree_map::BTreeMap,
};
diff --git a/runtime/dancebox/Cargo.toml b/runtime/dancebox/Cargo.toml
index 73f8bfb49..ec4efde40 100644
--- a/runtime/dancebox/Cargo.toml
+++ b/runtime/dancebox/Cargo.toml
@@ -60,6 +60,7 @@ pallet-sudo = { workspace = true }
pallet-timestamp = { workspace = true }
pallet-transaction-payment = { workspace = true }
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
+pallet-treasury = { workspace = true }
pallet-tx-pause = { workspace = true }
pallet-utility = { workspace = true }
sp-api = { workspace = true }
@@ -193,6 +194,7 @@ std = [
"pallet-timestamp/std",
"pallet-transaction-payment-rpc-runtime-api/std",
"pallet-transaction-payment/std",
+ "pallet-treasury/std",
"pallet-tx-pause/std",
"pallet-utility/std",
"pallet-xcm-benchmarks?/std",
@@ -276,6 +278,7 @@ runtime-benchmarks = [
"pallet-staking/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
+ "pallet-treasury/runtime-benchmarks",
"pallet-tx-pause/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"pallet-xcm-benchmarks/runtime-benchmarks",
@@ -339,6 +342,7 @@ try-runtime = [
"pallet-sudo/try-runtime",
"pallet-timestamp/try-runtime",
"pallet-transaction-payment/try-runtime",
+ "pallet-treasury/try-runtime",
"pallet-tx-pause/try-runtime",
"pallet-utility/try-runtime",
"pallet-xcm/try-runtime",
diff --git a/runtime/dancebox/src/lib.rs b/runtime/dancebox/src/lib.rs
index 71118bc0c..c466d5e9e 100644
--- a/runtime/dancebox/src/lib.rs
+++ b/runtime/dancebox/src/lib.rs
@@ -49,9 +49,10 @@ use {
parameter_types,
traits::{
fungible::{Balanced, Credit, Inspect},
+ tokens::{PayFromAccount, UnityAssetBalanceConversion},
ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse,
- InsideBoth, InstanceFilter, OffchainWorker, OnFinalize, OnIdle, OnInitialize,
- OnRuntimeUpgrade, ValidatorRegistration,
+ Imbalance, InsideBoth, InstanceFilter, OffchainWorker, OnFinalize, OnIdle,
+ OnInitialize, OnRuntimeUpgrade, OnUnbalanced, ValidatorRegistration,
},
weights::{
constants::{
@@ -68,6 +69,7 @@ use {
EnsureRoot,
},
nimbus_primitives::NimbusId,
+ pallet_balances::NegativeImbalance,
pallet_collator_assignment::{GetRandomnessForNextBlock, RotateCollatorsEveryNSessions},
pallet_invulnerables::InvulnerableRewardDistribution,
pallet_pooled_staking::traits::{IsCandidateEligible, Timer},
@@ -86,6 +88,7 @@ use {
create_runtime_str, generic, impl_opaque_keys,
traits::{
AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, Hash as HashT,
+ IdentityLookup,
},
transaction_validity::{TransactionSource, TransactionValidity},
AccountId32, ApplyExtrinsicResult,
@@ -417,6 +420,44 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = pallet_balances::weights::SubstrateWeight;
}
+pub struct DealWithFees(sp_std::marker::PhantomData);
+impl OnUnbalanced> for DealWithFees
+where
+ R: pallet_balances::Config + pallet_treasury::Config,
+ pallet_treasury::Pallet: OnUnbalanced>,
+{
+ // this seems to be called for substrate-based transactions
+ fn on_unbalanceds(mut fees_then_tips: impl Iterator- >) {
+ if let Some(fees) = fees_then_tips.next() {
+ // 80% is burned, 20% goes to the treasury
+ // Same policy applies for tips as well
+ let burn_percentage = 80;
+ let treasury_percentage = 20;
+
+ let (_, to_treasury) = fees.ration(burn_percentage, treasury_percentage);
+ // Balances pallet automatically burns dropped Negative Imbalances by decreasing total_supply accordingly
+ as OnUnbalanced<_>>::on_unbalanced(to_treasury);
+
+ // handle tip if there is one
+ if let Some(tip) = fees_then_tips.next() {
+ let (_, to_treasury) = tip.ration(burn_percentage, treasury_percentage);
+ as OnUnbalanced<_>>::on_unbalanced(to_treasury);
+ }
+ }
+ }
+
+ // this is called from pallet_evm for Ethereum-based transactions
+ // (technically, it calls on_unbalanced, which calls this when non-zero)
+ fn on_nonzero_unbalanced(amount: NegativeImbalance) {
+ // 80% is burned, 20% goes to the treasury
+ let burn_percentage = 80;
+ let treasury_percentage = 20;
+
+ let (_, to_treasury) = amount.ration(burn_percentage, treasury_percentage);
+ as OnUnbalanced<_>>::on_unbalanced(to_treasury);
+ }
+}
+
parameter_types! {
pub const TransactionByteFee: Balance = 1;
pub const FeeMultiplier: Multiplier = Multiplier::from_u32(1);
@@ -424,8 +465,8 @@ parameter_types! {
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- // This will burn the fees
- type OnChargeTransaction = CurrencyAdapter;
+ // This will burn 80% from fees & tips and deposit the remainder into the treasury
+ type OnChargeTransaction = CurrencyAdapter>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = WeightToFee;
type LengthToFee = ConstantMultiplier;
@@ -1392,6 +1433,42 @@ impl pallet_identity::Config for Runtime {
type WeightInfo = pallet_identity::weights::SubstrateWeight;
}
+parameter_types! {
+ pub const TreasuryId: PalletId = PalletId(*b"tns/tsry");
+ pub const ProposalBond: Permill = Permill::from_percent(5);
+ pub TreasuryAccount: AccountId = Treasury::account_id();
+}
+
+impl pallet_treasury::Config for Runtime {
+ type PalletId = TreasuryId;
+ type Currency = Balances;
+
+ type ApproveOrigin = EnsureRoot;
+ type RejectOrigin = EnsureRoot;
+ type RuntimeEvent = RuntimeEvent;
+ // If proposal gets rejected, bond goes to treasury
+ type OnSlash = Treasury;
+ type ProposalBond = ProposalBond;
+ type ProposalBondMinimum = ConstU128<{ 1 * currency::DANCE * currency::SUPPLY_FACTOR }>;
+ type SpendPeriod = ConstU32<{ 6 * DAYS }>;
+ type Burn = ();
+ type BurnDestination = ();
+ type MaxApprovals = ConstU32<100>;
+ type WeightInfo = pallet_treasury::weights::SubstrateWeight;
+ type SpendFunds = ();
+ type ProposalBondMaximum = ();
+ type SpendOrigin = frame_support::traits::NeverEnsureOrigin; // Same as Polkadot
+ type AssetKind = ();
+ type Beneficiary = AccountId;
+ type BeneficiaryLookup = IdentityLookup;
+ type Paymaster = PayFromAccount;
+ // TODO: implement pallet-asset-rate to allow the treasury to spend other assets
+ type BalanceConverter = UnityAssetBalanceConversion;
+ type PayoutPeriod = ConstU32<0>;
+ #[cfg(feature = "runtime-benchmarks")]
+ type BenchmarkHelper = ();
+}
+
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub enum Runtime
@@ -1434,6 +1511,9 @@ construct_runtime!(
// InflationRewards must be after Session and AuthorInherent
InflationRewards: pallet_inflation_rewards = 35,
+ // Treasury stuff.
+ Treasury: pallet_treasury::{Pallet, Storage, Config, Event, Call} = 40,
+
//XCM
XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50,
CumulusXcm: cumulus_pallet_xcm::{Pallet, Event, Origin} = 51,
@@ -1459,6 +1539,7 @@ mod benches {
[pallet_sudo, Sudo]
[pallet_proxy, Proxy]
[pallet_utility, Utility]
+ [pallet_treasury, Treasury]
[pallet_tx_pause, TxPause]
[pallet_balances, Balances]
[pallet_identity, Identity]
diff --git a/runtime/dancebox/src/xcm_config.rs b/runtime/dancebox/src/xcm_config.rs
index e99e6e2f1..4108afbe8 100644
--- a/runtime/dancebox/src/xcm_config.rs
+++ b/runtime/dancebox/src/xcm_config.rs
@@ -333,11 +333,12 @@ impl pallet_asset_rate::Config for Runtime {
type BenchmarkHelper = ForeignAssetBenchmarkHelper;
}
-use crate::ForeignAssets;
-use sp_runtime::{traits::CheckedDiv, FixedPointNumber};
-use staging_xcm_builder::FungiblesAdapter;
-use staging_xcm_builder::NoChecking;
-use staging_xcm_executor::traits::JustTry;
+use {
+ crate::ForeignAssets,
+ sp_runtime::{traits::CheckedDiv, FixedPointNumber},
+ staging_xcm_builder::{FungiblesAdapter, NoChecking},
+ staging_xcm_executor::traits::JustTry,
+};
/// Means for transacting foreign assets from different global consensus.
pub type ForeignFungiblesTransactor = FungiblesAdapter<
diff --git a/runtime/flashbox/Cargo.toml b/runtime/flashbox/Cargo.toml
index 218c45d58..cdd4fce78 100644
--- a/runtime/flashbox/Cargo.toml
+++ b/runtime/flashbox/Cargo.toml
@@ -55,6 +55,7 @@ pallet-sudo = { workspace = true }
pallet-timestamp = { workspace = true }
pallet-transaction-payment = { workspace = true }
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
+pallet-treasury = { workspace = true }
pallet-tx-pause = { workspace = true }
pallet-utility = { workspace = true }
sp-api = { workspace = true }
@@ -160,6 +161,7 @@ std = [
"pallet-timestamp/std",
"pallet-transaction-payment-rpc-runtime-api/std",
"pallet-transaction-payment/std",
+ "pallet-treasury/std",
"pallet-tx-pause/std",
"pallet-utility/std",
"parachain-info/std",
@@ -225,6 +227,7 @@ runtime-benchmarks = [
"pallet-services-payment/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
+ "pallet-treasury/runtime-benchmarks",
"pallet-tx-pause/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"polkadot-parachain-primitives/runtime-benchmarks",
@@ -266,6 +269,7 @@ try-runtime = [
"pallet-sudo/try-runtime",
"pallet-timestamp/try-runtime",
"pallet-transaction-payment/try-runtime",
+ "pallet-treasury/try-runtime",
"pallet-tx-pause/try-runtime",
"pallet-utility/try-runtime",
"parachain-info/try-runtime",
diff --git a/runtime/flashbox/src/lib.rs b/runtime/flashbox/src/lib.rs
index 6d2ece965..ce860c441 100644
--- a/runtime/flashbox/src/lib.rs
+++ b/runtime/flashbox/src/lib.rs
@@ -43,9 +43,10 @@ use {
parameter_types,
traits::{
fungible::{Balanced, Credit, Inspect},
+ tokens::{PayFromAccount, UnityAssetBalanceConversion},
ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse,
- InsideBoth, InstanceFilter, OffchainWorker, OnFinalize, OnIdle, OnInitialize,
- OnRuntimeUpgrade,
+ Imbalance, InsideBoth, InstanceFilter, OffchainWorker, OnFinalize, OnIdle,
+ OnInitialize, OnRuntimeUpgrade, OnUnbalanced,
},
weights::{
constants::{
@@ -62,6 +63,7 @@ use {
EnsureRoot,
},
nimbus_primitives::NimbusId,
+ pallet_balances::NegativeImbalance,
pallet_invulnerables::InvulnerableRewardDistribution,
pallet_registrar::RegistrarHooks,
pallet_registrar_runtime_api::ContainerChainGenesisData,
@@ -76,7 +78,9 @@ use {
sp_core::{crypto::KeyTypeId, Decode, Encode, Get, MaxEncodedLen, OpaqueMetadata},
sp_runtime::{
create_runtime_str, generic, impl_opaque_keys,
- traits::{AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT},
+ traits::{
+ AccountIdConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, IdentityLookup,
+ },
transaction_validity::{TransactionSource, TransactionValidity},
AccountId32, ApplyExtrinsicResult,
},
@@ -400,6 +404,44 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = pallet_balances::weights::SubstrateWeight;
}
+pub struct DealWithFees(sp_std::marker::PhantomData);
+impl OnUnbalanced> for DealWithFees
+where
+ R: pallet_balances::Config + pallet_treasury::Config,
+ pallet_treasury::Pallet: OnUnbalanced>,
+{
+ // this seems to be called for substrate-based transactions
+ fn on_unbalanceds(mut fees_then_tips: impl Iterator
- >) {
+ if let Some(fees) = fees_then_tips.next() {
+ // 80% is burned, 20% goes to the treasury
+ // Same policy applies for tips as well
+ let burn_percentage = 80;
+ let treasury_percentage = 20;
+
+ let (_, to_treasury) = fees.ration(burn_percentage, treasury_percentage);
+ // Balances pallet automatically burns dropped Negative Imbalances by decreasing total_supply accordingly
+ as OnUnbalanced<_>>::on_unbalanced(to_treasury);
+
+ // handle tip if there is one
+ if let Some(tip) = fees_then_tips.next() {
+ let (_, to_treasury) = tip.ration(burn_percentage, treasury_percentage);
+ as OnUnbalanced<_>>::on_unbalanced(to_treasury);
+ }
+ }
+ }
+
+ // this is called from pallet_evm for Ethereum-based transactions
+ // (technically, it calls on_unbalanced, which calls this when non-zero)
+ fn on_nonzero_unbalanced(amount: NegativeImbalance) {
+ // 80% is burned, 20% goes to the treasury
+ let burn_percentage = 80;
+ let treasury_percentage = 20;
+
+ let (_, to_treasury) = amount.ration(burn_percentage, treasury_percentage);
+ as OnUnbalanced<_>>::on_unbalanced(to_treasury);
+ }
+}
+
parameter_types! {
pub const TransactionByteFee: Balance = 1;
pub const FeeMultiplier: Multiplier = Multiplier::from_u32(1);
@@ -408,7 +450,7 @@ parameter_types! {
impl pallet_transaction_payment::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
// This will burn the fees
- type OnChargeTransaction = CurrencyAdapter;
+ type OnChargeTransaction = CurrencyAdapter>;
type OperationalFeeMultiplier = ConstU8<5>;
type WeightToFee = WeightToFee;
type LengthToFee = ConstantMultiplier;
@@ -1136,6 +1178,41 @@ impl pallet_identity::Config for Runtime {
type WeightInfo = pallet_identity::weights::SubstrateWeight;
}
+parameter_types! {
+ pub const TreasuryId: PalletId = PalletId(*b"tns/tsry");
+ pub const ProposalBond: Permill = Permill::from_percent(5);
+ pub TreasuryAccount: AccountId = Treasury::account_id();
+}
+
+impl pallet_treasury::Config for Runtime {
+ type PalletId = TreasuryId;
+ type Currency = Balances;
+
+ type ApproveOrigin = EnsureRoot;
+ type RejectOrigin = EnsureRoot;
+ type RuntimeEvent = RuntimeEvent;
+ // If proposal gets rejected, bond goes to treasury
+ type OnSlash = Treasury;
+ type ProposalBond = ProposalBond;
+ type ProposalBondMinimum = ConstU128<{ 1 * currency::DANCE * currency::SUPPLY_FACTOR }>;
+ type SpendPeriod = ConstU32<{ 6 * DAYS }>;
+ type Burn = ();
+ type BurnDestination = ();
+ type MaxApprovals = ConstU32<100>;
+ type WeightInfo = pallet_treasury::weights::SubstrateWeight;
+ type SpendFunds = ();
+ type ProposalBondMaximum = ();
+ type SpendOrigin = frame_support::traits::NeverEnsureOrigin; // Same as Polkadot
+ type AssetKind = ();
+ type Beneficiary = AccountId;
+ type BeneficiaryLookup = IdentityLookup;
+ type Paymaster = PayFromAccount;
+ type BalanceConverter = UnityAssetBalanceConversion;
+ type PayoutPeriod = ConstU32<0>;
+ #[cfg(feature = "runtime-benchmarks")]
+ type BenchmarkHelper = ();
+}
+
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub enum Runtime
@@ -1177,6 +1254,9 @@ construct_runtime!(
// InflationRewards must be after Session and AuthorInherent
InflationRewards: pallet_inflation_rewards = 35,
+ // Treasury stuff.
+ Treasury: pallet_treasury::{Pallet, Storage, Config, Event, Call} = 40,
+
// More system support stuff
RelayStorageRoots: pallet_relay_storage_roots = 60,
@@ -1193,6 +1273,7 @@ mod benches {
[pallet_sudo, Sudo]
[pallet_proxy, Proxy]
[pallet_utility, Utility]
+ [pallet_treasury, Treasury]
[pallet_tx_pause, TxPause]
[pallet_balances, Balances]
[pallet_identity, Identity]
diff --git a/test/suites/common-tanssi/fees/test_fee_balance_transfer.ts b/test/suites/common-tanssi/fees/test_fee_balance_transfer.ts
index 083c31c7f..e4681fcf0 100644
--- a/test/suites/common-tanssi/fees/test_fee_balance_transfer.ts
+++ b/test/suites/common-tanssi/fees/test_fee_balance_transfer.ts
@@ -171,7 +171,7 @@ describeSuite({
it({
id: "E04",
- title: "Fees are burned",
+ title: "80% of Fees are burned",
test: async function () {
const totalSupplyBefore = (await polkadotJs.query.balances.totalIssuance()).toBigInt();
const balanceBefore = (await polkadotJs.query.system.account(alice.address)).data.free.toBigInt();
@@ -194,7 +194,7 @@ describeSuite({
const totalSupplyAfter = (await polkadotJs.query.balances.totalIssuance()).toBigInt();
- expect(totalSupplyAfter - totalSupplyBefore).to.equal(issuance - fee);
+ expect(totalSupplyAfter - totalSupplyBefore).to.equal(issuance - (fee * 4n) / 5n);
},
});
diff --git a/test/suites/common-tanssi/pallet-treasury/test_pallet_treasury.ts b/test/suites/common-tanssi/pallet-treasury/test_pallet_treasury.ts
new file mode 100644
index 000000000..f35da9933
--- /dev/null
+++ b/test/suites/common-tanssi/pallet-treasury/test_pallet_treasury.ts
@@ -0,0 +1,183 @@
+import "@tanssi/api-augment";
+import { describeSuite, expect, beforeAll } from "@moonwall/cli";
+import { ApiPromise } from "@polkadot/api";
+import { KeyringPair } from "@moonwall/util";
+import { extractFeeAuthor } from "util/block";
+
+describeSuite({
+ id: "CT0901",
+ title: "Treasury pallet test suite",
+ foundationMethods: "dev",
+
+ testCases: ({ it, context }) => {
+ let polkadotJs: ApiPromise;
+ let sudo_alice: KeyringPair;
+ let user_charlie: KeyringPair;
+ let user_dave: KeyringPair;
+ let user_bob: KeyringPair;
+ // From Pallet Id "tns/tsry" -> Account
+ const treasury_address = "5EYCAe5jXiVvytpxmBupXPCNE9Vduq7gPeTwy9xMgQtKWMnR";
+
+ beforeAll(async () => {
+ polkadotJs = context.polkadotJs();
+ sudo_alice = context.keyring.alice;
+ user_charlie = context.keyring.charlie;
+ user_dave = context.keyring.dave;
+ user_bob = context.keyring.bob;
+ });
+
+ it({
+ id: "E01",
+ title: "20% of fees & tips go for treasury account",
+ test: async function () {
+ // Gets the initial pot deposit value
+ const initial_pot = await polkadotJs.query.system.account(treasury_address);
+ const initial_free_pot = initial_pot.data.free.toBigInt();
+
+ // Executes a tx adding an additional tip
+ const tx = polkadotJs.tx.balances.transferAllowDeath(user_charlie.address, 200_000);
+ const signedTx = await tx.signAsync(user_dave, { tip: 100_000 });
+ await context.createBlock([signedTx]);
+ const events = await polkadotJs.query.system.events();
+ const fee = extractFeeAuthor(events, user_dave.address).amount.toBigInt();
+
+ // Gets the new pot deposit value
+ const new_pot = await polkadotJs.query.system.account(treasury_address);
+ const new_free_pot = new_pot.data.free.toBigInt();
+
+ // Division operation rounding
+ const rounding = fee % 5n > 0 ? 1n : 0n;
+
+ // Treasury pot should increase by 20% of the paid fee & tip
+ expect(new_free_pot).to.be.equal(initial_free_pot + fee / 5n + rounding);
+ },
+ });
+
+ it({
+ id: "E02",
+ title: "Create proposal locks minimum bond from proposer",
+ test: async function () {
+ // Gets the initial reserved amount from the proposer
+ const proposer_initial_balance = await polkadotJs.query.system.account(user_charlie.address);
+ const proposer_initial_reserved_balance = proposer_initial_balance.data.reserved.toBigInt();
+
+ // minimum configured bond > 5% of the proposal
+ const tx = polkadotJs.tx.treasury.proposeSpend(1, user_dave.address);
+ const signedTx = await tx.signAsync(user_charlie);
+ await context.createBlock([signedTx]);
+
+ const proposer_new_balance = await polkadotJs.query.system.account(user_charlie.address);
+ const proposer_new_reserved_balance = proposer_new_balance.data.reserved.toBigInt();
+
+ // reserved value should be the minimum bond
+ expect(proposer_new_reserved_balance).to.be.equal(
+ proposer_initial_reserved_balance + 1_000_000_000_000n * 100n
+ );
+ },
+ });
+
+ it({
+ id: "E03",
+ title: "Create proposal locks 5% of the proposal from proposer's account",
+ test: async function () {
+ // Gets the initial reserved amount from the proposer
+ const proposer_initial_balance = await polkadotJs.query.system.account(user_dave.address);
+ const proposer_initial_reserved_balance = proposer_initial_balance.data.reserved.toBigInt();
+
+ // minimum configured bond > 5% of the proposal
+ const proposal_value = 1_000_000_000_000_000_000n;
+ const tx = polkadotJs.tx.treasury.proposeSpend(proposal_value, user_charlie.address);
+ const signedTx = await tx.signAsync(user_dave);
+ await context.createBlock([signedTx]);
+
+ const proposer_new_balance = await polkadotJs.query.system.account(user_dave.address);
+ const proposer_new_reserved_balance = proposer_new_balance.data.reserved.toBigInt();
+
+ // reserved value should be 5% from the total amount requested in the proposal
+ expect(proposer_new_reserved_balance).to.be.equal(
+ proposer_initial_reserved_balance + (proposal_value * 5n) / 100n
+ );
+ },
+ });
+
+ it({
+ id: "E04",
+ title: "Bond goes to treasury upon proposal rejection",
+ test: async function () {
+ // Gets the initial pot deposit value
+ const initial_pot = await polkadotJs.query.system.account(treasury_address);
+ const initial_free_pot = initial_pot.data.free.toBigInt();
+
+ // Creates a proposal
+ const proposal_value = 1_000_000_000_000_000_000n;
+ const tx = polkadotJs.tx.treasury.proposeSpend(proposal_value, user_dave.address);
+ const signedTx = await tx.signAsync(user_bob);
+ await context.createBlock([signedTx]);
+
+ // Proposal is rejected
+ const tx_rejection = polkadotJs.tx.treasury.rejectProposal(2);
+ const signedTx_rejection = await polkadotJs.tx.sudo.sudo(tx_rejection).signAsync(sudo_alice);
+ await context.createBlock([signedTx_rejection]);
+
+ // Gets the after rejection pot deposit value
+ const new_pot = await polkadotJs.query.system.account(treasury_address);
+ const new_free_pot = new_pot.data.free.toBigInt();
+
+ // Pot value should be >= the initial value + reserved proposal bond
+ expect(new_free_pot).toBeGreaterThan(initial_free_pot + (proposal_value * 5n) / 100n);
+ },
+ });
+
+ it({
+ id: "E05",
+ title: "Proposal is approved",
+ test: async function () {
+ // initial approvals count
+ const initial_approvals_count = await context.polkadotJs().query.treasury.approvals();
+
+ // Creates a proposal
+ const proposal_value = 100n;
+ const tx = polkadotJs.tx.treasury.proposeSpend(proposal_value, user_dave.address);
+ const signedTx = await tx.signAsync(user_bob);
+ await context.createBlock([signedTx]);
+
+ // Proposal is approved
+ const tx_approval = polkadotJs.tx.treasury.approveProposal(3);
+ const signedTx_approval = await polkadotJs.tx.sudo.sudo(tx_approval).signAsync(sudo_alice);
+ await context.createBlock([signedTx_approval]);
+
+ // New approvals count
+ const new_approvals_count = await context.polkadotJs().query.treasury.approvals();
+
+ // There should be 1 new approval
+ expect(new_approvals_count.length).to.be.equal(initial_approvals_count.length + 1);
+ },
+ });
+
+ it({
+ id: "E06",
+ title: "Non root can not approve proposals",
+ test: async function () {
+ // initial approvals count
+ const initial_approvals_count = await context.polkadotJs().query.treasury.approvals();
+
+ // Creates a proposal
+ const proposal_value = 100n;
+ const tx = polkadotJs.tx.treasury.proposeSpend(proposal_value, user_dave.address);
+ const signedTx = await tx.signAsync(user_bob);
+ await context.createBlock([signedTx]);
+
+ // Proposal is approved
+ const tx_approval = polkadotJs.tx.treasury.approveProposal(4);
+ const signedTx_approval = await tx_approval.signAsync(user_charlie);
+ await context.createBlock([signedTx_approval]);
+
+ // New approvals count
+ const new_approvals_count = await context.polkadotJs().query.treasury.approvals();
+
+ // There should be no new approvals
+ expect(new_approvals_count.length).to.be.equal(initial_approvals_count.length);
+ },
+ });
+ },
+});
diff --git a/typescript-api/src/dancebox/interfaces/augment-api-consts.ts b/typescript-api/src/dancebox/interfaces/augment-api-consts.ts
index 748e4c31c..76fbec8fc 100644
--- a/typescript-api/src/dancebox/interfaces/augment-api-consts.ts
+++ b/typescript-api/src/dancebox/interfaces/augment-api-consts.ts
@@ -6,9 +6,11 @@
import "@polkadot/api-base/types/consts";
import type { ApiTypes, AugmentedConst } from "@polkadot/api-base/types";
-import type { u128, u16, u32, u64, u8 } from "@polkadot/types-codec";
+import type { Option, u128, u16, u32, u64, u8 } from "@polkadot/types-codec";
import type { Codec } from "@polkadot/types-codec/types";
+import type { Permill } from "@polkadot/types/interfaces/runtime";
import type {
+ FrameSupportPalletId,
FrameSystemLimitsBlockLength,
FrameSystemLimitsBlockWeights,
SpVersionRuntimeVersion,
@@ -198,6 +200,33 @@ declare module "@polkadot/api-base/types/consts" {
/** Generic const */
[key: string]: Codec;
};
+ treasury: {
+ /** Percentage of spare funds (if any) that are burnt per spend period. */
+ burn: Permill & AugmentedConst;
+ /**
+ * The maximum number of approvals that can wait in the spending queue.
+ *
+ * NOTE: This parameter is also used within the Bounties Pallet extension if enabled.
+ */
+ maxApprovals: u32 & AugmentedConst;
+ /** The treasury's pallet id, used for deriving its sovereign account ID. */
+ palletId: FrameSupportPalletId & AugmentedConst;
+ /** The period during which an approved treasury spend has to be claimed. */
+ payoutPeriod: u32 & AugmentedConst;
+ /**
+ * Fraction of a proposal's value that should be bonded in order to place the proposal. An accepted proposal gets
+ * these back. A rejected proposal does not.
+ */
+ proposalBond: Permill & AugmentedConst;
+ /** Maximum amount of funds that should be placed in a deposit for making a proposal. */
+ proposalBondMaximum: Option & AugmentedConst;
+ /** Minimum amount of funds that should be placed in a deposit for making a proposal. */
+ proposalBondMinimum: u128 & AugmentedConst;
+ /** Period between successive spends. */
+ spendPeriod: u32 & AugmentedConst;
+ /** Generic const */
+ [key: string]: Codec;
+ };
txPause: {
/**
* Maximum length for pallet name and call name SCALE encoded string names.
diff --git a/typescript-api/src/dancebox/interfaces/augment-api-errors.ts b/typescript-api/src/dancebox/interfaces/augment-api-errors.ts
index 7728773e8..1b3864e99 100644
--- a/typescript-api/src/dancebox/interfaces/augment-api-errors.ts
+++ b/typescript-api/src/dancebox/interfaces/augment-api-errors.ts
@@ -392,6 +392,34 @@ declare module "@polkadot/api-base/types/errors" {
/** Generic error */
[key: string]: AugmentedError;
};
+ treasury: {
+ /** The payment has already been attempted. */
+ AlreadyAttempted: AugmentedError;
+ /** The spend is not yet eligible for payout. */
+ EarlyPayout: AugmentedError;
+ /** The balance of the asset kind is not convertible to the balance of the native asset. */
+ FailedToConvertBalance: AugmentedError;
+ /** The payment has neither failed nor succeeded yet. */
+ Inconclusive: AugmentedError;
+ /** The spend origin is valid but the amount it is allowed to spend is lower than the amount to be spent. */
+ InsufficientPermission: AugmentedError;
+ /** Proposer's balance is too low. */
+ InsufficientProposersBalance: AugmentedError;
+ /** No proposal, bounty or spend at that index. */
+ InvalidIndex: AugmentedError;
+ /** The payout was not yet attempted/claimed. */
+ NotAttempted: AugmentedError;
+ /** There was some issue with the mechanism of payment. */
+ PayoutError: AugmentedError;
+ /** Proposal has not been approved. */
+ ProposalNotApproved: AugmentedError;
+ /** The spend has expired and cannot be claimed. */
+ SpendExpired: AugmentedError;
+ /** Too many approvals in the queue. */
+ TooManyApprovals: AugmentedError;
+ /** Generic error */
+ [key: string]: AugmentedError;
+ };
txPause: {
/** The call is paused. */
IsPaused: AugmentedError;
diff --git a/typescript-api/src/dancebox/interfaces/augment-api-events.ts b/typescript-api/src/dancebox/interfaces/augment-api-events.ts
index dd648dbb3..0800f750b 100644
--- a/typescript-api/src/dancebox/interfaces/augment-api-events.ts
+++ b/typescript-api/src/dancebox/interfaces/augment-api-events.ts
@@ -1004,6 +1004,58 @@ declare module "@polkadot/api-base/types/events" {
/** Generic event */
[key: string]: AugmentedEvent;
};
+ treasury: {
+ /** A new asset spend proposal has been approved. */
+ AssetSpendApproved: AugmentedEvent<
+ ApiType,
+ [index: u32, assetKind: Null, amount: u128, beneficiary: AccountId32, validFrom: u32, expireAt: u32],
+ { index: u32; assetKind: Null; amount: u128; beneficiary: AccountId32; validFrom: u32; expireAt: u32 }
+ >;
+ /** An approved spend was voided. */
+ AssetSpendVoided: AugmentedEvent;
+ /** Some funds have been allocated. */
+ Awarded: AugmentedEvent<
+ ApiType,
+ [proposalIndex: u32, award: u128, account: AccountId32],
+ { proposalIndex: u32; award: u128; account: AccountId32 }
+ >;
+ /** Some of our funds have been burnt. */
+ Burnt: AugmentedEvent;
+ /** Some funds have been deposited. */
+ Deposit: AugmentedEvent;
+ /** A payment happened. */
+ Paid: AugmentedEvent;
+ /** A payment failed and can be retried. */
+ PaymentFailed: AugmentedEvent;
+ /** New proposal. */
+ Proposed: AugmentedEvent;
+ /** A proposal was rejected; funds were slashed. */
+ Rejected: AugmentedEvent<
+ ApiType,
+ [proposalIndex: u32, slashed: u128],
+ { proposalIndex: u32; slashed: u128 }
+ >;
+ /** Spending has finished; this is the amount that rolls over until next spend. */
+ Rollover: AugmentedEvent;
+ /** A new spend proposal has been approved. */
+ SpendApproved: AugmentedEvent<
+ ApiType,
+ [proposalIndex: u32, amount: u128, beneficiary: AccountId32],
+ { proposalIndex: u32; amount: u128; beneficiary: AccountId32 }
+ >;
+ /** We have ended a spend period and will now allocate funds. */
+ Spending: AugmentedEvent;
+ /** A spend was processed and removed from the storage. It might have been successfully paid or it may have expired. */
+ SpendProcessed: AugmentedEvent;
+ /** The inactive funds of the pallet have been updated. */
+ UpdatedInactive: AugmentedEvent<
+ ApiType,
+ [reactivated: u128, deactivated: u128],
+ { reactivated: u128; deactivated: u128 }
+ >;
+ /** Generic event */
+ [key: string]: AugmentedEvent;
+ };
txPause: {
/** This pallet, or a specific call is now paused. */
CallPaused: AugmentedEvent<
diff --git a/typescript-api/src/dancebox/interfaces/augment-api-query.ts b/typescript-api/src/dancebox/interfaces/augment-api-query.ts
index 90e5deeb3..af7c1d627 100644
--- a/typescript-api/src/dancebox/interfaces/augment-api-query.ts
+++ b/typescript-api/src/dancebox/interfaces/augment-api-query.ts
@@ -63,6 +63,8 @@ import type {
PalletProxyProxyDefinition,
PalletRegistrarDepositInfo,
PalletTransactionPaymentReleases,
+ PalletTreasuryProposal,
+ PalletTreasurySpendStatus,
PalletXcmQueryStatus,
PalletXcmRemoteLockedFungibleRecord,
PalletXcmVersionMigrationStage,
@@ -1090,6 +1092,32 @@ declare module "@polkadot/api-base/types/storage" {
/** Generic query */
[key: string]: QueryableStorageEntry;
};
+ treasury: {
+ /** Proposal indices that have been approved but not yet awarded. */
+ approvals: AugmentedQuery Observable>, []> & QueryableStorageEntry;
+ /** The amount which has been reported as inactive to Currency. */
+ deactivated: AugmentedQuery Observable, []> & QueryableStorageEntry;
+ /** Number of proposals that have been made. */
+ proposalCount: AugmentedQuery Observable, []> & QueryableStorageEntry;
+ /** Proposals that have been made. */
+ proposals: AugmentedQuery<
+ ApiType,
+ (arg: u32 | AnyNumber | Uint8Array) => Observable