Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2d250e3

Browse files
authoredMar 18, 2025··
Merge branch 'master' into seemant-update-template-readmes
2 parents 6c926a0 + e4f7814 commit 2d250e3

File tree

5 files changed

+126
-17
lines changed

5 files changed

+126
-17
lines changed
 

‎prdoc/pr_7723.prdoc

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
title: '[pallet-bounties] Allow bounties to never expire'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |
5+
Refactored the `update_due` calculation to use `saturating_add`, allowing bounties to remain active
6+
indefinitely without requiring `extend_bounty_expiry` and preventing automatic curator slashing for
7+
inactivity. Previously, setting `BountyUpdatePeriod` to a large value, such as `BlockNumber::max_value()`,
8+
could cause an overflow.
9+
10+
crates:
11+
- name: pallet-bounties
12+
bump: patch
13+
- name: pallet-child-bounties
14+
bump: patch

‎substrate/frame/bounties/src/benchmarking.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,26 @@ benchmarks_instance_pallet! {
146146
);
147147
}
148148

149-
// Worst case when curator is inactive and any sender unassigns the curator.
149+
// Worst case when curator is inactive and any sender unassigns the curator,
150+
// or if `BountyUpdatePeriod` is large enough and `RejectOrigin` executes the call.
150151
unassign_curator {
151152
setup_pot_account::<T, I>();
152153
let (curator_lookup, bounty_id) = create_bounty::<T, I>()?;
153154
Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
154155
let bounty_id = BountyCount::<T, I>::get() - 1;
155-
set_block_number::<T, I>(T::SpendPeriod::get() + T::BountyUpdatePeriod::get() + 2u32.into());
156-
let caller = whitelisted_caller();
157-
}: _(RawOrigin::Signed(caller), bounty_id)
156+
let bounty_update_period = T::BountyUpdatePeriod::get();
157+
let inactivity_timeout = T::SpendPeriod::get().saturating_add(bounty_update_period);
158+
set_block_number::<T, I>(inactivity_timeout.saturating_add(2u32.into()));
159+
160+
// If `BountyUpdatePeriod` overflows the inactivity timeout the benchmark still executes the slash
161+
let origin = if Pallet::<T, I>::treasury_block_number() <= inactivity_timeout {
162+
let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
163+
T::RejectOrigin::try_successful_origin().unwrap_or_else(|_| RawOrigin::Signed(curator).into())
164+
} else {
165+
let caller = whitelisted_caller();
166+
RawOrigin::Signed(caller).into()
167+
};
168+
}: _<T::RuntimeOrigin>(origin, bounty_id)
158169

159170
accept_curator {
160171
setup_pot_account::<T, I>();

‎substrate/frame/bounties/src/lib.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,12 @@ pub mod pallet {
221221
#[pallet::constant]
222222
type BountyDepositPayoutDelay: Get<BlockNumberFor<Self, I>>;
223223

224-
/// Bounty duration in blocks.
224+
/// The time limit for a curator to act before a bounty expires.
225+
///
226+
/// The period that starts when a curator is approved, during which they must execute or
227+
/// update the bounty via `extend_bounty_expiry`. If missed, the bounty expires, and the
228+
/// curator may be slashed. If `BlockNumberFor::MAX`, bounties stay active indefinitely,
229+
/// removing the need for `extend_bounty_expiry`.
225230
#[pallet::constant]
226231
type BountyUpdatePeriod: Get<BlockNumberFor<Self, I>>;
227232

@@ -578,8 +583,8 @@ pub mod pallet {
578583
T::Currency::reserve(curator, deposit)?;
579584
bounty.curator_deposit = deposit;
580585

581-
let update_due =
582-
Self::treasury_block_number() + T::BountyUpdatePeriod::get();
586+
let update_due = Self::treasury_block_number()
587+
.saturating_add(T::BountyUpdatePeriod::get());
583588
bounty.status =
584589
BountyStatus::Active { curator: curator.clone(), update_due };
585590

@@ -820,9 +825,9 @@ pub mod pallet {
820825
match bounty.status {
821826
BountyStatus::Active { ref curator, ref mut update_due } => {
822827
ensure!(*curator == signer, Error::<T, I>::RequireCurator);
823-
*update_due = (Self::treasury_block_number() +
824-
T::BountyUpdatePeriod::get())
825-
.max(*update_due);
828+
*update_due = Self::treasury_block_number()
829+
.saturating_add(T::BountyUpdatePeriod::get())
830+
.max(*update_due);
826831
},
827832
_ => return Err(Error::<T, I>::UnexpectedStatus.into()),
828833
}

‎substrate/frame/bounties/src/tests.rs

+68-3
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,14 @@ parameter_types! {
137137
pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50);
138138
pub const CuratorDepositMax: Balance = 1_000;
139139
pub const CuratorDepositMin: Balance = 3;
140-
140+
pub static BountyUpdatePeriod: u64 = 20;
141141
}
142142

143143
impl Config for Test {
144144
type RuntimeEvent = RuntimeEvent;
145145
type BountyDepositBase = ConstU64<80>;
146146
type BountyDepositPayoutDelay = ConstU64<3>;
147-
type BountyUpdatePeriod = ConstU64<20>;
147+
type BountyUpdatePeriod = BountyUpdatePeriod;
148148
type CuratorDepositMultiplier = CuratorDepositMultiplier;
149149
type CuratorDepositMax = CuratorDepositMax;
150150
type CuratorDepositMin = CuratorDepositMin;
@@ -160,7 +160,7 @@ impl Config<Instance1> for Test {
160160
type RuntimeEvent = RuntimeEvent;
161161
type BountyDepositBase = ConstU64<80>;
162162
type BountyDepositPayoutDelay = ConstU64<3>;
163-
type BountyUpdatePeriod = ConstU64<20>;
163+
type BountyUpdatePeriod = BountyUpdatePeriod;
164164
type CuratorDepositMultiplier = CuratorDepositMultiplier;
165165
type CuratorDepositMax = CuratorDepositMax;
166166
type CuratorDepositMin = CuratorDepositMin;
@@ -1416,3 +1416,68 @@ fn approve_bounty_with_curator_proposed_unassign_works() {
14161416
assert_eq!(last_event(), BountiesEvent::CuratorUnassigned { bounty_id: 0 });
14171417
});
14181418
}
1419+
1420+
#[test]
1421+
fn accept_curator_sets_update_due_correctly() {
1422+
ExtBuilder::default().build_and_execute(|| {
1423+
// Given (BountyUpdatePeriod = 20)
1424+
let bounty_id = 0;
1425+
let proposer = 0;
1426+
let fee = 10;
1427+
let curator = 4;
1428+
Balances::make_free_balance_be(&Treasury::account_id(), 101);
1429+
Balances::make_free_balance_be(&curator, 12);
1430+
assert_ok!(Bounties::propose_bounty(
1431+
RuntimeOrigin::signed(proposer),
1432+
50,
1433+
b"12345".to_vec()
1434+
));
1435+
assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));
1436+
go_to_block(4);
1437+
assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_id, curator, fee));
1438+
1439+
// When
1440+
assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(curator), bounty_id));
1441+
1442+
// Then
1443+
assert_eq!(
1444+
pallet_bounties::Bounties::<Test>::get(bounty_id).unwrap().status,
1445+
BountyStatus::Active { curator, update_due: 24 }
1446+
);
1447+
1448+
// Given (BountyUpdatePeriod = BlockNumber::max_value())
1449+
BountyUpdatePeriod::set(BlockNumberFor::<Test>::max_value());
1450+
Balances::make_free_balance_be(&Treasury1::account_id(), 101);
1451+
assert_ok!(Bounties1::propose_bounty(
1452+
RuntimeOrigin::signed(proposer),
1453+
50,
1454+
b"12345".to_vec()
1455+
));
1456+
assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), bounty_id));
1457+
go_to_block(6);
1458+
<Treasury1 as OnInitialize<u64>>::on_initialize(6);
1459+
assert_ok!(Bounties1::propose_curator(RuntimeOrigin::root(), bounty_id, curator, fee));
1460+
1461+
// When
1462+
assert_ok!(Bounties1::accept_curator(RuntimeOrigin::signed(curator), bounty_id));
1463+
1464+
// Then
1465+
assert_eq!(
1466+
pallet_bounties::Bounties::<Test, Instance1>::get(bounty_id).unwrap().status,
1467+
BountyStatus::Active { curator, update_due: BlockNumberFor::<Test>::max_value() }
1468+
);
1469+
1470+
// When
1471+
assert_ok!(Bounties1::extend_bounty_expiry(
1472+
RuntimeOrigin::signed(curator),
1473+
bounty_id,
1474+
Vec::new()
1475+
));
1476+
1477+
// Then
1478+
assert_eq!(
1479+
pallet_bounties::Bounties::<Test, Instance1>::get(bounty_id).unwrap().status,
1480+
BountyStatus::Active { curator, update_due: BlockNumberFor::<Test>::max_value() }
1481+
);
1482+
});
1483+
}

‎substrate/frame/child-bounties/src/benchmarking.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -267,17 +267,31 @@ mod benchmarks {
267267
Ok(())
268268
}
269269

270-
// Worst case when curator is inactive and any sender un-assigns the curator.
270+
// Worst case when curator is inactive and any sender un-assigns the curator,
271+
// or if `BountyUpdatePeriod` is large enough and `RejectOrigin` executes the call.
271272
#[benchmark]
272273
fn unassign_curator() -> Result<(), BenchmarkError> {
273274
setup_pot_account::<T>();
274275
let bounty_setup = activate_child_bounty::<T>(0, T::MaximumReasonLength::get())?;
275276
Treasury::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
276-
set_block_number::<T>(T::SpendPeriod::get() + T::BountyUpdatePeriod::get() + 1u32.into());
277-
let caller = whitelisted_caller();
277+
let bounty_update_period = T::BountyUpdatePeriod::get();
278+
let inactivity_timeout = T::SpendPeriod::get().saturating_add(bounty_update_period);
279+
set_block_number::<T>(inactivity_timeout.saturating_add(1u32.into()));
280+
281+
// If `BountyUpdatePeriod` overflows the inactivity timeout the benchmark still
282+
// executes the slash
283+
let origin: T::RuntimeOrigin = if Pallet::<T>::treasury_block_number() <= inactivity_timeout
284+
{
285+
let child_curator = bounty_setup.child_curator;
286+
T::RejectOrigin::try_successful_origin()
287+
.unwrap_or_else(|_| RawOrigin::Signed(child_curator).into())
288+
} else {
289+
let caller = whitelisted_caller();
290+
RawOrigin::Signed(caller).into()
291+
};
278292

279293
#[extrinsic_call]
280-
_(RawOrigin::Signed(caller), bounty_setup.bounty_id, bounty_setup.child_bounty_id);
294+
_(origin as T::RuntimeOrigin, bounty_setup.bounty_id, bounty_setup.child_bounty_id);
281295

282296
Ok(())
283297
}

0 commit comments

Comments
 (0)
Please sign in to comment.