Skip to content

fix(tholos-v2): bound max_total_weight/max_position ratio to raise Sybil cost (closes #168) - #215

Merged
collinsezedike merged 3 commits into
drydocs:mainfrom
Ranjeet2063:feat/issue-168-bound-max-total-weight-to-position-ratio
Sep 12, 2026
Merged

collinsezedike merged 3 commits into
drydocs:mainfrom
Ranjeet2063:feat/issue-168-bound-max-total-weight-to-position-ratio

Conversation

@Ranjeet2063

Copy link
Copy Markdown
Contributor

Closes #168

Summary

Enforces max_total_weight <= max_position * MAX_TOTAL_WEIGHT_TO_POSITION_RATIO (ratio = 10) at initialize time. A single actor must now control at least 10 distinct positions to approach the eligible-total threshold, raising the capital cost of Sybil-style address splitting without requiring an external identity system.

Findings Addressed from Maintainer Review

  1. docs/src/V2_BOND_SIZING.md Profile Rows:
    • Updated Public testnet / low value profile row (max_position = 5x base_bond, max_total_weight = 50x base_bond, exactly 10x ratio).
    • Updated Higher-value mainnet candidate profile row (max_position = 3x base_bond, max_total_weight = 30x base_bond, exactly 10x ratio).
    • Documented the contract-enforced 10x ratio bound in the max_total_weight sizing guidance.
  2. scripts/testnet-load-v2.sh:
    • Sized MAX_TOTAL_WEIGHT=100000000 (10x MAX_POSITION=10000000), ensuring the testnet load script passes contract initialization.
  3. No Dead Overflow Guards:
    • Verified that since max_position <= max_total_weight <= MAX_SETTLEMENT_TOTAL_WEIGHT (10^19), the product max_position * MAX_TOTAL_WEIGHT_TO_POSITION_RATIO cannot overflow i128. Clean check without unreachable branch logic.

Changes

  • contracts/tholos-v2/src/lib.rs:
    • Adds Error::InvalidWeightRatio = 37 with doc comments.
    • Adds MAX_TOTAL_WEIGHT_TO_POSITION_RATIO: i128 = 10 constant.
    • Validates ratio bound in initialize().
  • contracts/tholos-v2/src/test.rs:
    • Adds test_initialize_accepts_max_total_weight_at_ratio_bound (at exact 10x bound, passes).
    • Adds test_initialize_rejects_max_total_weight_above_ratio_bound (one over bound, rejects with InvalidWeightRatio).
    • Updates test_register_exceeds_max_position_fails to satisfy the 10x ratio invariant.
  • docs/src/CONTRACT_V2.md:
    • Documents max_total_weight ratio bound in parameter table.
    • Adds InvalidWeightRatio to error table.
    • Documents residual plutocratic risk note before get_policy.

Verification

  • cargo test -p tholos-v2: 124 passed, 0 failed.
  • cargo clippy -p tholos-v2 --all-targets: Clean, 0 warnings.
  • cargo fmt --check: Clean formatting verified.

…bil cost

- Enforces max_total_weight <= max_position * MAX_TOTAL_WEIGHT_TO_POSITION_RATIO (10) in initialize()
- Adds Error::InvalidWeightRatio = 37
- Updates test_register_exceeds_max_position_fails and adds ratio bound unit tests
- Updates docs/src/V2_BOND_SIZING.md profile rows and max_total_weight guidance
- Updates scripts/testnet-load-v2.sh to use 10x ratio bound (100,000,000)
- Documents InvalidWeightRatio and residual plutocratic risk in docs/src/CONTRACT_V2.md

Closes #168
Copilot AI lite review requested due to automatic review settings September 11, 2026 12:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ranjeet2063 A few things need attention before this can merge.

docs/src/DEPLOYMENT_V2.md's max_position/max_total_weight guidance was not updated for the new ratio bound. It still says max_position must be "at least 1 and at most max_total_weight" with no mention of the new 10x floor, and it suggests setting max_position to "perhaps 10-20% of max_total_weight", but 10% is exactly the new legal boundary and anything below it (which the phrasing invites) now reverts with InvalidWeightRatio. Update that table to state the new constraint.

docs/src/V2_BOND_SIZING.md's max_total_weight formula, m >= max(10, N_voters x k + 2), is now mathematically incompatible with the new m <= 10k ceiling for any deployment with more than about 9 full-max_position voters (N_voters x k + 2 <= 10k forces N_voters <= 10 - 2/k). The doc text mentions the new ratio bound in passing but doesn't reconcile it with the formula it sits next to, so a deployer following the documented formula for a larger committee will produce parameters this contract now rejects.

No docs/src/CHANGELOG.md entry for a new public error variant and a new initialize validation rule. The repo's convention for a v2 behavior change is a Fixed entry ending in Closes #168 (see the maintainer's own dd8c043 for the pattern).

Issue #168 explicitly asked for a short written ratio analysis to be confirmed with the maintainer before implementation, since this is a design trade-off rather than a pure bug fix. That didn't happen here, and it matters concretely: the two profile rows in V2_BOND_SIZING.md were brought into compliance by shrinking max_total_weight (100x to 50x base_bond, 200x to 30x base_bond) rather than raising max_position, which lowers the mainnet profile's ceiling on legitimate honest counter-stake from roughly 198x to roughly 28x base_bond. That may well be the right call, but it's a real trade-off this issue asked to be surfaced and agreed on explicitly, not decided implicitly by which side of the inequality got adjusted to pass the new check.

…elog for 10x ratio bound

- Updates docs/src/DEPLOYMENT_V2.md max_position/max_total_weight guidance documenting 10x floor and 10% legal boundary
- Reconciles docs/src/V2_BOND_SIZING.md capacity formula m >= max(10, N_voters * k_avg + 2) with m <= 10k ceiling
- Documents single-whale resistance vs counter-stake liquidity trade-offs in narrative guidance
- Adds docs/src/CHANGELOG.md Fixed entry for Error::InvalidWeightRatio ending in Closes #168
@Ranjeet2063

Copy link
Copy Markdown
Contributor Author

@collinsezedike Thank you for the thorough and constructive review! All points have been addressed in commit 07c158e:

1. docs/src/DEPLOYMENT_V2.md

Updated the parameter guidance table to explicitly state the 10x ratio bound constraint:

  • Documented that max_position must be at least ceil(max_total_weight / 10) (contract reverts with InvalidWeightRatio if max_position is below 10% of max_total_weight).
  • Updated the narrative so integrators are aware that the previous "10–20%" suggestion sits directly on the boundary, and anything below 10% is rejected at initialize.

2. Formula Reconciliation in docs/src/V2_BOND_SIZING.md

Reconciled max_total_weight = m * base_bond with the new ceiling m <= 10 * k:

  • Clarified the mathematical boundary: assuming full-cap voter deposits ( = \text{max_position} / \text{base_bond}$), {\text{voters}} \cdot k + 2 \le 10k$ forces {\text{voters}} \le 10 - 2/k$ (at most 9 full-cap voters).
  • Reconciled this for larger committees ({\text{voters}} > 10$) by sizing $ against expected average stake {\text{avg}}$ ({\text{voters}} \cdot k_{\text{avg}} + 2 \le m \le 10k$).

3. docs/src/CHANGELOG.md

Added a new Fixed entry under ## [Unreleased] matching the repo's pattern:

  • Documents contracts/tholos-v2 validation of max_total_weight <= max_position * MAX_TOTAL_WEIGHT_TO_POSITION_RATIO (10) and Error::InvalidWeightRatio = 37, ending in Closes #168.

4. Written Ratio Analysis & Trade-off Explanation

Regarding the trade-off between shrinking max_total_weight vs. raising max_position:

  • The Design Trade-Off:
    • Enforcing max_total_weight <= 10 * max_position creates a structural coupling between whale resistance per address and aggregate counter-stake capacity:
      • Low $ (e.g. = 3$): Maximizes per-address whale resistance (no single address can hold $&gt;10%$ of round capacity), but caps aggregate third-party counter-stake to $\approx 28 \times \text{base_bond}$.
      • High $ (e.g. = 10 \dots 20$): Expands counter-stake headroom up to 8 \times \dots 198 \times \text{base_bond}$, but permits a single well-capitalized address to hold a larger absolute stake (0 \dots 20 \times \text{base_bond}$).
  • Why the Conservative Profile was Chosen in the Table:
    • In V2_BOND_SIZING.md, the Higher-value mainnet profile emphasizes maximum single-address Sybil deterrence under the assumption that high-value disputes may draw coordinated manipulation attempts. However, we have now explicitly documented in both V2_BOND_SIZING.md and DEPLOYMENT_V2.md that deployments prioritizing deep liquidity can proportionally scale $ up to 0 \times \dots 20 \times \text{base_bond}$ to unlock 00 \times \dots 200 \times \text{base_bond}$ in total weight while strictly respecting the 10x ratio bound.

All 124 unit and proptest suites in tholos-v2 pass green, and clippy/fmt are clean. Looking forward to your final sign-off!

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ranjeet2063 All four points are resolved. DEPLOYMENT_V2.md now states the 10x floor and the 10% boundary correctly, V2_BOND_SIZING.md's formula reconciliation for larger committees (average-stake sizing) is mathematically sound, the CHANGELOG entry follows the repo's convention and closes #168, and the trade-off write-up on the ratio choice is clear and reasonable. The conservative profile choice for the mainnet row is a defensible default given that document's stated threat model, with the scale-up path for higher counter-stake ceilings now documented alongside it.

Thanks for the contribution. Feel free to pick up another open issue.

@Ranjeet2063

Copy link
Copy Markdown
Contributor Author

Hi @collinsezedike, thank you again for reviewing and approving this PR! All 124 tests are passing green (cargo test -p tholos-v2) and boundary checks are verified. Whenever you have a moment for the merge, please let me know if any further tweaks are needed on my end. Glad to contribute to Tholos! 🚀

@collinsezedike
collinsezedike merged commit 2414ac5 into drydocs:main Sep 12, 2026
4 checks passed
@collinsezedike

Copy link
Copy Markdown
Collaborator

@Ranjeet2063 If you found this useful, a star on the repo goes a long way.

@Ranjeet2063

Copy link
Copy Markdown
Contributor Author

Hi @collinsezedike, just starred the repository! ⭐ Really enjoyed working on the Tholos V2 architecture, mathematical models, and Sybil resistance ratio bounds.

If the Tholos / Drydocs team has upcoming roadmap milestones, Soroban smart contract security sprints, or is looking for a dedicated remote Rust/Soroban engineer for part-time or full-time contract roles, I would love to collaborate.

Feel free to connect directly anytime:

@collinsezedike

Copy link
Copy Markdown
Collaborator

Hey @Ranjeet2063, thank you for the contribution, and thank you for the star on the repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] register() has no Sybil resistance in tholos-v2

3 participants