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

Statemind audit results #217

Merged
merged 70 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
06d4b62
remove TimelockedGovernance.executeProposal
bulbozaur Nov 20, 2024
b0c98e4
reseal pause check fix
bulbozaur Nov 20, 2024
bf18ce8
HashConsensus historical data
bulbozaur Nov 20, 2024
1a47551
allow to schedule if quorum already reached
bulbozaur Nov 20, 2024
e787026
Additional submitted proposal eventdata
bulbozaur Nov 20, 2024
72c6136
fix natspec
bulbozaur Nov 20, 2024
7f89246
Merge pull request #209 from lidofinance/develop
bulbozaur Nov 22, 2024
0a3c43e
Deny scheduling while zero quorum
bulbozaur Nov 17, 2024
525089b
remove requestWithdrawals method in the Escrow contract
rkolpakov Oct 14, 2024
8675143
Merge pull request #186 from lidofinance/fix/remove-requestWithdrawals
bulbozaur Nov 22, 2024
1cf0d0a
detailed events for accountUnstETHFinalized
bulbozaur Nov 22, 2024
9484a6b
Merge branch 'audits/statemind-responses' into fix/remove-executeProp…
bulbozaur Nov 22, 2024
baa7a49
Merge branch 'audits/statemind-responses' into fix/hashConsensus
bulbozaur Nov 22, 2024
803759a
use one slot for hash state
bulbozaur Nov 22, 2024
97b3c78
Merge branch 'audits/statemind-responses' into fix/submit-proposal-pr…
bulbozaur Nov 22, 2024
9f74af5
update spec
bulbozaur Nov 22, 2024
ae46713
change ops order
bulbozaur Nov 22, 2024
d26f2c3
Merge branch 'audits/statemind-responses' into fix/incorect-natspec
bulbozaur Nov 22, 2024
70542a5
Missing constructor check for TiebreakerActivationTimeout
bulbozaur Nov 26, 2024
2d44940
unregisterProposer gas optimization
bulbozaur Nov 26, 2024
f6f5433
Absence of zero check: unlockUnstETH reverts on empty ids
bulbozaur Nov 26, 2024
b17faca
remove unused methods
bulbozaur Nov 26, 2024
55f3ecd
gas optimization
bulbozaur Nov 26, 2024
f30e2cf
add state check
bulbozaur Nov 26, 2024
339e469
isWithdrawalsBatchesClosed naming
bulbozaur Nov 26, 2024
a299026
Missing parameter sanity checks: minAssetsLockDuration gt zero
bulbozaur Nov 26, 2024
d96ebf7
Add missing view Escrow.test_getVetoerUnstETHIds
bulbozaur Nov 26, 2024
c5bb59d
simplify proposal status checks in ExecutableProposals library
bulbozaur Nov 26, 2024
6b6338c
Remove TiebreakerCoreCommittee nonce check
bulbozaur Nov 26, 2024
821c379
Add constructor checks for valid governance and timelock addresses
bulbozaur Nov 26, 2024
397938c
Timestamp.now optimization
bulbozaur Nov 26, 2024
b02dfda
rageQuitExtensionPeriodStartedAt as local var
bulbozaur Nov 26, 2024
494a70d
storage usage optimization
bulbozaur Nov 26, 2024
cc4f0e2
Timestamp type usage
bulbozaur Nov 26, 2024
40ca9e6
Merge pull request #181 from lidofinance/fix/tiebraker-init
bulbozaur Nov 26, 2024
c5e0359
Merge pull request #203 from lidofinance/fix/pause-check
bulbozaur Nov 26, 2024
de2e6ef
review fixes
bulbozaur Nov 27, 2024
140175e
Merge pull request #202 from lidofinance/fix/remove-executeProposal
bulbozaur Nov 27, 2024
256239b
review fixes
bulbozaur Nov 27, 2024
a0fa7e9
unit tests
bulbozaur Nov 27, 2024
34c1cf3
refactor proposal status checks in ExecutableProposals
bulbozaur Nov 27, 2024
0202b5d
naming
bulbozaur Nov 27, 2024
dd7f80c
review fixes
bulbozaur Nov 27, 2024
453d4db
using uint32 safely
bulbozaur Nov 27, 2024
d2c1506
restore test check
bulbozaur Nov 27, 2024
f896066
restore TiebreakerCoreCommittee nonce check
bulbozaur Nov 27, 2024
f1ee5a3
fix test value
bulbozaur Nov 27, 2024
7301c0d
fix wrong check value type
bulbozaur Nov 27, 2024
e9e31da
Merge pull request #216 from lidofinance/audit-fix/redundant-proposal…
bulbozaur Nov 27, 2024
5678106
Merge pull request #214 from lidofinance/fix/missing-getter
bulbozaur Nov 27, 2024
8e0aaa0
Merge pull request #212 from lidofinance/audit-fix/gas-optimizations
bulbozaur Nov 27, 2024
c12b36c
Merge pull request #211 from lidofinance/audit-fix/missing-checks
bulbozaur Nov 27, 2024
83b4c3e
Merge pull request #208 from lidofinance/fix/incorect-natspec
bulbozaur Nov 27, 2024
2b85627
Merge pull request #205 from lidofinance/fix/submit-proposal-proposer
bulbozaur Nov 27, 2024
032455b
Merge branch 'audits/statemind-responses' into audit-fix/WithdrawalsB…
bulbozaur Nov 27, 2024
140be7b
Merge pull request #213 from lidofinance/audit-fix/WithdrawalsBatches…
bulbozaur Nov 27, 2024
0ce5711
Merge pull request #190 from lidofinance/fix/revert-on-unsteth-state
bulbozaur Nov 27, 2024
2b4c2dd
refactor data types
bulbozaur Nov 28, 2024
fee88cf
timelockDuration getter
bulbozaur Nov 28, 2024
da0dca9
functions order
bulbozaur Nov 28, 2024
bcfc53d
refactor setQuorum checks
bulbozaur Nov 28, 2024
48f49c1
Merge branch 'audits/statemind-responses' into fix/hashConsensus
bulbozaur Nov 28, 2024
9b8cf69
using uint256 for historical values
bulbozaur Nov 28, 2024
6e34653
Fix the check in the addSealableWithdrawalBlocker()
bulbozaur Nov 28, 2024
ecfa820
Merge pull request #221 from lidofinance/fix/tiebraker-sealable-resum…
bulbozaur Nov 28, 2024
f0dc233
Merge pull request #204 from lidofinance/fix/hashConsensus
bulbozaur Nov 28, 2024
b754de5
Add EmergencyProtectedTimelock.MIN_EXECUTION_DELAY sanity check
Psirex Nov 29, 2024
515254b
Improve EmergencyProtectedTimelock unit tests
Psirex Nov 29, 2024
ba2809c
Update plan-b and spec docs
Psirex Nov 29, 2024
6328ec1
Merge pull request #223 from lidofinance/audit-fix/timelock-state-con…
Psirex Nov 29, 2024
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
9 changes: 7 additions & 2 deletions contracts/DualGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ contract DualGovernance is IDualGovernance {
error ProposalSchedulingBlocked(uint256 proposalId);
error ResealIsNotAllowedInNormalState();
error InvalidResealCommittee(address resealCommittee);
error InvalidTiebreakerActivationTimeoutBounds();

// ---
// Events
Expand Down Expand Up @@ -144,6 +145,10 @@ contract DualGovernance is IDualGovernance {
// ---

constructor(ExternalDependencies memory dependencies, SanityCheckParams memory sanityCheckParams) {
if (sanityCheckParams.minTiebreakerActivationTimeout > sanityCheckParams.maxTiebreakerActivationTimeout) {
revert InvalidTiebreakerActivationTimeoutBounds();
}

TIMELOCK = dependencies.timelock;
RESEAL_MANAGER = dependencies.resealManager;

Expand Down Expand Up @@ -188,7 +193,7 @@ contract DualGovernance is IDualGovernance {
revert ProposalSubmissionBlocked();
}
Proposers.Proposer memory proposer = _proposers.getProposer(msg.sender);
proposalId = TIMELOCK.submit(proposer.executor, calls, metadata);
proposalId = TIMELOCK.submit(proposer.account, proposer.executor, calls, metadata);
}

/// @notice Schedules a previously submitted proposal for execution in the Dual Governance system.
Expand Down Expand Up @@ -363,7 +368,7 @@ contract DualGovernance is IDualGovernance {
_proposers.unregister(proposer);

/// @dev after the removal of the proposer, check that admin executor still belongs to some proposer
if (!_proposers.isExecutor(TIMELOCK.getAdminExecutor())) {
if (!_proposers.isExecutor(msg.sender)) {
revert UnownedAdminExecutor();
}
}
Expand Down
51 changes: 42 additions & 9 deletions contracts/EmergencyProtectedTimelock.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {Duration} from "./types/Duration.sol";
import {Timestamp} from "./types/Timestamp.sol";
import {Duration, Durations} from "./types/Duration.sol";

import {IOwnable} from "./interfaces/IOwnable.sol";
import {IEmergencyProtectedTimelock} from "./interfaces/IEmergencyProtectedTimelock.sol";
Expand Down Expand Up @@ -32,17 +32,23 @@ contract EmergencyProtectedTimelock is IEmergencyProtectedTimelock {
// ---

/// @notice The parameters for the sanity checks.
/// @param minExecutionDelay The minimum allowable duration for the combined after-submit and after-schedule delays.
/// @param maxAfterSubmitDelay The maximum allowable delay before a submitted proposal can be scheduled for execution.
/// @param maxAfterScheduleDelay The maximum allowable delay before a scheduled proposal can be executed.
/// @param maxEmergencyModeDuration The maximum time the timelock can remain in emergency mode.
/// @param maxEmergencyProtectionDuration The maximum time the emergency protection mechanism can be activated.
struct SanityCheckParams {
Duration minExecutionDelay;
Duration maxAfterSubmitDelay;
Duration maxAfterScheduleDelay;
Duration maxEmergencyModeDuration;
Duration maxEmergencyProtectionDuration;
}

/// @notice Represents the minimum allowed time that must pass between the submission of a proposal and its execution.
/// @dev The minimum permissible value for the sum of `afterScheduleDelay` and `afterSubmitDelay`.
Duration public immutable MIN_EXECUTION_DELAY;

/// @notice The upper bound for the delay required before a submitted proposal can be scheduled for execution.
Duration public immutable MAX_AFTER_SUBMIT_DELAY;

Expand Down Expand Up @@ -79,31 +85,49 @@ contract EmergencyProtectedTimelock is IEmergencyProtectedTimelock {
// Constructor
// ---

constructor(SanityCheckParams memory sanityCheckParams, address adminExecutor) {
constructor(
SanityCheckParams memory sanityCheckParams,
address adminExecutor,
Duration afterSubmitDelay,
Duration afterScheduleDelay
) {
_ADMIN_EXECUTOR = adminExecutor;

MIN_EXECUTION_DELAY = sanityCheckParams.minExecutionDelay;
MAX_AFTER_SUBMIT_DELAY = sanityCheckParams.maxAfterSubmitDelay;
MAX_AFTER_SCHEDULE_DELAY = sanityCheckParams.maxAfterScheduleDelay;
MAX_EMERGENCY_MODE_DURATION = sanityCheckParams.maxEmergencyModeDuration;
MAX_EMERGENCY_PROTECTION_DURATION = sanityCheckParams.maxEmergencyProtectionDuration;

if (afterSubmitDelay > Durations.ZERO) {
_timelockState.setAfterSubmitDelay(afterSubmitDelay, MAX_AFTER_SUBMIT_DELAY);
}

if (afterScheduleDelay > Durations.ZERO) {
_timelockState.setAfterScheduleDelay(afterScheduleDelay, MAX_AFTER_SCHEDULE_DELAY);
}

_timelockState.checkExecutionDelay(MIN_EXECUTION_DELAY);
}

// ---
// Main Timelock Functionality
// ---

/// @notice Submits a new proposal to execute a series of calls through an executor.
/// @param proposer The address of the proposer submitting the proposal.
/// @param executor The address of the executor contract that will execute the calls.
/// @param calls An array of `ExternalCall` structs representing the calls to be executed.
/// @param metadata A string containing additional information about the proposal.
/// @return newProposalId The id of the newly created proposal.
function submit(
address proposer,
address executor,
ExternalCall[] calldata calls,
string calldata metadata
) external returns (uint256 newProposalId) {
_timelockState.checkCallerIsGovernance();
newProposalId = _proposals.submit(executor, calls, metadata);
newProposalId = _proposals.submit(proposer, executor, calls, metadata);
}

/// @notice Schedules a proposal for execution after a specified delay.
Expand Down Expand Up @@ -137,13 +161,22 @@ contract EmergencyProtectedTimelock is IEmergencyProtectedTimelock {
_timelockState.setGovernance(newGovernance);
}

/// @notice Configures the delays for submitting and scheduling proposals, within defined upper bounds.
/// @param afterSubmitDelay The delay required before a submitted proposal can be scheduled.
/// @param afterScheduleDelay The delay required before a scheduled proposal can be executed.
function setupDelays(Duration afterSubmitDelay, Duration afterScheduleDelay) external {
/// @notice Sets the delay required to pass from the submission of a proposal before it can be scheduled for execution.
/// Ensures that the new delay value complies with the defined sanity check bounds.
/// @param newAfterSubmitDelay The delay required before a submitted proposal can be scheduled.
function setAfterSubmitDelay(Duration newAfterSubmitDelay) external {
_checkCallerIsAdminExecutor();
_timelockState.setAfterSubmitDelay(newAfterSubmitDelay, MAX_AFTER_SUBMIT_DELAY);
_timelockState.checkExecutionDelay(MIN_EXECUTION_DELAY);
}

/// @notice Sets the delay required to pass from the scheduling of a proposal before it can be executed.
/// Ensures that the new delay value complies with the defined sanity check bounds.
/// @param newAfterScheduleDelay The delay required before a scheduled proposal can be executed.
function setAfterScheduleDelay(Duration newAfterScheduleDelay) external {
_checkCallerIsAdminExecutor();
_timelockState.setAfterSubmitDelay(afterSubmitDelay, MAX_AFTER_SUBMIT_DELAY);
_timelockState.setAfterScheduleDelay(afterScheduleDelay, MAX_AFTER_SCHEDULE_DELAY);
_timelockState.setAfterScheduleDelay(newAfterScheduleDelay, MAX_AFTER_SCHEDULE_DELAY);
_timelockState.checkExecutionDelay(MIN_EXECUTION_DELAY);
}

/// @notice Transfers ownership of the executor contract to a new owner.
Expand Down
52 changes: 19 additions & 33 deletions contracts/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ contract Escrow is IEscrow {
/// that were previously locked by the vetoer.
/// @param unstETHIds An array of ids representing the unstETH NFTs to be unlocked.
function unlockUnstETH(uint256[] memory unstETHIds) external {
if (unstETHIds.length == 0) {
revert EmptyUnstETHIds();
}
DUAL_GOVERNANCE.activateNextState();
_escrowState.checkSignallingEscrow();
_accounting.checkMinAssetsLockDurationPassed(msg.sender, _escrowState.minAssetsLockDuration);
Expand Down Expand Up @@ -283,33 +286,6 @@ contract Escrow is IEscrow {
DUAL_GOVERNANCE.activateNextState();
}

// ---
// Convert To NFT
// ---

/// @notice Allows vetoers to convert their locked stETH or wstETH tokens into unstETH NFTs on behalf of the
/// Veto Signalling Escrow contract.
/// @param stETHAmounts An array representing the amounts of stETH to be converted into unstETH NFTs.
/// @return unstETHIds An array of ids representing the newly created unstETH NFTs corresponding to
/// the converted stETH amounts.
function requestWithdrawals(uint256[] calldata stETHAmounts) external returns (uint256[] memory unstETHIds) {
DUAL_GOVERNANCE.activateNextState();
_escrowState.checkSignallingEscrow();

unstETHIds = WITHDRAWAL_QUEUE.requestWithdrawals(stETHAmounts, address(this));
WithdrawalRequestStatus[] memory statuses = WITHDRAWAL_QUEUE.getWithdrawalStatus(unstETHIds);

uint256 sharesTotal = 0;
for (uint256 i = 0; i < statuses.length; ++i) {
sharesTotal += statuses[i].amountOfShares;
}
_accounting.accountStETHSharesUnlock(msg.sender, SharesValues.from(sharesTotal));
_accounting.accountUnstETHLock(msg.sender, unstETHIds, statuses);

/// @dev Skip calling activateNextState here to save gas, as converting stETH to unstETH NFTs
/// does not affect the RageQuit support.
}

// ---
// Start Rage Quit
// ---
Expand Down Expand Up @@ -540,23 +516,33 @@ contract Escrow is IEscrow {
state.lastAssetsLockTimestamp = assets.lastAssetsLockTimestamp.toSeconds();
}

// @notice Retrieves the unstETH NFT ids of the specified vetoer.
/// @param vetoer The address of the vetoer whose unstETH NFTs are being queried.
/// @return unstETHIds An array of unstETH NFT ids locked by the vetoer.
function getVetoerUnstETHIds(address vetoer) external view returns (uint256[] memory unstETHIds) {
unstETHIds = _accounting.assets[vetoer].unstETHIds;
}

/// @notice Returns the total count of unstETH NFTs that have not been claimed yet.
/// @return unclaimedUnstETHIdsCount The total number of unclaimed unstETH NFTs.
function getUnclaimedUnstETHIdsCount() external view returns (uint256) {
_escrowState.checkRageQuitEscrow();
return _batchesQueue.getTotalUnclaimedUnstETHIdsCount();
}

/// @notice Retrieves the unstETH NFT ids of the next batch available for claiming.
/// @param limit The maximum number of unstETH NFTs to return in the batch.
/// @return unstETHIds An array of unstETH NFT IDs available for the next withdrawal batch.
/// @return unstETHIds An array of unstETH NFT ids available for the next withdrawal batch.
function getNextWithdrawalBatch(uint256 limit) external view returns (uint256[] memory unstETHIds) {
return _batchesQueue.getNextWithdrawalsBatches(limit);
_escrowState.checkRageQuitEscrow();
unstETHIds = _batchesQueue.getNextWithdrawalsBatches(limit);
}

/// @notice Returns whether all withdrawal batches have been finalized.
/// @return isWithdrawalsBatchesFinalized A boolean value indicating whether all withdrawal batches have been
/// finalized (`true`) or not (`false`).
function isWithdrawalsBatchesFinalized() external view returns (bool) {
/// @notice Returns whether all withdrawal batches have been closed.
/// @return isWithdrawalsBatchesClosed A boolean value indicating whether all withdrawal batches have been
/// closed (`true`) or not (`false`).
function isWithdrawalsBatchesClosed() external view returns (bool) {
_escrowState.checkRageQuitEscrow();
return _batchesQueue.isClosed();
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/ResealManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ contract ResealManager is IResealManager {
_checkCallerIsGovernance();

uint256 sealableResumeSinceTimestamp = ISealable(sealable).getResumeSinceTimestamp();
if (sealableResumeSinceTimestamp < block.timestamp || sealableResumeSinceTimestamp == PAUSE_INFINITELY) {
if (block.timestamp >= sealableResumeSinceTimestamp || sealableResumeSinceTimestamp == PAUSE_INFINITELY) {
revert SealableWrongPauseState();
}
Address.functionCall(sealable, abi.encodeWithSelector(ISealable.resume.selector));
Expand All @@ -66,7 +66,7 @@ contract ResealManager is IResealManager {
_checkCallerIsGovernance();

uint256 sealableResumeSinceTimestamp = ISealable(sealable).getResumeSinceTimestamp();
if (sealableResumeSinceTimestamp < block.timestamp) {
if (block.timestamp >= sealableResumeSinceTimestamp) {
revert SealableWrongPauseState();
}
Address.functionCall(sealable, abi.encodeWithSelector(ISealable.resume.selector));
Expand Down
16 changes: 9 additions & 7 deletions contracts/TimelockedGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ contract TimelockedGovernance is IGovernance {
// ---

error CallerIsNotGovernance(address caller);
error InvalidGovernance(address governance);
error InvalidTimelock(ITimelock timelock);

// ---
// Immutable Variables
Expand All @@ -30,6 +32,12 @@ contract TimelockedGovernance is IGovernance {
/// @param governance The address of the governance contract.
/// @param timelock The address of the timelock contract.
constructor(address governance, ITimelock timelock) {
if (governance == address(0)) {
revert InvalidGovernance(governance);
}
if (address(timelock) == address(0)) {
revert InvalidTimelock(timelock);
}
GOVERNANCE = governance;
TIMELOCK = timelock;
}
Expand All @@ -43,7 +51,7 @@ contract TimelockedGovernance is IGovernance {
string calldata metadata
) external returns (uint256 proposalId) {
_checkCallerIsGovernance();
return TIMELOCK.submit(TIMELOCK.getAdminExecutor(), calls, metadata);
return TIMELOCK.submit(msg.sender, TIMELOCK.getAdminExecutor(), calls, metadata);
}

/// @notice Schedules a submitted proposal.
Expand All @@ -52,12 +60,6 @@ contract TimelockedGovernance is IGovernance {
TIMELOCK.schedule(proposalId);
}

/// @notice Executes a scheduled proposal.
/// @param proposalId The id of the proposal to be executed.
function executeProposal(uint256 proposalId) external {
TIMELOCK.execute(proposalId);
}

/// @notice Checks if a proposal can be scheduled.
/// @param proposalId The id of the proposal to check.
/// @return A boolean indicating whether the proposal can be scheduled.
Expand Down
Loading