Skip to content

feat: two-safe rule - #12

Merged
wjmelements merged 19 commits into
mainfrom
two-safe-rule
Jul 30, 2026
Merged

feat: two-safe rule#12
wjmelements merged 19 commits into
mainfrom
two-safe-rule

Conversation

@wjmelements

@wjmelements wjmelements commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #2

UnanimousGovernance

This implements the two-safe rule as it will be used for discretionary actions in the smart contracts:

Some actions require consensus of the owners, and are annotated with the unanimous modifier. They may also have a hold period, measured in epochs. If an action has unanimous consent it will either take immediate effect or, after the mandatory holding period, anyone can trigger its completion. During a holding period, any owner can cancel the operation.

Events:

  • Submit
  • Approve
  • Reject

Owners

The multiple owners are tracked in Owners. Their approvals are tracked with a bitset in order to minimize the PendingTask storage footprint (1 slot per pending action). There is also a mapping to allow O(1) isOwner checking, and to track representative bits for the bitset.

Events:

  • OwnerAdded
  • OwnerRemoved

@wjmelements
wjmelements requested a review from rvagg July 25, 2026 04:31
@wjmelements wjmelements added the enhancement New feature or request label Jul 25, 2026
@wjmelements wjmelements linked an issue Jul 25, 2026 that may be closed by this pull request
@FilOzzy FilOzzy added this to FOC Jul 25, 2026
@github-project-automation github-project-automation Bot moved this to 📌 Triage in FOC Jul 25, 2026
@wjmelements
wjmelements marked this pull request as draft July 25, 2026 23:46
@wjmelements

wjmelements commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator Author

wjmelements marked this pull request as draft

I've thought of a better way to do this.

@wjmelements
wjmelements marked this pull request as ready for review July 26, 2026 04:23
@github-project-automation github-project-automation Bot moved this to Backlog in Solstice Jul 26, 2026
@wjmelements wjmelements moved this from Backlog to In review in Solstice Jul 26, 2026
Comment thread .github/workflows/test.yml Outdated
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
Assisted-by: Claude:claude-sonnet-4-6
@wjmelements
wjmelements requested a review from rvagg July 27, 2026 18:05
OwnerSet allOwners = OwnersLibrary.getAllOwners();

// modify
if (loaded.approvals & allOwners == allOwners) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
if (loaded.approvals & allOwners == allOwners) {
if ((loaded.approvals & allOwners) == allOwners) {

does the linter have a problem with this? easy to misread this (and the one on line 56, and I guess 49 has the same shape); it's one char away from && so in an if it just looks wrong.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

does the linter have a problem with this?

No.

it's one char away from && so in an if it just looks wrong

It's the same bitwise arithmetic syntax as other languages. I would normally only add extra parentheses to avoid readers having to lookup the operator precedence rules, which can differ language-to-language. == is not such a situation.

Another reason this & won't be confused with && is that then the first evaluation would be allOwners == allOwners, always true.

Besides bitwise operations, languages Python and C++ commonly use the bitwise operators & and | for bitset operations like this one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah, I get all of this, all very true, but I think in cases like this where there's a possibility of misreading or someone not having robotic recall of precedence rules in their brains its always good to be as clear as possible, parentheses don't hurt, they don't change the code, but they do make code incredibly clear where precedence rules or syntax isn't doing the work of adding clarity; an extremely cheap addition to reduce cognitive overhead

Comment thread src/lib/UnanimousGovernance.sol
Comment thread src/lib/Owners.sol
Comment thread src/lib/Owners.sol
Comment thread src/lib/PendingTask.sol
@rvagg

rvagg commented Jul 29, 2026

Copy link
Copy Markdown
Member

/// @dev Veto stale PendingTasks when removing an owner to avoid approvals carrying over.

From the Owners library raises an important point, but also I don't believe this is either actioned in the code or actionable in the current form. Mutating the OwnerSet while you have PendingTasks in play is going to get you into real trouble; you could leave tasks unable to complete, or tasks that are already signed off by owners that were removed and probably shouldn't be. Can we just put a tag into PendingTask that versions it against a set of owners (the actual list of owners perhaps, or a version that we increment in the owner set) and then we can invalidate it whenever there's any kind of mutation in owners.

Comment thread src/lib/UnanimousGovernance.sol Outdated
Comment thread src/lib/UnanimousGovernance.sol
@wjmelements

Copy link
Copy Markdown
Collaborator Author

/// @dev Veto stale PendingTasks when removing an owner to avoid approvals carrying over.

From the Owners library raises an important point, but also I don't believe this is either actioned in the code or actionable in the current form. Mutating the OwnerSet while you have PendingTasks in play is going to get you into real trouble; you could leave tasks unable to complete, or tasks that are already signed off by owners that were removed and probably shouldn't be. Can we just put a tag into PendingTask that versions it against a set of owners (the actual list of owners perhaps, or a version that we increment in the owner set) and then we can invalidate it whenever there's any kind of mutation in owners.

No. The bitmasking handles this. I think if you spell out the exact way that it could get us into trouble, you would realize how ridiculous it is. First, it's not true that the tasks would be unable to complete. Second, the real danger is that a stale task from 159 owners ago might be passively approved by the next owner, and this isn't prevented at all by your proposed mitigation. However it is resolved by my mitigation. If a task is really sitting stale through 159 owners, it should have been vetoed.

@rvagg

rvagg commented Jul 29, 2026

Copy link
Copy Markdown
Member

Follow-up on visibility: #14

re removals:

The bitmasking handles this
yeah, fair, withdrawn.

@wjmelements
wjmelements requested a review from rvagg July 29, 2026 23:47
@github-project-automation github-project-automation Bot moved this from 📌 Triage to ✔️ Approved by reviewer in FOC Jul 30, 2026
@wjmelements
wjmelements merged commit 2e4636a into main Jul 30, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from ✔️ Approved by reviewer to 🎉 Done in FOC Jul 30, 2026
@github-project-automation github-project-automation Bot moved this from In review to Done in Solstice Jul 30, 2026
@rvagg
rvagg deleted the two-safe-rule branch August 3, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

The Two-Safe Rule

3 participants