diff --git a/contracts/committees/EmergencyActivationCommittee.sol b/contracts/committees/EmergencyActivationCommittee.sol index 13f1a0bd..7e01fd48 100644 --- a/contracts/committees/EmergencyActivationCommittee.sol +++ b/contracts/committees/EmergencyActivationCommittee.sol @@ -5,6 +5,8 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {HashConsensus} from "./HashConsensus.sol"; import {ITimelock} from "../interfaces/ITimelock.sol"; +import {Duration, Durations} from "../types/Duration.sol"; +import {Timestamp} from "../types/Timestamp.sol"; /// @title Emergency Activation Committee Contract /// @notice This contract allows a committee to approve and execute an emergency activation @@ -19,7 +21,7 @@ contract EmergencyActivationCommittee is HashConsensus { address[] memory committeeMembers, uint256 executionQuorum, address emergencyProtectedTimelock - ) HashConsensus(owner, 0) { + ) HashConsensus(owner, Durations.from(0)) { EMERGENCY_PROTECTED_TIMELOCK = emergencyProtectedTimelock; _addMembers(committeeMembers, executionQuorum); @@ -34,12 +36,13 @@ contract EmergencyActivationCommittee is HashConsensus { /// @notice Gets the current state of the emergency activation vote /// @return support The number of votes in support of the activation - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the activation has been executed function getActivateEmergencyModeState() public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { return _getHashState(EMERGENCY_ACTIVATION_HASH); } diff --git a/contracts/committees/EmergencyExecutionCommittee.sol b/contracts/committees/EmergencyExecutionCommittee.sol index c6c0e704..ea6b5622 100644 --- a/contracts/committees/EmergencyExecutionCommittee.sol +++ b/contracts/committees/EmergencyExecutionCommittee.sol @@ -5,6 +5,8 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {HashConsensus} from "./HashConsensus.sol"; import {ProposalsList} from "./ProposalsList.sol"; import {ITimelock} from "../interfaces/ITimelock.sol"; +import {Timestamp} from "../types/Timestamp.sol"; +import {Durations} from "../types/Duration.sol"; enum ProposalType { EmergencyExecute, @@ -22,7 +24,7 @@ contract EmergencyExecutionCommittee is HashConsensus, ProposalsList { address[] memory committeeMembers, uint256 executionQuorum, address emergencyProtectedTimelock - ) HashConsensus(owner, 0) { + ) HashConsensus(owner, Durations.from(0)) { EMERGENCY_PROTECTED_TIMELOCK = emergencyProtectedTimelock; _addMembers(committeeMembers, executionQuorum); @@ -46,12 +48,13 @@ contract EmergencyExecutionCommittee is HashConsensus, ProposalsList { /// @notice Gets the current state of an emergency execution proposal /// @param proposalId The ID of the proposal /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the proposal has been executed function getEmergencyExecuteState(uint256 proposalId) public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { (, bytes32 key) = _encodeEmergencyExecute(proposalId); return _getHashState(key); @@ -93,14 +96,15 @@ contract EmergencyExecutionCommittee is HashConsensus, ProposalsList { _pushProposal(proposalKey, uint256(ProposalType.EmergencyReset), bytes("")); } - /// @notice Gets the current state of an emergency reset opprosal + /// @notice Gets the current state of an emergency reset proposal /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the proposal has been executed function getEmergencyResetState() public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { bytes32 proposalKey = _encodeEmergencyResetProposalKey(); return _getHashState(proposalKey); diff --git a/contracts/committees/HashConsensus.sol b/contracts/committees/HashConsensus.sol index 57387243..c0e8a1f0 100644 --- a/contracts/committees/HashConsensus.sol +++ b/contracts/committees/HashConsensus.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.26; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {Duration} from "../types/Duration.sol"; +import {Timestamp, Timestamps} from "../types/Timestamp.sol"; /// @title HashConsensus Contract /// @notice This contract provides a consensus mechanism based on hash voting among members @@ -15,7 +17,7 @@ abstract contract HashConsensus is Ownable { event QuorumSet(uint256 quorum); event HashUsed(bytes32 hash); event Voted(address indexed signer, bytes32 hash, bool support); - event TimelockDurationSet(uint256 timelockDuration); + event TimelockDurationSet(Duration timelockDuration); error DuplicatedMember(address account); error AccountIsNotMember(address account); @@ -23,22 +25,23 @@ abstract contract HashConsensus is Ownable { error HashAlreadyUsed(bytes32 hash); error QuorumIsNotReached(); error InvalidQuorum(); - error InvalidTimelockDuration(uint256 timelock); + error InvalidTimelockDuration(Duration timelock); error TimelockNotPassed(); + error ProposalAlreadyScheduled(bytes32 hash); struct HashState { - uint40 quorumAt; - uint40 usedAt; + Timestamp scheduledAt; + Timestamp usedAt; } uint256 public quorum; - uint256 public timelockDuration; + Duration public timelockDuration; mapping(bytes32 => HashState) private _hashStates; EnumerableSet.AddressSet private _members; mapping(address signer => mapping(bytes32 => bool)) public approves; - constructor(address owner, uint256 timelock) Ownable(owner) { + constructor(address owner, Duration timelock) Ownable(owner) { timelockDuration = timelock; emit TimelockDurationSet(timelock); } @@ -48,7 +51,7 @@ abstract contract HashConsensus is Ownable { /// @param hash The hash to vote on /// @param support Indicates whether the member supports the hash function _vote(bytes32 hash, bool support) internal { - if (_hashStates[hash].usedAt > 0) { + if (_hashStates[hash].usedAt > Timestamps.from(0)) { revert HashAlreadyUsed(hash); } @@ -57,8 +60,9 @@ abstract contract HashConsensus is Ownable { } uint256 heads = _getSupport(hash); - if (heads == quorum - 1 && support == true) { - _hashStates[hash].quorumAt = uint40(block.timestamp); + // heads compares to quorum - 1 because the current vote is not counted yet + if (heads >= quorum - 1 && support == true && _hashStates[hash].scheduledAt == Timestamps.from(0)) { + _hashStates[hash].scheduledAt = Timestamps.from(block.timestamp); } approves[msg.sender][hash] = support; @@ -69,7 +73,7 @@ abstract contract HashConsensus is Ownable { /// @dev Internal function that handles marking a hash as used /// @param hash The hash to mark as used function _markUsed(bytes32 hash) internal { - if (_hashStates[hash].usedAt > 0) { + if (_hashStates[hash].usedAt > Timestamps.from(0)) { revert HashAlreadyUsed(hash); } @@ -78,11 +82,11 @@ abstract contract HashConsensus is Ownable { if (support == 0 || support < quorum) { revert QuorumIsNotReached(); } - if (block.timestamp < _hashStates[hash].quorumAt + timelockDuration) { + if (timelockDuration.addTo(_hashStates[hash].scheduledAt) > Timestamps.from(block.timestamp)) { revert TimelockNotPassed(); } - _hashStates[hash].usedAt = uint40(block.timestamp); + _hashStates[hash].usedAt = Timestamps.from(block.timestamp); emit HashUsed(hash); } @@ -91,16 +95,18 @@ abstract contract HashConsensus is Ownable { /// @dev Internal function to retrieve the state of a hash /// @param hash The hash to get the state for /// @return support The number of votes in support of the hash - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return scheduledAt The timestamp when the quorum was reached or scheduleProposal was called /// @return isUsed Whether the hash has been used function _getHashState(bytes32 hash) internal view - returns (uint256 support, uint256 execuitionQuorum, bool isUsed) + returns (uint256 support, uint256 executionQuorum, Timestamp scheduledAt, bool isUsed) { support = _getSupport(hash); - execuitionQuorum = quorum; - isUsed = _hashStates[hash].usedAt > 0; + executionQuorum = quorum; + scheduledAt = _hashStates[hash].scheduledAt; + isUsed = _hashStates[hash].usedAt > Timestamps.from(0); } /// @notice Adds new members to the contract and sets the execution quorum. @@ -154,7 +160,7 @@ abstract contract HashConsensus is Ownable { /// @notice Sets the timelock duration /// @dev Only callable by the owner /// @param timelock The new timelock duration in seconds - function setTimelockDuration(uint256 timelock) public { + function setTimelockDuration(Duration timelock) public { _checkOwner(); if (timelock == timelockDuration) { revert InvalidTimelockDuration(timelock); @@ -171,6 +177,26 @@ abstract contract HashConsensus is Ownable { _setQuorum(newQuorum); } + /// @notice Schedules a proposal for execution if quorum is reached and it has not been scheduled yet. + /// @dev This function schedules a proposal for execution if the quorum is reached and + /// the proposal has not been scheduled yet. Could happen when execution quorum was set to the same value as + /// current support of the proposal. + /// @param hash The hash of the proposal to be scheduled + function schedule(bytes32 hash) public { + if (_hashStates[hash].usedAt > Timestamps.from(0)) { + revert HashAlreadyUsed(hash); + } + + if (_getSupport(hash) < quorum) { + revert QuorumIsNotReached(); + } + if (_hashStates[hash].scheduledAt > Timestamps.from(0)) { + revert ProposalAlreadyScheduled(hash); + } + + _hashStates[hash].scheduledAt = Timestamps.from(block.timestamp); + } + /// @notice Sets the execution quorum required for certain operations. /// @dev The quorum value must be greater than zero and not exceed the current number of members. /// @param executionQuorum The new quorum value to be set. diff --git a/contracts/committees/ResealCommittee.sol b/contracts/committees/ResealCommittee.sol index b2050ea8..a6e4a99b 100644 --- a/contracts/committees/ResealCommittee.sol +++ b/contracts/committees/ResealCommittee.sol @@ -6,6 +6,8 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IDualGovernance} from "../interfaces/IDualGovernance.sol"; import {HashConsensus} from "./HashConsensus.sol"; import {ProposalsList} from "./ProposalsList.sol"; +import {Timestamp} from "../types/Timestamp.sol"; +import {Duration} from "../types/Duration.sol"; /// @title Reseal Committee Contract /// @notice This contract allows a committee to vote on and execute resealing proposals @@ -20,7 +22,7 @@ contract ResealCommittee is HashConsensus, ProposalsList { address[] memory committeeMembers, uint256 executionQuorum, address dualGovernance, - uint256 timelock + Duration timelock ) HashConsensus(owner, timelock) { DUAL_GOVERNANCE = dualGovernance; @@ -42,12 +44,13 @@ contract ResealCommittee is HashConsensus, ProposalsList { /// @dev Retrieves the state of the reseal proposal for a sealed address /// @param sealable The addresses for the reseal proposal /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the proposal has been executed function getResealState(address sealable) public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { (, bytes32 key) = _encodeResealProposal(sealable); return _getHashState(key); diff --git a/contracts/committees/TiebreakerCore.sol b/contracts/committees/TiebreakerCore.sol index b82380f7..bcb75d7a 100644 --- a/contracts/committees/TiebreakerCore.sol +++ b/contracts/committees/TiebreakerCore.sol @@ -7,10 +7,12 @@ import {ITiebreakerCore} from "../interfaces/ITiebreaker.sol"; import {IDualGovernance} from "../interfaces/IDualGovernance.sol"; import {HashConsensus} from "./HashConsensus.sol"; import {ProposalsList} from "./ProposalsList.sol"; +import {Timestamp} from "../types/Timestamp.sol"; +import {Duration} from "../types/Duration.sol"; enum ProposalType { ScheduleProposal, - ResumeSelable + ResumeSealable } /// @title Tiebreaker Core Contract @@ -23,7 +25,7 @@ contract TiebreakerCore is ITiebreakerCore, HashConsensus, ProposalsList { mapping(address => uint256) private _sealableResumeNonces; - constructor(address owner, address dualGovernance, uint256 timelock) HashConsensus(owner, timelock) { + constructor(address owner, address dualGovernance, Duration timelock) HashConsensus(owner, timelock) { DUAL_GOVERNANCE = dualGovernance; } @@ -45,12 +47,13 @@ contract TiebreakerCore is ITiebreakerCore, HashConsensus, ProposalsList { /// @dev Retrieves the state of the schedule proposal for a given proposal ID /// @param proposalId The ID of the proposal /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the proposal has been executed function getScheduleProposalState(uint256 proposalId) public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { (, bytes32 key) = _encodeScheduleProposal(proposalId); return _getHashState(key); @@ -96,7 +99,7 @@ contract TiebreakerCore is ITiebreakerCore, HashConsensus, ProposalsList { } (bytes memory proposalData, bytes32 key) = _encodeSealableResume(sealable, nonce); _vote(key, true); - _pushProposal(key, uint256(ProposalType.ResumeSelable), proposalData); + _pushProposal(key, uint256(ProposalType.ResumeSealable), proposalData); } /// @notice Gets the current state of a resume sealable proposal @@ -104,12 +107,13 @@ contract TiebreakerCore is ITiebreakerCore, HashConsensus, ProposalsList { /// @param sealable The address to resume /// @param nonce The nonce for the resume proposal /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the proposal has been executed function getSealableResumeState( address sealable, uint256 nonce - ) public view returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) { + ) public view returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { (, bytes32 key) = _encodeSealableResume(sealable, nonce); return _getHashState(key); } @@ -136,7 +140,7 @@ contract TiebreakerCore is ITiebreakerCore, HashConsensus, ProposalsList { address sealable, uint256 nonce ) private pure returns (bytes memory data, bytes32 key) { - data = abi.encode(ProposalType.ResumeSelable, sealable, nonce); + data = abi.encode(ProposalType.ResumeSealable, sealable, nonce); key = keccak256(data); } } diff --git a/contracts/committees/TiebreakerSubCommittee.sol b/contracts/committees/TiebreakerSubCommittee.sol index 810fdff9..e4113143 100644 --- a/contracts/committees/TiebreakerSubCommittee.sol +++ b/contracts/committees/TiebreakerSubCommittee.sol @@ -6,10 +6,12 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ITiebreakerCore} from "../interfaces/ITiebreaker.sol"; import {HashConsensus} from "./HashConsensus.sol"; import {ProposalsList} from "./ProposalsList.sol"; +import {Timestamp} from "../types/Timestamp.sol"; +import {Durations} from "../types/Duration.sol"; enum ProposalType { ScheduleProposal, - ResumeSelable + ResumeSealable } /// @title Tiebreaker SubCommittee Contract @@ -23,7 +25,7 @@ contract TiebreakerSubCommittee is HashConsensus, ProposalsList { address[] memory committeeMembers, uint256 executionQuorum, address tiebreakerCore - ) HashConsensus(owner, 0) { + ) HashConsensus(owner, Durations.from(0)) { TIEBREAKER_CORE = tiebreakerCore; _addMembers(committeeMembers, executionQuorum); @@ -38,7 +40,7 @@ contract TiebreakerSubCommittee is HashConsensus, ProposalsList { /// @param proposalId The ID of the proposal to schedule function scheduleProposal(uint256 proposalId) public { _checkCallerIsMember(); - (bytes memory proposalData, bytes32 key) = _encodeAproveProposal(proposalId); + (bytes memory proposalData, bytes32 key) = _encodeApproveProposal(proposalId); _vote(key, true); _pushProposal(key, uint256(ProposalType.ScheduleProposal), proposalData); } @@ -47,14 +49,15 @@ contract TiebreakerSubCommittee is HashConsensus, ProposalsList { /// @dev Retrieves the state of the schedule proposal for a given proposal ID /// @param proposalId The ID of the proposal /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The number of votes required to reach quorum /// @return isExecuted Whether the proposal has been executed function getScheduleProposalState(uint256 proposalId) public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { - (, bytes32 key) = _encodeAproveProposal(proposalId); + (, bytes32 key) = _encodeApproveProposal(proposalId); return _getHashState(key); } @@ -62,7 +65,7 @@ contract TiebreakerSubCommittee is HashConsensus, ProposalsList { /// @dev Executes the schedule proposal by calling the scheduleProposal function on the Tiebreaker Core contract /// @param proposalId The ID of the proposal to schedule function executeScheduleProposal(uint256 proposalId) public { - (, bytes32 key) = _encodeAproveProposal(proposalId); + (, bytes32 key) = _encodeApproveProposal(proposalId); _markUsed(key); Address.functionCall( TIEBREAKER_CORE, abi.encodeWithSelector(ITiebreakerCore.scheduleProposal.selector, proposalId) @@ -74,7 +77,7 @@ contract TiebreakerSubCommittee is HashConsensus, ProposalsList { /// @param proposalId The ID of the proposal to schedule /// @return data The encoded proposal data /// @return key The generated proposal key - function _encodeAproveProposal(uint256 proposalId) internal pure returns (bytes memory data, bytes32 key) { + function _encodeApproveProposal(uint256 proposalId) internal pure returns (bytes memory data, bytes32 key) { data = abi.encode(ProposalType.ScheduleProposal, proposalId); key = keccak256(data); } @@ -87,21 +90,23 @@ contract TiebreakerSubCommittee is HashConsensus, ProposalsList { /// @dev Allows committee members to vote on resuming a sealable address /// @param sealable The address to resume function sealableResume(address sealable) public { + _checkCallerIsMember(); (bytes memory proposalData, bytes32 key,) = _encodeSealableResume(sealable); _vote(key, true); - _pushProposal(key, uint256(ProposalType.ResumeSelable), proposalData); + _pushProposal(key, uint256(ProposalType.ResumeSealable), proposalData); } /// @notice Gets the current state of a resume sealable proposal /// @dev Retrieves the state of the resume sealable proposal for a given address /// @param sealable The address to resume /// @return support The number of votes in support of the proposal - /// @return execuitionQuorum The required number of votes for execution + /// @return executionQuorum The required number of votes for execution + /// @return quorumAt The timestamp when the quorum was reached /// @return isExecuted Whether the proposal has been executed function getSealableResumeState(address sealable) public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp quorumAt, bool isExecuted) { (, bytes32 key,) = _encodeSealableResume(sealable); return _getHashState(key); diff --git a/test/scenario/emergency-committee.t.sol b/test/scenario/emergency-committee.t.sol index c1962a37..b5d3b438 100644 --- a/test/scenario/emergency-committee.t.sol +++ b/test/scenario/emergency-committee.t.sol @@ -39,19 +39,19 @@ contract EmergencyCommitteeTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _emergencyActivationCommittee.quorum() - 1; i++) { vm.prank(members[i]); _emergencyActivationCommittee.approveActivateEmergencyMode(); - (support, quorum, isExecuted) = _emergencyActivationCommittee.getActivateEmergencyModeState(); + (support, quorum,, isExecuted) = _emergencyActivationCommittee.getActivateEmergencyModeState(); assert(support < quorum); assert(isExecuted == false); } vm.prank(members[members.length - 1]); _emergencyActivationCommittee.approveActivateEmergencyMode(); - (support, quorum, isExecuted) = _emergencyActivationCommittee.getActivateEmergencyModeState(); + (support, quorum,, isExecuted) = _emergencyActivationCommittee.getActivateEmergencyModeState(); assert(support == quorum); assert(isExecuted == false); _emergencyActivationCommittee.executeActivateEmergencyMode(); - (support, quorum, isExecuted) = _emergencyActivationCommittee.getActivateEmergencyModeState(); + (support, quorum,, isExecuted) = _emergencyActivationCommittee.getActivateEmergencyModeState(); assert(isExecuted == true); // Emergency Execute @@ -59,19 +59,19 @@ contract EmergencyCommitteeTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _emergencyExecutionCommittee.quorum() - 1; i++) { vm.prank(members[i]); _emergencyExecutionCommittee.voteEmergencyExecute(proposalIdToExecute, true); - (support, quorum, isExecuted) = _emergencyExecutionCommittee.getEmergencyExecuteState(proposalIdToExecute); + (support, quorum,, isExecuted) = _emergencyExecutionCommittee.getEmergencyExecuteState(proposalIdToExecute); assert(support < quorum); assert(isExecuted == false); } vm.prank(members[members.length - 1]); _emergencyExecutionCommittee.voteEmergencyExecute(proposalIdToExecute, true); - (support, quorum, isExecuted) = _emergencyExecutionCommittee.getEmergencyExecuteState(proposalIdToExecute); + (support, quorum,, isExecuted) = _emergencyExecutionCommittee.getEmergencyExecuteState(proposalIdToExecute); assert(support == quorum); assert(isExecuted == false); _emergencyExecutionCommittee.executeEmergencyExecute(proposalIdToExecute); - (support, quorum, isExecuted) = _emergencyExecutionCommittee.getEmergencyExecuteState(proposalIdToExecute); + (support, quorum,, isExecuted) = _emergencyExecutionCommittee.getEmergencyExecuteState(proposalIdToExecute); assert(isExecuted == true); } } diff --git a/test/scenario/reseal-committee.t.sol b/test/scenario/reseal-committee.t.sol index 15414182..9ceb10ee 100644 --- a/test/scenario/reseal-committee.t.sol +++ b/test/scenario/reseal-committee.t.sol @@ -38,14 +38,14 @@ contract ResealCommitteeTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _resealCommittee.quorum() - 1; i++) { vm.prank(members[i]); _resealCommittee.voteReseal(sealable, true); - (support, quorum, isExecuted) = _resealCommittee.getResealState(sealable); + (support, quorum,, isExecuted) = _resealCommittee.getResealState(sealable); assert(support < quorum); assert(isExecuted == false); } vm.prank(members[members.length - 1]); _resealCommittee.voteReseal(sealable, true); - (support, quorum, isExecuted) = _resealCommittee.getResealState(sealable); + (support, quorum,, isExecuted) = _resealCommittee.getResealState(sealable); assert(support == quorum); assert(isExecuted == false); diff --git a/test/scenario/tiebreaker.t.sol b/test/scenario/tiebreaker.t.sol index 54228184..a7062717 100644 --- a/test/scenario/tiebreaker.t.sol +++ b/test/scenario/tiebreaker.t.sol @@ -42,19 +42,19 @@ contract TiebreakerScenarioTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _tiebreakerSubCommittees[0].quorum() - 1; i++) { vm.prank(members[i]); _tiebreakerSubCommittees[0].scheduleProposal(proposalIdToExecute); - (support, quorum, isExecuted) = _tiebreakerSubCommittees[0].getScheduleProposalState(proposalIdToExecute); + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[0].getScheduleProposalState(proposalIdToExecute); assertTrue(support < quorum); assertFalse(isExecuted); } vm.prank(members[members.length - 1]); _tiebreakerSubCommittees[0].scheduleProposal(proposalIdToExecute); - (support, quorum, isExecuted) = _tiebreakerSubCommittees[0].getScheduleProposalState(proposalIdToExecute); + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[0].getScheduleProposalState(proposalIdToExecute); assertEq(support, quorum); assertFalse(isExecuted); _tiebreakerSubCommittees[0].executeScheduleProposal(proposalIdToExecute); - (support, quorum, isExecuted) = _tiebreakerCoreCommittee.getScheduleProposalState(proposalIdToExecute); + (support, quorum,, isExecuted) = _tiebreakerCoreCommittee.getScheduleProposalState(proposalIdToExecute); assertTrue(support < quorum); // Tiebreaker subcommittee 1 @@ -62,24 +62,24 @@ contract TiebreakerScenarioTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _tiebreakerSubCommittees[1].quorum() - 1; i++) { vm.prank(members[i]); _tiebreakerSubCommittees[1].scheduleProposal(proposalIdToExecute); - (support, quorum, isExecuted) = _tiebreakerSubCommittees[1].getScheduleProposalState(proposalIdToExecute); + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[1].getScheduleProposalState(proposalIdToExecute); assertTrue(support < quorum); assertEq(isExecuted, false); } vm.prank(members[members.length - 1]); _tiebreakerSubCommittees[1].scheduleProposal(proposalIdToExecute); - (support, quorum, isExecuted) = _tiebreakerSubCommittees[1].getScheduleProposalState(proposalIdToExecute); + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[1].getScheduleProposalState(proposalIdToExecute); assertEq(support, quorum); assertFalse(isExecuted); // Approve proposal for scheduling _tiebreakerSubCommittees[1].executeScheduleProposal(proposalIdToExecute); - (support, quorum, isExecuted) = _tiebreakerCoreCommittee.getScheduleProposalState(proposalIdToExecute); + (support, quorum,, isExecuted) = _tiebreakerCoreCommittee.getScheduleProposalState(proposalIdToExecute); assertEq(support, quorum); // Waiting for submit delay pass - _wait(Durations.from(_tiebreakerCoreCommittee.timelockDuration())); + _wait(_tiebreakerCoreCommittee.timelockDuration()); _tiebreakerCoreCommittee.executeScheduleProposal(proposalIdToExecute); } @@ -114,7 +114,7 @@ contract TiebreakerScenarioTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _tiebreakerSubCommittees[0].quorum() - 1; i++) { vm.prank(members[i]); _tiebreakerSubCommittees[0].sealableResume(address(_lido.withdrawalQueue)); - (support, quorum, isExecuted) = + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[0].getSealableResumeState(address(_lido.withdrawalQueue)); assertTrue(support < quorum); assertFalse(isExecuted); @@ -122,13 +122,13 @@ contract TiebreakerScenarioTest is ScenarioTestBlueprint { vm.prank(members[members.length - 1]); _tiebreakerSubCommittees[0].sealableResume(address(_lido.withdrawalQueue)); - (support, quorum, isExecuted) = + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[0].getSealableResumeState(address(_lido.withdrawalQueue)); assertEq(support, quorum); assertFalse(isExecuted); _tiebreakerSubCommittees[0].executeSealableResume(address(_lido.withdrawalQueue)); - (support, quorum, isExecuted) = _tiebreakerCoreCommittee.getSealableResumeState( + (support, quorum,, isExecuted) = _tiebreakerCoreCommittee.getSealableResumeState( address(_lido.withdrawalQueue), _tiebreakerCoreCommittee.getSealableResumeNonce(address(_lido.withdrawalQueue)) ); @@ -139,7 +139,7 @@ contract TiebreakerScenarioTest is ScenarioTestBlueprint { for (uint256 i = 0; i < _tiebreakerSubCommittees[1].quorum() - 1; i++) { vm.prank(members[i]); _tiebreakerSubCommittees[1].sealableResume(address(_lido.withdrawalQueue)); - (support, quorum, isExecuted) = + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[1].getSealableResumeState(address(_lido.withdrawalQueue)); assertTrue(support < quorum); assertEq(isExecuted, false); @@ -147,20 +147,20 @@ contract TiebreakerScenarioTest is ScenarioTestBlueprint { vm.prank(members[members.length - 1]); _tiebreakerSubCommittees[1].sealableResume(address(_lido.withdrawalQueue)); - (support, quorum, isExecuted) = + (support, quorum,, isExecuted) = _tiebreakerSubCommittees[1].getSealableResumeState(address(_lido.withdrawalQueue)); assertEq(support, quorum); assertFalse(isExecuted); _tiebreakerSubCommittees[1].executeSealableResume(address(_lido.withdrawalQueue)); - (support, quorum, isExecuted) = _tiebreakerCoreCommittee.getSealableResumeState( + (support, quorum,, isExecuted) = _tiebreakerCoreCommittee.getSealableResumeState( address(_lido.withdrawalQueue), _tiebreakerCoreCommittee.getSealableResumeNonce(address(_lido.withdrawalQueue)) ); assertEq(support, quorum); // Waiting for submit delay pass - _wait(Durations.from(_tiebreakerCoreCommittee.timelockDuration())); + _wait(_tiebreakerCoreCommittee.timelockDuration()); _tiebreakerCoreCommittee.executeSealableResume(address(_lido.withdrawalQueue)); diff --git a/test/unit/HashConsensus.t.sol b/test/unit/HashConsensus.t.sol index 966c7a52..987c8624 100644 --- a/test/unit/HashConsensus.t.sol +++ b/test/unit/HashConsensus.t.sol @@ -7,14 +7,15 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Vm} from "forge-std/Test.sol"; import {HashConsensus} from "../../contracts/committees/HashConsensus.sol"; -import {Duration} from "../../contracts/types/Duration.sol"; +import {Duration, Durations} from "../../contracts/types/Duration.sol"; +import {Timestamp, Timestamps} from "../../contracts/types/Timestamp.sol"; contract HashConsensusInstance is HashConsensus { constructor( address owner, address[] memory newMembers, uint256 executionQuorum, - uint256 timelock + Duration timelock ) HashConsensus(owner, timelock) { _addMembers(newMembers, executionQuorum); } @@ -37,8 +38,8 @@ abstract contract HashConsensusUnitTest is UnitTest { } } - function test_constructorInitializesCorrectly() public { - uint256 timelock = 1; + function test_constructor_InitializesCorrectly() public { + Duration timelock = Durations.from(1); vm.expectEmit(); emit Ownable.OwnershipTransferred(address(0), _owner); @@ -54,11 +55,11 @@ abstract contract HashConsensusUnitTest is UnitTest { new HashConsensusInstance(_owner, _committeeMembers, _quorum, timelock); } - function test_constructorRevertsWithZeroQuorum() public { + function test_constructor_RevertOn_WithZeroQuorum() public { uint256 invalidQuorum = 0; vm.expectRevert(abi.encodeWithSelector(HashConsensus.InvalidQuorum.selector)); - new HashConsensusInstance(_owner, _committeeMembers, invalidQuorum, 1); + new HashConsensusInstance(_owner, _committeeMembers, invalidQuorum, Durations.from(1)); } function test_isMember() public { @@ -80,7 +81,7 @@ abstract contract HashConsensusUnitTest is UnitTest { } } - function test_addMembers_stranger_call() public { + function test_addMembers_RevertOn_StrangerCall() public { address[] memory membersToAdd = new address[](1); membersToAdd[0] = makeAddr("NEW_MEMBER"); assertEq(_hashConsensus.isMember(membersToAdd[0]), false); @@ -96,7 +97,7 @@ abstract contract HashConsensusUnitTest is UnitTest { } } - function test_addMembers_reverts_on_duplicate() public { + function test_addMembers_RevertOn_Duplicate() public { address[] memory membersToAdd = new address[](1); membersToAdd[0] = _committeeMembers[0]; assertEq(_hashConsensus.isMember(membersToAdd[0]), true); @@ -106,7 +107,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.addMembers(membersToAdd, _quorum); } - function test_addMembers_reverts_on_duplicate_in_array() public { + function test_addMembers_RevertOn_DuplicateInArray() public { address[] memory membersToAdd = new address[](2); membersToAdd[0] = makeAddr("NEW_MEMBER"); membersToAdd[1] = makeAddr("NEW_MEMBER"); @@ -117,7 +118,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.addMembers(membersToAdd, _quorum); } - function test_addMember_reverts_on_invalid_quorum() public { + function test_addMember_RevertOn_InvalidQuorum() public { address[] memory membersToAdd = new address[](1); membersToAdd[0] = makeAddr("NEW_MEMBER"); assertEq(_hashConsensus.isMember(membersToAdd[0]), false); @@ -159,7 +160,7 @@ abstract contract HashConsensusUnitTest is UnitTest { assertEq(committeeMembers[committeeMembers.length - 1], membersToAdd[1]); } - function test_removeMembers_stranger_call() public { + function test_removeMembers_RevertOn_StrangerCall() public { address[] memory membersToRemove = new address[](1); membersToRemove[0] = _committeeMembers[0]; assertEq(_hashConsensus.isMember(membersToRemove[0]), true); @@ -175,7 +176,7 @@ abstract contract HashConsensusUnitTest is UnitTest { } } - function test_removeMembers_reverts_on_member_is_not_exist() public { + function test_removeMembers_RevertOn_member_is_not_exist() public { address[] memory membersToRemove = new address[](1); membersToRemove[0] = _stranger; assertEq(_hashConsensus.isMember(membersToRemove[0]), false); @@ -185,7 +186,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.removeMembers(membersToRemove, _quorum); } - function test_removeMembers_reverts_on_member_duplicate_in_array() public { + function test_removeMembers_RevertOn_member_duplicate_in_array() public { address[] memory membersToRemove = new address[](2); membersToRemove[0] = _committeeMembers[0]; membersToRemove[1] = _committeeMembers[0]; @@ -197,7 +198,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.removeMembers(membersToRemove, _quorum); } - function test_removeMembers_reverts_on_invalid_quorum() public { + function test_removeMembers_RevertOn_invalid_quorum() public { address[] memory membersToRemove = new address[](1); membersToRemove[0] = _committeeMembers[0]; assertEq(_hashConsensus.isMember(membersToRemove[0]), true); @@ -240,8 +241,8 @@ abstract contract HashConsensusUnitTest is UnitTest { } } - function test_setTimelockDurationByOwner() public { - uint256 newTimelockDuration = 200; + function test_setTimelockDuration_ByOwner() public { + Duration newTimelockDuration = Durations.from(200); vm.expectEmit(true, false, false, true); emit HashConsensus.TimelockDurationSet(newTimelockDuration); @@ -252,16 +253,16 @@ abstract contract HashConsensusUnitTest is UnitTest { assertEq(_hashConsensus.timelockDuration(), newTimelockDuration); } - function test_setTimelockDurationRevertsIfNotOwner() public { - uint256 newTimelockDuration = 200; + function test_setTimelockDuration_RevertOn_IfNotOwner() public { + Duration newTimelockDuration = Durations.from(200); vm.prank(address(0x123)); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(0x123))); _hashConsensus.setTimelockDuration(newTimelockDuration); } - function test_setTimelockDurationRevertsIfValueIsSame() public { - uint256 newTimelockDuration = 300; + function test_setTimelockDuration_RevertOn_IfValueIsSame() public { + Duration newTimelockDuration = Durations.from(300); vm.startPrank(_owner); _hashConsensus.setTimelockDuration(newTimelockDuration); @@ -270,8 +271,8 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.setTimelockDuration(newTimelockDuration); } - function testTimelockDurationEventEmitted() public { - uint256 newTimelockDuration = 300; + function test_setTimelockDuration_EventEmitted() public { + Duration newTimelockDuration = Durations.from(300); vm.expectEmit(true, false, false, true); emit HashConsensus.TimelockDurationSet(newTimelockDuration); @@ -280,7 +281,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.setTimelockDuration(newTimelockDuration); } - function test_setQuorumByOwner() public { + function test_setQuorum_ByOwner() public { uint256 newQuorum = 2; vm.expectEmit(true, false, false, true); @@ -293,7 +294,7 @@ abstract contract HashConsensusUnitTest is UnitTest { assertEq(_hashConsensus.quorum(), newQuorum); } - function test_setQuorumRevertsIfNotOwner() public { + function test_setQuorum_RevertOn_IfNotOwner() public { uint256 newQuorum = 2; vm.prank(address(0x123)); @@ -301,7 +302,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.setQuorum(newQuorum); } - function test_setQuorumRevertsIfZeroQuorum() public { + function test_setQuorum_RevertOn_IfZeroQuorum() public { uint256 invalidQuorum = 0; vm.prank(_owner); @@ -309,7 +310,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.setQuorum(invalidQuorum); } - function test_setQuorumRevertsIfQuorumExceedsMembers() public { + function test_setQuorum_RevertOn_IfQuorumExceedsMembers() public { uint256 invalidQuorum = _committeeMembers.length + 1; vm.prank(_owner); @@ -317,7 +318,7 @@ abstract contract HashConsensusUnitTest is UnitTest { _hashConsensus.setQuorum(invalidQuorum); } - function test_setQuorumRevertsIfQuorumIsSame() public { + function test_setQuorum_RevertOn_IfQuorumIsSame() public { uint256 invalidQuorum = 2; vm.startPrank(_owner); @@ -347,7 +348,7 @@ contract Target { } contract HashConsensusWrapper is HashConsensus { - event OnlyMemberModifierPassed(); + event OnlyMemberPassed(); Target internal _target; @@ -355,7 +356,7 @@ contract HashConsensusWrapper is HashConsensus { address owner, address[] memory newMembers, uint256 executionQuorum, - uint256 timelock, + Duration timelock, Target target ) HashConsensus(owner, timelock) { _target = target; @@ -374,7 +375,7 @@ contract HashConsensusWrapper is HashConsensus { function getHashState(bytes32 hash) public view - returns (uint256 support, uint256 execuitionQuorum, bool isExecuted) + returns (uint256 support, uint256 executionQuorum, Timestamp scheduledAt, bool isExecuted) { return _getHashState(hash); } @@ -385,7 +386,7 @@ contract HashConsensusWrapper is HashConsensus { function onlyMemberProtected() public { _checkCallerIsMember(); - emit OnlyMemberModifierPassed(); + emit OnlyMemberPassed(); } } @@ -399,8 +400,7 @@ contract HashConsensusInternalUnitTest is HashConsensusUnitTest { function setUp() public { _target = new Target(); - _hashConsensusWrapper = - new HashConsensusWrapper(_owner, _committeeMembers, _quorum, _timelock.toSeconds(), _target); + _hashConsensusWrapper = new HashConsensusWrapper(_owner, _committeeMembers, _quorum, _timelock, _target); _hashConsensus = HashConsensus(_hashConsensusWrapper); data = abi.encode(address(_target)); dataHash = keccak256(data); @@ -430,41 +430,57 @@ contract HashConsensusInternalUnitTest is HashConsensusUnitTest { function test_getHashState() public { uint256 support; - uint256 execuitionQuorum; + uint256 executionQuorum; + Timestamp scheduledAt; bool isExecuted; - (support, execuitionQuorum, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); + (support, executionQuorum, scheduledAt, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); assertEq(support, 0); - assertEq(execuitionQuorum, _quorum); + assertEq(executionQuorum, _quorum); + assertEq(scheduledAt, Timestamps.from(0)); assertEq(isExecuted, false); + Timestamp expectedQuorumAt = Timestamps.from(block.timestamp); + for (uint256 i = 0; i < _membersCount; ++i) { - (support, execuitionQuorum, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); + (support, executionQuorum, scheduledAt, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); assertEq(support, i); - assertEq(execuitionQuorum, _quorum); + assertEq(executionQuorum, _quorum); + if (i >= executionQuorum) { + assertEq(scheduledAt, expectedQuorumAt); + } else { + assertEq(scheduledAt, Timestamps.from(0)); + } assertEq(isExecuted, false); vm.prank(_committeeMembers[i]); _hashConsensusWrapper.vote(dataHash, true); - (support, execuitionQuorum, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); + (support, executionQuorum, scheduledAt, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); assertEq(support, i + 1); - assertEq(execuitionQuorum, _quorum); + assertEq(executionQuorum, _quorum); + if (i >= executionQuorum - 1) { + assertEq(scheduledAt, expectedQuorumAt); + } else { + assertEq(scheduledAt, Timestamps.from(0)); + } assertEq(isExecuted, false); } - (support, execuitionQuorum, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); + (support, executionQuorum, scheduledAt, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); assertEq(support, _membersCount); - assertEq(execuitionQuorum, _quorum); + assertEq(executionQuorum, _quorum); + assertEq(scheduledAt, expectedQuorumAt); assertEq(isExecuted, false); _wait(_timelock); _hashConsensusWrapper.execute(dataHash); - (support, execuitionQuorum, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); + (support, executionQuorum, scheduledAt, isExecuted) = _hashConsensusWrapper.getHashState(dataHash); assertEq(support, _membersCount); - assertEq(execuitionQuorum, _quorum); + assertEq(executionQuorum, _quorum); + assertEq(scheduledAt, expectedQuorumAt); assertEq(isExecuted, true); } @@ -498,7 +514,7 @@ contract HashConsensusInternalUnitTest is HashConsensusUnitTest { assertEq(_hashConsensusWrapper.approves(_committeeMembers[0], dataHash), false); } - function test_vote_reverts_on_executed() public { + function test_vote_RevertsOn_executed() public { for (uint256 i = 0; i < _quorum; ++i) { vm.prank(_committeeMembers[i]); _hashConsensusWrapper.vote(dataHash, true); @@ -540,14 +556,97 @@ contract HashConsensusInternalUnitTest is HashConsensusUnitTest { _hashConsensusWrapper.execute(dataHash); } - function test_onlyMemberModifier() public { + function test_onlyMember() public { vm.prank(_stranger); vm.expectRevert(abi.encodeWithSelector(HashConsensus.CallerIsNotMember.selector, _stranger)); _hashConsensusWrapper.onlyMemberProtected(); - vm.prank(_committeeMembers[0]); - vm.expectEmit(address(_hashConsensus)); - emit HashConsensusWrapper.OnlyMemberModifierPassed(); + vm.prank(_owner); + vm.expectRevert(abi.encodeWithSelector(HashConsensus.CallerIsNotMember.selector, _owner)); _hashConsensusWrapper.onlyMemberProtected(); + + for (uint256 i = 0; i < _committeeMembers.length; i++) { + vm.prank(_committeeMembers[i]); + vm.expectEmit(address(_hashConsensus)); + emit HashConsensusWrapper.OnlyMemberPassed(); + _hashConsensusWrapper.onlyMemberProtected(); + } + } + + function test_schedule_RevertOn_IfHashIsUsed() public { + bytes32 hash = keccak256("hash"); + + for (uint256 i = 0; i < _quorum; ++i) { + vm.prank(_committeeMembers[i]); + _hashConsensusWrapper.vote(hash, true); + } + + _wait(Duration.wrap(3600)); + + _hashConsensusWrapper.execute(hash); + + vm.expectRevert(abi.encodeWithSelector(HashConsensus.HashAlreadyUsed.selector, hash)); + _hashConsensusWrapper.schedule(hash); + } + + function test_schedule_RevertOn_IfQuorumAlreadyReached() public { + bytes32 hash = keccak256("hash"); + + _wait(_timelock); + + for (uint256 i = 0; i < _quorum; ++i) { + vm.prank(_committeeMembers[i]); + _hashConsensusWrapper.vote(hash, true); + } + + (,, Timestamp scheduledAtBefore,) = _hashConsensusWrapper.getHashState(hash); + + _wait(_timelock); + vm.expectRevert(abi.encodeWithSignature("ProposalAlreadyScheduled(bytes32)", hash)); + _hashConsensusWrapper.schedule(hash); + + (,, Timestamp scheduledAtAfter,) = _hashConsensusWrapper.getHashState(hash); + + assertEq(scheduledAtBefore, scheduledAtAfter); + } + + function test_schedule_RevertOn_IfQuorumIsNotReached() public { + bytes32 hash = keccak256("hash"); + + for (uint256 i = 0; i < _quorum - 1; ++i) { + vm.prank(_committeeMembers[i]); + _hashConsensusWrapper.vote(hash, true); + } + + (,, Timestamp scheduledAtBefore,) = _hashConsensusWrapper.getHashState(hash); + assertEq(scheduledAtBefore, Timestamps.from(0)); + + vm.expectRevert(abi.encodeWithSignature("QuorumIsNotReached()")); + _hashConsensusWrapper.schedule(hash); + + (,, Timestamp scheduledAtAfter,) = _hashConsensusWrapper.getHashState(hash); + assertEq(scheduledAtAfter, Timestamps.from(0)); + } + + function test_schedule() public { + bytes32 hash = keccak256("hash"); + + for (uint256 i = 0; i < _quorum - 1; ++i) { + vm.prank(_committeeMembers[i]); + _hashConsensusWrapper.vote(hash, true); + } + + vm.prank(_owner); + _hashConsensusWrapper.setQuorum(_quorum - 1); + + (,, Timestamp scheduledAtBefore,) = _hashConsensusWrapper.getHashState(hash); + + assertEq(scheduledAtBefore, Timestamps.from(0)); + + _hashConsensusWrapper.schedule(hash); + + (,, Timestamp scheduledAtAfter,) = _hashConsensusWrapper.getHashState(hash); + + assertEq(scheduledAtAfter, Timestamps.from(block.timestamp)); } } diff --git a/test/utils/SetupDeployment.sol b/test/utils/SetupDeployment.sol index c90aa848..34b20a15 100644 --- a/test/utils/SetupDeployment.sol +++ b/test/utils/SetupDeployment.sol @@ -167,7 +167,7 @@ abstract contract SetupDeployment is Test { _tiebreakerCoreCommittee = _deployEmptyTiebreakerCoreCommittee({ owner: address(this), // temporary set owner to deployer, to add sub committees manually dualGovernance: _dualGovernance, - timelock: TIEBREAKER_EXECUTION_DELAY.toSeconds() + timelock: TIEBREAKER_EXECUTION_DELAY }); address[] memory coreCommitteeMembers = new address[](TIEBREAKER_SUB_COMMITTEES_COUNT); @@ -337,7 +337,9 @@ abstract contract SetupDeployment is Test { committeeMembers[i] = makeAddr(string(abi.encode(0xFA + i * membersCount + 65))); } - return new ResealCommittee(address(_adminExecutor), committeeMembers, quorum, address(_dualGovernance), 0); + return new ResealCommittee( + address(_adminExecutor), committeeMembers, quorum, address(_dualGovernance), Durations.from(0) + ); } function _deployTimelockedGovernance( @@ -403,7 +405,7 @@ abstract contract SetupDeployment is Test { function _deployEmptyTiebreakerCoreCommittee( address owner, IDualGovernance dualGovernance, - uint256 timelock + Duration timelock ) internal returns (TiebreakerCore) { return new TiebreakerCore({owner: owner, dualGovernance: address(dualGovernance), timelock: timelock}); }