Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Pallets: Treasury spend() should use Pay trait #13607

Closed
wants to merge 72 commits into from

Conversation

tonyalaribe
Copy link
Contributor

@tonyalaribe tonyalaribe commented Mar 15, 2023

Motivation

To support a more flexible treasury pallet, we need to prevent strongly coupling the treasury implementation to the Currency, fungible or fungibles traits.
Instead, this PR begins an attempt at a treasury pallet which is implemented over a more generic trait, which could then be implemented over fungibles, fungible, or even over pallet_xcm. Opening more possibilities including the ability for the treasury to control and spend assets which are available on remote parachains.

Approach

This PR leverages the new pay trait, as the means of actually implementing spend. The long term goal will be to deprecate the other non-spend extrinsics. Eg propose_spend(), reject_proposal, etc, so that the spend() extrinsic becomes the de-factor way of using spend().

A user story could be that all the proposal management logic happens through a referendum from the respective pallet, and that referendum is able to trigger the spend() action in the treasury pallet, which gets paid out at the next SpendPeriod. By doing this, the treasury becomes much simpler in scope, and becomes a pallet which can be used to control spending or paying out assets from accounts controlled by an origin.

So in practical terms, it could be possible to have many treasury instances for every account which is controlled by a collective. Eg the Governance could control a treasury over funds in statemint and control a treasury over funds on even a remote parachain or on the local chain, via the respective implementations of pay: PayFromAccount, PayFungibles, PayOverXcm or even a hypothetical PayNonFungible

polkadot companion: paritytech/polkadot#7058

TODO

  • Avoid depending on Assets and Balances pallet.

@tonyalaribe tonyalaribe force-pushed the aa/multi-asset-support-in-treasury branch from 41ebd63 to 57aacbe Compare March 16, 2023 11:13
@tonyalaribe tonyalaribe changed the title pallet_treasury: implement treasury pallet over the pay trait pallet_treasury: implement treasury pallet spend() over the pay trait Mar 16, 2023
frame/treasury/src/lib.rs Show resolved Hide resolved
frame/treasury/src/lib.rs Outdated Show resolved Hide resolved
@tonyalaribe tonyalaribe marked this pull request as ready for review March 22, 2023 17:54
@tonyalaribe tonyalaribe added A0-please_review Pull request needs code review. C3-medium PR touches the given topic and has a medium impact on builders. D9-needsaudit 👮 PR contains changes to fund-managing logic that should be properly reviewed and externally audited E6-needs_polkadot_pr This is an issue that needs to be implemented upstream in Polkadot. labels Mar 22, 2023
frame/treasury/src/lib.rs Outdated Show resolved Hide resolved
});
},
},
Some(payment_id) => match T::Paymaster::check_payment(payment_id) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be timing out after some timeout

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pallet_xcm does not timeout now. but I think it should be on both sides

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you propose?
With the current implementation, we would simply keep retrying the transaction each spend period until it succeeds, and that seems sufficient to me.

Also, the treasury pallet knows nothing about the XCM timeouts nor does it even know anything about XCM.
Eg lots of parachains will implement their treasuries using the PayFromAccount or PayFungibles implementations which have no timeouts. So i'm not convinced about adding any timeout logic into the substrate treasury implementation, as that is too specific to XCM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the treasury pallet does not need to know about XCM to have a timeout for some async status checks it does.
if for some reason it never gets some of the fail/success statuses, by this design it keeps checking the status forever.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a concept of retries, so we retry a payment a given number of times.
About the timeouts, It would only be useful in the XCM context, and in the case of XCM, when it expires, the check_payment method would return a status unknown, which we now treat as a failure, so it would fail immediately at that point. I think with that workflow, there won't be any extra utility for the timeouts, and if that use-case comes up, then we can include it in a subsequent PR.

frame/treasury/src/lib.rs Show resolved Hide resolved
frame/treasury/src/lib.rs Outdated Show resolved Hide resolved
frame/treasury/src/lib.rs Outdated Show resolved Hide resolved
frame/treasury/src/lib.rs Outdated Show resolved Hide resolved
"Paymaster::pay failed for PendingPayment with index: {:?}",
key
);
// try again in the next `T::SpendPeriod`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the payment statuses should be check with a different interval, not SpendPeriod. if we separate the pay job and the check status one, we will be able to have more accurate weights for these jobs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. let me explore this more

});
// Force the payment to none, so a fresh payment is sent during the next
// T::SpendPeriod.
p.payment_id = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which means we retry endlessly, looks wrong to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's no harm. This gives time for governance to solve the reason for the failure. But what would you propose?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if its not recoverable fail, how governance will solve it?

}
}

total_weight += T::WeightInfo::on_initialize_pending_payments(proposals_len);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be checking if there is enough capacity left before executing pay, scheduler is a good example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By scheduler, what do you mean? Maybe point me to a file or code line of what you mean

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scheduler pallet, jobs on_initialize

// Check to see if we should spend some funds!
if (n % T::SpendPeriod::get()).is_zero() {
Self::spend_funds()
Self::spend_funds().saturating_add(Self::spend_funds_local())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there might be not enough weight capacity left for spend_funds_local .
and spend period might be quite long, for polkadot it is 24 days.

create_pending_payments::<T,_>(p)?;
}: {
Treasury::<T, _>::on_initialize(T::BlockNumber::zero());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need some check here that pay was executed

@@ -138,5 +183,12 @@ benchmarks_instance_pallet! {
Treasury::<T, _>::on_initialize(T::BlockNumber::zero());
}

on_initialize_pending_payments {
let p in 0 .. T::MaxApprovals::get();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MaxApprovals limit is not enforced for pending payments

<Treasury as OnInitialize<u64>>::on_initialize(2);
assert_eq!(Assets::reducible_balance(0, &Treasury::account_id(), Expendable, Polite), 0);
assert_eq!(Assets::reducible_balance(0, &6, Expendable, Polite), 100);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like many cases of the new functionality is not covered, would be good to have more tests

@tonyalaribe
Copy link
Contributor Author

I made some larger changes to the spend method, such that it now accepts two main arguments.

  • A vector of assets
  • A beneficiary

The vector of assets can be anything that encodes a way to retrieve a balance from it, but in the kusama/runtimes context, it also allows us to use a type that the PayOverXcm can support. Here's a video demo of the new workflow:

Polkadot_Substrate.Portal.-.26.May.2023.mp4

@gavofyork
Copy link
Member

gavofyork commented May 29, 2023

Why a vec of assets? This introduces problematic edge cases - e.g. what happens if one asset can be transferred, but another later fails? A pallet/user can always make a batch of spend calls.

@gavofyork
Copy link
Member

gavofyork commented May 29, 2023

Can you make a separate PR for the alterations to trait Pay since it's non-controversial and depended upon by other downstream code (e.g. paritytech/polkadot#6900) and it would be nice to merge soon.

EDIT: Done in #14258

@joepetrowski
Copy link
Contributor

Why a vec of assets? This introduces problematic edge cases - e.g. what happens if one asset can be transferred, but another later fails? A pallet/user can always make a batch of spend calls.

This came up on a call where we were discussing the API, and IIRC the idea was that for now we ensure!(assets.len() == 1) until we are confident with such edge case handling, but if there were a use case in the future that spend proposals contained many assets, we wouldn't have to break the API. A batch of many spends wouldn't account for budget and put it on the right track, while a single spend with a vec of assets should properly account for the total value and enforce the origin correctly.

@tonyalaribe
Copy link
Contributor Author

Why a vec of assets? This introduces problematic edge cases - e.g. what happens if one asset can be transferred, but another later fails? A pallet/user can always make a batch of spend calls.

Just to add to what Joe said, another reason was to be consistent with some other APIs, eg how the initiate_teleport method accepts a vector of assets and a destination. So this could be more flexible long term and we won't have to break the API contract in a few months to support spending multiple asset classes in a single transaction.

@tonyalaribe tonyalaribe requested a review from a team May 30, 2023 10:38
@paritytech-ci paritytech-ci requested a review from a team May 30, 2023 10:38
@gavofyork
Copy link
Member

gavofyork commented May 30, 2023

Why not introduce spend_many if/when we decide we need to support multi-asset spends? I'm still not sure what the advantage is over batch([spend, spend, ...])...

@tonyalaribe
Copy link
Contributor Author

Why not introduce spend_many if/when we decide we need to support multi-asset spends? I'm still not sure what the advantage is over batch([spend, spend, ...])...

Yeah, I agree with this. I would revert to spends on a single asset for now, except someone has a counterargument.

@gavofyork gavofyork changed the title pallet_treasury: implement treasury pallet spend() over the pay trait Treasury: spend() should use Pay trait May 31, 2023
@gavofyork gavofyork changed the title Treasury: spend() should use Pay trait Pallets: Treasury spend() should use Pay trait May 31, 2023
@paritytech-cicd-pr
Copy link

The CI pipeline was cancelled due to failure one of the required jobs.
Job name: cargo-check-benches
Logs: https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/2946290

@muharem
Copy link
Contributor

muharem commented Jun 26, 2023

closing in favour of #14434

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A0-please_review Pull request needs code review. B1-note_worthy Changes should be noted in the release notes C3-medium PR touches the given topic and has a medium impact on builders. D9-needsaudit 👮 PR contains changes to fund-managing logic that should be properly reviewed and externally audited E6-needs_polkadot_pr This is an issue that needs to be implemented upstream in Polkadot. T1-runtime This PR/Issue is related to the topic “runtime”.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

8 participants