Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use array instead of bitmap #888

Merged
merged 3 commits into from
Dec 2, 2024
Merged
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
51 changes: 43 additions & 8 deletions contracts/0.8.25/vaults/Delegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -391,32 +391,67 @@ contract Delegation is Dashboard, IReportReceiver {
}

/**
* @dev Modifier that requires approval from all committee members within a voting period.
* Uses a bitmap to track new votes within the call instead of updating storage immediately.
* @param _committee Array of role identifiers that form the voting committee.
* @param _votingPeriod Time window in seconds during which votes remain valid.
* @dev Modifier that implements a mechanism for multi-role committee approval.
* Each unique function call (identified by msg.data: selector + arguments) requires
* approval from all committee role members within a specified time window.
*
* The voting process works as follows:
* 1. When a committee member calls the function:
* - Their vote is counted immediately
* - If not enough votes exist, their vote is recorded
* - If they're not a committee member, the call reverts
*
* 2. Vote counting:
* - Counts the current caller's votes if they're a committee member
* - Counts existing votes that are within the voting period
* - All votes must occur within the same voting period window
*
* 3. Execution:
* - If all committee members have voted within the period, executes the function
* - On successful execution, clears all voting state for this call
* - If not enough votes, stores the current votes
* - Thus, if the caller has all the roles, the function is executed immediately
*
* 4. Gas Optimization:
* - Votes are stored in a deferred manner using a memory array
* - Vote storage writes only occur if the function cannot be executed immediately
* - This prevents unnecessary storage writes when all votes are present,
* because the votes are cleared anyway after the function is executed,
* - i.e. this optimization is beneficial for the deciding caller and
* saves 1 storage write for each role the deciding caller has
*
* @param _committee Array of role identifiers that form the voting committee
* @param _votingPeriod Time window in seconds during which votes remain valid
*
* @notice Votes expire after the voting period and must be recast
* @notice All committee members must vote within the same voting period
* @notice Only committee members can initiate votes
*
* @custom:security-note Each unique function call (including parameters) requires its own set of votes
*/
modifier onlyIfVotedBy(bytes32[] memory _committee, uint256 _votingPeriod) {
bytes32 callId = keccak256(msg.data);
uint256 committeeSize = _committee.length;
uint256 votingStart = block.timestamp - _votingPeriod;
uint256 voteTally = 0;
uint256 votesToUpdateBitmap = 0;
bool[] memory deferredVotes = new bool[](committeeSize);
bool isCommitteeMember = false;

for (uint256 i = 0; i < committeeSize; ++i) {
bytes32 role = _committee[i];

if (super.hasRole(role, msg.sender)) {
isCommitteeMember = true;
voteTally++;
votesToUpdateBitmap |= (1 << i);
deferredVotes[i] = true;

emit RoleMemberVoted(msg.sender, role, block.timestamp, msg.data);
} else if (votings[callId][role] >= votingStart) {
voteTally++;
}
}

if (votesToUpdateBitmap == 0) revert NotACommitteeMember();
if (!isCommitteeMember) revert NotACommitteeMember();

if (voteTally == committeeSize) {
for (uint256 i = 0; i < committeeSize; ++i) {
Expand All @@ -426,7 +461,7 @@ contract Delegation is Dashboard, IReportReceiver {
_;
} else {
for (uint256 i = 0; i < committeeSize; ++i) {
if ((votesToUpdateBitmap & (1 << i)) != 0) {
if (deferredVotes[i]) {
bytes32 role = _committee[i];
votings[callId][role] = block.timestamp;
}
Expand Down
Loading