From dbebf539e6ae2c796fb1026be0740fdeec247f85 Mon Sep 17 00:00:00 2001
From: Josep M Sobrepere <jm.sobrepere@gmail.com>
Date: Thu, 20 Mar 2025 12:05:52 +0100
Subject: [PATCH] Treasury: update expire date on payout (#7958) (#7959)

Closes #7958

Resets the `payout.expire_at` field with the `PayoutPeriod` every time
that there is a valid Payout attempt.

---------

Co-authored-by: Victor Oliva <olivarra1@gmail.com>
---
 prdoc/pr_7959.prdoc                   | 10 +++++++++
 substrate/frame/treasury/src/lib.rs   |  1 +
 substrate/frame/treasury/src/tests.rs | 29 +++++++++++++++++++++++++++
 3 files changed, 40 insertions(+)
 create mode 100644 prdoc/pr_7959.prdoc

diff --git a/prdoc/pr_7959.prdoc b/prdoc/pr_7959.prdoc
new file mode 100644
index 00000000000..29e14083588
--- /dev/null
+++ b/prdoc/pr_7959.prdoc
@@ -0,0 +1,10 @@
+title: Update expire date on treasury payout
+doc:
+- audience: Runtime Dev
+  description: |-
+    Resets the `payout.expire_at` field with the `PayoutPeriod` every time that there is a valid Payout attempt.
+    Prior to this change, when a spend is approved, it receives an expiry date so that if it’s never claimed, it automatically expires. This makes sense under normal circumstances. However, if someone attempts to claim a valid payout and there isn’t sufficient liquidity to fulfill it, the expiry date currently remains unchanged. This effectively penalizes the claimant in the same way as if they had never requested the payout in the first place.
+    With this change users are not penalized for liquidity shortages and have a fair window to claim once the funds are available.
+crates:
+- name: pallet-treasury
+  bump: patch
diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs
index 2a4f49fd5b3..9d89c80f794 100644
--- a/substrate/frame/treasury/src/lib.rs
+++ b/substrate/frame/treasury/src/lib.rs
@@ -743,6 +743,7 @@ pub mod pallet {
 				.map_err(|_| Error::<T, I>::PayoutError)?;
 
 			spend.status = PaymentState::Attempted { id };
+			spend.expire_at = now.saturating_add(T::PayoutPeriod::get());
 			Spends::<T, I>::insert(index, spend);
 
 			Self::deposit_event(Event::<T, I>::Paid { index, payment_id: id });
diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs
index 2c2ceac5862..22322c3c5f7 100644
--- a/substrate/frame/treasury/src/tests.rs
+++ b/substrate/frame/treasury/src/tests.rs
@@ -669,6 +669,35 @@ fn spend_payout_works() {
 	});
 }
 
+#[test]
+fn payout_extends_expiry() {
+	ExtBuilder::default().build().execute_with(|| {
+		assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
+
+		System::set_block_number(1);
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		// Fail a payout at block 4
+		System::set_block_number(4);
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+		assert_eq!(paid(6, 1), 2);
+		let payment_id = get_payment_id(0).expect("no payment attempt");
+		// spend payment is failed
+		set_status(payment_id, PaymentStatus::Failure);
+		unpay(6, 1, 2);
+
+		// check status to set the correct state
+		assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
+		System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 0, payment_id }.into());
+
+		// Retrying at after the initial expiry date but before the new one succeeds
+		System::set_block_number(7);
+
+		// the payout can be retried now
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+		assert_eq!(paid(6, 1), 2);
+	});
+}
+
 #[test]
 fn payout_retry_works() {
 	ExtBuilder::default().build().execute_with(|| {
-- 
GitLab