Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
52698ec
pallet-bounties: extends update_due infinitly if BountiesUpdatePeriod…
BigTava Feb 26, 2025
fa19b53
pallet-bounties: fmt
BigTava Feb 26, 2025
4e49830
pallet-bounties: changes docs for config parameter and adds prdoc
BigTava Feb 26, 2025
08c637d
pallet-bounties: fix benchmarking
BigTava Feb 26, 2025
2478bfc
Merge branch 'master' into tiago-remove-bounties-expiry
BigTava Feb 26, 2025
c8cb79e
pallet-child-bounties: fix benchamarking
BigTava Feb 27, 2025
4654163
pallet-child-bounties: move Bounded from lib to benchmarking
BigTava Feb 27, 2025
2e22a58
pallet-bounties: improves prdoc and parameter docs
BigTava Feb 27, 2025
3890ab0
pallet-bounties: refactors prdoc and tests BountyUpdatePeriod set to …
BigTava Feb 27, 2025
91eb76d
docs: provides migration path
BigTava Feb 27, 2025
2ef5617
pallet-bounties: remove debug statements
BigTava Feb 28, 2025
1ee5b56
docs: improves migration path and adds function to reuse update_due c…
BigTava Feb 28, 2025
a927617
pallet-bounties: fmt
BigTava Feb 28, 2025
cda4673
pallet-bounties: adds docs to calculate_update_due
BigTava Feb 28, 2025
5c18edd
docs: adds API change in migration
BigTava Feb 28, 2025
e0600e3
docs: adds sentence on how to migrate to new change and removes migra…
BigTava Mar 4, 2025
133ca5b
pallet-bounties: improves code readability, docs, and add dynamic par…
BigTava Mar 4, 2025
6e42b1e
pallet-child-bounties: improves code readability
BigTava Mar 4, 2025
5ce7249
docs: refactor to describe only the use of `saturating_add` to preven…
BigTava Mar 7, 2025
c34e68e
pallet-bounties: refactor to only use `saturating_add` instead of cha…
BigTava Mar 7, 2025
a143787
pallet-child-bounties: refactors since BountyUpdatePeriod is no longe…
BigTava Mar 7, 2025
7a9f641
pallet-bounties: handles benchmarking call fail when `BountyUpdatePe…
BigTava Mar 8, 2025
e254cec
docs: refactor prdoc
BigTava Mar 17, 2025
1f6b1bb
pallet-bounties: refactor unassign_curator benchmark to always perfor…
BigTava Mar 17, 2025
3bde188
pallet-bounties: defaults to some signed caller if RejectOrigin is un…
BigTava Mar 17, 2025
e754423
pallet-bounties: defaults to curator if RejectOrigin is unavailable
BigTava Mar 17, 2025
0b8c2a5
pallet-child-bounties: minor
BigTava Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions prdoc/pr_7723.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
title: '[pallet-bounties] Allow bounties to never expire'
doc:
- audience: Runtime Dev
description: |
Refactored the `update_due` calculation to use `saturating_add`, allowing bounties to remain active
indefinitely without requiring `extend_bounty_expiry` and preventing automatic curator slashing for
inactivity. Previously, setting `BountyUpdatePeriod` to a large value, such as `BlockNumber::max_value()`,
could cause an overflow.

crates:
- name: pallet-bounties
bump: patch
- name: pallet-child-bounties
bump: patch
19 changes: 15 additions & 4 deletions substrate/frame/bounties/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,26 @@ benchmarks_instance_pallet! {
);
}

// Worst case when curator is inactive and any sender unassigns the curator.
// Worst case when curator is inactive and any sender unassigns the curator,
// or if `BountyUpdatePeriod` is large enough and `RejectOrigin` executes the call.
unassign_curator {
setup_pot_account::<T, I>();
let (curator_lookup, bounty_id) = create_bounty::<T, I>()?;
Treasury::<T, I>::on_initialize(frame_system::Pallet::<T>::block_number());
let bounty_id = BountyCount::<T, I>::get() - 1;
set_block_number::<T, I>(T::SpendPeriod::get() + T::BountyUpdatePeriod::get() + 2u32.into());
let caller = whitelisted_caller();
}: _(RawOrigin::Signed(caller), bounty_id)
let bounty_update_period = T::BountyUpdatePeriod::get();
let inactivity_timeout = T::SpendPeriod::get().saturating_add(bounty_update_period);
set_block_number::<T, I>(inactivity_timeout.saturating_add(2u32.into()));

// If `BountyUpdatePeriod` overflows the inactivity timeout the benchmark still executes the slash
let origin = if Pallet::<T, I>::treasury_block_number() <= inactivity_timeout {
let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?;
T::RejectOrigin::try_successful_origin().unwrap_or_else(|_| RawOrigin::Signed(curator).into())
} else {
let caller = whitelisted_caller();
RawOrigin::Signed(caller).into()
};
}: _<T::RuntimeOrigin>(origin, bounty_id)

accept_curator {
setup_pot_account::<T, I>();
Expand Down
17 changes: 11 additions & 6 deletions substrate/frame/bounties/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ pub mod pallet {
#[pallet::constant]
type BountyDepositPayoutDelay: Get<BlockNumberFor<Self, I>>;

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

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

let update_due =
Self::treasury_block_number() + T::BountyUpdatePeriod::get();
let update_due = Self::treasury_block_number()
.saturating_add(T::BountyUpdatePeriod::get());
bounty.status =
BountyStatus::Active { curator: curator.clone(), update_due };

Expand Down Expand Up @@ -820,9 +825,9 @@ pub mod pallet {
match bounty.status {
BountyStatus::Active { ref curator, ref mut update_due } => {
ensure!(*curator == signer, Error::<T, I>::RequireCurator);
*update_due = (Self::treasury_block_number() +
T::BountyUpdatePeriod::get())
.max(*update_due);
*update_due = Self::treasury_block_number()
.saturating_add(T::BountyUpdatePeriod::get())
.max(*update_due);
},
_ => return Err(Error::<T, I>::UnexpectedStatus.into()),
}
Expand Down
71 changes: 68 additions & 3 deletions substrate/frame/bounties/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,14 +137,14 @@ parameter_types! {
pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50);
pub const CuratorDepositMax: Balance = 1_000;
pub const CuratorDepositMin: Balance = 3;

pub static BountyUpdatePeriod: u64 = 20;
}

impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type BountyDepositBase = ConstU64<80>;
type BountyDepositPayoutDelay = ConstU64<3>;
type BountyUpdatePeriod = ConstU64<20>;
type BountyUpdatePeriod = BountyUpdatePeriod;
type CuratorDepositMultiplier = CuratorDepositMultiplier;
type CuratorDepositMax = CuratorDepositMax;
type CuratorDepositMin = CuratorDepositMin;
Expand All @@ -160,7 +160,7 @@ impl Config<Instance1> for Test {
type RuntimeEvent = RuntimeEvent;
type BountyDepositBase = ConstU64<80>;
type BountyDepositPayoutDelay = ConstU64<3>;
type BountyUpdatePeriod = ConstU64<20>;
type BountyUpdatePeriod = BountyUpdatePeriod;
type CuratorDepositMultiplier = CuratorDepositMultiplier;
type CuratorDepositMax = CuratorDepositMax;
type CuratorDepositMin = CuratorDepositMin;
Expand Down Expand Up @@ -1416,3 +1416,68 @@ fn approve_bounty_with_curator_proposed_unassign_works() {
assert_eq!(last_event(), BountiesEvent::CuratorUnassigned { bounty_id: 0 });
});
}

#[test]
fn accept_curator_sets_update_due_correctly() {
ExtBuilder::default().build_and_execute(|| {
// Given (BountyUpdatePeriod = 20)
let bounty_id = 0;
let proposer = 0;
let fee = 10;
let curator = 4;
Balances::make_free_balance_be(&Treasury::account_id(), 101);
Balances::make_free_balance_be(&curator, 12);
assert_ok!(Bounties::propose_bounty(
RuntimeOrigin::signed(proposer),
50,
b"12345".to_vec()
));
assert_ok!(Bounties::approve_bounty(RuntimeOrigin::root(), 0));
go_to_block(4);
assert_ok!(Bounties::propose_curator(RuntimeOrigin::root(), bounty_id, curator, fee));

// When
assert_ok!(Bounties::accept_curator(RuntimeOrigin::signed(curator), bounty_id));

// Then
assert_eq!(
pallet_bounties::Bounties::<Test>::get(bounty_id).unwrap().status,
BountyStatus::Active { curator, update_due: 24 }
);

// Given (BountyUpdatePeriod = BlockNumber::max_value())
BountyUpdatePeriod::set(BlockNumberFor::<Test>::max_value());
Balances::make_free_balance_be(&Treasury1::account_id(), 101);
assert_ok!(Bounties1::propose_bounty(
RuntimeOrigin::signed(proposer),
50,
b"12345".to_vec()
));
assert_ok!(Bounties1::approve_bounty(RuntimeOrigin::root(), bounty_id));
go_to_block(6);
<Treasury1 as OnInitialize<u64>>::on_initialize(6);
assert_ok!(Bounties1::propose_curator(RuntimeOrigin::root(), bounty_id, curator, fee));

// When
assert_ok!(Bounties1::accept_curator(RuntimeOrigin::signed(curator), bounty_id));

// Then
assert_eq!(
pallet_bounties::Bounties::<Test, Instance1>::get(bounty_id).unwrap().status,
BountyStatus::Active { curator, update_due: BlockNumberFor::<Test>::max_value() }
);

// When
assert_ok!(Bounties1::extend_bounty_expiry(
RuntimeOrigin::signed(curator),
bounty_id,
Vec::new()
));

// Then
assert_eq!(
pallet_bounties::Bounties::<Test, Instance1>::get(bounty_id).unwrap().status,
BountyStatus::Active { curator, update_due: BlockNumberFor::<Test>::max_value() }
);
});
}
22 changes: 18 additions & 4 deletions substrate/frame/child-bounties/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,17 +267,31 @@ mod benchmarks {
Ok(())
}

// Worst case when curator is inactive and any sender un-assigns the curator.
// Worst case when curator is inactive and any sender un-assigns the curator,
// or if `BountyUpdatePeriod` is large enough and `RejectOrigin` executes the call.
#[benchmark]
fn unassign_curator() -> Result<(), BenchmarkError> {
setup_pot_account::<T>();
let bounty_setup = activate_child_bounty::<T>(0, T::MaximumReasonLength::get())?;
Treasury::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
set_block_number::<T>(T::SpendPeriod::get() + T::BountyUpdatePeriod::get() + 1u32.into());
let caller = whitelisted_caller();
let bounty_update_period = T::BountyUpdatePeriod::get();
let inactivity_timeout = T::SpendPeriod::get().saturating_add(bounty_update_period);
set_block_number::<T>(inactivity_timeout.saturating_add(1u32.into()));

// If `BountyUpdatePeriod` overflows the inactivity timeout the benchmark still
// executes the slash
let origin: T::RuntimeOrigin = if Pallet::<T>::treasury_block_number() <= inactivity_timeout
{
let child_curator = bounty_setup.child_curator;
T::RejectOrigin::try_successful_origin()
.unwrap_or_else(|_| RawOrigin::Signed(child_curator).into())
} else {
let caller = whitelisted_caller();
RawOrigin::Signed(caller).into()
};

#[extrinsic_call]
_(RawOrigin::Signed(caller), bounty_setup.bounty_id, bounty_setup.child_bounty_id);
_(origin as T::RuntimeOrigin, bounty_setup.bounty_id, bounty_setup.child_bounty_id);

Ok(())
}
Expand Down
Loading