Skip to content

Fix issue #14#67

Open
Fiuum1 wants to merge 3 commits intoBreadchainCoop:devfrom
Fiuum1:fix-issue-14
Open

Fix issue #14#67
Fiuum1 wants to merge 3 commits intoBreadchainCoop:devfrom
Fiuum1:fix-issue-14

Conversation

@Fiuum1
Copy link

@Fiuum1 Fiuum1 commented Mar 13, 2026

Hey @RonTuretzky and @exo404,

As requested, this PR implements the solution for Issue #14: the system now uses a 33% contest threshold to trigger a veto.

Here is a technical overview of the modifications I've made, although GitHub already gives us an overview of the modifications:

SafetyNet.sol:

  • 'isContested[]' replaced by 'isVetoed[]'
  • Removed "emit WithdrawalContested()" from 'contest()'
  • Added 'contestCount' initialization (=0) in new Request creation in '_withdraw()'
  • Added 'contestCount' increment in 'contest()'
  • Removed 'vote()' function
  • Removed 'requestVotes' parameter (mapping)
  • Removed "_isVotingOnGoing()' function
  • Modified 'createRequest()' function, replacing 'yesVotes' and 'noVotes' initialization (=0) with 'contestCount' initialization (=0)
  • Added in 'contest()' a mechanism to check if contestCount is enough (compared to threshold) to reject withdrawal
  • Replaced 'consensusTreshold' parameter in 'SafetyNetCreated' event in 'create()' function with 'creationstruct with 'contestTreshold' parameter
  • Added "emit WithdrawalVetoed()" in contest()
  • Added 'AlreadyContestedByMember()' error in contest()
  • Added 'hasContested' parameter (mapping)
  • Added "if (hasContested[_requestId][msg.sender]) revert AlreadyContestedByMember()" in contest()

ISafetyNet.sol:

  • 'AlreadyContested()' replaced by 'AlreadyVetoed()'
  • Removed 'WithdrawalContested()' event
  • Removed 'yesVotes' e 'noVotes', replaced by 'contestCount'
  • Removed 'vote()' interface
  • Removed 'Voted()' event
  • Removed 'AlreadyVoted()' error
  • Removed 'NotAllVoted()' error
  • Removed 'votingWindow' parameter from 'SafetyNet' struct
  • Modified 'RequestEnded()' event
  • Removed 'VotingWindowsClosed()' error
  • Replaced 'consensusTreshold' parameter in 'SafetyNet' struct with 'contestTreshold' parameter, representing the percentage of members who contest required to reject a request
  • Added 'WithdrawalVetoed()' event
  • Added 'AlreadyContestedByMember()' error

All tests (Unit and Fuzz) have been updated and pass successfully.

Looking forward to your feedback!
Fabio

@Fiuum1
Copy link
Author

Fiuum1 commented Mar 13, 2026

P.S: Sorry but I forgot to remove modifications.txt from src/contracts/

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the SafetyNet withdrawal-request governance flow from a vote-based mechanism to a contest/veto-based mechanism, updating the core contract interface and aligning unit/fuzz tests with the new request model.

Changes:

  • Replace request voting (yes/no votes + voting window) with contest counting and a veto threshold.
  • Update ISafetyNet structs/events/errors and the SafetyNet contract storage/execution logic to match the contest/veto flow.
  • Remove voting-focused fuzz tests and add new fuzz coverage for contesting/veto boundaries and timeout behavior.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/unit/SafetyNetUnit.sol Updates unit tests to use contestThreshold, contestCount, and veto semantics.
test/invariants/fuzz/SafetyNetFuzzBase.t.sol Updates fuzz default config to use contest-based parameters (removes voting window defaults).
test/invariants/fuzz/SafetyNetFuzz_RequestsVoting.t.sol Removes the voting-oriented fuzz suite.
test/invariants/fuzz/SafetyNetFuzz_RequestsContesting.t.sol Adds contest/veto-focused fuzz scenarios and threshold-boundary properties.
test/invariants/fuzz/SafetyNetFuzz_DepositWithdraw.t.sol Updates request struct destructuring for the new Request layout.
test/invariants/fuzz/SafetyNetFuzz_Create.t.sol Updates creation fuzzing to vary contestThreshold instead of consensus/voting fields.
src/interfaces/ISafetyNet.sol Renames/reshapes config and request fields/events/errors to reflect contest/veto instead of voting.
src/contracts/SafetyNet.sol Implements contest counting + vetoing, removes voting state and vote execution path.
src/contracts/modification.txt Adds an empty file under contracts (no content).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

/// @param timestamp Creation time of the request
/// @param yesVotes Number of yes votes received
/// @param noVotes Number of no votes received
/// @param contestCount Number of contestation received
Comment on lines +105 to 107
/// @notice Emitted when vetoing on a request is completed
event RequestEnded(uint256 indexed id, uint256 contestCount);

Comment on lines +272 to +278
// Check if consensus on contestation has been reached after this contestastion
SafetyNet memory _safetyNet = safetyNets[_request.safetyNetId];
_request.contestCount++;

if (_request.contestCount > _safetyNet.members.length * _safetyNet.contestThreshold / 100) {
isVetoed[_requestId] = true; // Vetoed by at least 33% of the members
emit WithdrawalVetoed(_requestId, _request.owner, block.timestamp);
Comment on lines +272 to +276
// Check if consensus on contestation has been reached after this contestastion
SafetyNet memory _safetyNet = safetyNets[_request.safetyNetId];
_request.contestCount++;

if (_request.contestCount > _safetyNet.members.length * _safetyNet.contestThreshold / 100) {
Comment on lines 283 to 288
function executeContestedWithdrawal(uint256 _idRequest) external override nonReentrant {
Request memory _request = requests[_idRequest];
if (isExecuted[_idRequest]) revert AlreadyExecuted();

SafetyNet memory _safetyNet = safetyNets[_request.safetyNetId];

Comment on lines +326 to 331
function test_CreateWhenContestThresholdIsGreaterThan100() external {
_allowToken(address(_token));
ISafetyNet.SafetyNet memory _safetyNet = _defaultSafetyNet(address(_token));
_safetyNet.consensusThreshold = 150;
_safetyNet.contestThreshold = 100;
uint256 id = _sn.create(_safetyNet);
assertEq(id, 0);
Comment on lines +196 to +198
// Force veto status directly for testing timeout behavior
vm.prank(address(_safetyNet)); // Workaround: since window is closed, we simulate vetoed status

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.

2 participants