Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Changes from 1 commit
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
1f31ad9
add some compact annotation
xlc Apr 21, 2020
f3e7230
implement bounties for treasury
xlc Apr 21, 2020
8c16d4f
fix test build
xlc Apr 22, 2020
3efc22a
remove some duplicated code
xlc Apr 22, 2020
817770d
fix build
xlc Apr 22, 2020
a1b30c1
add tests
xlc Apr 22, 2020
5fce0d8
Merge remote-tracking branch 'origin/master' into bounty
xlc Apr 22, 2020
d143080
fix build
xlc Apr 22, 2020
3f5d446
Merge remote-tracking branch 'origin/master' into bounty
xlc Apr 23, 2020
829dc20
fix tests
xlc Apr 23, 2020
b4f86f1
Merge branch 'master' into bounty
xlc May 20, 2020
45b7717
rename
xlc May 20, 2020
d079500
merge deposit byte fee
xlc May 20, 2020
ce0bc02
add comments
xlc May 20, 2020
19f3bc3
Merge branch 'master' into bounty
xlc May 28, 2020
2e9697e
refactor storage
xlc May 28, 2020
92e8050
Merge remote-tracking branch 'origin/master' into bounty
xlc Jun 6, 2020
a5c26bb
support sub bounty
xlc Jun 6, 2020
18c9bab
emit BountyBecameActive when sub bounty is created
xlc Jun 6, 2020
e7fa5e3
able to contribute bounty
xlc Jun 6, 2020
4968e3b
allow curator to cancel bounty
xlc Jun 7, 2020
ee57848
remove bounty contribution
xlc Jun 8, 2020
0fb4ff2
implement bounty expiry
xlc Jun 8, 2020
f9901a0
Able to extend bounty
xlc Jun 8, 2020
8129646
Merge remote-tracking branch 'origin/master' into bounty
xlc Jun 21, 2020
d58794c
fix build and update tests
xlc Jun 21, 2020
d432a7a
create sub bounty test
xlc Jun 21, 2020
8141f62
add more tests
xlc Jun 21, 2020
4b1ea01
Merge remote-tracking branch 'origin/master' into bounty
xlc Jun 25, 2020
a63680c
add benchmarks for bounties
xlc Jun 28, 2020
ecb3893
Merge remote-tracking branch 'origin/master' into bounty
xlc Jun 28, 2020
b1d8d2f
fix build
xlc Jun 28, 2020
749c66b
Merge branch 'master' into bounty
xlc Jun 29, 2020
b6786e8
line width
xlc Jul 1, 2020
88c2046
fix benchmarking test
xlc Jul 1, 2020
fbc12f0
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 1, 2020
e174c98
update trait
xlc Jul 1, 2020
697c1e3
fix typo
xlc Jul 5, 2020
1bd7fc0
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 5, 2020
e1e01ee
Update lib.rs
Jul 6, 2020
afe301b
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 7, 2020
24f58bf
Merge pull request #1 from rrtti/patch-1
xlc Jul 8, 2020
dd7739b
update docs
xlc Jul 8, 2020
e8ffa3b
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 9, 2020
08065ac
add MaximumSubBountyDepth
xlc Jul 9, 2020
aacd9af
put BountyValueMinimum into storage
xlc Jul 10, 2020
c1f9900
rework bount depth
xlc Jul 11, 2020
705775d
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 14, 2020
2b8ecaa
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 21, 2020
bfae64b
split on_initialize benchmarks
shawntabrizi Jul 21, 2020
d605cb0
remove components from constant functions
shawntabrizi Jul 21, 2020
e175bc0
Update weight integration into treasury
shawntabrizi Jul 21, 2020
2ff28ee
Update reject proposal read/writes
shawntabrizi Jul 21, 2020
6a0cc13
fix weight calculation
xlc Jul 21, 2020
c222128
Ignore weights with 0 factor
shawntabrizi Jul 21, 2020
a5c897e
Remove 0 multipliers
shawntabrizi Jul 21, 2020
be92d1f
Merge remote-tracking branch 'origin/master' into bounty
xlc Jul 27, 2020
4441cdc
add some docs
xlc Jul 27, 2020
5927720
allow unused for generated code
xlc Jul 27, 2020
4b6f6e7
line width
xlc Jul 27, 2020
8a73200
allow RejectOrigin to cancel a pending payout bounty
xlc Jul 29, 2020
2ecd9f3
require BountyValueMinimum > ED
xlc Jul 29, 2020
06a305e
make BountyValueMinimum configurable by chain spec
xlc Jul 29, 2020
e86b63d
Merge remote-tracking branch 'origin/master' into bounty
xlc Aug 3, 2020
72a7a4c
remove sub-bounty features
xlc Aug 4, 2020
3c1a020
update curator
xlc Aug 4, 2020
dc8b295
accept curator
xlc Aug 7, 2020
e9b34be
unassign and cancel
xlc Aug 9, 2020
14b758a
fix tests
xlc Aug 9, 2020
ef05e56
new tests
xlc Aug 10, 2020
c32c3ca
Merge remote-tracking branch 'origin/master' into bounty
xlc Aug 10, 2020
9f87045
Update lib.rs
Aug 10, 2020
901b915
fix test
xlc Aug 10, 2020
af3931a
Merge remote-tracking branch 'origin/master' into bounty
xlc Aug 10, 2020
e3798eb
Merge pull request #4 from rrtti/patch-2
xlc Aug 10, 2020
6f40541
update extend_bounty_expiry
xlc Aug 11, 2020
6a14ec0
fix benchmarking
xlc Aug 11, 2020
3012300
add new benchmarking code
xlc Aug 11, 2020
343eb1c
add docs
xlc Aug 11, 2020
f724898
fix tests
xlc Aug 11, 2020
1f0c809
Update benchmarking.rs
shawntabrizi Aug 11, 2020
664f6e8
Make BountyValueMinimum a trait config instead of stroage value
xlc Aug 11, 2020
571059a
fix runtime build
xlc Aug 11, 2020
858a838
Update weights
shawntabrizi Aug 11, 2020
be0967e
Merge branch 'bounty' of https://github.com/laminar-protocol/substrat…
shawntabrizi Aug 12, 2020
e4dd5ff
Update default_weights.rs
shawntabrizi Aug 12, 2020
c2ee191
update weights
shawntabrizi Aug 12, 2020
a2e6306
update
xlc Aug 12, 2020
c49981c
update comments
xlc Aug 16, 2020
465aaf3
unreserve curator fee
xlc Aug 16, 2020
2d2b1a4
update tests
xlc Aug 16, 2020
363bdec
update benchmarks
xlc Aug 18, 2020
7482e59
fix curator deposit handling
xlc Aug 18, 2020
ca54bfc
Merge remote-tracking branch 'origin/master' into bounty
xlc Aug 18, 2020
58ce671
trigger CI
xlc Aug 18, 2020
31491d3
fix benchmarking
xlc Aug 19, 2020
a81e017
use append instead of mutate push
xlc Aug 19, 2020
e0d0daf
additional noop tests
shawntabrizi Aug 19, 2020
1dec474
improve fee hanlding. update event docs
xlc Aug 19, 2020
27cd80c
RejectOrigin to unassign
xlc Aug 19, 2020
85baba7
update bounty cancel logic
xlc Aug 19, 2020
05478ba
use Zero::zero() over 0.into()
xlc Aug 23, 2020
8033517
Merge remote-tracking branch 'origin/master' into bounty
xlc Aug 23, 2020
c70d864
fix tests
xlc Aug 23, 2020
76657c7
Merge remote-tracking branch 'origin/master' into bounty
xlc Sep 9, 2020
0db424e
Merge branch 'master' into bounty
xlc Sep 16, 2020
54d0bbc
fix benchmarks
xlc Sep 16, 2020
3795109
proposed fixes to bounties
shawntabrizi Sep 17, 2020
e833a53
fix tests
shawntabrizi Sep 17, 2020
e6f95c6
fix benchmarks
shawntabrizi Sep 17, 2020
0982f46
update weightinfo
shawntabrizi Sep 17, 2020
784a69a
use closure
shawntabrizi Sep 17, 2020
f645aca
Merge pull request #5 from paritytech/shawntabrizi-bounty-fixes
xlc Sep 17, 2020
7372444
fix compile
shawntabrizi Sep 18, 2020
8015965
update weights
shawntabrizi Sep 18, 2020
bb2a86f
Merge branch 'master' into pr/5715
shawntabrizi Sep 18, 2020
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
Prev Previous commit
Next Next commit
proposed fixes to bounties
  • Loading branch information
shawntabrizi committed Sep 17, 2020
commit 37951096e6d0d267e5b60e467b8f65bc8ed5d0af
202 changes: 115 additions & 87 deletions frame/treasury/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,17 +332,17 @@ pub enum BountyStatus<AccountId, BlockNumber> {
Approved,
/// The bounty is funded and waiting for curator assignment.
Funded,
/// A curator is assigned. Waiting for acceptance from curator.
CuratorAssigned {
/// A curator has been proposed by the `ApproveOrigin`. Waiting for acceptance from the curator.
CuratorProposed {
/// The assigned curator of this bounty.
curator: AccountId,
},
/// The bounty is active and waiting to be awarded.
Active {
/// The curator of this bounty.
curator: AccountId,
/// Expiry block
expires: BlockNumber,
/// An update from the curator is due by this block, else they are considered inactive.
update_due: BlockNumber,
},
/// The bounty is awarded and waiting to released after a delay.
PendingPayout {
Expand Down Expand Up @@ -478,6 +478,9 @@ decl_error! {
InvalidValue,
/// Invalid bounty fee.
InvalidFee,
/// A bounty payout is pending.
/// To cancel the bounty, you must unassign and slash the curator.
PendingPayout,
}
}

Expand Down Expand Up @@ -793,7 +796,7 @@ decl_module! {
Self::payout_tip(hash, tip);
}

/// Propose for a new bounty.
/// Propose a new bounty.
///
/// The dispatch origin for this call must be _Signed_.
///
Expand All @@ -812,39 +815,9 @@ decl_module! {
description: Vec<u8>,
) {
let proposer = ensure_signed(origin)?;

Self::create_bounty(proposer, description, value)?;
}

/// Reject a bounty proposal. The original deposit will be slashed.
///
/// # <weight>
/// - O(1).
/// - Limited storage reads.
/// - Two DB clear.
/// # </weight>
#[weight = T::WeightInfo::reject_bounty()]
fn reject_bounty(origin, #[compact] bounty_id: BountyIndex) {
T::RejectOrigin::ensure_origin(origin)?;

Bounties::<T, I>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let bounty = maybe_bounty.as_ref().ok_or(Error::<T, I>::InvalidIndex)?;
ensure!(bounty.status == BountyStatus::Proposed, Error::<T, I>::UnexpectedStatus);

BountyDescriptions::<I>::remove(bounty_id);

let value = bounty.bond;
let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0;
T::OnSlash::on_unbalanced(imbalance);

Self::deposit_event(Event::<T, I>::BountyRejected(bounty_id, value));

*maybe_bounty = None;

Ok(())
})?;
}

/// Approve a bounty proposal. At a later time, the bounty will be funded and become active
/// and the original deposit will be returned.
///
Expand Down Expand Up @@ -893,23 +866,33 @@ decl_module! {
Bounties::<T, I>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T, I>::InvalidIndex)?;
match bounty.status {
BountyStatus::Funded | BountyStatus::CuratorAssigned { .. } => {},
BountyStatus::Funded | BountyStatus::CuratorProposed { .. } => {},
_ => return Err(Error::<T, I>::UnexpectedStatus.into()),
};

ensure!(fee < bounty.value, Error::<T, I>::InvalidFee);

bounty.status = BountyStatus::CuratorAssigned { curator };
bounty.status = BountyStatus::CuratorProposed { curator };
bounty.fee = fee;

Ok(())
})?;
}

/// Unassign curator from a bounty.
/// If the bounty is on active or pending payout status, the curator deposit will be slashed from curator.
///
/// May only be called from `T::ApproveOrigin` or the curator.
/// This function can only be called by the `RejectOrigin` a signed origin.
///
/// If this function is called by the `RejectOrigin`, we assume that the curator is malicious
/// or inactive. As a result, we will slash the curator when possible.
///
/// If the origin is the curator, we take this as a sign they are unable to do their job and
/// they willingly give up. We could slash them, but for now we allow them to recover their
/// deposit and exit without issue. (We may want to change this if it is abused.)
///
/// Finally, the origin can be anyone if and only if the curator is "inactive". This allows
/// anyone in the community to call out that a curator is not doing their due diligence, and
/// we should pick a new curator. In this case the curator should also be slashed.
///
/// # <weight>
/// - O(1).
Expand All @@ -921,23 +904,68 @@ decl_module! {
origin,
#[compact] bounty_id: ProposalIndex,
) {
let maybe_curator = ensure_signed(origin.clone())
let maybe_sender = ensure_signed(origin.clone())
.map(Some)
.or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;

Bounties::<T, I>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T, I>::InvalidIndex)?;
match bounty.status {
BountyStatus::CuratorAssigned { ref curator } => {
ensure!(maybe_curator.map_or(true, |c| c == *curator), BadOrigin);
},
BountyStatus::Active { ref curator, .. } | BountyStatus::PendingPayout { ref curator, .. } => {
ensure!(maybe_curator.map_or(true, |c| c == *curator), BadOrigin);
let imbalance = T::Currency::slash_reserved(curator, bounty.curator_deposit).0;

macro_rules! slash_curator {
($curator:ident) => {
let imbalance = T::Currency::slash_reserved($curator, bounty.curator_deposit).0;
T::OnSlash::on_unbalanced(imbalance);
bounty.curator_deposit = Zero::zero();
}
};

match bounty.status {
BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {
// No curator to unassign at this point.
return Err(Error::<T, I>::UnexpectedStatus.into())
}
BountyStatus::CuratorProposed { ref curator } => {
// A curator has been proposed, but not accepted yet.
// Either `RejectOrigin` or the proposed curator can unassign the curator.
ensure!(maybe_sender.map_or(true, |sender| sender == *curator), BadOrigin);
},
_ => return Err(Error::<T, I>::UnexpectedStatus.into()),
BountyStatus::Active { ref curator, ref update_due } => {
// The bounty is active.
match maybe_sender {
// If the `RejectOrigin` is calling this function, slash the curator.
None => {
slash_curator!(curator);
// Continue to change bounty status below...
},
Some(sender) => {
// If the sender is not the curator, and the curator is inactive,
// slash the curator.
if sender != *curator {
let block_number = system::Module::<T>::block_number();
if *update_due < block_number {
slash_curator!(curator);
// Continue to change bounty status below...
} else {
// Curator has more time to give an update.
return Err(Error::<T, I>::Premature.into())
}
} else {
// Else this is the curator, willingly giving up their role.
// Give back their deposit.
let _ = T::Currency::unreserve(&curator, bounty.curator_deposit);
// Continue to change bounty status below...
}
},
}
},
BountyStatus::PendingPayout { ref curator, .. } => {
// The bounty is pending payout, so only council can unassign a curator.
// By doing so, they are claiming the curator is acting maliciously, so
// we slash the curator.
ensure!(maybe_sender.is_none(), BadOrigin);
slash_curator!(curator);
// Continue to change bounty status below...
}
};

bounty.status = BountyStatus::Funded;
Expand All @@ -963,15 +991,15 @@ decl_module! {
let mut bounty = maybe_bounty.as_mut().ok_or(Error::<T, I>::InvalidIndex)?;

match bounty.status {
BountyStatus::CuratorAssigned { ref curator } => {
BountyStatus::CuratorProposed { ref curator } => {
ensure!(signer == *curator, Error::<T, I>::RequireCurator);

let deposit = T::BountyCuratorDeposit::get() * bounty.fee;
T::Currency::reserve(curator, deposit)?;
bounty.curator_deposit = deposit;

let expires = system::Module::<T>::block_number() + T::BountyDuration::get();
bounty.status = BountyStatus::Active { curator: curator.clone(), expires };
let update_due = system::Module::<T>::block_number() + T::BountyDuration::get();
bounty.status = BountyStatus::Active { curator: curator.clone(), update_due };

Ok(())
},
Expand Down Expand Up @@ -1046,53 +1074,53 @@ decl_module! {
})?;
}

/// Cancel an active bounty. All the funds will be send to treasury.
/// If cancel is not initiated by `T::RejectOrigin`, curator deposit will be slashed.
/// Cancel a proposed or active bounty. All the funds will be sent to treasury and
/// the curator deposit will be unreserved if possible.
///
/// Only curator or `T::RejectOrigin` is able to cancel bounty.
/// Only `T::RejectOrigin` is able to cancel a bounty.
///
/// - `bounty_id`: Bounty ID to cancel.
#[weight = T::WeightInfo::cancel_bounty()]
fn cancel_bounty(origin, #[compact] bounty_id: BountyIndex) {
let maybe_curator = ensure_signed(origin.clone())
.map(Some)
.or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?;
T::RejectOrigin::ensure_origin(origin)?;

Bounties::<T, I>::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult {
let bounty = maybe_bounty.as_ref().ok_or(Error::<T, I>::InvalidIndex)?;

match &bounty.status {
BountyStatus::Funded |
BountyStatus::CuratorAssigned { .. } => {
ensure!(maybe_curator.is_none(), BadOrigin);
},
BountyStatus::PendingPayout { curator, .. } => {
if let Some(signer) = maybe_curator {
ensure!(signer == *curator, Error::<T, I>::RequireCurator);
BountyStatus::Proposed => {
// The reject origin would like to cancel a proposed bounty.
BountyDescriptions::<I>::remove(bounty_id);
let value = bounty.bond;
let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0;
T::OnSlash::on_unbalanced(imbalance);
*maybe_bounty = None;

let imbalance = T::Currency::slash_reserved(curator, bounty.curator_deposit).0;
T::OnSlash::on_unbalanced(imbalance);
} else {
// Cancelled by council, refund deposit
let _ = T::Currency::unreserve(&curator, bounty.curator_deposit);
}
Self::deposit_event(Event::<T, I>::BountyRejected(bounty_id, value));
// Return early, nothing else to do.
return Ok(())
},
BountyStatus::Active { curator, expires } => {
if let Some(signer) = maybe_curator {
let now = system::Module::<T>::block_number();
if *expires > now {
// only curator can cancel unexpired bounty
ensure!(signer == *curator, Error::<T, I>::RequireCurator);
}

let imbalance = T::Currency::slash_reserved(curator, bounty.curator_deposit).0;
T::OnSlash::on_unbalanced(imbalance);
} else {
// Cancelled by council, refund deposit
let _ = T::Currency::unreserve(&curator, bounty.curator_deposit);
}
BountyStatus::Approved => {
// For weight reasons, we don't allow a council to cancel in this phase.
// We ask for them to wait until it is funded before they can cancel.
return Err(Error::<T, I>::UnexpectedStatus.into())
},
_ => return Err(Error::<T, I>::UnexpectedStatus.into()),
BountyStatus::Funded |
BountyStatus::CuratorProposed { .. } => {
// Nothing extra to do besides the removal of the bounty below.
},
BountyStatus::Active { curator, .. } => {
// Cancelled by council, refund deposit of the working curator.
let _ = T::Currency::unreserve(&curator, bounty.curator_deposit);
// Then execute removal of the bounty below.
},
BountyStatus::PendingPayout { .. } => {
// Bounty is already pending payout. If council wants to cancel
// this bounty, it should mean the curator was acting maliciously.
// So the council should first unassign the curator, slashing their
// deposit.
return Err(Error::<T, I>::PendingPayout.into())
}
}

let bounty_account = Self::bounty_account_id(bounty_id);
Expand Down Expand Up @@ -1123,9 +1151,9 @@ decl_module! {
let bounty = maybe_bounty.as_mut().ok_or(Error::<T, I>::InvalidIndex)?;

match bounty.status {
BountyStatus::Active { ref curator, ref mut expires } => {
BountyStatus::Active { ref curator, ref mut update_due } => {
ensure!(*curator == signer, Error::<T, I>::RequireCurator);
*expires = (system::Module::<T>::block_number() + T::BountyDuration::get()).max(*expires);
*update_due = (system::Module::<T>::block_number() + T::BountyDuration::get()).max(*update_due);
},
_ => return Err(Error::<T, I>::UnexpectedStatus.into()),
}
Expand Down