Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

27 changes: 13 additions & 14 deletions contracts/tholos-v2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,8 @@ pub enum Error {
/// separately from this issue's third-party registration path).
CannotRegisterAsFixedParty = 16,
InvalidPositionAmount = 17,
/// A new position's amount was below `policy.min_resolution_bond`.
/// A deposit amount (initial or top-up) was below
/// `policy.min_resolution_bond`.
BelowMinimumResolutionBond = 18,
/// A position's total (after aggregating this deposit) exceeded
/// `policy.max_position`.
Expand Down Expand Up @@ -1162,13 +1163,13 @@ impl TholosV2 {
/// already have fixed positions from `dispute`; a way for them to top up
/// those positions is tracked separately from this issue.
///
/// A first-time deposit must be at least `policy.min_resolution_bond`.
/// A top-up (same voter, same assertion) aggregates into the existing
/// position and must reuse its original `commitment`, a position's
/// committed side can never change after funding. Rejects atomically,
/// with no position or weight created, if the resulting position size or
/// eligible total would exceed `policy.max_position` /
/// `policy.max_total_weight`.
/// A deposit (both first-time and top-up) must be at least
/// `policy.min_resolution_bond`. A top-up (same voter, same assertion)
/// aggregates into the existing position and must reuse its original
/// `commitment`, a position's committed side can never change after
/// funding. Rejects atomically, with no position or weight created, if the
/// resulting position size or eligible total would exceed
/// `policy.max_position` / `policy.max_total_weight`.
///
/// A qualifying deposit (one landing within `anti_snipe_extension_secs`
/// of the current deadline) pushes the registration deadline out by
Expand Down Expand Up @@ -1208,6 +1209,9 @@ impl TholosV2 {
if amount <= 0 {
return Err(Error::InvalidPositionAmount);
}
if amount < assertion.policy.min_resolution_bond {
return Err(Error::BelowMinimumResolutionBond);
}

let mut resolution: Resolution = env
.storage()
Expand Down Expand Up @@ -1243,12 +1247,7 @@ impl TholosV2 {
}
position.amount
}
None => {
if amount < assertion.policy.min_resolution_bond {
return Err(Error::BelowMinimumResolutionBond);
}
0
}
None => 0,
};

let new_amount = previous_amount + amount;
Expand Down
77 changes: 68 additions & 9 deletions contracts/tholos-v2/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,18 +1223,42 @@ fn test_register_top_up_aggregates() {

let c = commitment(&f.env, 1);
f.client.register(&voter, &id, &DEFAULT_BOND, &c);
f.client.register(&voter, &id, &50, &c);
f.client.register(&voter, &id, &DEFAULT_BOND, &c);

let position = f.client.get_position(&id, &voter);
assert_eq!(position.amount, DEFAULT_BOND + 50);
assert_eq!(position.amount, DEFAULT_BOND * 2);

let resolution = f.client.get_resolution(&id);
assert_eq!(
resolution.eligible_total,
DEFAULT_BOND * 2 + DEFAULT_BOND + 50
DEFAULT_BOND * 2 + DEFAULT_BOND * 2
);
}

#[test]
fn test_register_top_up_below_minimum_fails() {
let f = Fixture::new();
let asserter = f.funded_address();
let disputer = f.funded_address();
let voter = f.funded_address();
f.mint(&voter, DEFAULT_MINT);

let id = f.asserted(&asserter);
f.client.dispute(&disputer, &id);

let c = commitment(&f.env, 1);
f.client.register(&voter, &id, &DEFAULT_BOND, &c);

let result = f.client.try_register(&voter, &id, &1, &c);
assert_eq!(result, Err(Ok(Error::BelowMinimumResolutionBond)));

let result = f.client.try_register(&voter, &id, &(DEFAULT_BOND - 1), &c);
assert_eq!(result, Err(Ok(Error::BelowMinimumResolutionBond)));

let position = f.client.get_position(&id, &voter);
assert_eq!(position.amount, DEFAULT_BOND);
}

#[test]
fn test_register_top_up_with_different_commitment_fails() {
let f = Fixture::new();
Expand All @@ -1251,7 +1275,7 @@ fn test_register_top_up_with_different_commitment_fails() {

let result = f
.client
.try_register(&voter, &id, &50, &commitment(&f.env, 2));
.try_register(&voter, &id, &DEFAULT_BOND, &commitment(&f.env, 2));
assert_eq!(result, Err(Ok(Error::CommitmentMismatch)));
}

Expand Down Expand Up @@ -1377,6 +1401,43 @@ fn test_register_extends_deadline_on_late_qualifying_deposit() {
assert!(after.registration_deadline > before.registration_deadline);
}

#[test]
fn test_register_dust_top_up_cannot_extend_deadline() {
let f = Fixture::new();
let asserter = f.funded_address();
let disputer = f.funded_address();
let voter = f.funded_address();
f.mint(&voter, DEFAULT_MINT);

let id = f.asserted(&asserter);
f.client.dispute(&disputer, &id);

let c = commitment(&f.env, 1);
f.client.register(&voter, &id, &DEFAULT_BOND, &c);

let before = f.client.get_resolution(&id);

// Land within the last anti_snipe_extension_secs of the deadline.
f.env
.ledger()
.with_mut(|l| l.timestamp = before.registration_deadline - DEFAULT_ANTI_SNIPE_EXT_SECS + 1);

// 1-unit dust top-up fails with BelowMinimumResolutionBond and does not extend deadline.
let result = f.client.try_register(&voter, &id, &1, &c);
assert_eq!(result, Err(Ok(Error::BelowMinimumResolutionBond)));

let unchanged = f.client.get_resolution(&id);
assert_eq!(
unchanged.registration_deadline,
before.registration_deadline
);

// A top-up meeting min_resolution_bond succeeds and extends the deadline.
f.client.register(&voter, &id, &DEFAULT_BOND, &c);
let after = f.client.get_resolution(&id);
assert!(after.registration_deadline > before.registration_deadline);
}

#[test]
fn test_register_extension_capped_at_hard_deadline() {
let f = Fixture::new();
Expand Down Expand Up @@ -1845,9 +1906,8 @@ fn test_strict_majority_boundary_requires_more_than_half() {
f.client.dispute(&disputer, &id);
let policy_hash = f.client.get_assertion(&id).policy_hash;

// A top-up (not a first-time deposit) isn't held to min_resolution_bond,
// so this voter can land on an odd eligible_total: 100 (first deposit)
// + 1 (top-up) = 101, for eligible_total = 100 + 100 + 101 = 301.
// A deposit of 101 (>= min_resolution_bond 100) lands on an odd
// eligible_total: 100 (asserter) + 100 (disputer) + 101 (voter) = 301.
// agree_weight ends up 100 (asserter) + 101 (voter) = 201, which is
// checked against `301 - 201 = 100`, exercising the subtraction form
// against an odd total rather than an even one like every other test
Expand All @@ -1862,8 +1922,7 @@ fn test_strict_majority_boundary_requires_more_than_half() {
true,
&s,
);
f.client.register(&voter, &id, &DEFAULT_BOND, &c);
f.client.register(&voter, &id, &1, &c);
f.client.register(&voter, &id, &101, &c);

f.advance_past_registration_deadline(id);
f.client.reveal(&voter, &id, &true, &s);
Expand Down
Loading