From f4810ff2e8e0e75c15b8698d28adebf53edaf0a3 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:48:16 -0600 Subject: [PATCH 01/43] feat: total delegation poc first attempt --- src/L2ArbitrumToken.sol | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index fe9ba482b..05dc8c3eb 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -8,6 +8,7 @@ import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20Pe import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "./TransferAndCallToken.sol"; +import "@openzeppelin/contracts/utils/Checkpoints.sol"; /// @title L2 Arbitrum Token /// @notice The L2 counterparty of the Arbitrum token. @@ -27,6 +28,8 @@ contract L2ArbitrumToken is OwnableUpgradeable, TransferAndCallToken { + using Checkpoints for Checkpoints.History; + string private constant NAME = "Arbitrum"; string private constant SYMBOL = "ARB"; /// @notice The minimum amount of time that must elapse before a mint is allowed @@ -41,6 +44,42 @@ contract L2ArbitrumToken is /// @notice The time at which the next mint is allowed - timestamp uint256 public nextMint; + /// @dev History of the total amount of delegated tokens + /// The initial value is an estimate of the total delegation at the time of upgrade proposal creation. + /// Another proposal can be made later to update this value if needed. + Checkpoints.History private _totalDelegationHistory; + + /// @notice Called at proposal #1 + /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. + /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. + function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { + _totalDelegationHistory.push(initialTotalDelegation); + } + + /// @notice Called at proposal #2 + /// @param initialEstimationErrorAdjustment The amount the adjustment was off by, negated. This is added to the current total delegation. + function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { + _totalDelegationHistory.push( + uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) + ); + } + + function getTotalDelegation() + external + view + returns (uint256) + { + return _totalDelegationHistory.latest(); + } + + function getTotalDelegationAt(uint256 blockNumber) + external + view + returns (uint256) + { + return _totalDelegationHistory.getAtBlock(blockNumber); + } + constructor() { _disableInitializers(); } @@ -89,6 +128,24 @@ contract L2ArbitrumToken is override(ERC20Upgradeable, ERC20VotesUpgradeable) { super._afterTokenTransfer(from, to, amount); + + address fromDelegate = delegates(from); + address toDelegate = delegates(to); + + if (fromDelegate != toDelegate) { + int256 delta = 0; + if (fromDelegate != address(0)) { + delta -= int256(amount); + } + if (toDelegate != address(0)) { + delta += int256(amount); + } + if (delta != 0) { + _totalDelegationHistory.push( + uint256(int256(_totalDelegationHistory.latest()) + delta) + ); + } + } } function _mint(address to, uint256 amount) From 07475a273fd5462509ddda7b1c39977aef7cdf23 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:06:46 -0700 Subject: [PATCH 02/43] update history on delegate --- src/L2ArbitrumToken.sol | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 05dc8c3eb..f0f60b1fc 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -123,15 +123,12 @@ contract L2ArbitrumToken is _mint(recipient, amount); } - function _afterTokenTransfer(address from, address to, uint256 amount) - internal - override(ERC20Upgradeable, ERC20VotesUpgradeable) - { - super._afterTokenTransfer(from, to, amount); - - address fromDelegate = delegates(from); - address toDelegate = delegates(to); + function _updateDelegationHistory( + address fromDelegate, + address toDelegate, + uint256 amount + ) internal { if (fromDelegate != toDelegate) { int256 delta = 0; if (fromDelegate != address(0)) { @@ -148,6 +145,27 @@ contract L2ArbitrumToken is } } + function _delegate(address delegator, address delegatee) internal virtual override { + super._delegate(delegator, delegatee); + _updateDelegationHistory( + delegates(delegator), + delegatee, + balanceOf(delegator) + ); + } + + function _afterTokenTransfer(address from, address to, uint256 amount) + internal + override(ERC20Upgradeable, ERC20VotesUpgradeable) + { + super._afterTokenTransfer(from, to, amount); + _updateDelegationHistory( + delegates(from), + delegates(to), + amount + ); + } + function _mint(address to, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) From c18ff53fbb3ef5ab24fcdb3a2be434072cda4158 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:57:05 -0700 Subject: [PATCH 03/43] update comment --- src/L2ArbitrumToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index f0f60b1fc..db90df9a5 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -57,7 +57,7 @@ contract L2ArbitrumToken is } /// @notice Called at proposal #2 - /// @param initialEstimationErrorAdjustment The amount the adjustment was off by, negated. This is added to the current total delegation. + /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { _totalDelegationHistory.push( uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) From 355dfc6730426e5bf64879c135b2c7775c653c01 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:08:18 -0600 Subject: [PATCH 04/43] comment --- src/L2ArbitrumToken.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index db90df9a5..658f30226 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -64,6 +64,8 @@ contract L2ArbitrumToken is ); } + /// @notice Get the current total delegation + /// @return The current total delegation function getTotalDelegation() external view @@ -72,6 +74,10 @@ contract L2ArbitrumToken is return _totalDelegationHistory.latest(); } + /// @notice Get the total delegation at a specific block number + /// If the blockNumber is prior to the first checkpoint, returns 0 + /// @param blockNumber The block number to get the total delegation at + /// @return The total delegation at the given block number function getTotalDelegationAt(uint256 blockNumber) external view From ace5babb03d936a7ca370a50507ffc680c976ee4 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:51:55 -0600 Subject: [PATCH 05/43] use deltas and anchor --- src/L2ArbitrumToken.sol | 114 +++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 658f30226..26f8dff27 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -44,47 +44,11 @@ contract L2ArbitrumToken is /// @notice The time at which the next mint is allowed - timestamp uint256 public nextMint; - /// @dev History of the total amount of delegated tokens - /// The initial value is an estimate of the total delegation at the time of upgrade proposal creation. - /// Another proposal can be made later to update this value if needed. - Checkpoints.History private _totalDelegationHistory; - - /// @notice Called at proposal #1 - /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. - /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. - function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { - _totalDelegationHistory.push(initialTotalDelegation); - } - - /// @notice Called at proposal #2 - /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. - function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { - _totalDelegationHistory.push( - uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) - ); - } - - /// @notice Get the current total delegation - /// @return The current total delegation - function getTotalDelegation() - external - view - returns (uint256) - { - return _totalDelegationHistory.latest(); - } - - /// @notice Get the total delegation at a specific block number - /// If the blockNumber is prior to the first checkpoint, returns 0 - /// @param blockNumber The block number to get the total delegation at - /// @return The total delegation at the given block number - function getTotalDelegationAt(uint256 blockNumber) - external - view - returns (uint256) - { - return _totalDelegationHistory.getAtBlock(blockNumber); - } + /// @dev History of the change in total amount of delegated tokens + /// Tracks the delta between total delegation at a given block and the anchor + /// The anchor is set via setTotalDelegationAnchor as a standalone one-time operation + Checkpoints.History private _totalDelegationDeltaHistory; + uint256 private _totalDelegationAnchor; constructor() { _disableInitializers(); @@ -114,6 +78,11 @@ contract L2ArbitrumToken is _transferOwnership(_owner); } + function setTotalDelegationAnchor(uint256 totalDelegationAnchor) external onlyOwner { + require(_totalDelegationAnchor == 0, "ARB: ANCHOR_ALREADY_SET"); + _totalDelegationAnchor = totalDelegationAnchor; + } + /// @notice Allows the owner to mint new tokens /// @dev Only allows minting below an inflation cap. /// Set to once per year, and a maximum of 2%. @@ -129,12 +98,48 @@ contract L2ArbitrumToken is _mint(recipient, amount); } + /// @notice Get the current total delegation + /// @dev If the anchor is unset, returns 0 + /// @return The current total delegation + function getTotalDelegation() external view returns (uint256) { + uint256 __totalDelegationAnchor = _totalDelegationAnchor; + if (__totalDelegationAnchor == 0) { + return 0; + } + return _adjustByAnchor(_totalDelegationDeltaHistory.latest(), __totalDelegationAnchor); + } + + /// @notice Get the total delegation at a specific block number + /// @dev If the anchor is unset, or the block number is before the first checkpoint, returns 0 + /// @param blockNumber The block number to get the total delegation at + /// @return The total delegation at the given block number + function getTotalDelegationAt(uint256 blockNumber) external view returns (uint256) { + uint256 __totalDelegationAnchor = _totalDelegationAnchor; + if ( + __totalDelegationAnchor == 0 + || blockNumber < _totalDelegationDeltaHistory._checkpoints[0]._blockNumber + ) { + return 0; + } + return _adjustByAnchor( + _totalDelegationDeltaHistory.getAtBlock(blockNumber), __totalDelegationAnchor + ); + } - function _updateDelegationHistory( - address fromDelegate, - address toDelegate, - uint256 amount - ) internal { + /// @dev Helper function to adjust a delegation delta checkpoint return value by an anchor + /// The checkpoint return value is stored as a uint224, but we treat it as a signed int224. + /// This is why the casting is a bit complex. + function _adjustByAnchor(uint256 checkpointReturnValue, uint256 anchor) + internal + pure + returns (uint256) + { + return uint256(int224(uint224(checkpointReturnValue)) + int256(anchor)); + } + + function _updateDelegationHistory(address fromDelegate, address toDelegate, uint256 amount) + internal + { if (fromDelegate != toDelegate) { int256 delta = 0; if (fromDelegate != address(0)) { @@ -144,20 +149,15 @@ contract L2ArbitrumToken is delta += int256(amount); } if (delta != 0) { - _totalDelegationHistory.push( - uint256(int256(_totalDelegationHistory.latest()) + delta) - ); + int224 latestDelta = int224(uint224(_totalDelegationDeltaHistory.latest())); + _totalDelegationDeltaHistory.push(uint256(latestDelta + delta)); } } } function _delegate(address delegator, address delegatee) internal virtual override { super._delegate(delegator, delegatee); - _updateDelegationHistory( - delegates(delegator), - delegatee, - balanceOf(delegator) - ); + _updateDelegationHistory(delegates(delegator), delegatee, balanceOf(delegator)); } function _afterTokenTransfer(address from, address to, uint256 amount) @@ -165,11 +165,7 @@ contract L2ArbitrumToken is override(ERC20Upgradeable, ERC20VotesUpgradeable) { super._afterTokenTransfer(from, to, amount); - _updateDelegationHistory( - delegates(from), - delegates(to), - amount - ); + _updateDelegationHistory(delegates(from), delegates(to), amount); } function _mint(address to, uint256 amount) From 2811442c6318e06f363dab75ae4b02bbdcce3d89 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 9 Oct 2025 08:08:39 -0600 Subject: [PATCH 06/43] Revert "use deltas and anchor" This reverts commit ace5babb03d936a7ca370a50507ffc680c976ee4. --- src/L2ArbitrumToken.sol | 114 +++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 26f8dff27..658f30226 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -44,11 +44,47 @@ contract L2ArbitrumToken is /// @notice The time at which the next mint is allowed - timestamp uint256 public nextMint; - /// @dev History of the change in total amount of delegated tokens - /// Tracks the delta between total delegation at a given block and the anchor - /// The anchor is set via setTotalDelegationAnchor as a standalone one-time operation - Checkpoints.History private _totalDelegationDeltaHistory; - uint256 private _totalDelegationAnchor; + /// @dev History of the total amount of delegated tokens + /// The initial value is an estimate of the total delegation at the time of upgrade proposal creation. + /// Another proposal can be made later to update this value if needed. + Checkpoints.History private _totalDelegationHistory; + + /// @notice Called at proposal #1 + /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. + /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. + function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { + _totalDelegationHistory.push(initialTotalDelegation); + } + + /// @notice Called at proposal #2 + /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. + function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { + _totalDelegationHistory.push( + uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) + ); + } + + /// @notice Get the current total delegation + /// @return The current total delegation + function getTotalDelegation() + external + view + returns (uint256) + { + return _totalDelegationHistory.latest(); + } + + /// @notice Get the total delegation at a specific block number + /// If the blockNumber is prior to the first checkpoint, returns 0 + /// @param blockNumber The block number to get the total delegation at + /// @return The total delegation at the given block number + function getTotalDelegationAt(uint256 blockNumber) + external + view + returns (uint256) + { + return _totalDelegationHistory.getAtBlock(blockNumber); + } constructor() { _disableInitializers(); @@ -78,11 +114,6 @@ contract L2ArbitrumToken is _transferOwnership(_owner); } - function setTotalDelegationAnchor(uint256 totalDelegationAnchor) external onlyOwner { - require(_totalDelegationAnchor == 0, "ARB: ANCHOR_ALREADY_SET"); - _totalDelegationAnchor = totalDelegationAnchor; - } - /// @notice Allows the owner to mint new tokens /// @dev Only allows minting below an inflation cap. /// Set to once per year, and a maximum of 2%. @@ -98,48 +129,12 @@ contract L2ArbitrumToken is _mint(recipient, amount); } - /// @notice Get the current total delegation - /// @dev If the anchor is unset, returns 0 - /// @return The current total delegation - function getTotalDelegation() external view returns (uint256) { - uint256 __totalDelegationAnchor = _totalDelegationAnchor; - if (__totalDelegationAnchor == 0) { - return 0; - } - return _adjustByAnchor(_totalDelegationDeltaHistory.latest(), __totalDelegationAnchor); - } - - /// @notice Get the total delegation at a specific block number - /// @dev If the anchor is unset, or the block number is before the first checkpoint, returns 0 - /// @param blockNumber The block number to get the total delegation at - /// @return The total delegation at the given block number - function getTotalDelegationAt(uint256 blockNumber) external view returns (uint256) { - uint256 __totalDelegationAnchor = _totalDelegationAnchor; - if ( - __totalDelegationAnchor == 0 - || blockNumber < _totalDelegationDeltaHistory._checkpoints[0]._blockNumber - ) { - return 0; - } - return _adjustByAnchor( - _totalDelegationDeltaHistory.getAtBlock(blockNumber), __totalDelegationAnchor - ); - } - /// @dev Helper function to adjust a delegation delta checkpoint return value by an anchor - /// The checkpoint return value is stored as a uint224, but we treat it as a signed int224. - /// This is why the casting is a bit complex. - function _adjustByAnchor(uint256 checkpointReturnValue, uint256 anchor) - internal - pure - returns (uint256) - { - return uint256(int224(uint224(checkpointReturnValue)) + int256(anchor)); - } - - function _updateDelegationHistory(address fromDelegate, address toDelegate, uint256 amount) - internal - { + function _updateDelegationHistory( + address fromDelegate, + address toDelegate, + uint256 amount + ) internal { if (fromDelegate != toDelegate) { int256 delta = 0; if (fromDelegate != address(0)) { @@ -149,15 +144,20 @@ contract L2ArbitrumToken is delta += int256(amount); } if (delta != 0) { - int224 latestDelta = int224(uint224(_totalDelegationDeltaHistory.latest())); - _totalDelegationDeltaHistory.push(uint256(latestDelta + delta)); + _totalDelegationHistory.push( + uint256(int256(_totalDelegationHistory.latest()) + delta) + ); } } } function _delegate(address delegator, address delegatee) internal virtual override { super._delegate(delegator, delegatee); - _updateDelegationHistory(delegates(delegator), delegatee, balanceOf(delegator)); + _updateDelegationHistory( + delegates(delegator), + delegatee, + balanceOf(delegator) + ); } function _afterTokenTransfer(address from, address to, uint256 amount) @@ -165,7 +165,11 @@ contract L2ArbitrumToken is override(ERC20Upgradeable, ERC20VotesUpgradeable) { super._afterTokenTransfer(from, to, amount); - _updateDelegationHistory(delegates(from), delegates(to), amount); + _updateDelegationHistory( + delegates(from), + delegates(to), + amount + ); } function _mint(address to, uint256 amount) From 7e2700aad1d86ccb54b2b63abce4bc221884f114 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 9 Oct 2025 09:50:11 -0600 Subject: [PATCH 07/43] document risk --- src/L2ArbitrumToken.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 658f30226..64908f94e 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -50,6 +50,9 @@ contract L2ArbitrumToken is Checkpoints.History private _totalDelegationHistory; /// @notice Called at proposal #1 + /// The initial estimate may be manipulable with artificial delegation/undelegation prior to the upgrade. + /// Since this value is only used for quorum calculation, and the quroum is clamped by the governors to an acceptable range, + /// the risk/impact of manipulation is low. /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { From 43d7b2e83286d1a927d5252d25ebe514d567c6cb Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:45:20 -0600 Subject: [PATCH 08/43] nonnegative tdh --- src/L2ArbitrumToken.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 64908f94e..8cb6651bf 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -147,8 +147,9 @@ contract L2ArbitrumToken is delta += int256(amount); } if (delta != 0) { + int256 newValue = int256(_totalDelegationHistory.latest()) + delta; _totalDelegationHistory.push( - uint256(int256(_totalDelegationHistory.latest()) + delta) + uint256(newValue < 0 ? int256(0) : newValue) ); } } From 00d35afe884235a6ef9e5baa8b426d0788c19431 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:46:54 -0600 Subject: [PATCH 09/43] comment --- src/L2ArbitrumToken.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 8cb6651bf..460815505 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -147,6 +147,9 @@ contract L2ArbitrumToken is delta += int256(amount); } if (delta != 0) { + // if the initial estimate is too low, and a large amount of tokens are undelegated + // it is technically possible that the newValue is negative + // if this happens, we clamp it to zero int256 newValue = int256(_totalDelegationHistory.latest()) + delta; _totalDelegationHistory.push( uint256(newValue < 0 ? int256(0) : newValue) From df46e5e895e24c4e4e15d17dd6a5458cf68fa862 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 10 Oct 2025 09:03:27 -0600 Subject: [PATCH 10/43] rearrange --- src/L2ArbitrumToken.sol | 81 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 460815505..50f40b523 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -49,46 +49,6 @@ contract L2ArbitrumToken is /// Another proposal can be made later to update this value if needed. Checkpoints.History private _totalDelegationHistory; - /// @notice Called at proposal #1 - /// The initial estimate may be manipulable with artificial delegation/undelegation prior to the upgrade. - /// Since this value is only used for quorum calculation, and the quroum is clamped by the governors to an acceptable range, - /// the risk/impact of manipulation is low. - /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. - /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. - function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { - _totalDelegationHistory.push(initialTotalDelegation); - } - - /// @notice Called at proposal #2 - /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. - function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { - _totalDelegationHistory.push( - uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) - ); - } - - /// @notice Get the current total delegation - /// @return The current total delegation - function getTotalDelegation() - external - view - returns (uint256) - { - return _totalDelegationHistory.latest(); - } - - /// @notice Get the total delegation at a specific block number - /// If the blockNumber is prior to the first checkpoint, returns 0 - /// @param blockNumber The block number to get the total delegation at - /// @return The total delegation at the given block number - function getTotalDelegationAt(uint256 blockNumber) - external - view - returns (uint256) - { - return _totalDelegationHistory.getAtBlock(blockNumber); - } - constructor() { _disableInitializers(); } @@ -117,6 +77,24 @@ contract L2ArbitrumToken is _transferOwnership(_owner); } + /// @notice Called at proposal #1 + /// The initial estimate may be manipulable with artificial delegation/undelegation prior to the upgrade. + /// Since this value is only used for quorum calculation, and the quroum is clamped by the governors to an acceptable range, + /// the risk/impact of manipulation is low. + /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. + /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. + function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { + _totalDelegationHistory.push(initialTotalDelegation); + } + + /// @notice Called at proposal #2 + /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. + function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { + _totalDelegationHistory.push( + uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) + ); + } + /// @notice Allows the owner to mint new tokens /// @dev Only allows minting below an inflation cap. /// Set to once per year, and a maximum of 2%. @@ -132,6 +110,27 @@ contract L2ArbitrumToken is _mint(recipient, amount); } + /// @notice Get the current total delegation + /// @return The current total delegation + function getTotalDelegation() + external + view + returns (uint256) + { + return _totalDelegationHistory.latest(); + } + + /// @notice Get the total delegation at a specific block number + /// If the blockNumber is prior to the first checkpoint, returns 0 + /// @param blockNumber The block number to get the total delegation at + /// @return The total delegation at the given block number + function getTotalDelegationAt(uint256 blockNumber) + external + view + returns (uint256) + { + return _totalDelegationHistory.getAtBlock(blockNumber); + } function _updateDelegationHistory( address fromDelegate, @@ -149,7 +148,7 @@ contract L2ArbitrumToken is if (delta != 0) { // if the initial estimate is too low, and a large amount of tokens are undelegated // it is technically possible that the newValue is negative - // if this happens, we clamp it to zero + // if this happens, we clamp it to zero to avoid underflow int256 newValue = int256(_totalDelegationHistory.latest()) + delta; _totalDelegationHistory.push( uint256(newValue < 0 ? int256(0) : newValue) From 0aa3a10fcdd39c60ac8f81c5fd1b406b0d04bea3 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:09:48 -0600 Subject: [PATCH 11/43] tests --- src/L2ArbitrumToken.sol | 10 +- test/L2ArbitrumToken.t.sol | 267 +++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 4 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 50f40b523..b200c1af2 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -83,15 +83,17 @@ contract L2ArbitrumToken is /// the risk/impact of manipulation is low. /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. - function postUpgradeInit1(uint256 initialTotalDelegation) external onlyOwner { + function postUpgradeInit(uint256 initialTotalDelegation) external onlyOwner { _totalDelegationHistory.push(initialTotalDelegation); } /// @notice Called at proposal #2 /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. - function postUpgradeInit2(int256 initialEstimationErrorAdjustment) external onlyOwner { + function adjustInitialTotalDelegationEstimate(int256 initialEstimationErrorAdjustment) external onlyOwner { + int256 newValue = int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment; + _totalDelegationHistory.push( - uint256(int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment) + uint256(newValue < 0 ? int256(0) : newValue) ); } @@ -158,12 +160,12 @@ contract L2ArbitrumToken is } function _delegate(address delegator, address delegatee) internal virtual override { - super._delegate(delegator, delegatee); _updateDelegationHistory( delegates(delegator), delegatee, balanceOf(delegator) ); + super._delegate(delegator, delegatee); } function _afterTokenTransfer(address from, address to, uint256 amount) diff --git a/test/L2ArbitrumToken.t.sol b/test/L2ArbitrumToken.t.sol index 89f0c9653..a1ee05c8b 100644 --- a/test/L2ArbitrumToken.t.sol +++ b/test/L2ArbitrumToken.t.sol @@ -35,6 +35,273 @@ contract L2ArbitrumTokenTest is Test { l2Token.initialize(l1Token, initialSupply, owner); } + // test initial estimate + function testInitialDvpEstimate(uint64 initialEstimate) public { + L2ArbitrumToken l2Token = deployAndInit(); + + // set an initial estimate + vm.prank(owner); + l2Token.postUpgradeInit(initialEstimate); + + assertEq( + l2Token.getTotalDelegation(), + initialEstimate + ); + } + + // test adjustment + function testDvpAdjustment( + uint64 initialEstimate, + int64 adjustment + ) public { + int256 expected = int256(uint256(initialEstimate)) + int256(adjustment); + if (expected < 0) { + expected = 0; + } + + L2ArbitrumToken l2Token = deployAndInit(); + + // set an initial estimate + vm.prank(owner); + l2Token.postUpgradeInit(initialEstimate); + + // adjust the estimate + vm.prank(owner); + l2Token.adjustInitialTotalDelegationEstimate(adjustment); + + assertEq( + l2Token.getTotalDelegation(), + uint256(expected) + ); + } + + // test goes up when self delegating + function testIncreaseDVPOnSelfDelegate() public { + L2ArbitrumToken l2Token = deployAndInit(); + vm.prank(owner); + l2Token.postUpgradeInit(10); + + // delegate some tokens + vm.prank(owner); + l2Token.delegate(owner); + + assertEq( + l2Token.getTotalDelegation(), 10 + initialSupply + ); + } + + // test goes up when delegating to another + function testIncreaseDVPOnDelegateToAnother() public { + L2ArbitrumToken l2Token = deployAndInit(); + vm.prank(owner); + l2Token.postUpgradeInit(10); + + vm.prank(owner); + l2Token.delegate(address(1)); + + assertEq( + l2Token.getTotalDelegation(), 10 + initialSupply + ); + } + + // test does not change when redelegating to same or another + function testNoChangeDVPOnRedelegateToSame() public { + L2ArbitrumToken l2Token = deployAndInit(); + vm.prank(owner); + l2Token.postUpgradeInit(0); + + // delegate some tokens + vm.prank(owner); + l2Token.delegate(owner); + assertEq( + l2Token.getTotalDelegation(), initialSupply + ); + + // redelegate to self again + vm.prank(owner); + l2Token.delegate(owner); + assertEq( + l2Token.getTotalDelegation(), initialSupply + ); + + // redelegate to another + vm.prank(owner); + l2Token.delegate(address(1)); + assertEq( + l2Token.getTotalDelegation(), initialSupply + ); + + // redelegate to another again + vm.prank(owner); + l2Token.delegate(address(1)); + assertEq( + l2Token.getTotalDelegation(), initialSupply + ); + } + + // test goes down when undelegating + function testDecreaseDVPOnUndelegate() public { + L2ArbitrumToken l2Token = deployAndInit(); + vm.prank(owner); + l2Token.postUpgradeInit(10); + + // delegate some tokens + vm.prank(owner); + l2Token.delegate(owner); + assertEq( + l2Token.getTotalDelegation(), 10 + initialSupply + ); + + // undelegate + vm.prank(owner); + l2Token.delegate(address(0)); + assertEq(l2Token.getTotalDelegation(), 10); + } + + // test does not revert on underflow + function testDvpNoRevertOnUnderflow() public { + L2ArbitrumToken l2Token = deployAndInit(); + + // delegate some tokens + vm.prank(owner); + l2Token.delegate(owner); + + // lower the estimate by some + vm.prank(owner); + l2Token.adjustInitialTotalDelegationEstimate(-10); + + // create a snapshot so we can test transfer and undelegate + uint256 snap = vm.snapshot(); + + // undelegate should NOT REVERT + vm.prank(owner); + l2Token.delegate(address(0)); + + // final value should be zero + assertEq(l2Token.getTotalDelegation(), 0); + + // transfer should NOT REVERT + vm.revertTo(snap); + assertEq( + l2Token.getTotalDelegation(), initialSupply - 10 + ); + vm.prank(owner); + l2Token.transfer(address(1234), initialSupply); + assertEq(l2Token.getTotalDelegation(), 0); + } + + function testDvpIncreaseOnTransferToDelegator() public { + L2ArbitrumToken l2Token = deployAndInit(); + + address recipient = address(1234); + + // delegate some tokens + vm.prank(recipient); + l2Token.delegate(address(1)); + + uint256 transferAmount = 105; + + vm.prank(owner); + l2Token.transfer(recipient, transferAmount); + + assertEq( + l2Token.getTotalDelegation(), + transferAmount + ); + } + + function testDvpNoChangeOnTransferToNonDelegator() public { + L2ArbitrumToken l2Token = deployAndInit(); + + address recipient = address(1234); + + vm.prank(owner); + l2Token.transfer(recipient, 105); + + assertEq(l2Token.getTotalDelegation(), 0); + } + + function testDvpNoChangeOnTransferToDelegator() public { + L2ArbitrumToken l2Token = deployAndInit(); + + address recipient = address(1234); + + // delegate some tokens + vm.prank(recipient); + l2Token.delegate(address(1)); + vm.prank(owner); + l2Token.delegate(address(2)); + + assertEq(l2Token.getTotalDelegation(), initialSupply); + + uint256 transferAmount = 105; + + vm.prank(owner); + l2Token.transfer(recipient, transferAmount); + + assertEq(l2Token.getTotalDelegation(), initialSupply); + } + + function testDvpNoChangeOnSelfTransfer() public { + L2ArbitrumToken l2Token = deployAndInit(); + + // delegate some tokens + vm.prank(owner); + l2Token.delegate(address(1)); + + assertEq(l2Token.getTotalDelegation(), initialSupply); + + uint256 transferAmount = 105; + + vm.prank(owner); + l2Token.transfer(owner, transferAmount); + + assertEq(l2Token.getTotalDelegation(), initialSupply); + + vm.prank(owner); + l2Token.transfer(address(2), transferAmount); + assertEq( + l2Token.getTotalDelegation(), initialSupply - transferAmount + ); + vm.prank(address(2)); + l2Token.transfer(address(2), transferAmount); + assertEq( + l2Token.getTotalDelegation(), initialSupply - transferAmount + ); + } + + function testDvpDecreaseOnTransferFromDelegator() public { + L2ArbitrumToken l2Token = deployAndInit(); + + uint256 transferAmount = 105; + + vm.prank(owner); + l2Token.delegate(address(1)); + + assertEq(l2Token.getTotalDelegation(), initialSupply); + + vm.prank(owner); + l2Token.transfer(address(2), transferAmount); + assertEq( + l2Token.getTotalDelegation(), initialSupply - transferAmount + ); + } + + // test when block is before first checkpoint + function testDvpAtBlockBeforeFirstCheckpoint() public { + L2ArbitrumToken l2Token = deployAndInit(); + vm.prank(owner); + l2Token.postUpgradeInit(10); + + uint256 blockNum = block.number; + + vm.roll(blockNum + 1); + + assertEq(l2Token.getTotalDelegationAt(blockNum - 1), 0); + assertEq(l2Token.getTotalDelegationAt(blockNum), 10); + assertEq(l2Token.getTotalDelegation(), 10); + } + function testNoLogicContractInit() public { L2ArbitrumToken token = new L2ArbitrumToken(); From d8fa38fae952c0f3fba39c03aca537e481941908 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:09:37 -0600 Subject: [PATCH 12/43] update sigs and storage --- test/signatures/L2ArbitrumToken | 8 ++++++++ test/storage/L2ArbitrumToken | 2 ++ 2 files changed, 10 insertions(+) diff --git a/test/signatures/L2ArbitrumToken b/test/signatures/L2ArbitrumToken index e2ecfc269..b0bb37f50 100644 --- a/test/signatures/L2ArbitrumToken +++ b/test/signatures/L2ArbitrumToken @@ -10,6 +10,8 @@ |---------------------------------------------------------------+------------| | MIN_MINT_INTERVAL() | a9f8ad04 | |---------------------------------------------------------------+------------| +| adjustInitialTotalDelegationEstimate(int256) | 4123cad7 | +|---------------------------------------------------------------+------------| | allowance(address,address) | dd62ed3e | |---------------------------------------------------------------+------------| | approve(address,uint256) | 095ea7b3 | @@ -36,6 +38,10 @@ |---------------------------------------------------------------+------------| | getPastVotes(address,uint256) | 3a46b1a8 | |---------------------------------------------------------------+------------| +| getTotalDelegation() | 69455e6a | +|---------------------------------------------------------------+------------| +| getTotalDelegationAt(uint256) | 3d95bd78 | +|---------------------------------------------------------------+------------| | getVotes(address) | 9ab24eb0 | |---------------------------------------------------------------+------------| | increaseAllowance(address,uint256) | 39509351 | @@ -58,6 +64,8 @@ |---------------------------------------------------------------+------------| | permit(address,address,uint256,uint256,uint8,bytes32,bytes32) | d505accf | |---------------------------------------------------------------+------------| +| postUpgradeInit(uint256) | 7f257770 | +|---------------------------------------------------------------+------------| | renounceOwnership() | 715018a6 | |---------------------------------------------------------------+------------| | symbol() | 95d89b41 | diff --git a/test/storage/L2ArbitrumToken b/test/storage/L2ArbitrumToken index 93dd0cea2..dbca9e5b0 100644 --- a/test/storage/L2ArbitrumToken +++ b/test/storage/L2ArbitrumToken @@ -49,5 +49,7 @@ | l1Address | address | 354 | 0 | 20 | src/L2ArbitrumToken.sol:L2ArbitrumToken | |----------------------------------+---------------------------------------------------------------+------+--------+-------+-----------------------------------------| | nextMint | uint256 | 355 | 0 | 32 | src/L2ArbitrumToken.sol:L2ArbitrumToken | +|----------------------------------+---------------------------------------------------------------+------+--------+-------+-----------------------------------------| +| _totalDelegationHistory | struct Checkpoints.History | 356 | 0 | 32 | src/L2ArbitrumToken.sol:L2ArbitrumToken | ╰----------------------------------+---------------------------------------------------------------+------+--------+-------+-----------------------------------------╯ From b848bd0ea4338abd61dbe5a462056938cfc21899 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:09:49 -0600 Subject: [PATCH 13/43] require adjustment nonnegative --- src/L2ArbitrumToken.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index b200c1af2..19367a179 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -91,10 +91,12 @@ contract L2ArbitrumToken is /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. function adjustInitialTotalDelegationEstimate(int256 initialEstimationErrorAdjustment) external onlyOwner { int256 newValue = int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment; - - _totalDelegationHistory.push( - uint256(newValue < 0 ? int256(0) : newValue) - ); + + // negative newValue should be impossible + // since the adjustment should bring the value to true total delegation + // which is at minimum zero + require(newValue >= 0, "ARB: NEGATIVE_TOTAL_DELEGATION"); + _totalDelegationHistory.push(uint256(newValue)); } /// @notice Allows the owner to mint new tokens From 2cd3eeb78afbe69097d81cf32ad6e994857d5e9b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 09:29:15 -0600 Subject: [PATCH 14/43] fix test --- test/L2ArbitrumToken.t.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/L2ArbitrumToken.t.sol b/test/L2ArbitrumToken.t.sol index a1ee05c8b..9f9f25c3c 100644 --- a/test/L2ArbitrumToken.t.sol +++ b/test/L2ArbitrumToken.t.sol @@ -55,9 +55,6 @@ contract L2ArbitrumTokenTest is Test { int64 adjustment ) public { int256 expected = int256(uint256(initialEstimate)) + int256(adjustment); - if (expected < 0) { - expected = 0; - } L2ArbitrumToken l2Token = deployAndInit(); @@ -67,7 +64,13 @@ contract L2ArbitrumTokenTest is Test { // adjust the estimate vm.prank(owner); + if (expected < 0) { + vm.expectRevert("ARB: NEGATIVE_TOTAL_DELEGATION"); + } l2Token.adjustInitialTotalDelegationEstimate(adjustment); + if (expected < 0) { + return; + } assertEq( l2Token.getTotalDelegation(), From 91b7c0df55a2243cef0aadd7afe19082f259a890 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:43:19 -0600 Subject: [PATCH 15/43] snapshot --- .gas-snapshot | 219 ++++++++++++++++++++++++++------------------------ 1 file changed, 116 insertions(+), 103 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 41f8acf4b..b2b4b3275 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -4,33 +4,33 @@ ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) ArbitrumDAOConstitutionTest:testOwnerCanSetHashTwice() (gas: 263824) -ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16332093) -ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19243747) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19247090) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19247035) -ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16335426) -ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16327408) -ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16329757) -ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16332176) -ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16337546) -ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16329656) -ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16451131) +ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16650777) +ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19563109) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19566970) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19566915) +ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16654110) +ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16646092) +ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16648441) +ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16650860) +ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16656252) +ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16648340) +ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16773817) ArbitrumVestingWalletFactoryTest:testDeploy() (gas: 4589688) ArbitrumVestingWalletFactoryTest:testOnlyOwnerCanCreateWallets() (gas: 1504286) -ArbitrumVestingWalletTest:testCastVote() (gas: 16201584) -ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16151341) -ArbitrumVestingWalletTest:testClaim() (gas: 16007768) -ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 15967955) -ArbitrumVestingWalletTest:testDelegate() (gas: 16081106) -ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16008435) -ArbitrumVestingWalletTest:testDoesDeploy() (gas: 15971342) -ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16008649) -ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16074917) -E2E:testE2E() (gas: 85079542) -FixedDelegateErc20WalletTest:testInit() (gas: 5822575) -FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 5816805) -FixedDelegateErc20WalletTest:testTransfer() (gas: 5932218) -FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 5897843) +ArbitrumVestingWalletTest:testCastVote() (gas: 16548556) +ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16498401) +ArbitrumVestingWalletTest:testClaim() (gas: 16351666) +ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16286661) +ArbitrumVestingWalletTest:testDelegate() (gas: 16428188) +ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16352311) +ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16290048) +ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16352525) +ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16419145) +E2E:testE2E() (gas: 85120906) +FixedDelegateErc20WalletTest:testInit() (gas: 6095530) +FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 6089082) +FixedDelegateErc20WalletTest:testTransfer() (gas: 6254297) +FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 6216726) InboxActionsTest:testPauseAndUpauseInbox() (gas: 370544) L1AddressRegistryTest:testAddressRegistryAddress() (gas: 47009) L1ArbitrumTimelockTest:testCancel() (gas: 5324642) @@ -60,40 +60,53 @@ L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10771066) L1GovernanceFactoryTest:testSetMinDelay() (gas: 10746048) L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10799003) L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54658) -L2ArbitrumGovernorTest:testCantReinit() (gas: 13669489) -L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 13706483) -L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 13679135) -L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 13673238) -L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 13812715) -L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 13737218) -L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 13664706) -L2ArbitrumTokenTest:testCanBurn() (gas: 4066835) -L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4101512) -L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4101514) -L2ArbitrumTokenTest:testCanMintTwiceWithWarp() (gas: 8190691) -L2ArbitrumTokenTest:testCanMintZero() (gas: 4081635) -L2ArbitrumTokenTest:testCanTransferAndCallContract() (gas: 4211883) -L2ArbitrumTokenTest:testCanTransferAndCallEmpty() (gas: 4096932) -L2ArbitrumTokenTest:testCannotMintMoreThan2Percent() (gas: 4071458) -L2ArbitrumTokenTest:testCannotMintNotOwner() (gas: 4069341) -L2ArbitrumTokenTest:testCannotMintTwice() (gas: 8158921) -L2ArbitrumTokenTest:testCannotMintWithoutFastForward() (gas: 4069700) -L2ArbitrumTokenTest:testCannotTransferAndCallNonReceiver() (gas: 4094203) -L2ArbitrumTokenTest:testCannotTransferAndCallReverter() (gas: 4154761) -L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 3800718) -L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 3800726) -L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 3800739) -L2ArbitrumTokenTest:testIsInitialised() (gas: 4072777) -L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 2693127) -L2GovernanceFactoryTest:testContractsDeployed() (gas: 28514933) -L2GovernanceFactoryTest:testContractsInitialized() (gas: 28551928) -L2GovernanceFactoryTest:testDeploySteps() (gas: 28526442) -L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 28523943) -L2GovernanceFactoryTest:testRoles() (gas: 28546930) -L2GovernanceFactoryTest:testSanityCheckValues() (gas: 28571182) -L2GovernanceFactoryTest:testSetMinDelay() (gas: 28519939) -L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 28572810) -L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 28812928) +L2ArbitrumGovernorTest:testCantReinit() (gas: 13941567) +L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 13978561) +L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 13951213) +L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 13945250) +L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 14131053) +L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 14009617) +L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 13936784) +L2ArbitrumTokenTest:testCanBurn() (gas: 4339618) +L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4374369) +L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4374349) +L2ArbitrumTokenTest:testCanMintTwiceWithWarp() (gas: 8736230) +L2ArbitrumTokenTest:testCanMintZero() (gas: 4354492) +L2ArbitrumTokenTest:testCanTransferAndCallContract() (gas: 4484646) +L2ArbitrumTokenTest:testCanTransferAndCallEmpty() (gas: 4369738) +L2ArbitrumTokenTest:testCannotMintMoreThan2Percent() (gas: 4343818) +L2ArbitrumTokenTest:testCannotMintNotOwner() (gas: 4341697) +L2ArbitrumTokenTest:testCannotMintTwice() (gas: 8704048) +L2ArbitrumTokenTest:testCannotMintWithoutFastForward() (gas: 4342145) +L2ArbitrumTokenTest:testCannotTransferAndCallNonReceiver() (gas: 4367021) +L2ArbitrumTokenTest:testCannotTransferAndCallReverter() (gas: 4427567) +L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4449395) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 4072613) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 4072620) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 4072678) +L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 257, μ: 4384586, ~: 4386226) +L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4387016) +L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4489912) +L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4481315) +L2ArbitrumTokenTest:testDvpNoChangeOnSelfTransfer() (gas: 4505813) +L2ArbitrumTokenTest:testDvpNoChangeOnTransferToDelegator() (gas: 4559065) +L2ArbitrumTokenTest:testDvpNoChangeOnTransferToNonDelegator() (gas: 4365723) +L2ArbitrumTokenTest:testDvpNoRevertOnUnderflow() (gas: 4468805) +L2ArbitrumTokenTest:testIncreaseDVPOnDelegateToAnother() (gas: 4455851) +L2ArbitrumTokenTest:testIncreaseDVPOnSelfDelegate() (gas: 4455973) +L2ArbitrumTokenTest:testInitialDvpEstimate(uint64) (runs: 257, μ: 4381466, ~: 4381466) +L2ArbitrumTokenTest:testIsInitialised() (gas: 4345304) +L2ArbitrumTokenTest:testNoChangeDVPOnRedelegateToSame() (gas: 4527875) +L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 2964987) +L2GovernanceFactoryTest:testContractsDeployed() (gas: 28787689) +L2GovernanceFactoryTest:testContractsInitialized() (gas: 28824684) +L2GovernanceFactoryTest:testDeploySteps() (gas: 28799198) +L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 28796699) +L2GovernanceFactoryTest:testRoles() (gas: 28819686) +L2GovernanceFactoryTest:testSanityCheckValues() (gas: 28844027) +L2GovernanceFactoryTest:testSetMinDelay() (gas: 28792695) +L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 28845566) +L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29131469) L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 30767668) L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 30771899) L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 25781453) @@ -101,7 +114,7 @@ L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 30769899) L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 30788992) NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) OfficeHoursActionTest:testConstructor() (gas: 9050) -OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317058, ~: 317184) +OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 257, μ: 317092, ~: 317184) OfficeHoursActionTest:testInvalidConstructorParameters() (gas: 235740) OfficeHoursActionTest:testPerformBeforeMinimumTimestamp() (gas: 8646) OfficeHoursActionTest:testPerformDuringOfficeHours() (gas: 9140) @@ -136,7 +149,7 @@ SecurityCouncilManagerTest:testRotateMember() (gas: 259133) SecurityCouncilManagerTest:testUpdateCohortAffordances() (gas: 83026) SecurityCouncilManagerTest:testUpdateFirstCohort() (gas: 295652) SecurityCouncilManagerTest:testUpdateRouter() (gas: 76296) -SecurityCouncilManagerTest:testUpdateRouterAffordacnes() (gas: 112379) +SecurityCouncilManagerTest:testUpdateRouterAffordacnes() (gas: 109879) SecurityCouncilManagerTest:testUpdateSecondCohort() (gas: 295657) SecurityCouncilMemberElectionGovernorTest:testCannotUseMoreVotesThanAvailable() (gas: 246997) SecurityCouncilMemberElectionGovernorTest:testCastBySig() (gas: 302852) @@ -153,26 +166,26 @@ SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanProp SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32916) SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42229) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 340066, ~: 339868) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 257, μ: 339713, ~: 339475) SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) -SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10159193) +SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10431271) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallParamRestriction() (gas: 56157) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallRestriction() (gas: 49685) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetLen() (gas: 35392) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetRestriction() (gas: 46987) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationUnexpectedCallDataLen() (gas: 41583) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationValuesRestriction() (gas: 61908) -SecurityCouncilMemberRemovalGovernorTest:testProposalDoesExpire() (gas: 272525) -SecurityCouncilMemberRemovalGovernorTest:testProposalExpirationDeadline() (gas: 134831) +SecurityCouncilMemberRemovalGovernorTest:testProposalDoesExpire() (gas: 272283) +SecurityCouncilMemberRemovalGovernorTest:testProposalExpirationDeadline() (gas: 134809) SecurityCouncilMemberRemovalGovernorTest:testRelay() (gas: 42123) SecurityCouncilMemberRemovalGovernorTest:testSeparateSelector() (gas: 23536) SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumerator() (gas: 30049) SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumeratorAffordance() (gas: 47631) -SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorInsufficientVotes() (gas: 358327) -SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorSufficientVotes() (gas: 361245) -SecurityCouncilMemberRemovalGovernorTest:testSuccessfulProposalAndCantAbstain() (gas: 142674) +SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorInsufficientVotes() (gas: 358107) +SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorSufficientVotes() (gas: 361025) +SecurityCouncilMemberRemovalGovernorTest:testSuccessfulProposalAndCantAbstain() (gas: 142630) SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 8094226) SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 8095064) SecurityCouncilMemberSyncActionTest:testCantDropBelowThreshhold() (gas: 8121127) @@ -208,41 +221,41 @@ SetInitialGovParamsActionTest:testL1() (gas: 259949) SetInitialGovParamsActionTest:testL2() (gas: 688933) SetSequencerInboxMaxTimeVariationActionTest:testSetMaxTimeVariation() (gas: 310296) SwitchManagerRolesActionTest:testAction() (gas: 6313) -TokenDistributorTest:testClaim() (gas: 5742744) -TokenDistributorTest:testClaimAndDelegate() (gas: 5850827) -TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 5748244) -TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 5803385) -TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 5803386) -TokenDistributorTest:testClaimFailsAfterEnd() (gas: 5704035) -TokenDistributorTest:testClaimFailsBeforeStart() (gas: 5703530) -TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 5686246) -TokenDistributorTest:testClaimFailsForTwice() (gas: 5741504) -TokenDistributorTest:testClaimFailsForUnknown() (gas: 5706111) -TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4134838) -TokenDistributorTest:testDoesDeploy() (gas: 5339553) -TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5404583) -TokenDistributorTest:testOldClaimStart() (gas: 4135401) -TokenDistributorTest:testSetRecipients() (gas: 5701945) -TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 5668810) -TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5420359) -TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 5712988) -TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5421819) -TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5422048) -TokenDistributorTest:testSetRecipientsTwice() (gas: 6391525) -TokenDistributorTest:testSetSweepReceiver() (gas: 5706262) -TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 5703881) -TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 5704842) -TokenDistributorTest:testSweep() (gas: 5751971) -TokenDistributorTest:testSweepAfterClaim() (gas: 5789954) -TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 5703615) -TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 5707314) -TokenDistributorTest:testSweepFailsTwice() (gas: 5750930) -TokenDistributorTest:testWithdraw() (gas: 5741198) -TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 5741220) -TokenDistributorTest:testWithdrawFailsTransfer() (gas: 5705817) -TokenDistributorTest:testZeroDelegateTo() (gas: 4132733) -TokenDistributorTest:testZeroOwner() (gas: 4132646) -TokenDistributorTest:testZeroReceiver() (gas: 4132675) +TokenDistributorTest:testClaim() (gas: 6086841) +TokenDistributorTest:testClaimAndDelegate() (gas: 6198153) +TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 6092342) +TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 6148161) +TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 6148162) +TokenDistributorTest:testClaimFailsAfterEnd() (gas: 6022940) +TokenDistributorTest:testClaimFailsBeforeStart() (gas: 6022435) +TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 6005151) +TokenDistributorTest:testClaimFailsForTwice() (gas: 6085579) +TokenDistributorTest:testClaimFailsForUnknown() (gas: 6025016) +TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4407115) +TokenDistributorTest:testDoesDeploy() (gas: 5612530) +TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5723510) +TokenDistributorTest:testOldClaimStart() (gas: 4407678) +TokenDistributorTest:testSetRecipients() (gas: 6020850) +TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 5987722) +TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5739242) +TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 6031903) +TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5740711) +TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5740940) +TokenDistributorTest:testSetRecipientsTwice() (gas: 6710475) +TokenDistributorTest:testSetSweepReceiver() (gas: 6025167) +TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 6022786) +TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 6023747) +TokenDistributorTest:testSweep() (gas: 6096112) +TokenDistributorTest:testSweepAfterClaim() (gas: 6159265) +TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 6022520) +TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 6026241) +TokenDistributorTest:testSweepFailsTwice() (gas: 6095049) +TokenDistributorTest:testWithdraw() (gas: 6063321) +TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 6063343) +TokenDistributorTest:testWithdrawFailsTransfer() (gas: 6024722) +TokenDistributorTest:testZeroDelegateTo() (gas: 4405010) +TokenDistributorTest:testZeroOwner() (gas: 4404923) +TokenDistributorTest:testZeroReceiver() (gas: 4404952) TokenDistributorTest:testZeroToken() (gas: 71889) TopNomineesGasTest:testTopNomineesGas() (gas: 4502996) UpgradeExecRouteBuilderTest:testAIP1Point2() (gas: 1444846) From 77cf3e950f7e2e236de334007c4ef85617898e6b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:58:27 -0600 Subject: [PATCH 16/43] update comment --- src/L2ArbitrumToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 19367a179..8613a756e 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -87,7 +87,7 @@ contract L2ArbitrumToken is _totalDelegationHistory.push(initialTotalDelegation); } - /// @notice Called at proposal #2 + /// @notice Adjusts total delegation value by the given amount /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. function adjustInitialTotalDelegationEstimate(int256 initialEstimationErrorAdjustment) external onlyOwner { int256 newValue = int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment; From 087fcff2027b9e9e1539c4adf24f76bf6e778742 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:59:40 -0600 Subject: [PATCH 17/43] format --- src/L2ArbitrumToken.sol | 44 +++++++++++++---------------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 8613a756e..b38bc7798 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -89,8 +89,12 @@ contract L2ArbitrumToken is /// @notice Adjusts total delegation value by the given amount /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. - function adjustInitialTotalDelegationEstimate(int256 initialEstimationErrorAdjustment) external onlyOwner { - int256 newValue = int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment; + function adjustInitialTotalDelegationEstimate(int256 initialEstimationErrorAdjustment) + external + onlyOwner + { + int256 newValue = + int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment; // negative newValue should be impossible // since the adjustment should bring the value to true total delegation @@ -116,11 +120,7 @@ contract L2ArbitrumToken is /// @notice Get the current total delegation /// @return The current total delegation - function getTotalDelegation() - external - view - returns (uint256) - { + function getTotalDelegation() external view returns (uint256) { return _totalDelegationHistory.latest(); } @@ -128,19 +128,13 @@ contract L2ArbitrumToken is /// If the blockNumber is prior to the first checkpoint, returns 0 /// @param blockNumber The block number to get the total delegation at /// @return The total delegation at the given block number - function getTotalDelegationAt(uint256 blockNumber) - external - view - returns (uint256) - { + function getTotalDelegationAt(uint256 blockNumber) external view returns (uint256) { return _totalDelegationHistory.getAtBlock(blockNumber); } - function _updateDelegationHistory( - address fromDelegate, - address toDelegate, - uint256 amount - ) internal { + function _updateDelegationHistory(address fromDelegate, address toDelegate, uint256 amount) + internal + { if (fromDelegate != toDelegate) { int256 delta = 0; if (fromDelegate != address(0)) { @@ -154,19 +148,13 @@ contract L2ArbitrumToken is // it is technically possible that the newValue is negative // if this happens, we clamp it to zero to avoid underflow int256 newValue = int256(_totalDelegationHistory.latest()) + delta; - _totalDelegationHistory.push( - uint256(newValue < 0 ? int256(0) : newValue) - ); + _totalDelegationHistory.push(uint256(newValue < 0 ? int256(0) : newValue)); } } } function _delegate(address delegator, address delegatee) internal virtual override { - _updateDelegationHistory( - delegates(delegator), - delegatee, - balanceOf(delegator) - ); + _updateDelegationHistory(delegates(delegator), delegatee, balanceOf(delegator)); super._delegate(delegator, delegatee); } @@ -175,11 +163,7 @@ contract L2ArbitrumToken is override(ERC20Upgradeable, ERC20VotesUpgradeable) { super._afterTokenTransfer(from, to, amount); - _updateDelegationHistory( - delegates(from), - delegates(to), - amount - ); + _updateDelegationHistory(delegates(from), delegates(to), amount); } function _mint(address to, uint256 amount) From 302dd666248e0a77532af60d7cf3cd911bfdd45b Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:02:45 -0600 Subject: [PATCH 18/43] add natspec --- src/L2ArbitrumToken.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index b38bc7798..5ec7995a3 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -132,6 +132,11 @@ contract L2ArbitrumToken is return _totalDelegationHistory.getAtBlock(blockNumber); } + /// @dev Checks if total delegation needs to be updated, and updates it if so + /// by adding a new checkpoint. + /// @param fromDelegate The address of the delegate the tokens are being moved from + /// @param toDelegate The address of the delegate the tokens are being moved to + /// @param amount The amount of tokens being moved function _updateDelegationHistory(address fromDelegate, address toDelegate, uint256 amount) internal { @@ -153,6 +158,7 @@ contract L2ArbitrumToken is } } + /// @dev Override ERC20VotesUpgradeable to update total delegation history when delegation changes function _delegate(address delegator, address delegatee) internal virtual override { _updateDelegationHistory(delegates(delegator), delegatee, balanceOf(delegator)); super._delegate(delegator, delegatee); From dd3f6ba2ebbe3797eb70fc7202510051a407a873 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:47:52 -0600 Subject: [PATCH 19/43] no double postUpgradeInit --- src/L2ArbitrumToken.sol | 6 +++++- test/L2ArbitrumToken.t.sol | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index 5ec7995a3..a3b65ff6a 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -77,13 +77,17 @@ contract L2ArbitrumToken is _transferOwnership(_owner); } - /// @notice Called at proposal #1 + /// @notice Called after upgrade to set the initial total delegation estimate /// The initial estimate may be manipulable with artificial delegation/undelegation prior to the upgrade. /// Since this value is only used for quorum calculation, and the quroum is clamped by the governors to an acceptable range, /// the risk/impact of manipulation is low. /// @param initialTotalDelegation The initial total delegation at the time of upgrade proposal creation. /// This is an estimate since it is chosen at proposal creation time and not effective until the proposal is executed. function postUpgradeInit(uint256 initialTotalDelegation) external onlyOwner { + require( + _totalDelegationHistory._checkpoints.length == 0, + "ARB: POST_UPGRADE_INIT_ALREADY_CALLED" + ); _totalDelegationHistory.push(initialTotalDelegation); } diff --git a/test/L2ArbitrumToken.t.sol b/test/L2ArbitrumToken.t.sol index 9f9f25c3c..d60807f41 100644 --- a/test/L2ArbitrumToken.t.sol +++ b/test/L2ArbitrumToken.t.sol @@ -49,6 +49,20 @@ contract L2ArbitrumTokenTest is Test { ); } + // test no double init + function testNoDoublePostUpgradeInit() public { + L2ArbitrumToken l2Token = deployAndInit(); + + // set an initial estimate + vm.prank(owner); + l2Token.postUpgradeInit(10); + + // try to set it again + vm.prank(owner); + vm.expectRevert("ARB: POST_UPGRADE_INIT_ALREADY_CALLED"); + l2Token.postUpgradeInit(20); + } + // test adjustment function testDvpAdjustment( uint64 initialEstimate, From 51b3006f6bcdef63595e4016e2e5ff9a5b008c10 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:53:45 -0600 Subject: [PATCH 20/43] rename and emit in adjustTotalDelegation --- src/L2ArbitrumToken.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/L2ArbitrumToken.sol b/src/L2ArbitrumToken.sol index a3b65ff6a..04a23c037 100644 --- a/src/L2ArbitrumToken.sol +++ b/src/L2ArbitrumToken.sol @@ -49,6 +49,8 @@ contract L2ArbitrumToken is /// Another proposal can be made later to update this value if needed. Checkpoints.History private _totalDelegationHistory; + event TotalDelegationAdjusted(uint256 previousTotalDelegation, uint256 newTotalDelegation); + constructor() { _disableInitializers(); } @@ -92,19 +94,21 @@ contract L2ArbitrumToken is } /// @notice Adjusts total delegation value by the given amount - /// @param initialEstimationErrorAdjustment The amount the initialTotalDelegation was off by, negated. This is added to the current total delegation. - function adjustInitialTotalDelegationEstimate(int256 initialEstimationErrorAdjustment) + /// @param adjustment The amount that the total delegation is off by, negated. This is added to the current total delegation. + function adjustTotalDelegation(int256 adjustment) external onlyOwner { - int256 newValue = - int256(_totalDelegationHistory.latest()) + initialEstimationErrorAdjustment; + uint256 latest = _totalDelegationHistory.latest(); + int256 newValue = int256(latest) + adjustment; // negative newValue should be impossible // since the adjustment should bring the value to true total delegation // which is at minimum zero require(newValue >= 0, "ARB: NEGATIVE_TOTAL_DELEGATION"); _totalDelegationHistory.push(uint256(newValue)); + + emit TotalDelegationAdjusted(latest, uint256(newValue)); } /// @notice Allows the owner to mint new tokens From f5076233b682eb7db1bf1e4dcd5745a005bff305 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:17:12 -0600 Subject: [PATCH 21/43] use DVP in governor (#363) * use DVP in governor * remove relic of dvp estimation in quorum calc * Revert "remove relic of dvp estimation in quorum calc" This reverts commit 3af8bce0bf641a5bc86bcabefe1b7943da32d5ac. * keep old quorum behavior when checkpoint is old * add quorum clamping * document quorum jumping on admin function * comment * test governor dvp * update sigs and storage * fix test * add comment * snapshot --- .gas-snapshot | 81 +++++++++++++++--------------- src/L2ArbitrumGovernor.sol | 69 +++++++++++++++++++++++-- test/L2ArbitrumGovernor.t.sol | 53 +++++++++++++++++++ test/signatures/L2ArbitrumGovernor | 8 +++ test/storage/L2ArbitrumGovernor | 6 ++- 5 files changed, 173 insertions(+), 44 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index b2b4b3275..aadbef7f6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,32 +1,32 @@ -AIP1Point2ActionTest:testAction() (gas: 629373) +AIP1Point2ActionTest:testAction() (gas: 629593) AIPNovaFeeRoutingActionTest:testAction() (gas: 3074) ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) ArbitrumDAOConstitutionTest:testOwnerCanSetHashTwice() (gas: 263824) -ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16650777) -ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19563109) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19566970) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19566915) -ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16654110) -ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16646092) -ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16648441) -ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16650860) -ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16656252) -ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16648340) -ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16773817) +ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16813688) +ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19726041) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19729902) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19729847) +ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16817021) +ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16809003) +ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16811352) +ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16813771) +ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16819184) +ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16811251) +ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16936728) ArbitrumVestingWalletFactoryTest:testDeploy() (gas: 4589688) ArbitrumVestingWalletFactoryTest:testOnlyOwnerCanCreateWallets() (gas: 1504286) -ArbitrumVestingWalletTest:testCastVote() (gas: 16548556) -ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16498401) -ArbitrumVestingWalletTest:testClaim() (gas: 16351666) -ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16286661) -ArbitrumVestingWalletTest:testDelegate() (gas: 16428188) -ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16352311) -ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16290048) -ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16352525) -ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16419145) -E2E:testE2E() (gas: 85120906) +ArbitrumVestingWalletTest:testCastVote() (gas: 16736828) +ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16661155) +ArbitrumVestingWalletTest:testClaim() (gas: 16514555) +ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16449550) +ArbitrumVestingWalletTest:testDelegate() (gas: 16591077) +ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16515200) +ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16452937) +ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16515414) +ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16582034) +E2E:testE2E() (gas: 85121103) FixedDelegateErc20WalletTest:testInit() (gas: 6095530) FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 6089082) FixedDelegateErc20WalletTest:testTransfer() (gas: 6254297) @@ -59,14 +59,15 @@ L1ArbitrumTokenTest:testRegisterTokenOnL2NotEnoughVal() (gas: 4425799) L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10771066) L1GovernanceFactoryTest:testSetMinDelay() (gas: 10746048) L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10799003) -L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54658) -L2ArbitrumGovernorTest:testCantReinit() (gas: 13941567) -L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 13978561) -L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 13951213) -L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 13945250) -L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 14131053) -L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 14009617) -L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 13936784) +L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54702) +L2ArbitrumGovernorTest:testCantReinit() (gas: 14134098) +L2ArbitrumGovernorTest:testDVPQuorumAndClamping() (gas: 14388732) +L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 14171159) +L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 14143656) +L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 14137780) +L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 14329812) +L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 14206327) +L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 14131750) L2ArbitrumTokenTest:testCanBurn() (gas: 4339618) L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4374369) L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4374349) @@ -98,15 +99,15 @@ L2ArbitrumTokenTest:testInitialDvpEstimate(uint64) (runs: 257, μ: 4381466, ~: 4 L2ArbitrumTokenTest:testIsInitialised() (gas: 4345304) L2ArbitrumTokenTest:testNoChangeDVPOnRedelegateToSame() (gas: 4527875) L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 2964987) -L2GovernanceFactoryTest:testContractsDeployed() (gas: 28787689) -L2GovernanceFactoryTest:testContractsInitialized() (gas: 28824684) -L2GovernanceFactoryTest:testDeploySteps() (gas: 28799198) -L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 28796699) -L2GovernanceFactoryTest:testRoles() (gas: 28819686) -L2GovernanceFactoryTest:testSanityCheckValues() (gas: 28844027) -L2GovernanceFactoryTest:testSetMinDelay() (gas: 28792695) -L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 28845566) -L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29131469) +L2GovernanceFactoryTest:testContractsDeployed() (gas: 29113300) +L2GovernanceFactoryTest:testContractsInitialized() (gas: 29150360) +L2GovernanceFactoryTest:testDeploySteps() (gas: 29124809) +L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 29122310) +L2GovernanceFactoryTest:testRoles() (gas: 29145297) +L2GovernanceFactoryTest:testSanityCheckValues() (gas: 29169793) +L2GovernanceFactoryTest:testSetMinDelay() (gas: 29118306) +L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 29171177) +L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29462025) L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 30767668) L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 30771899) L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 25781453) @@ -218,7 +219,7 @@ SecurityCouncilNomineeElectionGovernorTest:testSetNomineeVetter() (gas: 39905) SequencerActionsTest:testAddAndRemoveSequencer() (gas: 486652) SequencerActionsTest:testCantAddZeroAddress() (gas: 235659) SetInitialGovParamsActionTest:testL1() (gas: 259949) -SetInitialGovParamsActionTest:testL2() (gas: 688933) +SetInitialGovParamsActionTest:testL2() (gas: 688895) SetSequencerInboxMaxTimeVariationActionTest:testSetMaxTimeVariation() (gas: 310296) SwitchManagerRolesActionTest:testAction() (gas: 6313) TokenDistributorTest:testClaim() (gas: 6086841) diff --git a/src/L2ArbitrumGovernor.sol b/src/L2ArbitrumGovernor.sol index f08e2cb11..427e50d6f 100644 --- a/src/L2ArbitrumGovernor.sol +++ b/src/L2ArbitrumGovernor.sol @@ -14,6 +14,7 @@ import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {L2ArbitrumToken} from "./L2ArbitrumToken.sol"; /// @title L2ArbitrumGovernor /// @notice Governance controls for the Arbitrum DAO @@ -41,6 +42,15 @@ contract L2ArbitrumGovernor is /// Note that Excluded Address is a readable name with no code of PK associated with it, and thus can't vote. address public constant EXCLUDE_ADDRESS = address(0xA4b86); + /// @notice Maximum quorum allowed for a proposal + /// @dev Since the setting is not checkpointed, it is possible that an existing proposal + /// with quorum greater than the maximum can have its quorum suddenly jump to equal maximumQuorum + uint256 public maximumQuorum; + /// @notice Minimum quorum allowed for a proposal + /// @dev Since the setting is not checkpointed, it is possible that an existing proposal + /// with quorum lesser than the minimum can have its quorum suddenly jump to equal minimumQuorum + uint256 public minimumQuorum; + constructor() { _disableInitializers(); } @@ -121,21 +131,74 @@ contract L2ArbitrumGovernor is return address(this); } + /// @notice Set the quorum minimum and maximum + /// @dev Since the setting is not checkpointed, it is possible that an existing proposal + /// with quorum outside the new min/max can have its quorum suddenly jump to equal + /// the new min or max + function setQuorumMinAndMax(uint256 _minimumQuorum, uint256 _maximumQuorum) + external + onlyGovernance + { + require(_minimumQuorum < _maximumQuorum, "L2ArbitrumGovernor: MIN_GT_MAX"); + minimumQuorum = _minimumQuorum; + maximumQuorum = _maximumQuorum; + } + /// @notice Get "circulating" votes supply; i.e., total minus excluded vote exclude address. function getPastCirculatingSupply(uint256 blockNumber) public view virtual returns (uint256) { return token.getPastTotalSupply(blockNumber) - token.getPastVotes(EXCLUDE_ADDRESS, blockNumber); } + /// @notice Get total delegated votes minus excluded votes + /// @dev If the block number is prior to the first total delegation checkpoint, returns 0 + /// Can also return 0 if excluded > total delegation, which is extremely unlikely but possible + /// since L2ArbitrumToken.getTotalDelegationAt is initially an estimate + function getPastTotalDelegatedVotes(uint256 blockNumber) public view returns (uint256) { + uint256 totalDvp = L2ArbitrumToken(address(token)).getTotalDelegationAt(blockNumber); + + // getTotalDelegationAt may return 0 if the requested block is before the first checkpoint + if (totalDvp == 0) { + return 0; + } + + uint256 excluded = token.getPastVotes(EXCLUDE_ADDRESS, blockNumber); + + // it is possible (but unlikely) that excluded > totalDvp + // this is because getTotalDelegationAt is initially an _estimate_ of the total delegation + return totalDvp > excluded ? totalDvp - excluded : 0; + } + /// @notice Calculates the quorum size, excludes token delegated to the exclude address + /// @dev The calculated quorum is clamped between minimumQuorum and maximumQuorum function quorum(uint256 blockNumber) public view override(IGovernorUpgradeable, GovernorVotesQuorumFractionUpgradeable) returns (uint256) { - return (getPastCirculatingSupply(blockNumber) * quorumNumerator(blockNumber)) - / quorumDenominator(); + uint256 pastTotalDelegatedVotes = getPastTotalDelegatedVotes(blockNumber); + + // if pastTotalDelegatedVotes is 0, then blockNumber is almost certainly prior to the first totalDelegatedVotes checkpoint + // in this case we should use getPastCirculatingSupply to ensure quorum of pre-existing proposals is unchanged + // in the unlikely event that totalDvp is 0 for a block _after_ the dvp update, getPastCirculatingSupply will be used with a larger quorumNumerator, + // resulting in a much higher calculated quorum. This is okay because quorum is clamped. + uint256 calculatedQuorum = ( + ( + pastTotalDelegatedVotes == 0 + ? getPastCirculatingSupply(blockNumber) + : pastTotalDelegatedVotes + ) * quorumNumerator(blockNumber) + ) / quorumDenominator(); + + // clamp the calculated quorum between minimumQuorum and maximumQuorum + if (calculatedQuorum < minimumQuorum) { + return minimumQuorum; + } else if (calculatedQuorum > maximumQuorum) { + return maximumQuorum; + } else { + return calculatedQuorum; + } } /// @inheritdoc GovernorVotesQuorumFractionUpgradeable @@ -235,5 +298,5 @@ contract L2ArbitrumGovernor is * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[50] private __gap; + uint256[48] private __gap; } diff --git a/test/L2ArbitrumGovernor.t.sol b/test/L2ArbitrumGovernor.t.sol index 1318f761c..13988ea4d 100644 --- a/test/L2ArbitrumGovernor.t.sol +++ b/test/L2ArbitrumGovernor.t.sol @@ -50,6 +50,7 @@ contract L2ArbitrumGovernorTest is Test { proposalThreshold, initialVoteExtension ); + _setQuorumMinAndMax(l2ArbitrumGovernor, 0, type(uint256).max); return (l2ArbitrumGovernor, token, timelock); } @@ -208,4 +209,56 @@ contract L2ArbitrumGovernorTest is Test { vm.stopPrank(); } + + function testDVPQuorumAndClamping() external { + (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,) = deployAndInit(); + + vm.roll(2); + + // since total DVP is zero, the governor should fallback to circulating supply + // in this case quorum should be 2500 + assertEq(l2ArbitrumGovernor.quorum(1), 2500, "quorum should be 2500"); + + // test clamping in circ supply mode + _setQuorumMinAndMax(l2ArbitrumGovernor, 3000, 4000); + assertEq(l2ArbitrumGovernor.quorum(1), 3000, "quorum should be clamped to min 3000"); + _setQuorumMinAndMax(l2ArbitrumGovernor, 1, 2000); + assertEq(l2ArbitrumGovernor.quorum(1), 2000, "quorum should be clamped to max 2000"); + + // delegate some tokens to get into DVP mode + vm.prank(tokenOwner); + token.delegate(someRando); + vm.prank(tokenOwner); + token.transfer(address(1), 100); + vm.roll(3); + + assertEq(token.getTotalDelegationAt(2), initialTokenSupply - 100, "DVP error"); + + // make sure quorum is calculated based on DVP now + _setQuorumMinAndMax(l2ArbitrumGovernor, 0, type(uint256).max); + assertEq( + l2ArbitrumGovernor.quorum(2), + 2495, // ((initialTokenSupply - 100) * quorumNumerator) / 10_000, + "quorum should be based on DVP" + ); + + // test clamping in DVP mode + _setQuorumMinAndMax(l2ArbitrumGovernor, 2500, 3000); + assertEq(l2ArbitrumGovernor.quorum(2), 2500, "quorum should be clamped to min 2500"); + _setQuorumMinAndMax(l2ArbitrumGovernor, 1, 2000); + assertEq(l2ArbitrumGovernor.quorum(2), 2000, "quorum should be clamped to max 2000"); + } + + function _setQuorumMinAndMax( + L2ArbitrumGovernor l2ArbitrumGovernor, + uint256 min, + uint256 max + ) internal { + vm.prank(executor); + l2ArbitrumGovernor.relay( + address(l2ArbitrumGovernor), + 0, + abi.encodeWithSelector(l2ArbitrumGovernor.setQuorumMinAndMax.selector, min, max) + ); + } } diff --git a/test/signatures/L2ArbitrumGovernor b/test/signatures/L2ArbitrumGovernor index db1263e36..da71a2ea7 100644 --- a/test/signatures/L2ArbitrumGovernor +++ b/test/signatures/L2ArbitrumGovernor @@ -24,6 +24,8 @@ |------------------------------------------------------------------------------------+------------| | getPastCirculatingSupply(uint256) | 6e462680 | |------------------------------------------------------------------------------------+------------| +| getPastTotalDelegatedVotes(uint256) | 2fa8362b | +|------------------------------------------------------------------------------------+------------| | getVotes(address,uint256) | eb9019d4 | |------------------------------------------------------------------------------------+------------| | getVotesWithParams(address,uint256,bytes) | 9a802a6d | @@ -36,6 +38,10 @@ |------------------------------------------------------------------------------------+------------| | lateQuorumVoteExtension() | 32b8113e | |------------------------------------------------------------------------------------+------------| +| maximumQuorum() | 13a2e752 | +|------------------------------------------------------------------------------------+------------| +| minimumQuorum() | 8160f0b5 | +|------------------------------------------------------------------------------------+------------| | name() | 06fdde03 | |------------------------------------------------------------------------------------+------------| | onERC1155BatchReceived(address,address,uint256[],uint256[],bytes) | bc197c81 | @@ -76,6 +82,8 @@ |------------------------------------------------------------------------------------+------------| | setProposalThreshold(uint256) | ece40cc1 | |------------------------------------------------------------------------------------+------------| +| setQuorumMinAndMax(uint256,uint256) | 5a9fd1d5 | +|------------------------------------------------------------------------------------+------------| | setVotingDelay(uint256) | 70b0f660 | |------------------------------------------------------------------------------------+------------| | setVotingPeriod(uint256) | ea0217cf | diff --git a/test/storage/L2ArbitrumGovernor b/test/storage/L2ArbitrumGovernor index 8068f1fa4..0efda4be9 100644 --- a/test/storage/L2ArbitrumGovernor +++ b/test/storage/L2ArbitrumGovernor @@ -66,6 +66,10 @@ |-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| | __gap | uint256[49] | 605 | 0 | 1568 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | |-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| -| __gap | uint256[50] | 654 | 0 | 1600 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | +| maximumQuorum | uint256 | 654 | 0 | 32 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | +|-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| +| minimumQuorum | uint256 | 655 | 0 | 32 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | +|-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| +| __gap | uint256[48] | 656 | 0 | 1536 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | ╰-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------╯ From 542f80071724cabae2b564cf1f6efc484e36e637 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:28:41 -0600 Subject: [PATCH 22/43] DVP Action Contracts (#362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * use DVP in governor * first action * second action * fmt * comment * single action * remove relic of dvp estimation in quorum calc * Revert "remove relic of dvp estimation in quorum calc" This reverts commit 3af8bce0bf641a5bc86bcabefe1b7943da32d5ac. * Revert "single action" This reverts commit a132247acddfa541c77726ae669ac09eebee1047. * keep old quorum behavior when checkpoint is old * add quorum clamping * set min/max quorum in action * document quorum jumping on admin function * comment * test governor dvp * fix rename functions * update sigs and storage * update sigs and storage * fix test * add prop1 test, use relay * remove second action * format * add comment * snapshot * fix test path * snapshot * update sigs and storage * undo toml change * Update src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol Co-authored-by: José FP <105675159+TucksonDev@users.noreply.github.com> --------- Co-authored-by: José FP <105675159+TucksonDev@users.noreply.github.com> --- .gas-snapshot | 9 +- .../ActivateDvpQuorumAction.sol | 127 ++++++++++++++++ .../gov-actions/ActivateDvpQuorumAction.t.sol | 140 ++++++++++++++++++ test/signatures/ActivateDvpQuorumAction | 31 ++++ test/storage/ActivateDvpQuorumAction | 6 + 5 files changed, 309 insertions(+), 4 deletions(-) create mode 100644 src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol create mode 100644 test/gov-actions/ActivateDvpQuorumAction.t.sol create mode 100644 test/signatures/ActivateDvpQuorumAction create mode 100644 test/storage/ActivateDvpQuorumAction diff --git a/.gas-snapshot b/.gas-snapshot index aadbef7f6..e732670a6 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,5 +1,6 @@ AIP1Point2ActionTest:testAction() (gas: 629593) AIPNovaFeeRoutingActionTest:testAction() (gas: 3074) +ActivateDvpQuorumActionTest:testAction() (gas: 3074) ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) @@ -85,7 +86,7 @@ L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4449395) L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 4072613) L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 4072620) L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 4072678) -L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 257, μ: 4384586, ~: 4386226) +L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4384593, ~: 4386226) L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4387016) L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4489912) L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4481315) @@ -95,7 +96,7 @@ L2ArbitrumTokenTest:testDvpNoChangeOnTransferToNonDelegator() (gas: 4365723) L2ArbitrumTokenTest:testDvpNoRevertOnUnderflow() (gas: 4468805) L2ArbitrumTokenTest:testIncreaseDVPOnDelegateToAnother() (gas: 4455851) L2ArbitrumTokenTest:testIncreaseDVPOnSelfDelegate() (gas: 4455973) -L2ArbitrumTokenTest:testInitialDvpEstimate(uint64) (runs: 257, μ: 4381466, ~: 4381466) +L2ArbitrumTokenTest:testInitialDvpEstimate(uint64) (runs: 256, μ: 4381466, ~: 4381466) L2ArbitrumTokenTest:testIsInitialised() (gas: 4345304) L2ArbitrumTokenTest:testNoChangeDVPOnRedelegateToSame() (gas: 4527875) L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 2964987) @@ -115,7 +116,7 @@ L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 30769899) L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 30788992) NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) OfficeHoursActionTest:testConstructor() (gas: 9050) -OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 257, μ: 317092, ~: 317184) +OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317091, ~: 317184) OfficeHoursActionTest:testInvalidConstructorParameters() (gas: 235740) OfficeHoursActionTest:testPerformBeforeMinimumTimestamp() (gas: 8646) OfficeHoursActionTest:testPerformDuringOfficeHours() (gas: 9140) @@ -167,7 +168,7 @@ SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanProp SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32916) SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42229) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 257, μ: 339713, ~: 339475) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339688, ~: 339471) SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) diff --git a/src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol b/src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol new file mode 100644 index 000000000..2bf6cf9cb --- /dev/null +++ b/src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IL2AddressRegistry} from "./../../address-registries/L2AddressRegistryInterfaces.sol"; +import {L2ArbitrumGovernor} from "./../../../L2ArbitrumGovernor.sol"; +import {GovernorVotesQuorumFractionUpgradeable} from + "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; + +interface IArbTokenPostUpgradeInit { + function postUpgradeInit(uint256 initialTotalDelegation) external; +} + +/// @notice This action is performed as a governance proposal to activate the DVP quorum mechanism. +/// A second proposal (AdjustDvpEstimateAction) is recommended some time later to adjust the initial +/// total delegation estimate set in this proposal. +contract ActivateDvpQuorumAction { + address public immutable l2AddressRegistry; + address public immutable arbTokenProxy; + ProxyAdmin public immutable govProxyAdmin; + + address public immutable newGovernorImpl; + address public immutable newTokenImpl; + + uint256 public immutable newCoreQuorumNumerator; + uint256 public immutable coreMinimumQuorum; + uint256 public immutable coreMaximumQuorum; + uint256 public immutable newTreasuryQuorumNumerator; + uint256 public immutable treasuryMinimumQuorum; + uint256 public immutable treasuryMaximumQuorum; + uint256 public immutable initialTotalDelegationEstimate; + + constructor( + address _l2AddressRegistry, + address _arbTokenProxy, + ProxyAdmin _govProxyAdmin, + address _newGovernorImpl, + address _newTokenImpl, + uint256 _newCoreQuorumNumerator, + uint256 _coreMinimumQuorum, + uint256 _coreMaximumQuorum, + uint256 _newTreasuryQuorumNumerator, + uint256 _treasuryMinimumQuorum, + uint256 _treasuryMaximumQuorum, + uint256 _initialTotalDelegationEstimate + ) { + l2AddressRegistry = _l2AddressRegistry; + arbTokenProxy = _arbTokenProxy; + govProxyAdmin = _govProxyAdmin; + newGovernorImpl = _newGovernorImpl; + newTokenImpl = _newTokenImpl; + newCoreQuorumNumerator = _newCoreQuorumNumerator; + coreMinimumQuorum = _coreMinimumQuorum; + coreMaximumQuorum = _coreMaximumQuorum; + newTreasuryQuorumNumerator = _newTreasuryQuorumNumerator; + treasuryMinimumQuorum = _treasuryMinimumQuorum; + treasuryMaximumQuorum = _treasuryMaximumQuorum; + initialTotalDelegationEstimate = _initialTotalDelegationEstimate; + } + + /// @notice Performs the following: + /// 1. Upgrades the token contract + /// 2. Calls postUpgradeInit on the token contract to set the initial total delegation estimate + /// 3. Upgrades the core governor contract + /// 4. Sets the new quorum numerator for the core governor + /// 5. Sets the quorum min/max for the core governor + /// 6. Upgrades the treasury governor contract + /// 7. Sets the new quorum numerator for the treasury governor + /// 8. Sets the quorum min/max for the treasury governor + function perform() external { + // 1. Upgrade the token contract + govProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(arbTokenProxy)), newTokenImpl); + + // 2. Call postUpgradeInit on the token contract + IArbTokenPostUpgradeInit(arbTokenProxy).postUpgradeInit(initialTotalDelegationEstimate); + + // 3. Upgrade the core governor contract + address payable coreGov = payable(address(IL2AddressRegistry(l2AddressRegistry).coreGov())); + govProxyAdmin.upgrade(TransparentUpgradeableProxy(coreGov), newGovernorImpl); + + // 4. Set the new quorum numerator for the core governor + L2ArbitrumGovernor(coreGov).relay( + coreGov, + 0, + abi.encodeCall( + GovernorVotesQuorumFractionUpgradeable.updateQuorumNumerator, + (newCoreQuorumNumerator) + ) + ); + + // 5. Set the quorum min/max for the core governor + L2ArbitrumGovernor(coreGov).relay( + coreGov, + 0, + abi.encodeCall( + L2ArbitrumGovernor.setQuorumMinAndMax, (coreMinimumQuorum, coreMaximumQuorum) + ) + ); + + // 6. Upgrade the treasury governor contract + address payable treasuryGov = + payable(address(IL2AddressRegistry(l2AddressRegistry).treasuryGov())); + govProxyAdmin.upgrade(TransparentUpgradeableProxy(treasuryGov), newGovernorImpl); + + // 7. Set the new quorum numerator for the treasury governor + L2ArbitrumGovernor(treasuryGov).relay( + treasuryGov, + 0, + abi.encodeCall( + GovernorVotesQuorumFractionUpgradeable.updateQuorumNumerator, + (newTreasuryQuorumNumerator) + ) + ); + + // 8. Set the quorum min/max for the treasury governor + L2ArbitrumGovernor(treasuryGov).relay( + treasuryGov, + 0, + abi.encodeCall( + L2ArbitrumGovernor.setQuorumMinAndMax, + (treasuryMinimumQuorum, treasuryMaximumQuorum) + ) + ); + } +} diff --git a/test/gov-actions/ActivateDvpQuorumAction.t.sol b/test/gov-actions/ActivateDvpQuorumAction.t.sol new file mode 100644 index 000000000..11b98d3a9 --- /dev/null +++ b/test/gov-actions/ActivateDvpQuorumAction.t.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Test.sol"; +import {ActivateDvpQuorumAction} from + "../../src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol"; +import {L2ArbitrumGovernor} from "../../src/L2ArbitrumGovernor.sol"; +import {L2ArbitrumToken} from "../../src/L2ArbitrumToken.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +// forge test --fork-url $ARB_URL --fork-block-number 389149842 test/gov-actions/ActivateDvpQuorumAction.t.sol -vvvv +contract ActivateDvpQuorumActionTest is Test { + function testAction() external { + if (!isFork()) { + return; + } + + // ensure we are on a fork of arb1 before the upgrade + assertEq(block.chainid, 42_161); + assertEq(block.number, 23_569_916); // L1 block number corresponding to L2 block 389149842 + + L2ArbitrumGovernor coreGovernor = + L2ArbitrumGovernor(payable(0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9)); + L2ArbitrumGovernor treasuryGovernor = + L2ArbitrumGovernor(payable(0x789fC99093B09aD01C34DC7251D0C89ce743e5a4)); + + uint256 prevCoreQuorum = coreGovernor.quorum(23_569_915); + uint256 prevTreasuryQuorum = treasuryGovernor.quorum(23_569_915); + assertEq( + prevCoreQuorum, + 212_581_618_392_648_117_373_902_586, + "unexpected core quorum before upgrade" + ); + assertEq( + prevTreasuryQuorum, + 141_721_078_928_432_078_249_268_390, + "unexpected treasury quorum before upgrade" + ); + + address governorImpl = address(new L2ArbitrumGovernor()); + address tokenImpl = address(new L2ArbitrumToken()); + uint256 initialTDE = 5_275_963_013_349_154_829_183_295_064 + 200_000_000 ether; // excluded + 200M + + ActivateDvpQuorumAction action = new ActivateDvpQuorumAction({ + _l2AddressRegistry: 0x56C4E9Eb6c63aCDD19AeC2b1a00e4f0d7aBda9d3, + _arbTokenProxy: 0x912CE59144191C1204E64559FE8253a0e49E6548, + _govProxyAdmin: ProxyAdmin(0xdb216562328215E010F819B5aBe947bad4ca961e), + _newGovernorImpl: governorImpl, + _newTokenImpl: tokenImpl, + _newCoreQuorumNumerator: 5000, // 50% + _coreMinimumQuorum: 250 ether, + _coreMaximumQuorum: 250_000_000 ether, + _newTreasuryQuorumNumerator: 4000, // 40% + _treasuryMinimumQuorum: 240 ether, + _treasuryMaximumQuorum: 240_000_000 ether, + _initialTotalDelegationEstimate: initialTDE + }); + + // make sure all the immutables are set properly + assertEq(action.l2AddressRegistry(), 0x56C4E9Eb6c63aCDD19AeC2b1a00e4f0d7aBda9d3); + assertEq(action.arbTokenProxy(), 0x912CE59144191C1204E64559FE8253a0e49E6548); + assertEq(address(action.govProxyAdmin()), 0xdb216562328215E010F819B5aBe947bad4ca961e); + assertEq(action.newGovernorImpl(), governorImpl); + assertEq(action.newTokenImpl(), tokenImpl); + assertEq(action.newCoreQuorumNumerator(), 5000); + assertEq(action.coreMinimumQuorum(), 250 ether); + assertEq(action.coreMaximumQuorum(), 250_000_000 ether); + assertEq(action.newTreasuryQuorumNumerator(), 4000); + assertEq(action.treasuryMinimumQuorum(), 240 ether); + assertEq(action.treasuryMaximumQuorum(), 240_000_000 ether); + assertEq(action.initialTotalDelegationEstimate(), initialTDE); + + // execute the action + vm.prank(0xf7951D92B0C345144506576eC13Ecf5103aC905a); // L1 Timelock Alias + IUpgradeExecutor(0xCF57572261c7c2BCF21ffD220ea7d1a27D40A827).execute( + address(action), abi.encodeCall(ActivateDvpQuorumAction.perform, ()) + ); + + // verify the token was upgraded and initialized by checking the initial total delegation estimate + L2ArbitrumToken token = L2ArbitrumToken(0x912CE59144191C1204E64559FE8253a0e49E6548); + assertEq( + token.getTotalDelegation(), + initialTDE, + "initial total delegation estimate not set correctly" + ); + + // verify the governors were upgraded by checking minimum and maximum quorum values + assertEq( + coreGovernor.minimumQuorum(), + 250 ether, + "core governor minimum quorum not set correctly" + ); + assertEq( + coreGovernor.maximumQuorum(), + 250_000_000 ether, + "core governor maximum quorum not set correctly" + ); + assertEq( + treasuryGovernor.minimumQuorum(), + 240 ether, + "treasury governor minimum quorum not set correctly" + ); + assertEq( + treasuryGovernor.maximumQuorum(), + 240_000_000 ether, + "treasury governor maximum quorum not set correctly" + ); + + // ensure that quorum is unchanged at previous block + assertEq( + coreGovernor.quorum(23_569_915), + prevCoreQuorum, + "core governor quorum changed at previous block" + ); + assertEq( + treasuryGovernor.quorum(23_569_915), + prevTreasuryQuorum, + "treasury governor quorum changed at previous block" + ); + + // ensure quorum is being calculated correctly at next block + vm.roll(23_569_917); + uint256 expectedCoreQuorum = (5000 * 200_000_000 ether) / 10_000; + uint256 expectedTreasuryQuorum = (4000 * 200_000_000 ether) / 10_000; + assertEq( + coreGovernor.quorum(23_569_916), + expectedCoreQuorum, + "core governor quorum not calculated correctly at next block" + ); + assertEq( + treasuryGovernor.quorum(23_569_916), + expectedTreasuryQuorum, + "treasury governor quorum not calculated correctly at next block" + ); + } +} + +interface IUpgradeExecutor { + function execute(address upgrade, bytes memory upgradeCallData) external payable; +} diff --git a/test/signatures/ActivateDvpQuorumAction b/test/signatures/ActivateDvpQuorumAction new file mode 100644 index 000000000..e582900b7 --- /dev/null +++ b/test/signatures/ActivateDvpQuorumAction @@ -0,0 +1,31 @@ + +╭----------------------------------+------------╮ +| Method | Identifier | ++===============================================+ +| arbTokenProxy() | 82ffcf4b | +|----------------------------------+------------| +| coreMaximumQuorum() | 3be8caa3 | +|----------------------------------+------------| +| coreMinimumQuorum() | 503eb633 | +|----------------------------------+------------| +| govProxyAdmin() | 8086e788 | +|----------------------------------+------------| +| initialTotalDelegationEstimate() | 97280f00 | +|----------------------------------+------------| +| l2AddressRegistry() | 9b491216 | +|----------------------------------+------------| +| newCoreQuorumNumerator() | 602e6c0c | +|----------------------------------+------------| +| newGovernorImpl() | 3db381e1 | +|----------------------------------+------------| +| newTokenImpl() | 2c312779 | +|----------------------------------+------------| +| newTreasuryQuorumNumerator() | c16985c9 | +|----------------------------------+------------| +| perform() | b147f40c | +|----------------------------------+------------| +| treasuryMaximumQuorum() | 85c062b6 | +|----------------------------------+------------| +| treasuryMinimumQuorum() | 66f9a790 | +╰----------------------------------+------------╯ + diff --git a/test/storage/ActivateDvpQuorumAction b/test/storage/ActivateDvpQuorumAction new file mode 100644 index 000000000..1ec5dc079 --- /dev/null +++ b/test/storage/ActivateDvpQuorumAction @@ -0,0 +1,6 @@ + +╭------+------+------+--------+-------+----------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++================================================+ +╰------+------+------+--------+-------+----------╯ + From fc0d1bd697cc95c660e47c0c30590ded61f8e443 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:26:31 -0500 Subject: [PATCH 23/43] fix tests --- test/L2ArbitrumToken.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/L2ArbitrumToken.t.sol b/test/L2ArbitrumToken.t.sol index d60807f41..6de362097 100644 --- a/test/L2ArbitrumToken.t.sol +++ b/test/L2ArbitrumToken.t.sol @@ -81,7 +81,7 @@ contract L2ArbitrumTokenTest is Test { if (expected < 0) { vm.expectRevert("ARB: NEGATIVE_TOTAL_DELEGATION"); } - l2Token.adjustInitialTotalDelegationEstimate(adjustment); + l2Token.adjustTotalDelegation(adjustment); if (expected < 0) { return; } @@ -185,7 +185,7 @@ contract L2ArbitrumTokenTest is Test { // lower the estimate by some vm.prank(owner); - l2Token.adjustInitialTotalDelegationEstimate(-10); + l2Token.adjustTotalDelegation(-10); // create a snapshot so we can test transfer and undelegate uint256 snap = vm.snapshot(); From ec253f3084030c034b39f045ec72128507f11155 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Oct 2025 08:33:22 -0500 Subject: [PATCH 24/43] fix --- .gas-snapshot | 225 ++++++++++++++++---------------- test/signatures/L2ArbitrumToken | 2 +- 2 files changed, 114 insertions(+), 113 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index e732670a6..e63b6d2e8 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -5,33 +5,33 @@ ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) ArbitrumDAOConstitutionTest:testOwnerCanSetHashTwice() (gas: 263824) -ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16813688) -ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19726041) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19729902) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19729847) -ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16817021) -ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16809003) -ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16811352) -ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16813771) -ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16819184) -ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16811251) -ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16936728) +ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16849998) +ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19762395) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19766168) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19766113) +ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16853331) +ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16845313) +ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16847662) +ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16850081) +ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16855450) +ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16847561) +ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16972840) ArbitrumVestingWalletFactoryTest:testDeploy() (gas: 4589688) ArbitrumVestingWalletFactoryTest:testOnlyOwnerCanCreateWallets() (gas: 1504286) -ArbitrumVestingWalletTest:testCastVote() (gas: 16736828) -ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16661155) -ArbitrumVestingWalletTest:testClaim() (gas: 16514555) -ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16449550) -ArbitrumVestingWalletTest:testDelegate() (gas: 16591077) -ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16515200) -ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16452937) -ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16515414) -ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16582034) -E2E:testE2E() (gas: 85121103) -FixedDelegateErc20WalletTest:testInit() (gas: 6095530) -FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 6089082) -FixedDelegateErc20WalletTest:testTransfer() (gas: 6254297) -FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 6216726) +ArbitrumVestingWalletTest:testCastVote() (gas: 16773138) +ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16697465) +ArbitrumVestingWalletTest:testClaim() (gas: 16550799) +ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16485838) +ArbitrumVestingWalletTest:testDelegate() (gas: 16627365) +ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16551466) +ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16489225) +ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16551680) +ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16617948) +E2E:testE2E() (gas: 85121479) +FixedDelegateErc20WalletTest:testInit() (gas: 6131869) +FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 6125399) +FixedDelegateErc20WalletTest:testTransfer() (gas: 6290592) +FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 6253065) InboxActionsTest:testPauseAndUpauseInbox() (gas: 370544) L1AddressRegistryTest:testAddressRegistryAddress() (gas: 47009) L1ArbitrumTimelockTest:testCancel() (gas: 5324642) @@ -61,54 +61,55 @@ L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10771066) L1GovernanceFactoryTest:testSetMinDelay() (gas: 10746048) L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10799003) L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54702) -L2ArbitrumGovernorTest:testCantReinit() (gas: 14134098) -L2ArbitrumGovernorTest:testDVPQuorumAndClamping() (gas: 14388732) -L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 14171159) -L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 14143656) -L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 14137780) -L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 14329812) -L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 14206327) -L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 14131750) -L2ArbitrumTokenTest:testCanBurn() (gas: 4339618) -L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4374369) -L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4374349) -L2ArbitrumTokenTest:testCanMintTwiceWithWarp() (gas: 8736230) -L2ArbitrumTokenTest:testCanMintZero() (gas: 4354492) -L2ArbitrumTokenTest:testCanTransferAndCallContract() (gas: 4484646) -L2ArbitrumTokenTest:testCanTransferAndCallEmpty() (gas: 4369738) -L2ArbitrumTokenTest:testCannotMintMoreThan2Percent() (gas: 4343818) -L2ArbitrumTokenTest:testCannotMintNotOwner() (gas: 4341697) -L2ArbitrumTokenTest:testCannotMintTwice() (gas: 8704048) -L2ArbitrumTokenTest:testCannotMintWithoutFastForward() (gas: 4342145) -L2ArbitrumTokenTest:testCannotTransferAndCallNonReceiver() (gas: 4367021) -L2ArbitrumTokenTest:testCannotTransferAndCallReverter() (gas: 4427567) -L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4449395) -L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 4072613) -L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 4072620) -L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 4072678) -L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4384593, ~: 4386226) -L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4387016) -L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4489912) -L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4481315) -L2ArbitrumTokenTest:testDvpNoChangeOnSelfTransfer() (gas: 4505813) -L2ArbitrumTokenTest:testDvpNoChangeOnTransferToDelegator() (gas: 4559065) -L2ArbitrumTokenTest:testDvpNoChangeOnTransferToNonDelegator() (gas: 4365723) -L2ArbitrumTokenTest:testDvpNoRevertOnUnderflow() (gas: 4468805) -L2ArbitrumTokenTest:testIncreaseDVPOnDelegateToAnother() (gas: 4455851) -L2ArbitrumTokenTest:testIncreaseDVPOnSelfDelegate() (gas: 4455973) -L2ArbitrumTokenTest:testInitialDvpEstimate(uint64) (runs: 256, μ: 4381466, ~: 4381466) -L2ArbitrumTokenTest:testIsInitialised() (gas: 4345304) -L2ArbitrumTokenTest:testNoChangeDVPOnRedelegateToSame() (gas: 4527875) -L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 2964987) -L2GovernanceFactoryTest:testContractsDeployed() (gas: 29113300) -L2GovernanceFactoryTest:testContractsInitialized() (gas: 29150360) -L2GovernanceFactoryTest:testDeploySteps() (gas: 29124809) -L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 29122310) -L2GovernanceFactoryTest:testRoles() (gas: 29145297) -L2GovernanceFactoryTest:testSanityCheckValues() (gas: 29169793) -L2GovernanceFactoryTest:testSetMinDelay() (gas: 29118306) -L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 29171177) -L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29462025) +L2ArbitrumGovernorTest:testCantReinit() (gas: 14170386) +L2ArbitrumGovernorTest:testDVPQuorumAndClamping() (gas: 14425174) +L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 14207447) +L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 14179944) +L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 14174112) +L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 14366232) +L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 14242703) +L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 14168038) +L2ArbitrumTokenTest:testCanBurn() (gas: 4375935) +L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4410685) +L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4410687) +L2ArbitrumTokenTest:testCanMintTwiceWithWarp() (gas: 8808833) +L2ArbitrumTokenTest:testCanMintZero() (gas: 4390766) +L2ArbitrumTokenTest:testCanTransferAndCallContract() (gas: 4520940) +L2ArbitrumTokenTest:testCanTransferAndCallEmpty() (gas: 4406077) +L2ArbitrumTokenTest:testCannotMintMoreThan2Percent() (gas: 4380156) +L2ArbitrumTokenTest:testCannotMintNotOwner() (gas: 4377992) +L2ArbitrumTokenTest:testCannotMintTwice() (gas: 8776585) +L2ArbitrumTokenTest:testCannotMintWithoutFastForward() (gas: 4378417) +L2ArbitrumTokenTest:testCannotTransferAndCallNonReceiver() (gas: 4403338) +L2ArbitrumTokenTest:testCannotTransferAndCallReverter() (gas: 4463884) +L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4485899) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 4108951) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 4108903) +L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 4108994) +L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4421784, ~: 4424046) +L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4423454) +L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4526229) +L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4517654) +L2ArbitrumTokenTest:testDvpNoChangeOnSelfTransfer() (gas: 4541977) +L2ArbitrumTokenTest:testDvpNoChangeOnTransferToDelegator() (gas: 4595404) +L2ArbitrumTokenTest:testDvpNoChangeOnTransferToNonDelegator() (gas: 4401973) +L2ArbitrumTokenTest:testDvpNoRevertOnUnderflow() (gas: 4506504) +L2ArbitrumTokenTest:testIncreaseDVPOnDelegateToAnother() (gas: 4492355) +L2ArbitrumTokenTest:testIncreaseDVPOnSelfDelegate() (gas: 4492477) +L2ArbitrumTokenTest:testInitialDvpEstimate(uint64) (runs: 256, μ: 4417926, ~: 4417926) +L2ArbitrumTokenTest:testIsInitialised() (gas: 4381576) +L2ArbitrumTokenTest:testNoChangeDVPOnRedelegateToSame() (gas: 4564379) +L2ArbitrumTokenTest:testNoDoublePostUpgradeInit() (gas: 4418459) +L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 3001325) +L2GovernanceFactoryTest:testContractsDeployed() (gas: 29149632) +L2GovernanceFactoryTest:testContractsInitialized() (gas: 29186715) +L2GovernanceFactoryTest:testDeploySteps() (gas: 29161141) +L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 29158642) +L2GovernanceFactoryTest:testRoles() (gas: 29181629) +L2GovernanceFactoryTest:testSanityCheckValues() (gas: 29206037) +L2GovernanceFactoryTest:testSetMinDelay() (gas: 29154638) +L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 29207509) +L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29498401) L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 30767668) L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 30771899) L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 25781453) @@ -172,21 +173,21 @@ SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) -SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10431271) +SecurityCouncilMemberRemovalGovernorTest:testInitFails() (gas: 10467559) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallParamRestriction() (gas: 56157) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationCallRestriction() (gas: 49685) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetLen() (gas: 35392) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationTargetRestriction() (gas: 46987) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationUnexpectedCallDataLen() (gas: 41583) SecurityCouncilMemberRemovalGovernorTest:testProposalCreationValuesRestriction() (gas: 61908) -SecurityCouncilMemberRemovalGovernorTest:testProposalDoesExpire() (gas: 272283) +SecurityCouncilMemberRemovalGovernorTest:testProposalDoesExpire() (gas: 272415) SecurityCouncilMemberRemovalGovernorTest:testProposalExpirationDeadline() (gas: 134809) SecurityCouncilMemberRemovalGovernorTest:testRelay() (gas: 42123) SecurityCouncilMemberRemovalGovernorTest:testSeparateSelector() (gas: 23536) SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumerator() (gas: 30049) SecurityCouncilMemberRemovalGovernorTest:testSetVoteSuccessNumeratorAffordance() (gas: 47631) -SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorInsufficientVotes() (gas: 358107) -SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorSufficientVotes() (gas: 361025) +SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorInsufficientVotes() (gas: 358195) +SecurityCouncilMemberRemovalGovernorTest:testSuccessNumeratorSufficientVotes() (gas: 361113) SecurityCouncilMemberRemovalGovernorTest:testSuccessfulProposalAndCantAbstain() (gas: 142630) SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 8094226) SecurityCouncilMemberSyncActionTest:testAddOne() (gas: 8095064) @@ -223,41 +224,41 @@ SetInitialGovParamsActionTest:testL1() (gas: 259949) SetInitialGovParamsActionTest:testL2() (gas: 688895) SetSequencerInboxMaxTimeVariationActionTest:testSetMaxTimeVariation() (gas: 310296) SwitchManagerRolesActionTest:testAction() (gas: 6313) -TokenDistributorTest:testClaim() (gas: 6086841) -TokenDistributorTest:testClaimAndDelegate() (gas: 6198153) -TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 6092342) -TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 6148161) -TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 6148162) -TokenDistributorTest:testClaimFailsAfterEnd() (gas: 6022940) -TokenDistributorTest:testClaimFailsBeforeStart() (gas: 6022435) -TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 6005151) -TokenDistributorTest:testClaimFailsForTwice() (gas: 6085579) -TokenDistributorTest:testClaimFailsForUnknown() (gas: 6025016) -TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4407115) -TokenDistributorTest:testDoesDeploy() (gas: 5612530) -TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5723510) -TokenDistributorTest:testOldClaimStart() (gas: 4407678) -TokenDistributorTest:testSetRecipients() (gas: 6020850) -TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 5987722) -TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5739242) -TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 6031903) -TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5740711) -TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5740940) -TokenDistributorTest:testSetRecipientsTwice() (gas: 6710475) -TokenDistributorTest:testSetSweepReceiver() (gas: 6025167) -TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 6022786) -TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 6023747) -TokenDistributorTest:testSweep() (gas: 6096112) -TokenDistributorTest:testSweepAfterClaim() (gas: 6159265) -TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 6022520) -TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 6026241) -TokenDistributorTest:testSweepFailsTwice() (gas: 6095049) -TokenDistributorTest:testWithdraw() (gas: 6063321) -TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 6063343) -TokenDistributorTest:testWithdrawFailsTransfer() (gas: 6024722) -TokenDistributorTest:testZeroDelegateTo() (gas: 4405010) -TokenDistributorTest:testZeroOwner() (gas: 4404923) -TokenDistributorTest:testZeroReceiver() (gas: 4404952) +TokenDistributorTest:testClaim() (gas: 6123114) +TokenDistributorTest:testClaimAndDelegate() (gas: 6234360) +TokenDistributorTest:testClaimAndDelegateFailsForExpired() (gas: 6128615) +TokenDistributorTest:testClaimAndDelegateFailsForWrongSender() (gas: 6184412) +TokenDistributorTest:testClaimAndDelegateFailsWrongNonce() (gas: 6184413) +TokenDistributorTest:testClaimFailsAfterEnd() (gas: 6059257) +TokenDistributorTest:testClaimFailsBeforeStart() (gas: 6058752) +TokenDistributorTest:testClaimFailsForFalseTransfer() (gas: 6041468) +TokenDistributorTest:testClaimFailsForTwice() (gas: 6121874) +TokenDistributorTest:testClaimFailsForUnknown() (gas: 6061333) +TokenDistributorTest:testClaimStartAfterClaimEnd() (gas: 4443432) +TokenDistributorTest:testDoesDeploy() (gas: 5648847) +TokenDistributorTest:testDoesDeployAndDeposit() (gas: 5759805) +TokenDistributorTest:testOldClaimStart() (gas: 4443995) +TokenDistributorTest:testSetRecipients() (gas: 6057167) +TokenDistributorTest:testSetRecipientsFailsNotEnoughDeposit() (gas: 6024040) +TokenDistributorTest:testSetRecipientsFailsNotOwner() (gas: 5775581) +TokenDistributorTest:testSetRecipientsFailsWhenAddingTwice() (gas: 6068222) +TokenDistributorTest:testSetRecipientsFailsWrongAmountCount() (gas: 5777051) +TokenDistributorTest:testSetRecipientsFailsWrongRecipientCount() (gas: 5777280) +TokenDistributorTest:testSetRecipientsTwice() (gas: 6746774) +TokenDistributorTest:testSetSweepReceiver() (gas: 6061484) +TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 6059103) +TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 6060064) +TokenDistributorTest:testSweep() (gas: 6132341) +TokenDistributorTest:testSweepAfterClaim() (gas: 6195472) +TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 6058837) +TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 6062536) +TokenDistributorTest:testSweepFailsTwice() (gas: 6131300) +TokenDistributorTest:testWithdraw() (gas: 6099572) +TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 6099594) +TokenDistributorTest:testWithdrawFailsTransfer() (gas: 6061039) +TokenDistributorTest:testZeroDelegateTo() (gas: 4441327) +TokenDistributorTest:testZeroOwner() (gas: 4441240) +TokenDistributorTest:testZeroReceiver() (gas: 4441269) TokenDistributorTest:testZeroToken() (gas: 71889) TopNomineesGasTest:testTopNomineesGas() (gas: 4502996) UpgradeExecRouteBuilderTest:testAIP1Point2() (gas: 1444846) diff --git a/test/signatures/L2ArbitrumToken b/test/signatures/L2ArbitrumToken index b0bb37f50..f7d77aa3d 100644 --- a/test/signatures/L2ArbitrumToken +++ b/test/signatures/L2ArbitrumToken @@ -10,7 +10,7 @@ |---------------------------------------------------------------+------------| | MIN_MINT_INTERVAL() | a9f8ad04 | |---------------------------------------------------------------+------------| -| adjustInitialTotalDelegationEstimate(int256) | 4123cad7 | +| adjustTotalDelegation(int256) | ec20b526 | |---------------------------------------------------------------+------------| | allowance(address,address) | dd62ed3e | |---------------------------------------------------------------+------------| From 74e32d4c2baf07918b3367eecb70c1f8786bb79a Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 17 Oct 2025 10:24:20 -0500 Subject: [PATCH 25/43] move action --- .../AIPs/{TotalDvpQuorum => }/ActivateDvpQuorumAction.sol | 4 ++-- test/gov-actions/ActivateDvpQuorumAction.t.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/gov-action-contracts/AIPs/{TotalDvpQuorum => }/ActivateDvpQuorumAction.sol (96%) diff --git a/src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol b/src/gov-action-contracts/AIPs/ActivateDvpQuorumAction.sol similarity index 96% rename from src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol rename to src/gov-action-contracts/AIPs/ActivateDvpQuorumAction.sol index 2bf6cf9cb..cf0b0f963 100644 --- a/src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol +++ b/src/gov-action-contracts/AIPs/ActivateDvpQuorumAction.sol @@ -4,8 +4,8 @@ pragma solidity 0.8.16; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {IL2AddressRegistry} from "./../../address-registries/L2AddressRegistryInterfaces.sol"; -import {L2ArbitrumGovernor} from "./../../../L2ArbitrumGovernor.sol"; +import {IL2AddressRegistry} from "./../address-registries/L2AddressRegistryInterfaces.sol"; +import {L2ArbitrumGovernor} from "./../../L2ArbitrumGovernor.sol"; import {GovernorVotesQuorumFractionUpgradeable} from "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; diff --git a/test/gov-actions/ActivateDvpQuorumAction.t.sol b/test/gov-actions/ActivateDvpQuorumAction.t.sol index 11b98d3a9..0754cd7e8 100644 --- a/test/gov-actions/ActivateDvpQuorumAction.t.sol +++ b/test/gov-actions/ActivateDvpQuorumAction.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.16; import "forge-std/Test.sol"; import {ActivateDvpQuorumAction} from - "../../src/gov-action-contracts/AIPs/TotalDvpQuorum/ActivateDvpQuorumAction.sol"; + "../../src/gov-action-contracts/AIPs/ActivateDvpQuorumAction.sol"; import {L2ArbitrumGovernor} from "../../src/L2ArbitrumGovernor.sol"; import {L2ArbitrumToken} from "../../src/L2ArbitrumToken.sol"; import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; From 5efd5d536ec550951d2c5cabdb3acacc7ba5de33 Mon Sep 17 00:00:00 2001 From: Gary Ghayrat <61768337+garyghayrat@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:32:29 -0400 Subject: [PATCH 26/43] Governor v2 upgrade (#6) * Add V2 governor and basic testing, scripts for use later * Store proposers and revert cancel if not proposer * Add governor action contract, upgrade script, and tests * Add `MultiProxyUpgradeAction` contract and upgrade through proxy * Make fns virtual, mapping internal, and add natspec in GovernorV2 * Fix nits and separate test helper from deployConstants * Fix nits * Update naming in gov upgrade action contract * Fix nits and update constants * Update src/L2ArbitrumGovernorV2.sol --------- Co-authored-by: Ed Mazurek --- scripts/forge-scripts/DeployConstants.sol | 34 +++ .../forge-scripts/DeployImplementation.s.sol | 14 ++ .../DeployMultiProxyUpgradeAction.s.sol | 27 +++ .../SubmitUpgradeProposalScript.s.sol | 46 ++++ .../utils/EncodeL2ArbSysProposal.sol | 105 +++++++++ src/L2ArbitrumGovernorV2.sol | 63 ++++++ .../upgrade-proxy/MultiProxyUpgradeAction.sol | 70 ++++++ test/L2ArbitrumGovernorV2.t.sol | 199 ++++++++++++++++++ test/SubmitUpgradeProposal.t.sol | 173 +++++++++++++++ test/util/SetupNewGovernors.sol | 156 ++++++++++++++ 10 files changed, 887 insertions(+) create mode 100644 scripts/forge-scripts/DeployConstants.sol create mode 100644 scripts/forge-scripts/DeployImplementation.s.sol create mode 100644 scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol create mode 100644 scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol create mode 100644 scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol create mode 100644 src/L2ArbitrumGovernorV2.sol create mode 100644 src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol create mode 100644 test/L2ArbitrumGovernorV2.t.sol create mode 100644 test/SubmitUpgradeProposal.t.sol create mode 100644 test/util/SetupNewGovernors.sol diff --git a/scripts/forge-scripts/DeployConstants.sol b/scripts/forge-scripts/DeployConstants.sol new file mode 100644 index 000000000..f2a843d2a --- /dev/null +++ b/scripts/forge-scripts/DeployConstants.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +// Governor deployment constants that are shared between the Core Governor and the +// Treasury Governor. These should be carefully checked and reviewed before final deployment. +contract DeployConstants { + address public constant L2_ARB_TOKEN_ADDRESS = 0x912CE59144191C1204E64559FE8253a0e49E6548; + + address public constant L2_CORE_GOVERNOR = 0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9; + address public constant L2_CORE_GOVERNOR_TIMELOCK = 0x34d45e99f7D8c45ed05B5cA72D54bbD1fb3F98f0; + address public constant L2_TREASURY_GOVERNOR = 0x789fC99093B09aD01C34DC7251D0C89ce743e5a4; + address public constant L2_TREASURY_GOVERNOR_TIMELOCK = + 0xbFc1FECa8B09A5c5D3EFfE7429eBE24b9c09EF58; + address public constant L2_PROXY_ADMIN_OWNER = L2_UPGRADE_EXECUTOR; + address public constant L2_PROXY_ADMIN_CONTRACT = 0xdb216562328215E010F819B5aBe947bad4ca961e; + + address public constant L2_ARB_SYS = 0x0000000000000000000000000000000000000064; + address public constant L2_ARB_RETRYABLE_TX = 0x000000000000000000000000000000000000006E; + address public constant L2_SECURITY_COUNCIL_9 = 0x423552c0F05baCCac5Bfa91C6dCF1dc53a0A1641; + + address public constant L1_TIMELOCK = 0xE6841D92B0C345144506576eC13ECf5103aC7f49; + uint256 public constant L1_TIMELOCK_MIN_DELAY = 259_200; + address public constant L1_ARB_ONE_DELAYED_INBOX = 0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f; + + // =========================================================================================== + // TODO: Update these values after the deployment process + // =========================================================================================== + address public constant L2_ARBITRUM_GOVERNOR_V2_IMPLEMENTATION = address(0); // TODO: Update after the core governor is deployed. + bool public constant UPGRADE_PROPOSAL_PASSED_ONCHAIN = false; // TODO: Update after the upgrade proposal is passed. + // =========================================================================================== + + address public constant L2_UPGRADE_EXECUTOR = 0xCF57572261c7c2BCF21ffD220ea7d1a27D40A827; + address public constant L2_RETRYABLE_TICKET_MAGIC = 0xa723C008e76E379c55599D2E4d93879BeaFDa79C; +} diff --git a/scripts/forge-scripts/DeployImplementation.s.sol b/scripts/forge-scripts/DeployImplementation.s.sol new file mode 100644 index 000000000..f52b66448 --- /dev/null +++ b/scripts/forge-scripts/DeployImplementation.s.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; +import {Script} from "forge-std/Script.sol"; + +/// @notice Deploy script for the underlying implementation that will be used by both Governor proxies +contract DeployImplementation is Script { + function run() public returns (L2ArbitrumGovernorV2 _implementation) { + vm.startBroadcast(); + _implementation = new L2ArbitrumGovernorV2(); + vm.stopBroadcast(); + } +} diff --git a/scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol b/scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol new file mode 100644 index 000000000..1dd956afd --- /dev/null +++ b/scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import {Script} from "forge-std/Script.sol"; +import { + MultiProxyUpgradeAction +} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol"; +import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; + +/// @title DeployMultiProxyUpgradeAction. +/// @notice Script to deploy the `MultiProxyUpgradeAction` contract. +/// @dev `MultiProxyUpgradeAction` contract is used to upgrade multiple governor proxies in a single transaction. +contract DeployMultiProxyUpgradeAction is DeployConstants, Script { + function run(address _newGovernorImplementationAddress) + public + returns (MultiProxyUpgradeAction multiProxyUpgradeAction) + { + vm.startBroadcast(); + multiProxyUpgradeAction = new MultiProxyUpgradeAction( + L2_PROXY_ADMIN_CONTRACT, + L2_CORE_GOVERNOR, + L2_TREASURY_GOVERNOR, + _newGovernorImplementationAddress + ); + vm.stopBroadcast(); + } +} diff --git a/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol b/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol new file mode 100644 index 000000000..60f401feb --- /dev/null +++ b/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import {Script} from "forge-std/Script.sol"; +import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; +import "node_modules/@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; +import {EncodeL2ArbSysProposal} from "scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol"; + +contract SubmitUpgradeProposalScript is Script, DeployConstants, EncodeL2ArbSysProposal { + address PROPOSER_ADDRESS = + vm.envOr("PROPOSER_ADDRESS", 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD); //TODO: Update proposer address. + string PROPOSAL_DESCRIPTION = + vm.envOr("PROPOSAL_DESCRIPTION", string("Add proposal description here")); // TODO: Update proposal description. + + function run(address _multiProxyUpgradeAction, uint256 _minDelay) + public + returns ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description, + uint256 _proposalId + ) + { + return proposeUpgrade(_multiProxyUpgradeAction, _minDelay); + } + + function proposeUpgrade(address _multiProxyUpgradeAction, uint256 _minDelay) + internal + returns ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description, + uint256 _proposalId + ) + { + _description = PROPOSAL_DESCRIPTION; + (_targets, _values, _calldatas) = + encodeL2ArbSysProposal(_description, _multiProxyUpgradeAction, _minDelay); + vm.startBroadcast(PROPOSER_ADDRESS); + _proposalId = GovernorUpgradeable(payable(L2_CORE_GOVERNOR)) + .propose(_targets, _values, _calldatas, _description); + vm.stopBroadcast(); + } +} diff --git a/scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol b/scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol new file mode 100644 index 000000000..b975e67d4 --- /dev/null +++ b/scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; + +contract EncodeL2ArbSysProposal is DeployConstants { + function encodeL2ArbSysProposal( + string memory _proposalDescription, + address _oneOffUpgradeAddr, + uint256 _minDelay + ) + public + pure + returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) + { + targets = new address[](1); + values = new uint256[](1); + calldatas = new bytes[](1); + + targets[0] = L2_ARB_SYS; + calldatas[0] = + encodeArbSysProposalCalldata(_proposalDescription, _oneOffUpgradeAddr, _minDelay); + } + + function encodeArbSysProposalCalldata( + string memory _proposalDescription, + address _oneOffUpgradeAddr, + uint256 _minDelay + ) public pure returns (bytes memory proposalCalldata) { + address retryableTicketMagic = L2_RETRYABLE_TICKET_MAGIC; + + // the data to call the upgrade executor with + // it tells the upgrade executor how to call the upgrade contract, and what calldata to provide to it + bytes memory upgradeExecutorCallData = abi.encodeWithSelector( + IUpgradeExecutor.execute.selector, + _oneOffUpgradeAddr, + abi.encodeWithSelector(IMultiProxyUpgradeAction.perform.selector) + ); + + // the data provided to call the l1 timelock with + // specifies how to create a retryable ticket, which will then be used to call the upgrade executor with the + // data created from the step above + bytes memory l1TimelockData = abi.encodeWithSelector( + IL1Timelock.schedule.selector, + retryableTicketMagic, // tells the l1 timelock that we want to make a retryable, instead of an l1 upgrade + 0, // ignored for l2 upgrades + abi.encode( // these are the retryable data params + L1_ARB_ONE_DELAYED_INBOX, // the inbox we want to use, should be arb one or nova inbox + L2_UPGRADE_EXECUTOR, // the upgrade executor on the l2 network + 0, // no value in this upgrade + 0, // max gas - will be filled in when the retryable is actually executed + 0, // max fee per gas - will be filled in when the retryable is actually executed + upgradeExecutorCallData // call data created in the previous step + ), + bytes32(0), // no predecessor + keccak256(abi.encodePacked(_proposalDescription)), // prop description + _minDelay // delay for this proposal + ); + + // the data provided to the L2 Arbitrum Governor in the propose() method + // the target will be the ArbSys address on Arb One + proposalCalldata = abi.encodeWithSelector( + IArbSys.sendTxToL1.selector, // the execution of the proposal will create an L2->L1 cross chain message + L1_TIMELOCK, // the target of the cross chain message is the L1 timelock + l1TimelockData // call the l1 timelock with the data created in the previous step + ); + } +} + +interface IUpgradeExecutor { + function execute(address to, bytes calldata data) external payable; +} + +interface IL1Timelock { + function schedule( + address target, + uint256 value, + bytes calldata data, + bytes32 predecessor, + bytes32 salt, + uint256 delay + ) external; + function getMinDelay() external view returns (uint256); +} + +interface IArbSys { + function sendTxToL1(address destination, bytes calldata data) external payable returns (uint256); +} + +interface IL2ArbitrumGovernor { + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) external returns (uint256); +} + +interface IMultiProxyUpgradeAction { + function perform() external; +} + +interface IFixedDelegateErc20Wallet { + function transfer(address _token, address _to, uint256 _amount) external returns (bool); +} diff --git a/src/L2ArbitrumGovernorV2.sol b/src/L2ArbitrumGovernorV2.sol new file mode 100644 index 000000000..acef66490 --- /dev/null +++ b/src/L2ArbitrumGovernorV2.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import { + L2ArbitrumGovernor, + GovernorUpgradeable, + IGovernorUpgradeable +} from "src/L2ArbitrumGovernor.sol"; + +contract L2ArbitrumGovernorV2 is L2ArbitrumGovernor { + /// @notice Error thrown when attempting to cancel a proposal that is not in Pending state. + /// @param state The current state of the proposal. + error ProposalNotPending(GovernorUpgradeable.ProposalState state); + + /// @notice Error thrown when a non-proposer attempts to cancel a proposal. + /// @param sender The address attempting to cancel the proposal. + /// @param proposer The address of the actual proposer. + error NotProposer(address sender, address proposer); + + /// @notice Mapping from proposal ID to the address of the proposer. + /// @dev Used in cancel() to ensure only the proposer can cancel the proposal. + mapping(uint256 => address) internal proposers; + + /// @inheritdoc IGovernorUpgradeable + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { + uint256 _proposalId = GovernorUpgradeable.propose(targets, values, calldatas, description); + proposers[_proposalId] = msg.sender; + return _proposalId; + } + + /// @notice Allows a proposer to cancel a proposal when it is pending. + /// @param targets The proposal's targets. + /// @param values The proposal's values. + /// @param calldatas The proposal's calldatas. + /// @param descriptionHash The hash of the proposal's description. + /// @return The id of the proposal. + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public virtual returns (uint256) { + uint256 _proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + if (state(_proposalId) != ProposalState.Pending) { + revert ProposalNotPending(state(_proposalId)); + } + + address _proposer = proposers[_proposalId]; + if (msg.sender != _proposer) { + revert NotProposer(msg.sender, _proposer); + } + + delete proposers[_proposalId]; + + return GovernorUpgradeable._cancel(targets, values, calldatas, descriptionHash); + } +} diff --git a/src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol b/src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol new file mode 100644 index 000000000..0dd8d1406 --- /dev/null +++ b/src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import { + TimelockControllerUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyUpgradeAction} from "./ProxyUpgradeAction.sol"; + +/// @title MultiProxyUpgradeAction +/// @notice A contract to proxy upgrade the Core and Treasury Governor contracts. +/// @custom:security-contact https://immunefi.com/bug-bounty/arbitrum/information/ +contract MultiProxyUpgradeAction is ProxyUpgradeAction { + /// @notice The address of the Proxy Admin contract. + address public immutable PROXY_ADMIN; + /// @notice The address of the current Core Governor contract. + address public immutable CORE_GOVERNOR_ADDRESS; + /// @notice The address of the current Treasury Governor contract. + address public immutable TREASURY_GOVERNOR_ADDRESS; + /// @notice The address of the new Governor implementation contract. + address public immutable NEW_GOVERNOR_IMPLEMENTATION_ADDRESS; + + /// @notice Sets up the contract with the given parameters. + /// @param _proxyAdmin The address of the Proxy Admin contract. + /// @param _coreGovernorAddress The address of the current Core Governor contract. + /// @param _treasuryGovernorAddress The address of the current Treasury Governor contract. + /// @param _newGovernorImplementationAddress The address of the new Governor implementation contract. + constructor( + address _proxyAdmin, + address _coreGovernorAddress, + address _treasuryGovernorAddress, + address _newGovernorImplementationAddress + ) { + if ( + _proxyAdmin == address(0) || _coreGovernorAddress == address(0) + || _treasuryGovernorAddress == address(0) + || _newGovernorImplementationAddress == address(0) + ) { + revert("MultiProxyUpgradeAction: zero address"); + } + PROXY_ADMIN = _proxyAdmin; + CORE_GOVERNOR_ADDRESS = _coreGovernorAddress; + TREASURY_GOVERNOR_ADDRESS = _treasuryGovernorAddress; + NEW_GOVERNOR_IMPLEMENTATION_ADDRESS = _newGovernorImplementationAddress; + } + + /// @notice Proxy upgrades the Core and Treasury Governor contracts. + function perform() external { + perform(PROXY_ADMIN, payable(CORE_GOVERNOR_ADDRESS), NEW_GOVERNOR_IMPLEMENTATION_ADDRESS); + perform( + PROXY_ADMIN, payable(TREASURY_GOVERNOR_ADDRESS), NEW_GOVERNOR_IMPLEMENTATION_ADDRESS + ); + require( + ProxyAdmin(payable(PROXY_ADMIN)) + .getProxyImplementation(TransparentUpgradeableProxy(payable(CORE_GOVERNOR_ADDRESS))) + == NEW_GOVERNOR_IMPLEMENTATION_ADDRESS, + "MultiProxyUpgradeAction: Core Governor not upgraded" + ); + require( + ProxyAdmin(payable(PROXY_ADMIN)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(TREASURY_GOVERNOR_ADDRESS)) + ) == NEW_GOVERNOR_IMPLEMENTATION_ADDRESS, + "MultiProxyUpgradeAction: Treasury Governor not upgraded" + ); + } +} diff --git a/test/L2ArbitrumGovernorV2.t.sol b/test/L2ArbitrumGovernorV2.t.sol new file mode 100644 index 000000000..6629555cb --- /dev/null +++ b/test/L2ArbitrumGovernorV2.t.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import "forge-std/Test.sol"; +import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; +import {L2ArbitrumToken} from "src/L2ArbitrumToken.sol"; +import {ArbitrumTimelock} from "src/ArbitrumTimelock.sol"; +import { + TimelockControllerUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; +import { + IGovernorUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/IGovernorUpgradeable.sol"; +import {TestUtil} from "test/util/TestUtil.sol"; + +contract L2ArbitrumGovernorV2Test is Test { + // Core components + L2ArbitrumGovernorV2 internal governor; + L2ArbitrumToken internal token; + ArbitrumTimelock internal timelock; + + // Simple config + address internal constant L1_TOKEN_ADDRESS = address(0x1111); + address internal constant TOKEN_OWNER = address(0x2222); + address internal constant OWNER = address(0x3333); + + uint256 internal constant INITIAL_SUPPLY = 1_000_000 ether; + uint256 internal constant VOTING_DELAY = 1; + uint256 internal constant VOTING_PERIOD = 5; + uint256 internal constant QUORUM_NUMERATOR = 1; + uint256 internal constant PROPOSAL_THRESHOLD = 0; // allow proposals without voting power + uint64 internal constant VOTE_EXTENSION = 0; + + address internal constant PROXY_ADMIN = 0xc7183455a4C133Ae270771860664b6B7ec320bB1; + + function setUp() public virtual { + // Deploy token proxy and initialize + token = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); + token.initialize(L1_TOKEN_ADDRESS, INITIAL_SUPPLY, TOKEN_OWNER); + governor = L2ArbitrumGovernorV2( + payable(TestUtil.deployProxy(address(new L2ArbitrumGovernorV2()))) + ); + timelock = ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); + address[] memory proposers = new address[](1); + address[] memory executors = new address[](1); + proposers[0] = address(governor); + executors[0] = address(governor); + timelock.initialize(1, proposers, executors); + + // Initialize governor V2 + governor.initialize( + token, + TimelockControllerUpgradeable(payable(address(timelock))), + OWNER, + VOTING_DELAY, + VOTING_PERIOD, + QUORUM_NUMERATOR, + PROPOSAL_THRESHOLD, + VOTE_EXTENSION + ); + } + + function _basicProposal() + internal + pure + returns ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) + { + targets = new address[](1); + values = new uint256[](1); + calldatas = new bytes[](1); + targets[0] = address(0x1234); + values[0] = 0; + calldatas[0] = bytes(""); + description = "Test"; + } +} + +abstract contract Cancel is L2ArbitrumGovernorV2Test { + function testFuzz_CancelsPendingProposal(address _proposer) public { + vm.assume(_proposer != address(0)); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + ); + + vm.prank(_proposer); + uint256 canceledId = + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + assertEq(canceledId, proposalId); + assertEq( + uint256(governor.state(proposalId)), + uint256(IGovernorUpgradeable.ProposalState.Canceled) + ); + } + + function testFuzz_RevertIf_NotProposer(address _proposer, address _actor) public { + vm.assume(_proposer != address(0)); + vm.assume(_actor != _proposer && _actor != PROXY_ADMIN); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + ); + + vm.expectRevert( + abi.encodeWithSelector(L2ArbitrumGovernorV2.NotProposer.selector, _actor, _proposer) + ); + vm.prank(_actor); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_ProposalIsActive(address _proposer) public { + vm.assume(_proposer != address(0)); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + + vm.roll(block.number + governor.votingDelay() + 1); + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Active) + ); + + vm.prank(_proposer); + vm.expectRevert( + abi.encodeWithSelector( + L2ArbitrumGovernorV2.ProposalNotPending.selector, + IGovernorUpgradeable.ProposalState.Active + ) + ); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_AlreadyCanceled(address _proposer) public { + vm.assume(_proposer != address(0)); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + ); + + // First cancel + vm.prank(_proposer); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + assertEq( + uint256(governor.state(proposalId)), + uint256(IGovernorUpgradeable.ProposalState.Canceled) + ); + + vm.prank(_proposer); + vm.expectRevert( + abi.encodeWithSelector( + L2ArbitrumGovernorV2.ProposalNotPending.selector, + IGovernorUpgradeable.ProposalState.Canceled + ) + ); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + } +} + +contract MockGovernorCancel is Cancel { + function setUp() public override(L2ArbitrumGovernorV2Test) { + super.setUp(); + } +} diff --git a/test/SubmitUpgradeProposal.t.sol b/test/SubmitUpgradeProposal.t.sol new file mode 100644 index 000000000..e504bbbe7 --- /dev/null +++ b/test/SubmitUpgradeProposal.t.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import {Test} from "forge-std/Test.sol"; +import {SubmitUpgradeProposalScript} from "scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol"; +import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; +import { + MultiProxyUpgradeAction +} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol"; +import {SetupNewGovernors} from "test/util/SetupNewGovernors.sol"; +import { + ProxyUpgradeAndCallAction +} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/ProxyUpgradeAndCallAction.sol"; +import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +contract SubmitUpgradeProposalTest is SetupNewGovernors { + event Upgraded(address indexed implementation); + + function test_SuccessfullyExecuteUpgradeProposal() public { + MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( + L2_PROXY_ADMIN_CONTRACT, + L2_CORE_GOVERNOR, + L2_TREASURY_GOVERNOR, + address(newGovernorImplementation) + ); + + // Propose + ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description, + uint256 _proposalId + ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), + uint256(IGovernor.ProposalState.Pending) + ); + vm.roll(block.number + currentCoreGovernor.votingDelay() + 1); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), uint256(IGovernor.ProposalState.Active) + ); + + // Vote + for (uint256 i; i < _majorDelegates.length; i++) { + vm.prank(_majorDelegates[i]); + currentCoreGovernor.castVote(_proposalId, uint8(VoteType.For)); + } + + // Success + vm.roll(block.number + currentCoreGovernor.votingPeriod() + 1); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), + uint256(IGovernor.ProposalState.Succeeded) + ); + + // Queue + currentCoreGovernor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), uint256(IGovernor.ProposalState.Queued) + ); + vm.warp(block.timestamp + currentCoreTimelock.getMinDelay() + 1); + + vm.expectEmit(); + emit Upgraded(address(newGovernorImplementation)); + vm.expectEmit(); + emit Upgraded(address(newGovernorImplementation)); + + // Execute + currentCoreGovernor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); + + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), + uint256(IGovernor.ProposalState.Executed) + ); + assertEq( + ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(address(currentCoreGovernor))) + ), + address(newGovernorImplementation) + ); + assertEq( + ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(address(currentTreasuryGovernor))) + ), + address(newGovernorImplementation) + ); + assertEq( + ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_CORE_GOVERNOR))), + L2_PROXY_ADMIN_CONTRACT + ); + assertEq( + ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_TREASURY_GOVERNOR))), + L2_PROXY_ADMIN_CONTRACT + ); + } + + function test_DefeatedExecuteUpgradeProposalDoesNotUpdateImplementation() public { + address initialCoreGovernorImplementation = ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(address(currentCoreGovernor))) + ); + + address initialTreasuryGovernorImplementation = ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(address(currentTreasuryGovernor))) + ); + + MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( + L2_PROXY_ADMIN_CONTRACT, + L2_CORE_GOVERNOR, + L2_TREASURY_GOVERNOR, + address(newGovernorImplementation) + ); + + // Propose + ( + /*address[] memory _targets*/, + /*uint256[] memory _values*/, + /*bytes[] memory _calldatas*/, + /*string memory _description*/, + uint256 _proposalId + ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), + uint256(IGovernor.ProposalState.Pending) + ); + vm.roll(block.number + currentCoreGovernor.votingDelay() + 1); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), uint256(IGovernor.ProposalState.Active) + ); + + // Vote + for (uint256 i; i < _majorDelegates.length; i++) { + vm.prank(_majorDelegates[i]); + currentCoreGovernor.castVote(_proposalId, uint8(VoteType.Against)); + } + + // Defeat + vm.roll(block.number + currentCoreGovernor.votingPeriod() + 1); + assertEq( + uint256(currentCoreGovernor.state(_proposalId)), + uint256(IGovernor.ProposalState.Defeated) + ); + + assertEq( + ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(address(currentCoreGovernor))) + ), + initialCoreGovernorImplementation + ); + assertEq( + ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) + .getProxyImplementation( + TransparentUpgradeableProxy(payable(address(currentTreasuryGovernor))) + ), + initialTreasuryGovernorImplementation + ); + } +} + +interface IUpgradeExecutor { + function execute(address to, bytes calldata data) external payable; +} diff --git a/test/util/SetupNewGovernors.sol b/test/util/SetupNewGovernors.sol new file mode 100644 index 000000000..312202184 --- /dev/null +++ b/test/util/SetupNewGovernors.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.16; + +import {Test, console2} from "forge-std/Test.sol"; +import { + TimelockControllerUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; +import { + GovernorUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; +import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; +import {SubmitUpgradeProposalScript} from "scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol"; +import {DeployImplementation} from "scripts/forge-scripts/DeployImplementation.s.sol"; +import { + DeployMultiProxyUpgradeAction +} from "scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol"; +import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; +import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; +import {L2ArbitrumGovernor} from "src/L2ArbitrumGovernor.sol"; +import { + MultiProxyUpgradeAction +} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol"; + +abstract contract SetupNewGovernors is DeployConstants, Test { + // Deploy & setup scripts + SubmitUpgradeProposalScript submitUpgradeProposalScript; + MultiProxyUpgradeAction multiProxyUpgradeAction; + + // Current governors and timelocks + L2ArbitrumGovernor currentCoreGovernor; + TimelockControllerUpgradeable currentCoreTimelock; + L2ArbitrumGovernor currentTreasuryGovernor; + TimelockControllerUpgradeable currentTreasuryTimelock; + + // New governors + L2ArbitrumGovernorV2 newGovernorImplementation; + + uint256 constant FORK_BLOCK = 245_608_716; // Arbitrary recent block + address[] public _majorDelegates; + + enum ProposalState { + Pending, + Active, + Canceled, + Defeated, + Succeeded, + Queued, + Expired, + Executed + } + enum VoteType { + Against, + For, + Abstain + } + + function setUp() public virtual { + vm.createSelectFork( + vm.envOr( + "ARBITRUM_ONE_RPC_URL", string("Please set ARBITRUM_ONE_RPC_URL in your .env file") + ), + FORK_BLOCK + ); + + // Deploy Governor implementation contract + DeployImplementation _implementationDeployer = new DeployImplementation(); + address _implementation = address(_implementationDeployer.run()); + + // Deploy Governor proxy contracts + newGovernorImplementation = L2_ARBITRUM_GOVERNOR_V2_IMPLEMENTATION == address(0) + ? L2ArbitrumGovernorV2(payable(_implementation)) + : L2ArbitrumGovernorV2(payable(L2_ARBITRUM_GOVERNOR_V2_IMPLEMENTATION)); + + // Current governors and timelocks + currentCoreGovernor = L2ArbitrumGovernor(payable(L2_CORE_GOVERNOR)); + currentCoreTimelock = TimelockControllerUpgradeable(payable(L2_CORE_GOVERNOR_TIMELOCK)); + currentTreasuryGovernor = L2ArbitrumGovernor(payable(L2_TREASURY_GOVERNOR)); + currentTreasuryTimelock = + TimelockControllerUpgradeable(payable(L2_TREASURY_GOVERNOR_TIMELOCK)); + + // Deploy a mock ArbSys contract at L2_ARB_SYS + vm.allowCheatcodes(address(L2_ARB_SYS)); + MockArbSys mockArbSys = new MockArbSys(); + bytes memory code = address(mockArbSys).code; + vm.etch(L2_ARB_SYS, code); + + // Prepare the script to submit upgrade proposal + submitUpgradeProposalScript = new SubmitUpgradeProposalScript(); + DeployMultiProxyUpgradeAction deployMultiProxyUpgradeAction = + new DeployMultiProxyUpgradeAction(); + multiProxyUpgradeAction = + deployMultiProxyUpgradeAction.run(address(newGovernorImplementation)); + + // Set the major delegates for testing + _majorDelegates = new address[](18); + _majorDelegates[0] = 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD; // L2BEAT + _majorDelegates[1] = 0xF4B0556B9B6F53E00A1FDD2b0478Ce841991D8fA; // olimpio + _majorDelegates[2] = 0x11cd09a0c5B1dc674615783b0772a9bFD53e3A8F; // Gauntlet + _majorDelegates[3] = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; // Wintermute + _majorDelegates[4] = 0x0eB5B03c0303f2F47cD81d7BE4275AF8Ed347576; // Treasure + _majorDelegates[5] = 0xF92F185AbD9E00F56cb11B0b709029633d1E37B4; // + _majorDelegates[6] = 0x186e505097BFA1f3cF45c2C9D7a79dE6632C3cdc; + _majorDelegates[7] = 0x5663D01D8109DDFC8aACf09fBE51F2d341bb3643; + _majorDelegates[8] = 0x2ef27b114917dD53f8633440A7C0328fef132e2F; // MUX Protocol + _majorDelegates[9] = 0xE48C655276C23F1534AE2a87A2bf8A8A6585Df70; // ercwl + _majorDelegates[10] = 0x8A3e9846df0CDc723C06e4f0C642ffFF82b54610; + _majorDelegates[11] = 0xAD16ebE6FfC7d96624A380F394cD64395B0C6144; // DK (Premia) + _majorDelegates[12] = 0xA5dF0cf3F95C6cd97d998b9D990a86864095d9b0; // Blockworks Research + _majorDelegates[13] = 0x839395e20bbB182fa440d08F850E6c7A8f6F0780; // Griff Green + _majorDelegates[14] = 0x2e3BEf6830Ae84bb4225D318F9f61B6b88C147bF; // Camelot + _majorDelegates[15] = 0x8F73bE66CA8c79382f72139be03746343Bf5Faa0; // mihal.eth + _majorDelegates[16] = 0xb5B069370Ef24BC67F114e185D185063CE3479f8; // Frisson + _majorDelegates[17] = 0xdb5781a835b60110298fF7205D8ef9678Ff1f800; // yoav.eth + } +} + +/// @dev Here we mock ArbSys, the contract that the timelock uses to make an L2 to L1 call. Normal call flow would +/// then see the call flow to ArbOne Outbox, to L1 timelock, to L1 ArbOne Inbox, to L2 Retryable buffer, to L2 Upgrade +/// Executor. Here, we assume this L1 call flow occurs. We make loose assertions about what calldata at each of these +/// steps looks like, and we finally arrive at the decoded calldata to pass to Upgrade Executor. Everything from ArbSys +/// to UpgradeExecutor is "fake" here, while preserving some loose confidence. +contract MockArbSys is DeployConstants, Test { + function sendTxToL1(address _l1Target, bytes calldata _data) external { + ( + address _retryableTicketMagic, + /*uint256 _ignored*/, + bytes memory _retryableData, + /*bytes32 _predecessor*/, + /*bytes32 _description*/, + /*uint256 _minDelay*/ + ) = abi.decode(_data[4:], (address, uint256, bytes, bytes32, bytes32, uint256)); + + assertEq(_l1Target, L1_TIMELOCK); + assertEq(_retryableTicketMagic, L2_RETRYABLE_TICKET_MAGIC); + + ( + address _arbOneDelayedInbox, + address _upgradeExecutor, + /*uint256 _value*/, + /*uint256 _maxGas*/, + /*uint256 _maxFeePerGas*/, + bytes memory _upgradeExecutorCallData + ) = abi.decode(_retryableData, (address, address, uint256, uint256, uint256, bytes)); + + assertEq(_arbOneDelayedInbox, L1_ARB_ONE_DELAYED_INBOX); + assertEq(_upgradeExecutor, L2_UPGRADE_EXECUTOR); + + vm.prank(L2_SECURITY_COUNCIL_9); + (bool success,/*bytes memory data*/) = _upgradeExecutor.call(_upgradeExecutorCallData); + assertEq(success, true); + } +} + +interface IUpgradeExecutor { + function execute(address to, bytes calldata data) external payable; +} From 07efacdc193010ca582736bda717e2c2bca67047 Mon Sep 17 00:00:00 2001 From: Gary Ghayrat <61768337+garyghayrat@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:47:34 -0400 Subject: [PATCH 27/43] Add propose, queue, and execute tests (#7) * Add propose, queue, and execute tests * Add `vm.assume`s in test --- test/L2ArbitrumGovernorV2.t.sol | 402 ++++++++++++++++++++++++++++--- test/SubmitUpgradeProposal.t.sol | 9 +- test/util/SetupNewGovernors.sol | 26 +- 3 files changed, 379 insertions(+), 58 deletions(-) diff --git a/test/L2ArbitrumGovernorV2.t.sol b/test/L2ArbitrumGovernorV2.t.sol index 6629555cb..fdac0f542 100644 --- a/test/L2ArbitrumGovernorV2.t.sol +++ b/test/L2ArbitrumGovernorV2.t.sol @@ -12,8 +12,11 @@ import { IGovernorUpgradeable } from "@openzeppelin/contracts-upgradeable/governance/IGovernorUpgradeable.sol"; import {TestUtil} from "test/util/TestUtil.sol"; +import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; +import {SetupNewGovernors} from "test/util/SetupNewGovernors.sol"; +import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; -contract L2ArbitrumGovernorV2Test is Test { +contract L2ArbitrumGovernorV2Test is SetupNewGovernors { // Core components L2ArbitrumGovernorV2 internal governor; L2ArbitrumToken internal token; @@ -33,31 +36,165 @@ contract L2ArbitrumGovernorV2Test is Test { address internal constant PROXY_ADMIN = 0xc7183455a4C133Ae270771860664b6B7ec320bB1; - function setUp() public virtual { - // Deploy token proxy and initialize - token = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); - token.initialize(L1_TOKEN_ADDRESS, INITIAL_SUPPLY, TOKEN_OWNER); - governor = L2ArbitrumGovernorV2( - payable(TestUtil.deployProxy(address(new L2ArbitrumGovernorV2()))) + address[] internal _majorDelegates; + + // Mirror of IGovernorUpgradeable.ProposalCreated for expectEmit usage in tests + event ProposalCreated( + uint256 proposalId, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 startBlock, + uint256 endBlock, + string description + ); + // Mirror of IGovernorTimelockUpgradeable.ProposalQueued for expectEmit usage in tests + event ProposalQueued(uint256 proposalId, uint256 eta); + // Mirror of IGovernorUpgradeable.ProposalExecuted for expectEmit usage in tests + event ProposalExecuted(uint256 proposalId); + + function setUp() public virtual override { + if (_shouldPassAndExecuteUpgradeProposal()) { + super.setUp(); + _setMajorDelegates(); + _executeUpgradeProposal(); + governor = L2ArbitrumGovernorV2(payable(L2_CORE_GOVERNOR)); + timelock = ArbitrumTimelock(payable(L2_CORE_GOVERNOR_TIMELOCK)); + token = L2ArbitrumToken(payable(L2_ARB_TOKEN_ADDRESS)); + } else { + _setMajorDelegates(); + // Deploy token proxy and initialize + token = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); + token.initialize(L1_TOKEN_ADDRESS, INITIAL_SUPPLY, TOKEN_OWNER); + governor = L2ArbitrumGovernorV2( + payable(TestUtil.deployProxy(address(new L2ArbitrumGovernorV2()))) + ); + timelock = + ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); + address[] memory proposers = new address[](1); + address[] memory executors = new address[](1); + proposers[0] = address(governor); + executors[0] = address(governor); + timelock.initialize(1, proposers, executors); + + // Initialize governor V2 + governor.initialize( + token, + TimelockControllerUpgradeable(payable(address(timelock))), + OWNER, + VOTING_DELAY, + VOTING_PERIOD, + QUORUM_NUMERATOR, + PROPOSAL_THRESHOLD, + VOTE_EXTENSION + ); + } + } + + function _setMajorDelegates() internal virtual { + _majorDelegates = new address[](18); + // Set the major delegates for testing + _majorDelegates[0] = 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD; // L2BEAT + _majorDelegates[1] = 0xF4B0556B9B6F53E00A1FDD2b0478Ce841991D8fA; // olimpio + _majorDelegates[2] = 0x11cd09a0c5B1dc674615783b0772a9bFD53e3A8F; // Gauntlet + _majorDelegates[3] = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; // Wintermute + _majorDelegates[4] = 0x0eB5B03c0303f2F47cD81d7BE4275AF8Ed347576; // Treasure + _majorDelegates[5] = 0xF92F185AbD9E00F56cb11B0b709029633d1E37B4; // + _majorDelegates[6] = 0x186e505097BFA1f3cF45c2C9D7a79dE6632C3cdc; + _majorDelegates[7] = 0x5663D01D8109DDFC8aACf09fBE51F2d341bb3643; + _majorDelegates[8] = 0x2ef27b114917dD53f8633440A7C0328fef132e2F; // MUX Protocol + _majorDelegates[9] = 0xE48C655276C23F1534AE2a87A2bf8A8A6585Df70; // ercwl + _majorDelegates[10] = 0x8A3e9846df0CDc723C06e4f0C642ffFF82b54610; + _majorDelegates[11] = 0xAD16ebE6FfC7d96624A380F394cD64395B0C6144; // DK (Premia) + _majorDelegates[12] = 0xA5dF0cf3F95C6cd97d998b9D990a86864095d9b0; // Blockworks Research + _majorDelegates[13] = 0x839395e20bbB182fa440d08F850E6c7A8f6F0780; // Griff Green + _majorDelegates[14] = 0x2e3BEf6830Ae84bb4225D318F9f61B6b88C147bF; // Camelot + _majorDelegates[15] = 0x8F73bE66CA8c79382f72139be03746343Bf5Faa0; // mihal.eth + _majorDelegates[16] = 0xb5B069370Ef24BC67F114e185D185063CE3479f8; // Frisson + _majorDelegates[17] = 0xdb5781a835b60110298fF7205D8ef9678Ff1f800; // yoav.eth + } + + function _submitProposal( + uint256 _randomSeed, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) public returns (uint256 _proposalId) { + vm.prank(_getRandomProposer(_randomSeed)); + _proposalId = governor.propose(_targets, _values, _calldatas, _description); + + vm.roll(block.number + governor.votingDelay() + 1); + } + + function _submitAndQueueProposal( + uint256 _randomSeed, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) public returns (uint256 _proposalId) { + _proposalId = _submitProposal(_randomSeed, _targets, _values, _calldatas, _description); + + for (uint256 i; i < _majorDelegates.length; i++) { + vm.prank(_majorDelegates[i]); + governor.castVote(_proposalId, uint8(VoteType.For)); + } + vm.roll(block.number + governor.votingPeriod() + 1); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Succeeded) ); - timelock = ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); - address[] memory proposers = new address[](1); - address[] memory executors = new address[](1); - proposers[0] = address(governor); - executors[0] = address(governor); - timelock.initialize(1, proposers, executors); - - // Initialize governor V2 - governor.initialize( - token, - TimelockControllerUpgradeable(payable(address(timelock))), - OWNER, - VOTING_DELAY, - VOTING_PERIOD, - QUORUM_NUMERATOR, - PROPOSAL_THRESHOLD, - VOTE_EXTENSION + + governor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); + } + + function _submitQueueAndExecuteProposal( + uint256 _randomSeed, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) public returns (uint256 _proposalId) { + _proposalId = _submitAndQueueProposal( + _randomSeed, _targets, _values, _calldatas, _description ); + vm.warp(block.timestamp + timelock.getMinDelay() + 1); + governor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); + } + + function _executeUpgradeProposal() internal virtual { + ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description, + uint256 _proposalId + ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); + + // Activate proposal + vm.roll(block.number + currentCoreGovernor.votingDelay() + 1); + + // Vote + for (uint256 i; i < _majorDelegates.length; i++) { + vm.prank(_majorDelegates[i]); + currentCoreGovernor.castVote(_proposalId, uint8(VoteType.For)); + } + + // Success + vm.roll(block.number + currentCoreGovernor.votingPeriod() + 1); + + // Queue + currentCoreGovernor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); + vm.warp(block.timestamp + currentCoreTimelock.getMinDelay() + 1); + + // Execute + currentCoreGovernor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); + } + + function _shouldPassAndExecuteUpgradeProposal() internal pure virtual returns (bool) { + return true; } function _basicProposal() @@ -78,11 +215,16 @@ contract L2ArbitrumGovernorV2Test is Test { calldatas[0] = bytes(""); description = "Test"; } + + function _getRandomProposer(uint256 _randomSeed) internal view returns (address) { + uint256 randomIndex = _randomSeed % _majorDelegates.length; + return _majorDelegates[randomIndex]; + } } abstract contract Cancel is L2ArbitrumGovernorV2Test { - function testFuzz_CancelsPendingProposal(address _proposer) public { - vm.assume(_proposer != address(0)); + function testFuzz_CancelsPendingProposal(uint256 _randomSeed) public { + address _proposer = _getRandomProposer(_randomSeed); ( address[] memory targets, uint256[] memory values, @@ -107,9 +249,9 @@ abstract contract Cancel is L2ArbitrumGovernorV2Test { ); } - function testFuzz_RevertIf_NotProposer(address _proposer, address _actor) public { - vm.assume(_proposer != address(0)); - vm.assume(_actor != _proposer && _actor != PROXY_ADMIN); + function testFuzz_RevertIf_NotProposer(uint256 _randomSeed, address _actor) public { + address _proposer = _getRandomProposer(_randomSeed); + vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT && _actor != _proposer); ( address[] memory targets, uint256[] memory values, @@ -131,8 +273,8 @@ abstract contract Cancel is L2ArbitrumGovernorV2Test { governor.cancel(targets, values, calldatas, keccak256(bytes(description))); } - function testFuzz_RevertIf_ProposalIsActive(address _proposer) public { - vm.assume(_proposer != address(0)); + function testFuzz_RevertIf_ProposalIsActive(uint256 _randomSeed) public { + address _proposer = _getRandomProposer(_randomSeed); ( address[] memory targets, uint256[] memory values, @@ -158,8 +300,8 @@ abstract contract Cancel is L2ArbitrumGovernorV2Test { governor.cancel(targets, values, calldatas, keccak256(bytes(description))); } - function testFuzz_RevertIf_AlreadyCanceled(address _proposer) public { - vm.assume(_proposer != address(0)); + function testFuzz_RevertIf_AlreadyCanceled(uint256 _randomSeed) public { + address _proposer = _getRandomProposer(_randomSeed); ( address[] memory targets, uint256[] memory values, @@ -192,7 +334,199 @@ abstract contract Cancel is L2ArbitrumGovernorV2Test { } } -contract MockGovernorCancel is Cancel { +abstract contract Propose is L2ArbitrumGovernorV2Test { + function testFuzz_ProposerAboveThresholdCanPropose(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _proposalId = _submitProposal(_randomSeed, targets, values, calldatas, description); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Active) + ); + } + + function testFuzz_EmitsProposalCreatedEvent(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _expectedProposalId = + governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))); + uint256 _startBlock = block.number + governor.votingDelay(); + uint256 _endBlock = _startBlock + governor.votingPeriod(); + + vm.expectEmit(); + emit ProposalCreated( + _expectedProposalId, + _getRandomProposer(_randomSeed), + targets, + values, + new string[](targets.length), + calldatas, + _startBlock, + _endBlock, + description + ); + _submitProposal(_randomSeed, targets, values, calldatas, description); + } + + function testFuzz_ProposerBelowThresholdCannotPropose(address _proposer) public { + vm.assume(_proposer != L2_PROXY_ADMIN_CONTRACT); + vm.assume(governor.getVotes(_proposer, block.number - 1) < governor.proposalThreshold()); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.expectRevert("Governor: proposer votes below proposal threshold"); + vm.prank(_proposer); + governor.propose(targets, values, calldatas, description); + } +} + +abstract contract Queue is L2ArbitrumGovernorV2Test { + function testFuzz_QueuesASucceededProposal(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _proposalId = + _submitAndQueueProposal(_randomSeed, targets, values, calldatas, description); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Queued) + ); + } + + function testFuzz_EmitsQueueEvent(uint256 _randomSeed, address _actor) public { + vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _eta = block.timestamp + timelock.getMinDelay(); + uint256 _proposalId = _submitProposal(_randomSeed, targets, values, calldatas, description); + + for (uint256 i; i < _majorDelegates.length; i++) { + vm.prank(_majorDelegates[i]); + governor.castVote(_proposalId, uint8(VoteType.For)); + } + vm.roll(block.number + governor.votingPeriod() + 1); + + vm.expectEmit(); + emit ProposalQueued(_proposalId, _eta); + vm.prank(_actor); + governor.queue(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_ProposalIsNotSucceeded(uint256 _randomSeed, address _actor) public { + vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _proposalId = _submitProposal(_randomSeed, targets, values, calldatas, description); + + for (uint256 i; i < _majorDelegates.length; i++) { + vm.prank(_majorDelegates[i]); + governor.castVote(_proposalId, uint8(VoteType.Against)); + } + vm.roll(block.number + governor.votingPeriod() + 1); + + vm.prank(_actor); + vm.expectRevert("Governor: proposal not successful"); + governor.queue(targets, values, calldatas, keccak256(bytes(description))); + } +} + +abstract contract Execute is L2ArbitrumGovernorV2Test { + function testFuzz_ExecutesASucceededProposal(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _proposalId = + _submitQueueAndExecuteProposal(_randomSeed, targets, values, calldatas, description); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Executed) + ); + } + + function testFuzz_EmitsExecuteEvent(uint256 _randomSeed, address _actor) public { + vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + uint256 _proposalId = + _submitAndQueueProposal(_randomSeed, targets, values, calldatas, description); + vm.warp(block.timestamp + timelock.getMinDelay() + 1); + + vm.expectEmit(); + emit ProposalExecuted(_proposalId); + vm.prank(_actor); + governor.execute(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_OperationNotReady(uint256 _randomSeed, address _actor) public { + vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + _submitAndQueueProposal(_randomSeed, targets, values, calldatas, description); + + vm.prank(_actor); + vm.expectRevert(bytes("TimelockController: operation is not ready")); + governor.execute(targets, values, calldatas, keccak256(bytes(description))); + } +} + +contract GovernorCancel is Cancel { + function setUp() public override(L2ArbitrumGovernorV2Test) { + super.setUp(); + } +} + +contract GovernorPropose is Propose { + function setUp() public override(L2ArbitrumGovernorV2Test) { + super.setUp(); + } +} + +contract GovernorQueue is Queue { + function setUp() public override(L2ArbitrumGovernorV2Test) { + super.setUp(); + } +} + +contract GovernorExecute is Execute { function setUp() public override(L2ArbitrumGovernorV2Test) { super.setUp(); } diff --git a/test/SubmitUpgradeProposal.t.sol b/test/SubmitUpgradeProposal.t.sol index e504bbbe7..cc84adf06 100644 --- a/test/SubmitUpgradeProposal.t.sol +++ b/test/SubmitUpgradeProposal.t.sol @@ -16,10 +16,16 @@ import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.s import { TransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {L2ArbitrumGovernorV2Test} from "test/L2ArbitrumGovernorV2.t.sol"; -contract SubmitUpgradeProposalTest is SetupNewGovernors { +contract SubmitUpgradeProposalTest is SetupNewGovernors, L2ArbitrumGovernorV2Test { event Upgraded(address indexed implementation); + function setUp() public virtual override(SetupNewGovernors, L2ArbitrumGovernorV2Test) { + SetupNewGovernors.setUp(); + _setMajorDelegates(); + } + function test_SuccessfullyExecuteUpgradeProposal() public { MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( L2_PROXY_ADMIN_CONTRACT, @@ -123,6 +129,7 @@ contract SubmitUpgradeProposalTest is SetupNewGovernors { // Propose ( + /*address[] memory _targets*/, /*uint256[] memory _values*/, /*bytes[] memory _calldatas*/, diff --git a/test/util/SetupNewGovernors.sol b/test/util/SetupNewGovernors.sol index 312202184..321e6b228 100644 --- a/test/util/SetupNewGovernors.sol +++ b/test/util/SetupNewGovernors.sol @@ -36,7 +36,6 @@ abstract contract SetupNewGovernors is DeployConstants, Test { L2ArbitrumGovernorV2 newGovernorImplementation; uint256 constant FORK_BLOCK = 245_608_716; // Arbitrary recent block - address[] public _majorDelegates; enum ProposalState { Pending, @@ -90,27 +89,6 @@ abstract contract SetupNewGovernors is DeployConstants, Test { new DeployMultiProxyUpgradeAction(); multiProxyUpgradeAction = deployMultiProxyUpgradeAction.run(address(newGovernorImplementation)); - - // Set the major delegates for testing - _majorDelegates = new address[](18); - _majorDelegates[0] = 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD; // L2BEAT - _majorDelegates[1] = 0xF4B0556B9B6F53E00A1FDD2b0478Ce841991D8fA; // olimpio - _majorDelegates[2] = 0x11cd09a0c5B1dc674615783b0772a9bFD53e3A8F; // Gauntlet - _majorDelegates[3] = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; // Wintermute - _majorDelegates[4] = 0x0eB5B03c0303f2F47cD81d7BE4275AF8Ed347576; // Treasure - _majorDelegates[5] = 0xF92F185AbD9E00F56cb11B0b709029633d1E37B4; // - _majorDelegates[6] = 0x186e505097BFA1f3cF45c2C9D7a79dE6632C3cdc; - _majorDelegates[7] = 0x5663D01D8109DDFC8aACf09fBE51F2d341bb3643; - _majorDelegates[8] = 0x2ef27b114917dD53f8633440A7C0328fef132e2F; // MUX Protocol - _majorDelegates[9] = 0xE48C655276C23F1534AE2a87A2bf8A8A6585Df70; // ercwl - _majorDelegates[10] = 0x8A3e9846df0CDc723C06e4f0C642ffFF82b54610; - _majorDelegates[11] = 0xAD16ebE6FfC7d96624A380F394cD64395B0C6144; // DK (Premia) - _majorDelegates[12] = 0xA5dF0cf3F95C6cd97d998b9D990a86864095d9b0; // Blockworks Research - _majorDelegates[13] = 0x839395e20bbB182fa440d08F850E6c7A8f6F0780; // Griff Green - _majorDelegates[14] = 0x2e3BEf6830Ae84bb4225D318F9f61B6b88C147bF; // Camelot - _majorDelegates[15] = 0x8F73bE66CA8c79382f72139be03746343Bf5Faa0; // mihal.eth - _majorDelegates[16] = 0xb5B069370Ef24BC67F114e185D185063CE3479f8; // Frisson - _majorDelegates[17] = 0xdb5781a835b60110298fF7205D8ef9678Ff1f800; // yoav.eth } } @@ -146,7 +124,9 @@ contract MockArbSys is DeployConstants, Test { assertEq(_upgradeExecutor, L2_UPGRADE_EXECUTOR); vm.prank(L2_SECURITY_COUNCIL_9); - (bool success,/*bytes memory data*/) = _upgradeExecutor.call(_upgradeExecutorCallData); + ( + bool success, /*bytes memory data*/ + ) = _upgradeExecutor.call(_upgradeExecutorCallData); assertEq(success, true); } } From 4bde76b70eb469438707ba36a8cbac8fcdc6a975 Mon Sep 17 00:00:00 2001 From: Gary Ghayrat <61768337+garyghayrat@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:48:17 -0400 Subject: [PATCH 28/43] Add helper to check configuration and payload (#9) Co-authored-by: Ed Mazurek --- .../SubmitUpgradeProposalScript.s.sol | 38 ++++++ test/SubmitUpgradeProposal.t.sol | 129 +++++++++++++++++- 2 files changed, 162 insertions(+), 5 deletions(-) diff --git a/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol b/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol index 60f401feb..787116157 100644 --- a/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol +++ b/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol @@ -5,6 +5,10 @@ import {Script} from "forge-std/Script.sol"; import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; import "node_modules/@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; import {EncodeL2ArbSysProposal} from "scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + TransparentUpgradeableProxy +} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract SubmitUpgradeProposalScript is Script, DeployConstants, EncodeL2ArbSysProposal { address PROPOSER_ADDRESS = @@ -38,9 +42,43 @@ contract SubmitUpgradeProposalScript is Script, DeployConstants, EncodeL2ArbSysP _description = PROPOSAL_DESCRIPTION; (_targets, _values, _calldatas) = encodeL2ArbSysProposal(_description, _multiProxyUpgradeAction, _minDelay); + checkConfigurationAndPayload(_targets, _values, _calldatas); vm.startBroadcast(PROPOSER_ADDRESS); _proposalId = GovernorUpgradeable(payable(L2_CORE_GOVERNOR)) .propose(_targets, _values, _calldatas, _description); vm.stopBroadcast(); } + + function checkConfigurationAndPayload( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas + ) public view { + // 1) Verify the Upgrade Executor owns the ProxyAdmin + require( + ProxyAdmin(L2_PROXY_ADMIN_CONTRACT).owner() == L2_UPGRADE_EXECUTOR, + "ProxyAdmin owner mismatch" + ); + + // 2) Verify both Governor proxies are administered by the ProxyAdmin + require( + ProxyAdmin(L2_PROXY_ADMIN_CONTRACT) + .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_CORE_GOVERNOR))) + == L2_PROXY_ADMIN_CONTRACT, + "Core Governor proxy admin mismatch" + ); + require( + ProxyAdmin(L2_PROXY_ADMIN_CONTRACT) + .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_TREASURY_GOVERNOR))) + == L2_PROXY_ADMIN_CONTRACT, + "Treasury Governor proxy admin mismatch" + ); + + // 3) Validate the proposal payload structure + require( + _targets.length == 1 && _targets[0] == L2_ARB_SYS && _values.length == 1 + && _values[0] == 0 && _calldatas.length == 1, + "Invalid proposal arrays" + ); + } } diff --git a/test/SubmitUpgradeProposal.t.sol b/test/SubmitUpgradeProposal.t.sol index cc84adf06..8e5120849 100644 --- a/test/SubmitUpgradeProposal.t.sol +++ b/test/SubmitUpgradeProposal.t.sol @@ -129,11 +129,7 @@ contract SubmitUpgradeProposalTest is SetupNewGovernors, L2ArbitrumGovernorV2Tes // Propose ( - - /*address[] memory _targets*/, - /*uint256[] memory _values*/, - /*bytes[] memory _calldatas*/, - /*string memory _description*/, + ,/*address[] memory _targets*/ /*uint256[] memory _values*/ /*bytes[] memory _calldatas*/ /*string memory _description*/,,, uint256 _proposalId ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); assertEq( @@ -173,6 +169,129 @@ contract SubmitUpgradeProposalTest is SetupNewGovernors, L2ArbitrumGovernorV2Tes initialTreasuryGovernorImplementation ); } + + function testFuzz_RevertIf_ProxyAdminOwnerMismatch(address _wrongOwner) public { + vm.assume(_wrongOwner != L2_UPGRADE_EXECUTOR); + vm.assume(_wrongOwner != address(0)); + + MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( + L2_PROXY_ADMIN_CONTRACT, + L2_CORE_GOVERNOR, + L2_TREASURY_GOVERNOR, + address(newGovernorImplementation) + ); + + vm.prank(L2_UPGRADE_EXECUTOR); + ProxyAdmin(L2_PROXY_ADMIN_CONTRACT).transferOwnership(_wrongOwner); + + vm.expectRevert("ProxyAdmin owner mismatch"); + submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); + } + + function testFuzz_RevertIf_CoreGovernorProxyAdminMismatch(address _wrongProxyAdmin) public { + vm.assume(_wrongProxyAdmin != L2_PROXY_ADMIN_CONTRACT); + vm.assume(_wrongProxyAdmin != address(0)); + + address[] memory targets = new address[](1); + targets[0] = L2_ARB_SYS; + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = ""; + + vm.mockCall( + L2_PROXY_ADMIN_CONTRACT, + abi.encodeWithSelector( + ProxyAdmin.getProxyAdmin.selector, + TransparentUpgradeableProxy(payable(L2_CORE_GOVERNOR)) + ), + abi.encode(_wrongProxyAdmin) + ); + + vm.expectRevert("Core Governor proxy admin mismatch"); + submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); + } + + function testFuzz_RevertIf_TreasuryGovernorProxyAdminMismatch(address _wrongProxyAdmin) public { + vm.assume(_wrongProxyAdmin != L2_PROXY_ADMIN_CONTRACT); + vm.assume(_wrongProxyAdmin != address(0)); + + address[] memory targets = new address[](1); + targets[0] = L2_ARB_SYS; + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = ""; + + vm.mockCall( + L2_PROXY_ADMIN_CONTRACT, + abi.encodeWithSelector( + ProxyAdmin.getProxyAdmin.selector, + TransparentUpgradeableProxy(payable(L2_TREASURY_GOVERNOR)) + ), + abi.encode(_wrongProxyAdmin) + ); + + vm.expectRevert("Treasury Governor proxy admin mismatch"); + submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); + } + + function test_RevertIf_MultipleTargets() public { + address[] memory targets = new address[](2); + targets[0] = L2_ARB_SYS; + targets[1] = L2_ARB_SYS; + uint256[] memory values = new uint256[](2); + values[0] = 0; + values[1] = 0; + bytes[] memory calldatas = new bytes[](2); + calldatas[0] = ""; + calldatas[1] = ""; + + vm.expectRevert("Invalid proposal arrays"); + submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); + } + + function testFuzz_RevertIf_WrongTarget(address _wrongTarget) public { + vm.assume(_wrongTarget != L2_ARB_SYS); + vm.assume(_wrongTarget != address(0)); + + address[] memory targets = new address[](1); + targets[0] = _wrongTarget; + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = ""; + + vm.expectRevert("Invalid proposal arrays"); + submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); + } + + function testFuzz_RevertIf_NonZeroValue(uint256 _wrongValue) public { + vm.assume(_wrongValue != 0); + + address[] memory targets = new address[](1); + targets[0] = L2_ARB_SYS; + uint256[] memory values = new uint256[](1); + values[0] = _wrongValue; + bytes[] memory calldatas = new bytes[](1); + calldatas[0] = ""; + + vm.expectRevert("Invalid proposal arrays"); + submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); + } + + function test_RevertIf_MultipleCalldatas() public { + address[] memory targets = new address[](1); + targets[0] = L2_ARB_SYS; + uint256[] memory values = new uint256[](1); + values[0] = 0; + bytes[] memory calldatas = new bytes[](2); + calldatas[0] = ""; + calldatas[1] = ""; + + vm.expectRevert("Invalid proposal arrays"); + submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); + } } interface IUpgradeExecutor { From 38798f43778a4b54c3e8588f206db0faaa6a8901 Mon Sep 17 00:00:00 2001 From: wildmolasses Date: Fri, 7 Nov 2025 13:39:25 -0500 Subject: [PATCH 29/43] move changes onto V2ArbitrumGovernor --- foundry.lock | 14 + scripts/forge-scripts/DeployConstants.sol | 34 -- .../forge-scripts/DeployImplementation.s.sol | 14 - .../DeployMultiProxyUpgradeAction.s.sol | 27 - .../SubmitUpgradeProposalScript.s.sol | 84 --- .../utils/EncodeL2ArbSysProposal.sol | 105 ---- src/L2ArbitrumGovernor.sol | 178 +++++- src/L2ArbitrumGovernorV2.sol | 63 --- .../upgrade-proxy/MultiProxyUpgradeAction.sol | 70 --- test/L2ArbitrumGovernorV2.t.sol | 533 ------------------ test/SubmitUpgradeProposal.t.sol | 299 ---------- test/util/SetupNewGovernors.sol | 136 ----- 12 files changed, 170 insertions(+), 1387 deletions(-) create mode 100644 foundry.lock delete mode 100644 scripts/forge-scripts/DeployConstants.sol delete mode 100644 scripts/forge-scripts/DeployImplementation.s.sol delete mode 100644 scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol delete mode 100644 scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol delete mode 100644 scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol delete mode 100644 src/L2ArbitrumGovernorV2.sol delete mode 100644 src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol delete mode 100644 test/L2ArbitrumGovernorV2.t.sol delete mode 100644 test/SubmitUpgradeProposal.t.sol delete mode 100644 test/util/SetupNewGovernors.sol diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 000000000..fea9991c9 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,14 @@ +{ + "lib/forge-std": { + "rev": "e8a047e3f40f13fa37af6fe14e6e06283d9a060e" + }, + "lib/solady": { + "rev": "7175c21f95255dc7711ce84cc32080a41864abd6" + }, + "token-bridge-contracts": { + "branch": { + "name": "v1.2.5", + "rev": "7c90b43fee0072a3515147822faf5b20fbcf134b" + } + } +} \ No newline at end of file diff --git a/scripts/forge-scripts/DeployConstants.sol b/scripts/forge-scripts/DeployConstants.sol deleted file mode 100644 index f2a843d2a..000000000 --- a/scripts/forge-scripts/DeployConstants.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -// Governor deployment constants that are shared between the Core Governor and the -// Treasury Governor. These should be carefully checked and reviewed before final deployment. -contract DeployConstants { - address public constant L2_ARB_TOKEN_ADDRESS = 0x912CE59144191C1204E64559FE8253a0e49E6548; - - address public constant L2_CORE_GOVERNOR = 0xf07DeD9dC292157749B6Fd268E37DF6EA38395B9; - address public constant L2_CORE_GOVERNOR_TIMELOCK = 0x34d45e99f7D8c45ed05B5cA72D54bbD1fb3F98f0; - address public constant L2_TREASURY_GOVERNOR = 0x789fC99093B09aD01C34DC7251D0C89ce743e5a4; - address public constant L2_TREASURY_GOVERNOR_TIMELOCK = - 0xbFc1FECa8B09A5c5D3EFfE7429eBE24b9c09EF58; - address public constant L2_PROXY_ADMIN_OWNER = L2_UPGRADE_EXECUTOR; - address public constant L2_PROXY_ADMIN_CONTRACT = 0xdb216562328215E010F819B5aBe947bad4ca961e; - - address public constant L2_ARB_SYS = 0x0000000000000000000000000000000000000064; - address public constant L2_ARB_RETRYABLE_TX = 0x000000000000000000000000000000000000006E; - address public constant L2_SECURITY_COUNCIL_9 = 0x423552c0F05baCCac5Bfa91C6dCF1dc53a0A1641; - - address public constant L1_TIMELOCK = 0xE6841D92B0C345144506576eC13ECf5103aC7f49; - uint256 public constant L1_TIMELOCK_MIN_DELAY = 259_200; - address public constant L1_ARB_ONE_DELAYED_INBOX = 0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f; - - // =========================================================================================== - // TODO: Update these values after the deployment process - // =========================================================================================== - address public constant L2_ARBITRUM_GOVERNOR_V2_IMPLEMENTATION = address(0); // TODO: Update after the core governor is deployed. - bool public constant UPGRADE_PROPOSAL_PASSED_ONCHAIN = false; // TODO: Update after the upgrade proposal is passed. - // =========================================================================================== - - address public constant L2_UPGRADE_EXECUTOR = 0xCF57572261c7c2BCF21ffD220ea7d1a27D40A827; - address public constant L2_RETRYABLE_TICKET_MAGIC = 0xa723C008e76E379c55599D2E4d93879BeaFDa79C; -} diff --git a/scripts/forge-scripts/DeployImplementation.s.sol b/scripts/forge-scripts/DeployImplementation.s.sol deleted file mode 100644 index f52b66448..000000000 --- a/scripts/forge-scripts/DeployImplementation.s.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; -import {Script} from "forge-std/Script.sol"; - -/// @notice Deploy script for the underlying implementation that will be used by both Governor proxies -contract DeployImplementation is Script { - function run() public returns (L2ArbitrumGovernorV2 _implementation) { - vm.startBroadcast(); - _implementation = new L2ArbitrumGovernorV2(); - vm.stopBroadcast(); - } -} diff --git a/scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol b/scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol deleted file mode 100644 index 1dd956afd..000000000 --- a/scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import {Script} from "forge-std/Script.sol"; -import { - MultiProxyUpgradeAction -} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol"; -import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; - -/// @title DeployMultiProxyUpgradeAction. -/// @notice Script to deploy the `MultiProxyUpgradeAction` contract. -/// @dev `MultiProxyUpgradeAction` contract is used to upgrade multiple governor proxies in a single transaction. -contract DeployMultiProxyUpgradeAction is DeployConstants, Script { - function run(address _newGovernorImplementationAddress) - public - returns (MultiProxyUpgradeAction multiProxyUpgradeAction) - { - vm.startBroadcast(); - multiProxyUpgradeAction = new MultiProxyUpgradeAction( - L2_PROXY_ADMIN_CONTRACT, - L2_CORE_GOVERNOR, - L2_TREASURY_GOVERNOR, - _newGovernorImplementationAddress - ); - vm.stopBroadcast(); - } -} diff --git a/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol b/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol deleted file mode 100644 index 787116157..000000000 --- a/scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import {Script} from "forge-std/Script.sol"; -import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; -import "node_modules/@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; -import {EncodeL2ArbSysProposal} from "scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import { - TransparentUpgradeableProxy -} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - -contract SubmitUpgradeProposalScript is Script, DeployConstants, EncodeL2ArbSysProposal { - address PROPOSER_ADDRESS = - vm.envOr("PROPOSER_ADDRESS", 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD); //TODO: Update proposer address. - string PROPOSAL_DESCRIPTION = - vm.envOr("PROPOSAL_DESCRIPTION", string("Add proposal description here")); // TODO: Update proposal description. - - function run(address _multiProxyUpgradeAction, uint256 _minDelay) - public - returns ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description, - uint256 _proposalId - ) - { - return proposeUpgrade(_multiProxyUpgradeAction, _minDelay); - } - - function proposeUpgrade(address _multiProxyUpgradeAction, uint256 _minDelay) - internal - returns ( - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, - string memory _description, - uint256 _proposalId - ) - { - _description = PROPOSAL_DESCRIPTION; - (_targets, _values, _calldatas) = - encodeL2ArbSysProposal(_description, _multiProxyUpgradeAction, _minDelay); - checkConfigurationAndPayload(_targets, _values, _calldatas); - vm.startBroadcast(PROPOSER_ADDRESS); - _proposalId = GovernorUpgradeable(payable(L2_CORE_GOVERNOR)) - .propose(_targets, _values, _calldatas, _description); - vm.stopBroadcast(); - } - - function checkConfigurationAndPayload( - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas - ) public view { - // 1) Verify the Upgrade Executor owns the ProxyAdmin - require( - ProxyAdmin(L2_PROXY_ADMIN_CONTRACT).owner() == L2_UPGRADE_EXECUTOR, - "ProxyAdmin owner mismatch" - ); - - // 2) Verify both Governor proxies are administered by the ProxyAdmin - require( - ProxyAdmin(L2_PROXY_ADMIN_CONTRACT) - .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_CORE_GOVERNOR))) - == L2_PROXY_ADMIN_CONTRACT, - "Core Governor proxy admin mismatch" - ); - require( - ProxyAdmin(L2_PROXY_ADMIN_CONTRACT) - .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_TREASURY_GOVERNOR))) - == L2_PROXY_ADMIN_CONTRACT, - "Treasury Governor proxy admin mismatch" - ); - - // 3) Validate the proposal payload structure - require( - _targets.length == 1 && _targets[0] == L2_ARB_SYS && _values.length == 1 - && _values[0] == 0 && _calldatas.length == 1, - "Invalid proposal arrays" - ); - } -} diff --git a/scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol b/scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol deleted file mode 100644 index b975e67d4..000000000 --- a/scripts/forge-scripts/utils/EncodeL2ArbSysProposal.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; - -contract EncodeL2ArbSysProposal is DeployConstants { - function encodeL2ArbSysProposal( - string memory _proposalDescription, - address _oneOffUpgradeAddr, - uint256 _minDelay - ) - public - pure - returns (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) - { - targets = new address[](1); - values = new uint256[](1); - calldatas = new bytes[](1); - - targets[0] = L2_ARB_SYS; - calldatas[0] = - encodeArbSysProposalCalldata(_proposalDescription, _oneOffUpgradeAddr, _minDelay); - } - - function encodeArbSysProposalCalldata( - string memory _proposalDescription, - address _oneOffUpgradeAddr, - uint256 _minDelay - ) public pure returns (bytes memory proposalCalldata) { - address retryableTicketMagic = L2_RETRYABLE_TICKET_MAGIC; - - // the data to call the upgrade executor with - // it tells the upgrade executor how to call the upgrade contract, and what calldata to provide to it - bytes memory upgradeExecutorCallData = abi.encodeWithSelector( - IUpgradeExecutor.execute.selector, - _oneOffUpgradeAddr, - abi.encodeWithSelector(IMultiProxyUpgradeAction.perform.selector) - ); - - // the data provided to call the l1 timelock with - // specifies how to create a retryable ticket, which will then be used to call the upgrade executor with the - // data created from the step above - bytes memory l1TimelockData = abi.encodeWithSelector( - IL1Timelock.schedule.selector, - retryableTicketMagic, // tells the l1 timelock that we want to make a retryable, instead of an l1 upgrade - 0, // ignored for l2 upgrades - abi.encode( // these are the retryable data params - L1_ARB_ONE_DELAYED_INBOX, // the inbox we want to use, should be arb one or nova inbox - L2_UPGRADE_EXECUTOR, // the upgrade executor on the l2 network - 0, // no value in this upgrade - 0, // max gas - will be filled in when the retryable is actually executed - 0, // max fee per gas - will be filled in when the retryable is actually executed - upgradeExecutorCallData // call data created in the previous step - ), - bytes32(0), // no predecessor - keccak256(abi.encodePacked(_proposalDescription)), // prop description - _minDelay // delay for this proposal - ); - - // the data provided to the L2 Arbitrum Governor in the propose() method - // the target will be the ArbSys address on Arb One - proposalCalldata = abi.encodeWithSelector( - IArbSys.sendTxToL1.selector, // the execution of the proposal will create an L2->L1 cross chain message - L1_TIMELOCK, // the target of the cross chain message is the L1 timelock - l1TimelockData // call the l1 timelock with the data created in the previous step - ); - } -} - -interface IUpgradeExecutor { - function execute(address to, bytes calldata data) external payable; -} - -interface IL1Timelock { - function schedule( - address target, - uint256 value, - bytes calldata data, - bytes32 predecessor, - bytes32 salt, - uint256 delay - ) external; - function getMinDelay() external view returns (uint256); -} - -interface IArbSys { - function sendTxToL1(address destination, bytes calldata data) external payable returns (uint256); -} - -interface IL2ArbitrumGovernor { - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) external returns (uint256); -} - -interface IMultiProxyUpgradeAction { - function perform() external; -} - -interface IFixedDelegateErc20Wallet { - function transfer(address _token, address _to, uint256 _amount) external returns (bool); -} diff --git a/src/L2ArbitrumGovernor.sol b/src/L2ArbitrumGovernor.sol index 427e50d6f..cee1e1579 100644 --- a/src/L2ArbitrumGovernor.sol +++ b/src/L2ArbitrumGovernor.sol @@ -3,15 +3,11 @@ pragma solidity 0.8.16; import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorPreventLateQuorumUpgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorPreventLateQuorumUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {L2ArbitrumToken} from "./L2ArbitrumToken.sol"; @@ -31,6 +27,15 @@ contract L2ArbitrumGovernor is GovernorPreventLateQuorumUpgradeable, OwnableUpgradeable { + /// @notice Error thrown when attempting to cancel a proposal that is not in Pending state. + /// @param state The current state of the proposal. + error ProposalNotPending(GovernorUpgradeable.ProposalState state); + + /// @notice Error thrown when a non-proposer attempts to cancel a proposal. + /// @param sender The address attempting to cancel the proposal. + /// @param proposer The address of the actual proposer. + error NotProposer(address sender, address proposer); + /// @notice address for which votes will not be counted toward quorum /// @dev A portion of the Arbitrum tokens will be held by entities (eg the treasury) that /// are not eligible to vote. However, even if their voting/delegation is restricted their @@ -51,6 +56,10 @@ contract L2ArbitrumGovernor is /// with quorum lesser than the minimum can have its quorum suddenly jump to equal minimumQuorum uint256 public minimumQuorum; + /// @notice Mapping from proposal ID to the address of the proposer. + /// @dev Used in cancel() to ensure only the proposer can cancel the proposal. + mapping(uint256 => address) internal proposers; + constructor() { _disableInitializers(); } @@ -121,6 +130,50 @@ contract L2ArbitrumGovernor is AddressUpgradeable.functionCallWithValue(target, data, value); } + /// @inheritdoc IGovernorUpgradeable + /// @dev See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. + function propose( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) public virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { + require( + _isValidDescriptionForProposer(msg.sender, description), "Governor: proposer restricted" + ); + uint256 _proposalId = GovernorUpgradeable.propose(targets, values, calldatas, description); + proposers[_proposalId] = msg.sender; + return _proposalId; + } + + /// @notice Allows a proposer to cancel a proposal when it is pending. + /// @param targets The proposal's targets. + /// @param values The proposal's values. + /// @param calldatas The proposal's calldatas. + /// @param descriptionHash The hash of the proposal's description. + /// @return The id of the proposal. + function cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) public virtual returns (uint256) { + uint256 _proposalId = hashProposal(targets, values, calldatas, descriptionHash); + + if (state(_proposalId) != ProposalState.Pending) { + revert ProposalNotPending(state(_proposalId)); + } + + address _proposer = proposers[_proposalId]; + if (msg.sender != _proposer) { + revert NotProposer(msg.sender, _proposer); + } + + delete proposers[_proposalId]; + + return GovernorUpgradeable._cancel(targets, values, calldatas, descriptionHash); + } + /// @notice returns l2 executor address; used internally for onlyGovernance check function _executor() internal @@ -181,15 +234,13 @@ contract L2ArbitrumGovernor is // if pastTotalDelegatedVotes is 0, then blockNumber is almost certainly prior to the first totalDelegatedVotes checkpoint // in this case we should use getPastCirculatingSupply to ensure quorum of pre-existing proposals is unchanged - // in the unlikely event that totalDvp is 0 for a block _after_ the dvp update, getPastCirculatingSupply will be used with a larger quorumNumerator, + // in the unlikely event that totalDvp is 0 for a block _after_ the dvp update, getPastCirculatingSupply will be used with a larger quorumNumerator, // resulting in a much higher calculated quorum. This is okay because quorum is clamped. - uint256 calculatedQuorum = ( - ( - pastTotalDelegatedVotes == 0 - ? getPastCirculatingSupply(blockNumber) - : pastTotalDelegatedVotes - ) * quorumNumerator(blockNumber) - ) / quorumDenominator(); + uint256 calculatedQuorum = + ((pastTotalDelegatedVotes == 0 + ? getPastCirculatingSupply(blockNumber) + : pastTotalDelegatedVotes) + * quorumNumerator(blockNumber)) / quorumDenominator(); // clamp the calculated quorum between minimumQuorum and maximumQuorum if (calculatedQuorum < minimumQuorum) { @@ -275,11 +326,7 @@ contract L2ArbitrumGovernor is uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) - internal - override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) - returns (uint256) - { + ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint256) { return GovernorTimelockControlUpgradeable._cancel(targets, values, calldatas, descriptionHash); } @@ -293,10 +340,97 @@ contract L2ArbitrumGovernor is return GovernorTimelockControlUpgradeable.supportsInterface(interfaceId); } + /** + * @dev Check if the proposer is authorized to submit a proposal with the given description. + * + * If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string + * (case insensitive), then the submission of this proposal will only be authorized to said address. + * + * This is used for frontrunning protection. By adding this pattern at the end of their proposal, one can ensure + * that no other address can submit the same proposal. An attacker would have to either remove or change that part, + * which would result in a different proposal id. + * + * If the description does not match this pattern, it is unrestricted and anyone can submit it. This includes: + * - If the `0x???` part is not a valid hex string. + * - If the `0x???` part is a valid hex string, but does not contain exactly 40 hex digits. + * - If it ends with the expected suffix followed by newlines or other whitespace. + * - If it ends with some other similar suffix, e.g. `#other=abc`. + * - If it does not end with any such suffix. + */ + function _isValidDescriptionForProposer(address proposer, string memory description) + internal + view + virtual + returns (bool) + { + uint256 len = bytes(description).length; + + // Length is too short to contain a valid proposer suffix + if (len < 52) { + return true; + } + + // Extract what would be the `#proposer=0x` marker beginning the suffix + bytes12 marker; + assembly { + // - Start of the string contents in memory = description + 32 + // - First character of the marker = len - 52 + // - Length of "#proposer=0x0000000000000000000000000000000000000000" = 52 + // - We read the memory word starting at the first character of the marker: + // - (description + 32) + (len - 52) = description + (len - 20) + // - Note: Solidity will ignore anything past the first 12 bytes + marker := mload(add(description, sub(len, 20))) + } + + // If the marker is not found, there is no proposer suffix to check + if (marker != bytes12("#proposer=0x")) { + return true; + } + + // Parse the 40 characters following the marker as uint160 + uint160 recovered = 0; + for (uint256 i = len - 40; i < len; ++i) { + (bool isHex, uint8 value) = _tryHexToUint(bytes(description)[i]); + // If any of the characters is not a hex digit, ignore the suffix entirely + if (!isHex) { + return true; + } + recovered = (recovered << 4) | value; + } + + return recovered == uint160(proposer); + } + + /** + * @dev Try to parse a character from a string as a hex value. Returns `(true, value)` if the char is in + * `[0-9a-fA-F]` and `(false, 0)` otherwise. Value is guaranteed to be in the range `0 <= value < 16` + */ + function _tryHexToUint(bytes1 char) private pure returns (bool, uint8) { + uint8 c = uint8(char); + unchecked { + // Case 0-9 + if (47 < c && c < 58) { + return (true, c - 48); + } + // Case A-F + else if (64 < c && c < 71) { + return (true, c - 55); + } + // Case a-f + else if (96 < c && c < 103) { + return (true, c - 87); + } + // Else: not a hex char + else { + return (false, 0); + } + } + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[48] private __gap; + uint256[47] private __gap; } diff --git a/src/L2ArbitrumGovernorV2.sol b/src/L2ArbitrumGovernorV2.sol deleted file mode 100644 index acef66490..000000000 --- a/src/L2ArbitrumGovernorV2.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.16; - -import { - L2ArbitrumGovernor, - GovernorUpgradeable, - IGovernorUpgradeable -} from "src/L2ArbitrumGovernor.sol"; - -contract L2ArbitrumGovernorV2 is L2ArbitrumGovernor { - /// @notice Error thrown when attempting to cancel a proposal that is not in Pending state. - /// @param state The current state of the proposal. - error ProposalNotPending(GovernorUpgradeable.ProposalState state); - - /// @notice Error thrown when a non-proposer attempts to cancel a proposal. - /// @param sender The address attempting to cancel the proposal. - /// @param proposer The address of the actual proposer. - error NotProposer(address sender, address proposer); - - /// @notice Mapping from proposal ID to the address of the proposer. - /// @dev Used in cancel() to ensure only the proposer can cancel the proposal. - mapping(uint256 => address) internal proposers; - - /// @inheritdoc IGovernorUpgradeable - function propose( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) public virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { - uint256 _proposalId = GovernorUpgradeable.propose(targets, values, calldatas, description); - proposers[_proposalId] = msg.sender; - return _proposalId; - } - - /// @notice Allows a proposer to cancel a proposal when it is pending. - /// @param targets The proposal's targets. - /// @param values The proposal's values. - /// @param calldatas The proposal's calldatas. - /// @param descriptionHash The hash of the proposal's description. - /// @return The id of the proposal. - function cancel( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - bytes32 descriptionHash - ) public virtual returns (uint256) { - uint256 _proposalId = hashProposal(targets, values, calldatas, descriptionHash); - - if (state(_proposalId) != ProposalState.Pending) { - revert ProposalNotPending(state(_proposalId)); - } - - address _proposer = proposers[_proposalId]; - if (msg.sender != _proposer) { - revert NotProposer(msg.sender, _proposer); - } - - delete proposers[_proposalId]; - - return GovernorUpgradeable._cancel(targets, values, calldatas, descriptionHash); - } -} diff --git a/src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol b/src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol deleted file mode 100644 index 0dd8d1406..000000000 --- a/src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import { - TimelockControllerUpgradeable -} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import { - TransparentUpgradeableProxy -} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {ProxyUpgradeAction} from "./ProxyUpgradeAction.sol"; - -/// @title MultiProxyUpgradeAction -/// @notice A contract to proxy upgrade the Core and Treasury Governor contracts. -/// @custom:security-contact https://immunefi.com/bug-bounty/arbitrum/information/ -contract MultiProxyUpgradeAction is ProxyUpgradeAction { - /// @notice The address of the Proxy Admin contract. - address public immutable PROXY_ADMIN; - /// @notice The address of the current Core Governor contract. - address public immutable CORE_GOVERNOR_ADDRESS; - /// @notice The address of the current Treasury Governor contract. - address public immutable TREASURY_GOVERNOR_ADDRESS; - /// @notice The address of the new Governor implementation contract. - address public immutable NEW_GOVERNOR_IMPLEMENTATION_ADDRESS; - - /// @notice Sets up the contract with the given parameters. - /// @param _proxyAdmin The address of the Proxy Admin contract. - /// @param _coreGovernorAddress The address of the current Core Governor contract. - /// @param _treasuryGovernorAddress The address of the current Treasury Governor contract. - /// @param _newGovernorImplementationAddress The address of the new Governor implementation contract. - constructor( - address _proxyAdmin, - address _coreGovernorAddress, - address _treasuryGovernorAddress, - address _newGovernorImplementationAddress - ) { - if ( - _proxyAdmin == address(0) || _coreGovernorAddress == address(0) - || _treasuryGovernorAddress == address(0) - || _newGovernorImplementationAddress == address(0) - ) { - revert("MultiProxyUpgradeAction: zero address"); - } - PROXY_ADMIN = _proxyAdmin; - CORE_GOVERNOR_ADDRESS = _coreGovernorAddress; - TREASURY_GOVERNOR_ADDRESS = _treasuryGovernorAddress; - NEW_GOVERNOR_IMPLEMENTATION_ADDRESS = _newGovernorImplementationAddress; - } - - /// @notice Proxy upgrades the Core and Treasury Governor contracts. - function perform() external { - perform(PROXY_ADMIN, payable(CORE_GOVERNOR_ADDRESS), NEW_GOVERNOR_IMPLEMENTATION_ADDRESS); - perform( - PROXY_ADMIN, payable(TREASURY_GOVERNOR_ADDRESS), NEW_GOVERNOR_IMPLEMENTATION_ADDRESS - ); - require( - ProxyAdmin(payable(PROXY_ADMIN)) - .getProxyImplementation(TransparentUpgradeableProxy(payable(CORE_GOVERNOR_ADDRESS))) - == NEW_GOVERNOR_IMPLEMENTATION_ADDRESS, - "MultiProxyUpgradeAction: Core Governor not upgraded" - ); - require( - ProxyAdmin(payable(PROXY_ADMIN)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(TREASURY_GOVERNOR_ADDRESS)) - ) == NEW_GOVERNOR_IMPLEMENTATION_ADDRESS, - "MultiProxyUpgradeAction: Treasury Governor not upgraded" - ); - } -} diff --git a/test/L2ArbitrumGovernorV2.t.sol b/test/L2ArbitrumGovernorV2.t.sol deleted file mode 100644 index fdac0f542..000000000 --- a/test/L2ArbitrumGovernorV2.t.sol +++ /dev/null @@ -1,533 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import "forge-std/Test.sol"; -import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; -import {L2ArbitrumToken} from "src/L2ArbitrumToken.sol"; -import {ArbitrumTimelock} from "src/ArbitrumTimelock.sol"; -import { - TimelockControllerUpgradeable -} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; -import { - IGovernorUpgradeable -} from "@openzeppelin/contracts-upgradeable/governance/IGovernorUpgradeable.sol"; -import {TestUtil} from "test/util/TestUtil.sol"; -import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; -import {SetupNewGovernors} from "test/util/SetupNewGovernors.sol"; -import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; - -contract L2ArbitrumGovernorV2Test is SetupNewGovernors { - // Core components - L2ArbitrumGovernorV2 internal governor; - L2ArbitrumToken internal token; - ArbitrumTimelock internal timelock; - - // Simple config - address internal constant L1_TOKEN_ADDRESS = address(0x1111); - address internal constant TOKEN_OWNER = address(0x2222); - address internal constant OWNER = address(0x3333); - - uint256 internal constant INITIAL_SUPPLY = 1_000_000 ether; - uint256 internal constant VOTING_DELAY = 1; - uint256 internal constant VOTING_PERIOD = 5; - uint256 internal constant QUORUM_NUMERATOR = 1; - uint256 internal constant PROPOSAL_THRESHOLD = 0; // allow proposals without voting power - uint64 internal constant VOTE_EXTENSION = 0; - - address internal constant PROXY_ADMIN = 0xc7183455a4C133Ae270771860664b6B7ec320bB1; - - address[] internal _majorDelegates; - - // Mirror of IGovernorUpgradeable.ProposalCreated for expectEmit usage in tests - event ProposalCreated( - uint256 proposalId, - address proposer, - address[] targets, - uint256[] values, - string[] signatures, - bytes[] calldatas, - uint256 startBlock, - uint256 endBlock, - string description - ); - // Mirror of IGovernorTimelockUpgradeable.ProposalQueued for expectEmit usage in tests - event ProposalQueued(uint256 proposalId, uint256 eta); - // Mirror of IGovernorUpgradeable.ProposalExecuted for expectEmit usage in tests - event ProposalExecuted(uint256 proposalId); - - function setUp() public virtual override { - if (_shouldPassAndExecuteUpgradeProposal()) { - super.setUp(); - _setMajorDelegates(); - _executeUpgradeProposal(); - governor = L2ArbitrumGovernorV2(payable(L2_CORE_GOVERNOR)); - timelock = ArbitrumTimelock(payable(L2_CORE_GOVERNOR_TIMELOCK)); - token = L2ArbitrumToken(payable(L2_ARB_TOKEN_ADDRESS)); - } else { - _setMajorDelegates(); - // Deploy token proxy and initialize - token = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); - token.initialize(L1_TOKEN_ADDRESS, INITIAL_SUPPLY, TOKEN_OWNER); - governor = L2ArbitrumGovernorV2( - payable(TestUtil.deployProxy(address(new L2ArbitrumGovernorV2()))) - ); - timelock = - ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); - address[] memory proposers = new address[](1); - address[] memory executors = new address[](1); - proposers[0] = address(governor); - executors[0] = address(governor); - timelock.initialize(1, proposers, executors); - - // Initialize governor V2 - governor.initialize( - token, - TimelockControllerUpgradeable(payable(address(timelock))), - OWNER, - VOTING_DELAY, - VOTING_PERIOD, - QUORUM_NUMERATOR, - PROPOSAL_THRESHOLD, - VOTE_EXTENSION - ); - } - } - - function _setMajorDelegates() internal virtual { - _majorDelegates = new address[](18); - // Set the major delegates for testing - _majorDelegates[0] = 0x1B686eE8E31c5959D9F5BBd8122a58682788eeaD; // L2BEAT - _majorDelegates[1] = 0xF4B0556B9B6F53E00A1FDD2b0478Ce841991D8fA; // olimpio - _majorDelegates[2] = 0x11cd09a0c5B1dc674615783b0772a9bFD53e3A8F; // Gauntlet - _majorDelegates[3] = 0xB933AEe47C438f22DE0747D57fc239FE37878Dd1; // Wintermute - _majorDelegates[4] = 0x0eB5B03c0303f2F47cD81d7BE4275AF8Ed347576; // Treasure - _majorDelegates[5] = 0xF92F185AbD9E00F56cb11B0b709029633d1E37B4; // - _majorDelegates[6] = 0x186e505097BFA1f3cF45c2C9D7a79dE6632C3cdc; - _majorDelegates[7] = 0x5663D01D8109DDFC8aACf09fBE51F2d341bb3643; - _majorDelegates[8] = 0x2ef27b114917dD53f8633440A7C0328fef132e2F; // MUX Protocol - _majorDelegates[9] = 0xE48C655276C23F1534AE2a87A2bf8A8A6585Df70; // ercwl - _majorDelegates[10] = 0x8A3e9846df0CDc723C06e4f0C642ffFF82b54610; - _majorDelegates[11] = 0xAD16ebE6FfC7d96624A380F394cD64395B0C6144; // DK (Premia) - _majorDelegates[12] = 0xA5dF0cf3F95C6cd97d998b9D990a86864095d9b0; // Blockworks Research - _majorDelegates[13] = 0x839395e20bbB182fa440d08F850E6c7A8f6F0780; // Griff Green - _majorDelegates[14] = 0x2e3BEf6830Ae84bb4225D318F9f61B6b88C147bF; // Camelot - _majorDelegates[15] = 0x8F73bE66CA8c79382f72139be03746343Bf5Faa0; // mihal.eth - _majorDelegates[16] = 0xb5B069370Ef24BC67F114e185D185063CE3479f8; // Frisson - _majorDelegates[17] = 0xdb5781a835b60110298fF7205D8ef9678Ff1f800; // yoav.eth - } - - function _submitProposal( - uint256 _randomSeed, - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, - string memory _description - ) public returns (uint256 _proposalId) { - vm.prank(_getRandomProposer(_randomSeed)); - _proposalId = governor.propose(_targets, _values, _calldatas, _description); - - vm.roll(block.number + governor.votingDelay() + 1); - } - - function _submitAndQueueProposal( - uint256 _randomSeed, - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, - string memory _description - ) public returns (uint256 _proposalId) { - _proposalId = _submitProposal(_randomSeed, _targets, _values, _calldatas, _description); - - for (uint256 i; i < _majorDelegates.length; i++) { - vm.prank(_majorDelegates[i]); - governor.castVote(_proposalId, uint8(VoteType.For)); - } - vm.roll(block.number + governor.votingPeriod() + 1); - assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Succeeded) - ); - - governor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); - } - - function _submitQueueAndExecuteProposal( - uint256 _randomSeed, - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, - string memory _description - ) public returns (uint256 _proposalId) { - _proposalId = _submitAndQueueProposal( - _randomSeed, _targets, _values, _calldatas, _description - ); - vm.warp(block.timestamp + timelock.getMinDelay() + 1); - governor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); - } - - function _executeUpgradeProposal() internal virtual { - ( - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, - string memory _description, - uint256 _proposalId - ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); - - // Activate proposal - vm.roll(block.number + currentCoreGovernor.votingDelay() + 1); - - // Vote - for (uint256 i; i < _majorDelegates.length; i++) { - vm.prank(_majorDelegates[i]); - currentCoreGovernor.castVote(_proposalId, uint8(VoteType.For)); - } - - // Success - vm.roll(block.number + currentCoreGovernor.votingPeriod() + 1); - - // Queue - currentCoreGovernor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); - vm.warp(block.timestamp + currentCoreTimelock.getMinDelay() + 1); - - // Execute - currentCoreGovernor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); - } - - function _shouldPassAndExecuteUpgradeProposal() internal pure virtual returns (bool) { - return true; - } - - function _basicProposal() - internal - pure - returns ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) - { - targets = new address[](1); - values = new uint256[](1); - calldatas = new bytes[](1); - targets[0] = address(0x1234); - values[0] = 0; - calldatas[0] = bytes(""); - description = "Test"; - } - - function _getRandomProposer(uint256 _randomSeed) internal view returns (address) { - uint256 randomIndex = _randomSeed % _majorDelegates.length; - return _majorDelegates[randomIndex]; - } -} - -abstract contract Cancel is L2ArbitrumGovernorV2Test { - function testFuzz_CancelsPendingProposal(uint256 _randomSeed) public { - address _proposer = _getRandomProposer(_randomSeed); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); - - assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) - ); - - vm.prank(_proposer); - uint256 canceledId = - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); - assertEq(canceledId, proposalId); - assertEq( - uint256(governor.state(proposalId)), - uint256(IGovernorUpgradeable.ProposalState.Canceled) - ); - } - - function testFuzz_RevertIf_NotProposer(uint256 _randomSeed, address _actor) public { - address _proposer = _getRandomProposer(_randomSeed); - vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT && _actor != _proposer); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); - - assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) - ); - - vm.expectRevert( - abi.encodeWithSelector(L2ArbitrumGovernorV2.NotProposer.selector, _actor, _proposer) - ); - vm.prank(_actor); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); - } - - function testFuzz_RevertIf_ProposalIsActive(uint256 _randomSeed) public { - address _proposer = _getRandomProposer(_randomSeed); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); - - vm.roll(block.number + governor.votingDelay() + 1); - assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Active) - ); - - vm.prank(_proposer); - vm.expectRevert( - abi.encodeWithSelector( - L2ArbitrumGovernorV2.ProposalNotPending.selector, - IGovernorUpgradeable.ProposalState.Active - ) - ); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); - } - - function testFuzz_RevertIf_AlreadyCanceled(uint256 _randomSeed) public { - address _proposer = _getRandomProposer(_randomSeed); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); - assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) - ); - - // First cancel - vm.prank(_proposer); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); - assertEq( - uint256(governor.state(proposalId)), - uint256(IGovernorUpgradeable.ProposalState.Canceled) - ); - - vm.prank(_proposer); - vm.expectRevert( - abi.encodeWithSelector( - L2ArbitrumGovernorV2.ProposalNotPending.selector, - IGovernorUpgradeable.ProposalState.Canceled - ) - ); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); - } -} - -abstract contract Propose is L2ArbitrumGovernorV2Test { - function testFuzz_ProposerAboveThresholdCanPropose(uint256 _randomSeed) public { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _proposalId = _submitProposal(_randomSeed, targets, values, calldatas, description); - assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Active) - ); - } - - function testFuzz_EmitsProposalCreatedEvent(uint256 _randomSeed) public { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _expectedProposalId = - governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))); - uint256 _startBlock = block.number + governor.votingDelay(); - uint256 _endBlock = _startBlock + governor.votingPeriod(); - - vm.expectEmit(); - emit ProposalCreated( - _expectedProposalId, - _getRandomProposer(_randomSeed), - targets, - values, - new string[](targets.length), - calldatas, - _startBlock, - _endBlock, - description - ); - _submitProposal(_randomSeed, targets, values, calldatas, description); - } - - function testFuzz_ProposerBelowThresholdCannotPropose(address _proposer) public { - vm.assume(_proposer != L2_PROXY_ADMIN_CONTRACT); - vm.assume(governor.getVotes(_proposer, block.number - 1) < governor.proposalThreshold()); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - vm.expectRevert("Governor: proposer votes below proposal threshold"); - vm.prank(_proposer); - governor.propose(targets, values, calldatas, description); - } -} - -abstract contract Queue is L2ArbitrumGovernorV2Test { - function testFuzz_QueuesASucceededProposal(uint256 _randomSeed) public { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _proposalId = - _submitAndQueueProposal(_randomSeed, targets, values, calldatas, description); - assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Queued) - ); - } - - function testFuzz_EmitsQueueEvent(uint256 _randomSeed, address _actor) public { - vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _eta = block.timestamp + timelock.getMinDelay(); - uint256 _proposalId = _submitProposal(_randomSeed, targets, values, calldatas, description); - - for (uint256 i; i < _majorDelegates.length; i++) { - vm.prank(_majorDelegates[i]); - governor.castVote(_proposalId, uint8(VoteType.For)); - } - vm.roll(block.number + governor.votingPeriod() + 1); - - vm.expectEmit(); - emit ProposalQueued(_proposalId, _eta); - vm.prank(_actor); - governor.queue(targets, values, calldatas, keccak256(bytes(description))); - } - - function testFuzz_RevertIf_ProposalIsNotSucceeded(uint256 _randomSeed, address _actor) public { - vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _proposalId = _submitProposal(_randomSeed, targets, values, calldatas, description); - - for (uint256 i; i < _majorDelegates.length; i++) { - vm.prank(_majorDelegates[i]); - governor.castVote(_proposalId, uint8(VoteType.Against)); - } - vm.roll(block.number + governor.votingPeriod() + 1); - - vm.prank(_actor); - vm.expectRevert("Governor: proposal not successful"); - governor.queue(targets, values, calldatas, keccak256(bytes(description))); - } -} - -abstract contract Execute is L2ArbitrumGovernorV2Test { - function testFuzz_ExecutesASucceededProposal(uint256 _randomSeed) public { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _proposalId = - _submitQueueAndExecuteProposal(_randomSeed, targets, values, calldatas, description); - assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Executed) - ); - } - - function testFuzz_EmitsExecuteEvent(uint256 _randomSeed, address _actor) public { - vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - uint256 _proposalId = - _submitAndQueueProposal(_randomSeed, targets, values, calldatas, description); - vm.warp(block.timestamp + timelock.getMinDelay() + 1); - - vm.expectEmit(); - emit ProposalExecuted(_proposalId); - vm.prank(_actor); - governor.execute(targets, values, calldatas, keccak256(bytes(description))); - } - - function testFuzz_RevertIf_OperationNotReady(uint256 _randomSeed, address _actor) public { - vm.assume(_actor != L2_PROXY_ADMIN_CONTRACT); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas, - string memory description - ) = _basicProposal(); - - _submitAndQueueProposal(_randomSeed, targets, values, calldatas, description); - - vm.prank(_actor); - vm.expectRevert(bytes("TimelockController: operation is not ready")); - governor.execute(targets, values, calldatas, keccak256(bytes(description))); - } -} - -contract GovernorCancel is Cancel { - function setUp() public override(L2ArbitrumGovernorV2Test) { - super.setUp(); - } -} - -contract GovernorPropose is Propose { - function setUp() public override(L2ArbitrumGovernorV2Test) { - super.setUp(); - } -} - -contract GovernorQueue is Queue { - function setUp() public override(L2ArbitrumGovernorV2Test) { - super.setUp(); - } -} - -contract GovernorExecute is Execute { - function setUp() public override(L2ArbitrumGovernorV2Test) { - super.setUp(); - } -} diff --git a/test/SubmitUpgradeProposal.t.sol b/test/SubmitUpgradeProposal.t.sol deleted file mode 100644 index 8e5120849..000000000 --- a/test/SubmitUpgradeProposal.t.sol +++ /dev/null @@ -1,299 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import {Test} from "forge-std/Test.sol"; -import {SubmitUpgradeProposalScript} from "scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol"; -import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; -import { - MultiProxyUpgradeAction -} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol"; -import {SetupNewGovernors} from "test/util/SetupNewGovernors.sol"; -import { - ProxyUpgradeAndCallAction -} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/ProxyUpgradeAndCallAction.sol"; -import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import { - TransparentUpgradeableProxy -} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {L2ArbitrumGovernorV2Test} from "test/L2ArbitrumGovernorV2.t.sol"; - -contract SubmitUpgradeProposalTest is SetupNewGovernors, L2ArbitrumGovernorV2Test { - event Upgraded(address indexed implementation); - - function setUp() public virtual override(SetupNewGovernors, L2ArbitrumGovernorV2Test) { - SetupNewGovernors.setUp(); - _setMajorDelegates(); - } - - function test_SuccessfullyExecuteUpgradeProposal() public { - MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( - L2_PROXY_ADMIN_CONTRACT, - L2_CORE_GOVERNOR, - L2_TREASURY_GOVERNOR, - address(newGovernorImplementation) - ); - - // Propose - ( - address[] memory _targets, - uint256[] memory _values, - bytes[] memory _calldatas, - string memory _description, - uint256 _proposalId - ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), - uint256(IGovernor.ProposalState.Pending) - ); - vm.roll(block.number + currentCoreGovernor.votingDelay() + 1); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), uint256(IGovernor.ProposalState.Active) - ); - - // Vote - for (uint256 i; i < _majorDelegates.length; i++) { - vm.prank(_majorDelegates[i]); - currentCoreGovernor.castVote(_proposalId, uint8(VoteType.For)); - } - - // Success - vm.roll(block.number + currentCoreGovernor.votingPeriod() + 1); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), - uint256(IGovernor.ProposalState.Succeeded) - ); - - // Queue - currentCoreGovernor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), uint256(IGovernor.ProposalState.Queued) - ); - vm.warp(block.timestamp + currentCoreTimelock.getMinDelay() + 1); - - vm.expectEmit(); - emit Upgraded(address(newGovernorImplementation)); - vm.expectEmit(); - emit Upgraded(address(newGovernorImplementation)); - - // Execute - currentCoreGovernor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); - - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), - uint256(IGovernor.ProposalState.Executed) - ); - assertEq( - ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(address(currentCoreGovernor))) - ), - address(newGovernorImplementation) - ); - assertEq( - ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(address(currentTreasuryGovernor))) - ), - address(newGovernorImplementation) - ); - assertEq( - ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_CORE_GOVERNOR))), - L2_PROXY_ADMIN_CONTRACT - ); - assertEq( - ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyAdmin(TransparentUpgradeableProxy(payable(L2_TREASURY_GOVERNOR))), - L2_PROXY_ADMIN_CONTRACT - ); - } - - function test_DefeatedExecuteUpgradeProposalDoesNotUpdateImplementation() public { - address initialCoreGovernorImplementation = ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(address(currentCoreGovernor))) - ); - - address initialTreasuryGovernorImplementation = ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(address(currentTreasuryGovernor))) - ); - - MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( - L2_PROXY_ADMIN_CONTRACT, - L2_CORE_GOVERNOR, - L2_TREASURY_GOVERNOR, - address(newGovernorImplementation) - ); - - // Propose - ( - ,/*address[] memory _targets*/ /*uint256[] memory _values*/ /*bytes[] memory _calldatas*/ /*string memory _description*/,,, - uint256 _proposalId - ) = submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), - uint256(IGovernor.ProposalState.Pending) - ); - vm.roll(block.number + currentCoreGovernor.votingDelay() + 1); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), uint256(IGovernor.ProposalState.Active) - ); - - // Vote - for (uint256 i; i < _majorDelegates.length; i++) { - vm.prank(_majorDelegates[i]); - currentCoreGovernor.castVote(_proposalId, uint8(VoteType.Against)); - } - - // Defeat - vm.roll(block.number + currentCoreGovernor.votingPeriod() + 1); - assertEq( - uint256(currentCoreGovernor.state(_proposalId)), - uint256(IGovernor.ProposalState.Defeated) - ); - - assertEq( - ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(address(currentCoreGovernor))) - ), - initialCoreGovernorImplementation - ); - assertEq( - ProxyAdmin(payable(L2_PROXY_ADMIN_CONTRACT)) - .getProxyImplementation( - TransparentUpgradeableProxy(payable(address(currentTreasuryGovernor))) - ), - initialTreasuryGovernorImplementation - ); - } - - function testFuzz_RevertIf_ProxyAdminOwnerMismatch(address _wrongOwner) public { - vm.assume(_wrongOwner != L2_UPGRADE_EXECUTOR); - vm.assume(_wrongOwner != address(0)); - - MultiProxyUpgradeAction multiProxyUpgradeAction = new MultiProxyUpgradeAction( - L2_PROXY_ADMIN_CONTRACT, - L2_CORE_GOVERNOR, - L2_TREASURY_GOVERNOR, - address(newGovernorImplementation) - ); - - vm.prank(L2_UPGRADE_EXECUTOR); - ProxyAdmin(L2_PROXY_ADMIN_CONTRACT).transferOwnership(_wrongOwner); - - vm.expectRevert("ProxyAdmin owner mismatch"); - submitUpgradeProposalScript.run(address(multiProxyUpgradeAction), L1_TIMELOCK_MIN_DELAY); - } - - function testFuzz_RevertIf_CoreGovernorProxyAdminMismatch(address _wrongProxyAdmin) public { - vm.assume(_wrongProxyAdmin != L2_PROXY_ADMIN_CONTRACT); - vm.assume(_wrongProxyAdmin != address(0)); - - address[] memory targets = new address[](1); - targets[0] = L2_ARB_SYS; - uint256[] memory values = new uint256[](1); - values[0] = 0; - bytes[] memory calldatas = new bytes[](1); - calldatas[0] = ""; - - vm.mockCall( - L2_PROXY_ADMIN_CONTRACT, - abi.encodeWithSelector( - ProxyAdmin.getProxyAdmin.selector, - TransparentUpgradeableProxy(payable(L2_CORE_GOVERNOR)) - ), - abi.encode(_wrongProxyAdmin) - ); - - vm.expectRevert("Core Governor proxy admin mismatch"); - submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); - } - - function testFuzz_RevertIf_TreasuryGovernorProxyAdminMismatch(address _wrongProxyAdmin) public { - vm.assume(_wrongProxyAdmin != L2_PROXY_ADMIN_CONTRACT); - vm.assume(_wrongProxyAdmin != address(0)); - - address[] memory targets = new address[](1); - targets[0] = L2_ARB_SYS; - uint256[] memory values = new uint256[](1); - values[0] = 0; - bytes[] memory calldatas = new bytes[](1); - calldatas[0] = ""; - - vm.mockCall( - L2_PROXY_ADMIN_CONTRACT, - abi.encodeWithSelector( - ProxyAdmin.getProxyAdmin.selector, - TransparentUpgradeableProxy(payable(L2_TREASURY_GOVERNOR)) - ), - abi.encode(_wrongProxyAdmin) - ); - - vm.expectRevert("Treasury Governor proxy admin mismatch"); - submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); - } - - function test_RevertIf_MultipleTargets() public { - address[] memory targets = new address[](2); - targets[0] = L2_ARB_SYS; - targets[1] = L2_ARB_SYS; - uint256[] memory values = new uint256[](2); - values[0] = 0; - values[1] = 0; - bytes[] memory calldatas = new bytes[](2); - calldatas[0] = ""; - calldatas[1] = ""; - - vm.expectRevert("Invalid proposal arrays"); - submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); - } - - function testFuzz_RevertIf_WrongTarget(address _wrongTarget) public { - vm.assume(_wrongTarget != L2_ARB_SYS); - vm.assume(_wrongTarget != address(0)); - - address[] memory targets = new address[](1); - targets[0] = _wrongTarget; - uint256[] memory values = new uint256[](1); - values[0] = 0; - bytes[] memory calldatas = new bytes[](1); - calldatas[0] = ""; - - vm.expectRevert("Invalid proposal arrays"); - submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); - } - - function testFuzz_RevertIf_NonZeroValue(uint256 _wrongValue) public { - vm.assume(_wrongValue != 0); - - address[] memory targets = new address[](1); - targets[0] = L2_ARB_SYS; - uint256[] memory values = new uint256[](1); - values[0] = _wrongValue; - bytes[] memory calldatas = new bytes[](1); - calldatas[0] = ""; - - vm.expectRevert("Invalid proposal arrays"); - submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); - } - - function test_RevertIf_MultipleCalldatas() public { - address[] memory targets = new address[](1); - targets[0] = L2_ARB_SYS; - uint256[] memory values = new uint256[](1); - values[0] = 0; - bytes[] memory calldatas = new bytes[](2); - calldatas[0] = ""; - calldatas[1] = ""; - - vm.expectRevert("Invalid proposal arrays"); - submitUpgradeProposalScript.checkConfigurationAndPayload(targets, values, calldatas); - } -} - -interface IUpgradeExecutor { - function execute(address to, bytes calldata data) external payable; -} diff --git a/test/util/SetupNewGovernors.sol b/test/util/SetupNewGovernors.sol deleted file mode 100644 index 321e6b228..000000000 --- a/test/util/SetupNewGovernors.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.16; - -import {Test, console2} from "forge-std/Test.sol"; -import { - TimelockControllerUpgradeable -} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; -import { - GovernorUpgradeable -} from "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; -import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; -import {SubmitUpgradeProposalScript} from "scripts/forge-scripts/SubmitUpgradeProposalScript.s.sol"; -import {DeployImplementation} from "scripts/forge-scripts/DeployImplementation.s.sol"; -import { - DeployMultiProxyUpgradeAction -} from "scripts/forge-scripts/DeployMultiProxyUpgradeAction.s.sol"; -import {DeployConstants} from "scripts/forge-scripts/DeployConstants.sol"; -import {L2ArbitrumGovernorV2} from "src/L2ArbitrumGovernorV2.sol"; -import {L2ArbitrumGovernor} from "src/L2ArbitrumGovernor.sol"; -import { - MultiProxyUpgradeAction -} from "src/gov-action-contracts/gov-upgrade-contracts/upgrade-proxy/MultiProxyUpgradeAction.sol"; - -abstract contract SetupNewGovernors is DeployConstants, Test { - // Deploy & setup scripts - SubmitUpgradeProposalScript submitUpgradeProposalScript; - MultiProxyUpgradeAction multiProxyUpgradeAction; - - // Current governors and timelocks - L2ArbitrumGovernor currentCoreGovernor; - TimelockControllerUpgradeable currentCoreTimelock; - L2ArbitrumGovernor currentTreasuryGovernor; - TimelockControllerUpgradeable currentTreasuryTimelock; - - // New governors - L2ArbitrumGovernorV2 newGovernorImplementation; - - uint256 constant FORK_BLOCK = 245_608_716; // Arbitrary recent block - - enum ProposalState { - Pending, - Active, - Canceled, - Defeated, - Succeeded, - Queued, - Expired, - Executed - } - enum VoteType { - Against, - For, - Abstain - } - - function setUp() public virtual { - vm.createSelectFork( - vm.envOr( - "ARBITRUM_ONE_RPC_URL", string("Please set ARBITRUM_ONE_RPC_URL in your .env file") - ), - FORK_BLOCK - ); - - // Deploy Governor implementation contract - DeployImplementation _implementationDeployer = new DeployImplementation(); - address _implementation = address(_implementationDeployer.run()); - - // Deploy Governor proxy contracts - newGovernorImplementation = L2_ARBITRUM_GOVERNOR_V2_IMPLEMENTATION == address(0) - ? L2ArbitrumGovernorV2(payable(_implementation)) - : L2ArbitrumGovernorV2(payable(L2_ARBITRUM_GOVERNOR_V2_IMPLEMENTATION)); - - // Current governors and timelocks - currentCoreGovernor = L2ArbitrumGovernor(payable(L2_CORE_GOVERNOR)); - currentCoreTimelock = TimelockControllerUpgradeable(payable(L2_CORE_GOVERNOR_TIMELOCK)); - currentTreasuryGovernor = L2ArbitrumGovernor(payable(L2_TREASURY_GOVERNOR)); - currentTreasuryTimelock = - TimelockControllerUpgradeable(payable(L2_TREASURY_GOVERNOR_TIMELOCK)); - - // Deploy a mock ArbSys contract at L2_ARB_SYS - vm.allowCheatcodes(address(L2_ARB_SYS)); - MockArbSys mockArbSys = new MockArbSys(); - bytes memory code = address(mockArbSys).code; - vm.etch(L2_ARB_SYS, code); - - // Prepare the script to submit upgrade proposal - submitUpgradeProposalScript = new SubmitUpgradeProposalScript(); - DeployMultiProxyUpgradeAction deployMultiProxyUpgradeAction = - new DeployMultiProxyUpgradeAction(); - multiProxyUpgradeAction = - deployMultiProxyUpgradeAction.run(address(newGovernorImplementation)); - } -} - -/// @dev Here we mock ArbSys, the contract that the timelock uses to make an L2 to L1 call. Normal call flow would -/// then see the call flow to ArbOne Outbox, to L1 timelock, to L1 ArbOne Inbox, to L2 Retryable buffer, to L2 Upgrade -/// Executor. Here, we assume this L1 call flow occurs. We make loose assertions about what calldata at each of these -/// steps looks like, and we finally arrive at the decoded calldata to pass to Upgrade Executor. Everything from ArbSys -/// to UpgradeExecutor is "fake" here, while preserving some loose confidence. -contract MockArbSys is DeployConstants, Test { - function sendTxToL1(address _l1Target, bytes calldata _data) external { - ( - address _retryableTicketMagic, - /*uint256 _ignored*/, - bytes memory _retryableData, - /*bytes32 _predecessor*/, - /*bytes32 _description*/, - /*uint256 _minDelay*/ - ) = abi.decode(_data[4:], (address, uint256, bytes, bytes32, bytes32, uint256)); - - assertEq(_l1Target, L1_TIMELOCK); - assertEq(_retryableTicketMagic, L2_RETRYABLE_TICKET_MAGIC); - - ( - address _arbOneDelayedInbox, - address _upgradeExecutor, - /*uint256 _value*/, - /*uint256 _maxGas*/, - /*uint256 _maxFeePerGas*/, - bytes memory _upgradeExecutorCallData - ) = abi.decode(_retryableData, (address, address, uint256, uint256, uint256, bytes)); - - assertEq(_arbOneDelayedInbox, L1_ARB_ONE_DELAYED_INBOX); - assertEq(_upgradeExecutor, L2_UPGRADE_EXECUTOR); - - vm.prank(L2_SECURITY_COUNCIL_9); - ( - bool success, /*bytes memory data*/ - ) = _upgradeExecutor.call(_upgradeExecutorCallData); - assertEq(success, true); - } -} - -interface IUpgradeExecutor { - function execute(address to, bytes calldata data) external payable; -} From ea58b8cb614b10d0cdec2c18d77805cee9c130d0 Mon Sep 17 00:00:00 2001 From: wildmolasses Date: Fri, 7 Nov 2025 13:47:16 -0500 Subject: [PATCH 30/43] remove custom errors --- src/L2ArbitrumGovernor.sol | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/L2ArbitrumGovernor.sol b/src/L2ArbitrumGovernor.sol index cee1e1579..858f1128e 100644 --- a/src/L2ArbitrumGovernor.sol +++ b/src/L2ArbitrumGovernor.sol @@ -27,15 +27,6 @@ contract L2ArbitrumGovernor is GovernorPreventLateQuorumUpgradeable, OwnableUpgradeable { - /// @notice Error thrown when attempting to cancel a proposal that is not in Pending state. - /// @param state The current state of the proposal. - error ProposalNotPending(GovernorUpgradeable.ProposalState state); - - /// @notice Error thrown when a non-proposer attempts to cancel a proposal. - /// @param sender The address attempting to cancel the proposal. - /// @param proposer The address of the actual proposer. - error NotProposer(address sender, address proposer); - /// @notice address for which votes will not be counted toward quorum /// @dev A portion of the Arbitrum tokens will be held by entities (eg the treasury) that /// are not eligible to vote. However, even if their voting/delegation is restricted their @@ -139,7 +130,8 @@ contract L2ArbitrumGovernor is string memory description ) public virtual override(IGovernorUpgradeable, GovernorUpgradeable) returns (uint256) { require( - _isValidDescriptionForProposer(msg.sender, description), "Governor: proposer restricted" + _isValidDescriptionForProposer(msg.sender, description), + "L2ArbitrumGovernor: PROPOSER_RESTRICTED" ); uint256 _proposalId = GovernorUpgradeable.propose(targets, values, calldatas, description); proposers[_proposalId] = msg.sender; @@ -160,14 +152,12 @@ contract L2ArbitrumGovernor is ) public virtual returns (uint256) { uint256 _proposalId = hashProposal(targets, values, calldatas, descriptionHash); - if (state(_proposalId) != ProposalState.Pending) { - revert ProposalNotPending(state(_proposalId)); - } + require( + state(_proposalId) == ProposalState.Pending, "L2ArbitrumGovernor: PROPOSAL_NOT_PENDING" + ); address _proposer = proposers[_proposalId]; - if (msg.sender != _proposer) { - revert NotProposer(msg.sender, _proposer); - } + require(msg.sender == _proposer, "L2ArbitrumGovernor: NOT_PROPOSER"); delete proposers[_proposalId]; From ed2f9deb0b19a701bbf1e606ddb2bfdafefcd200 Mon Sep 17 00:00:00 2001 From: wildmolasses Date: Fri, 7 Nov 2025 13:49:10 -0500 Subject: [PATCH 31/43] formatting --- src/L2ArbitrumGovernor.sol | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/L2ArbitrumGovernor.sol b/src/L2ArbitrumGovernor.sol index 858f1128e..509f3ca95 100644 --- a/src/L2ArbitrumGovernor.sol +++ b/src/L2ArbitrumGovernor.sol @@ -3,11 +3,15 @@ pragma solidity 0.8.16; import "@openzeppelin/contracts-upgradeable/governance/GovernorUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorSettingsUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorPreventLateQuorumUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; +import + "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; +import + "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorPreventLateQuorumUpgradeable.sol"; +import + "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorVotesUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol"; +import + "@openzeppelin/contracts-upgradeable/governance/extensions/GovernorTimelockControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {L2ArbitrumToken} from "./L2ArbitrumToken.sol"; @@ -224,13 +228,15 @@ contract L2ArbitrumGovernor is // if pastTotalDelegatedVotes is 0, then blockNumber is almost certainly prior to the first totalDelegatedVotes checkpoint // in this case we should use getPastCirculatingSupply to ensure quorum of pre-existing proposals is unchanged - // in the unlikely event that totalDvp is 0 for a block _after_ the dvp update, getPastCirculatingSupply will be used with a larger quorumNumerator, + // in the unlikely event that totalDvp is 0 for a block _after_ the dvp update, getPastCirculatingSupply will be used with a larger quorumNumerator, // resulting in a much higher calculated quorum. This is okay because quorum is clamped. - uint256 calculatedQuorum = - ((pastTotalDelegatedVotes == 0 - ? getPastCirculatingSupply(blockNumber) - : pastTotalDelegatedVotes) - * quorumNumerator(blockNumber)) / quorumDenominator(); + uint256 calculatedQuorum = ( + ( + pastTotalDelegatedVotes == 0 + ? getPastCirculatingSupply(blockNumber) + : pastTotalDelegatedVotes + ) * quorumNumerator(blockNumber) + ) / quorumDenominator(); // clamp the calculated quorum between minimumQuorum and maximumQuorum if (calculatedQuorum < minimumQuorum) { @@ -316,7 +322,11 @@ contract L2ArbitrumGovernor is uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash - ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint256) { + ) + internal + override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) + returns (uint256) + { return GovernorTimelockControlUpgradeable._cancel(targets, values, calldatas, descriptionHash); } From 7e06944e3c8325169936b1e19bce3af619b47c3a Mon Sep 17 00:00:00 2001 From: wildmolasses Date: Fri, 7 Nov 2025 17:05:29 -0500 Subject: [PATCH 32/43] remove foundry.lock --- foundry.lock | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 foundry.lock diff --git a/foundry.lock b/foundry.lock deleted file mode 100644 index fea9991c9..000000000 --- a/foundry.lock +++ /dev/null @@ -1,14 +0,0 @@ -{ - "lib/forge-std": { - "rev": "e8a047e3f40f13fa37af6fe14e6e06283d9a060e" - }, - "lib/solady": { - "rev": "7175c21f95255dc7711ce84cc32080a41864abd6" - }, - "token-bridge-contracts": { - "branch": { - "name": "v1.2.5", - "rev": "7c90b43fee0072a3515147822faf5b20fbcf134b" - } - } -} \ No newline at end of file From abfb95b68f30a90c2fd30748e770ca8f8d6d1ae4 Mon Sep 17 00:00:00 2001 From: wildmolasses Date: Fri, 7 Nov 2025 17:14:46 -0500 Subject: [PATCH 33/43] add audit --- ...overnor Cancel Upgrade Review__10_19_25.pdf | Bin 0 -> 475246 bytes src/L2ArbitrumGovernor.sol | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 audits/Arbitrum Governor Cancel Upgrade Review__10_19_25.pdf diff --git a/audits/Arbitrum Governor Cancel Upgrade Review__10_19_25.pdf b/audits/Arbitrum Governor Cancel Upgrade Review__10_19_25.pdf new file mode 100644 index 0000000000000000000000000000000000000000..60abc6542a168ae9f77cb12ea429ef9c652e2942 GIT binary patch literal 475246 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%0A`$d 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 literal 0 HcmV?d00001 diff --git a/src/L2ArbitrumGovernor.sol b/src/L2ArbitrumGovernor.sol index 509f3ca95..79a075aea 100644 --- a/src/L2ArbitrumGovernor.sol +++ b/src/L2ArbitrumGovernor.sol @@ -126,7 +126,7 @@ contract L2ArbitrumGovernor is } /// @inheritdoc IGovernorUpgradeable - /// @dev See {IGovernor-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. + /// @dev See {IGovernorUpgradeable-propose}. This function has opt-in frontrunning protection, described in {_isValidDescriptionForProposer}. function propose( address[] memory targets, uint256[] memory values, From a2f690adb9df17f89a810273d3d83a798daca9fb Mon Sep 17 00:00:00 2001 From: wildmolasses Date: Wed, 26 Nov 2025 23:54:39 -0500 Subject: [PATCH 34/43] add basic non-fork governor unit tests --- test/L2ArbitrumGovernor.t.sol | 475 ++++++++++++++++++++++++++++++++-- 1 file changed, 448 insertions(+), 27 deletions(-) diff --git a/test/L2ArbitrumGovernor.t.sol b/test/L2ArbitrumGovernor.t.sol index 13988ea4d..4e21e132a 100644 --- a/test/L2ArbitrumGovernor.t.sol +++ b/test/L2ArbitrumGovernor.t.sol @@ -1,13 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.16; +pragma solidity 0.8.16; import "../src/L2ArbitrumGovernor.sol"; import "../src/ArbitrumTimelock.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; import "../src/L2ArbitrumToken.sol"; import "./util/TestUtil.sol"; +import {L2ArbitrumToken} from "src/L2ArbitrumToken.sol"; +import {ArbitrumTimelock} from "src/ArbitrumTimelock.sol"; +import { + TimelockControllerUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/TimelockControllerUpgradeable.sol"; +import { + IGovernorUpgradeable +} from "@openzeppelin/contracts-upgradeable/governance/IGovernorUpgradeable.sol"; +import {TestUtil} from "test/util/TestUtil.sol"; +import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; import "forge-std/Test.sol"; @@ -21,22 +29,77 @@ contract L2ArbitrumGovernorTest is Test { uint256 quorumNumerator = 500; uint256 proposalThreshold = 1; uint64 initialVoteExtension = 5; - address[] stubAddressArray = [address(640)]; address someRando = address(741); address executor = address(842); + L2ArbitrumGovernor governor; + L2ArbitrumToken token; + ArbitrumTimelock timelock; + address governorProxyAdmin; + address tokenProxyAdmin; + + event ProposalCreated( + uint256 proposalId, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 startBlock, + uint256 endBlock, + string description + ); + event ProposalQueued(uint256 proposalId, uint256 eta); + event ProposalExecuted(uint256 proposalId); + + enum ProposalState { + Pending, + Active, + Canceled, + Defeated, + Succeeded, + Queued, + Expired, + Executed + } + enum VoteType { + Against, + For, + Abstain + } + + function setUp() public { + (governor, token, timelock, governorProxyAdmin, tokenProxyAdmin) = deployAndInit(); + } + function deployAndInit() - private - returns (L2ArbitrumGovernor, L2ArbitrumToken, ArbitrumTimelock) + public + returns (L2ArbitrumGovernor, L2ArbitrumToken, ArbitrumTimelock, address, address) { L2ArbitrumToken token = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); token.initialize(l1TokenAddress, initialTokenSupply, tokenOwner); + tokenProxyAdmin = abi.decode( + abi.encodePacked( + vm.load( + address(token), + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 + ) + ), + (address) + ); + + // predict governor address + address governorAddress = + computeCreateAddress(address(this), vm.getNonce(address(this)) + 5); + address[] memory owners = new address[](1); + owners[0] = governorAddress; ArbitrumTimelock timelock = ArbitrumTimelock(payable(TestUtil.deployProxy(address(new ArbitrumTimelock())))); - timelock.initialize(1, stubAddressArray, stubAddressArray); + timelock.initialize(1, owners, owners); + // timelock.initialize(1, stubAddressArray, stubAddressArray); L2ArbitrumGovernor l2ArbitrumGovernor = L2ArbitrumGovernor(payable(TestUtil.deployProxy(address(new L2ArbitrumGovernor())))); @@ -51,12 +114,108 @@ contract L2ArbitrumGovernorTest is Test { initialVoteExtension ); _setQuorumMinAndMax(l2ArbitrumGovernor, 0, type(uint256).max); - return (l2ArbitrumGovernor, token, timelock); + governorProxyAdmin = abi.decode( + abi.encodePacked( + vm.load( + address(l2ArbitrumGovernor), + 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 + ) + ), + (address) + ); + return (l2ArbitrumGovernor, token, timelock, governorProxyAdmin, tokenProxyAdmin); } + function _setQuorumMinAndMax(L2ArbitrumGovernor l2ArbitrumGovernor, uint256 min, uint256 max) + internal + { + vm.prank(executor); + l2ArbitrumGovernor.relay( + address(l2ArbitrumGovernor), + 0, + abi.encodeWithSelector(l2ArbitrumGovernor.setQuorumMinAndMax.selector, min, max) + ); + } + + function createAndMintToProposer(uint256 _randomSeed) internal returns (address) { + address proposer = address(uint160(_randomSeed)); + vm.assume( + proposer != address(0) && proposer != governorProxyAdmin && proposer != tokenProxyAdmin + ); + vm.warp(300_000_000_000_000_000); + vm.startPrank(tokenOwner); + token.mint(proposer, governor.proposalThreshold()); + vm.stopPrank(); + vm.prank(proposer); + token.delegate(proposer); + vm.roll(3); + return proposer; + } + + function _basicProposal() + internal + pure + returns (address[] memory, uint256[] memory, bytes[] memory, string memory) + { + return (new address[](1), new uint256[](1), new bytes[](1), "test"); + } + + function _submitProposal( + address _proposer, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) public returns (uint256 _proposalId) { + vm.prank(_proposer); + _proposalId = governor.propose(_targets, _values, _calldatas, _description); + + vm.roll(block.number + governor.votingDelay() + 1); + } + + function _submitAndQueueProposal( + address _proposer, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) public returns (uint256 _proposalId) { + _proposalId = _submitProposal(_proposer, _targets, _values, _calldatas, _description); + + vm.prank(_proposer); + governor.castVote(_proposalId, uint8(VoteType.For)); + vm.roll(block.number + governor.votingPeriod() + 1); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Succeeded) + ); + governor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); + return _proposalId; + } + + function _submitQueueAndExecuteProposal( + address _proposer, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) public returns (uint256 _proposalId) { + _proposalId = _submitAndQueueProposal( + _proposer, _targets, _values, _calldatas, _description + ); + + vm.warp(block.timestamp + timelock.getMinDelay() + 1); + governor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); + return _proposalId; + } +} + +contract MiscTests is L2ArbitrumGovernorTest { function testCantReinit() external { - (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token, ArbitrumTimelock timelock) = - deployAndInit(); + ( + L2ArbitrumGovernor l2ArbitrumGovernor, + L2ArbitrumToken token, + ArbitrumTimelock timelock,, + ) = deployAndInit(); vm.expectRevert("Initializable: contract is already initialized"); l2ArbitrumGovernor.initialize( @@ -72,13 +231,13 @@ contract L2ArbitrumGovernorTest is Test { } function testProperlyInitialized() external { - (L2ArbitrumGovernor l2ArbitrumGovernor,,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor,,,,) = deployAndInit(); assertEq(l2ArbitrumGovernor.votingDelay(), votingDelay, "votingDelay not set properly"); assertEq(l2ArbitrumGovernor.votingPeriod(), votingPeriod, "votingPeriod not set properly"); } function testPastCirculatingSupplyMint() external { - (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,,,) = deployAndInit(); vm.warp(200_000_000_000_000_000); vm.roll(2); @@ -99,7 +258,7 @@ contract L2ArbitrumGovernorTest is Test { } function testPastCirculatingSupplyExclude() external { - (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,,,) = deployAndInit(); address excludeAddress = l2ArbitrumGovernor.EXCLUDE_ADDRESS(); vm.roll(3); @@ -127,7 +286,7 @@ contract L2ArbitrumGovernorTest is Test { } function testPastCirculatingSupply() external { - (L2ArbitrumGovernor l2ArbitrumGovernor,,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor,,,,) = deployAndInit(); vm.warp(200_000_000_000_000_000); vm.roll(2); @@ -139,7 +298,7 @@ contract L2ArbitrumGovernorTest is Test { } function testExecutorPermissions() external { - (L2ArbitrumGovernor l2ArbitrumGovernor,,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor,,,,) = deployAndInit(); vm.startPrank(executor); l2ArbitrumGovernor.relay( @@ -181,7 +340,7 @@ contract L2ArbitrumGovernorTest is Test { } function testExecutorPermissionsFail() external { - (L2ArbitrumGovernor l2ArbitrumGovernor,,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor,,,,) = deployAndInit(); vm.startPrank(someRando); @@ -211,7 +370,7 @@ contract L2ArbitrumGovernorTest is Test { } function testDVPQuorumAndClamping() external { - (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,) = deployAndInit(); + (L2ArbitrumGovernor l2ArbitrumGovernor, L2ArbitrumToken token,,,) = deployAndInit(); vm.roll(2); @@ -248,17 +407,279 @@ contract L2ArbitrumGovernorTest is Test { _setQuorumMinAndMax(l2ArbitrumGovernor, 1, 2000); assertEq(l2ArbitrumGovernor.quorum(2), 2000, "quorum should be clamped to max 2000"); } +} - function _setQuorumMinAndMax( - L2ArbitrumGovernor l2ArbitrumGovernor, - uint256 min, - uint256 max - ) internal { - vm.prank(executor); - l2ArbitrumGovernor.relay( - address(l2ArbitrumGovernor), - 0, - abi.encodeWithSelector(l2ArbitrumGovernor.setQuorumMinAndMax.selector, min, max) +contract Cancel is L2ArbitrumGovernorTest { + function testFuzz_CancelsPendingProposal(uint256 _randomSeed) public { + address _proposer = createAndMintToProposer(_randomSeed); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + ); + + vm.prank(_proposer); + uint256 canceledId = + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + assertEq(canceledId, proposalId); + assertEq( + uint256(governor.state(proposalId)), + uint256(IGovernorUpgradeable.ProposalState.Canceled) ); } + + function testFuzz_RevertIf_NotProposer(uint256 _randomSeed, address _actor) public { + address _proposer = createAndMintToProposer(_randomSeed); + vm.assume(_actor != _proposer); + // vm.assume(_actor != proxyAdmin); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + ); + + vm.expectRevert("L2ArbitrumGovernor: NOT_PROPOSER"); + vm.prank(_actor); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_ProposalIsActive(uint256 _randomSeed) public { + address _proposer = createAndMintToProposer(_randomSeed); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + + vm.roll(block.number + governor.votingDelay() + 1); + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Active) + ); + + vm.prank(_proposer); + vm.expectRevert("L2ArbitrumGovernor: PROPOSAL_NOT_PENDING"); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_AlreadyCanceled(uint256 _randomSeed) public { + address _proposer = createAndMintToProposer(_randomSeed); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.prank(_proposer); + uint256 proposalId = governor.propose(targets, values, calldatas, description); + assertEq( + uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + ); + + // First cancel + vm.prank(_proposer); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + assertEq( + uint256(governor.state(proposalId)), + uint256(IGovernorUpgradeable.ProposalState.Canceled) + ); + + vm.prank(_proposer); + vm.expectRevert("L2ArbitrumGovernor: PROPOSAL_NOT_PENDING"); + governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + } +} + +contract Propose is L2ArbitrumGovernorTest { + function testFuzz_ProposerAboveThresholdCanPropose(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _proposalId = _submitProposal(_proposer, targets, values, calldatas, description); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Active) + ); + } + + function testFuzz_EmitsProposalCreatedEvent(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _expectedProposalId = + governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))); + uint256 _startBlock = block.number + governor.votingDelay(); + uint256 _endBlock = _startBlock + governor.votingPeriod(); + + vm.expectEmit(); + emit ProposalCreated( + _expectedProposalId, + _proposer, + targets, + values, + new string[](targets.length), + calldatas, + _startBlock, + _endBlock, + description + ); + _submitProposal(_proposer, targets, values, calldatas, description); + } + + function testFuzz_ProposerBelowThresholdCannotPropose(address _proposer) public { + vm.assume(governor.getVotes(_proposer, block.number - 1) < governor.proposalThreshold()); + vm.assume(_proposer != governorProxyAdmin); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + vm.expectRevert("Governor: proposer votes below proposal threshold"); + vm.prank(_proposer); + governor.propose(targets, values, calldatas, description); + } +} + +contract Queue is L2ArbitrumGovernorTest { + function testFuzz_QueuesASucceededProposal(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _proposalId = + _submitAndQueueProposal(_proposer, targets, values, calldatas, description); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Queued) + ); + } + + function testFuzz_EmitsQueueEvent(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _eta = block.timestamp + timelock.getMinDelay(); + uint256 _proposalId = _submitProposal(_proposer, targets, values, calldatas, description); + + vm.prank(_proposer); + governor.castVote(_proposalId, uint8(VoteType.For)); + vm.roll(block.number + governor.votingPeriod() + 1); + + vm.expectEmit(); + emit ProposalQueued(_proposalId, _eta); + governor.queue(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_ProposalIsNotSucceeded(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _proposalId = _submitProposal(_proposer, targets, values, calldatas, description); + + vm.prank(_proposer); + governor.castVote(_proposalId, uint8(VoteType.Against)); + vm.roll(block.number + governor.votingPeriod() + 1); + + vm.expectRevert("Governor: proposal not successful"); + governor.queue(targets, values, calldatas, keccak256(bytes(description))); + } +} + +contract Execute is L2ArbitrumGovernorTest { + function testFuzz_ExecutesASucceededProposal(uint256 _randomSeed) public { + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _proposalId = + _submitQueueAndExecuteProposal(_proposer, targets, values, calldatas, description); + assertEq( + uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Executed) + ); + } + + function testFuzz_EmitsExecuteEvent(uint256 _randomSeed, address _actor) public { + vm.assume(_actor != governorProxyAdmin); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + uint256 _proposalId = + _submitAndQueueProposal(_proposer, targets, values, calldatas, description); + vm.warp(block.timestamp + timelock.getMinDelay() + 1); + + vm.expectEmit(); + emit ProposalExecuted(_proposalId); + vm.prank(_actor); + governor.execute(targets, values, calldatas, keccak256(bytes(description))); + } + + function testFuzz_RevertIf_OperationNotReady(uint256 _randomSeed, address _actor) public { + vm.assume(_actor != governorProxyAdmin); + ( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + string memory description + ) = _basicProposal(); + + address _proposer = createAndMintToProposer(_randomSeed); + _submitAndQueueProposal(_proposer, targets, values, calldatas, description); + + vm.prank(_actor); + vm.expectRevert(bytes("TimelockController: operation is not ready")); + governor.execute(targets, values, calldatas, keccak256(bytes(description))); + } } From 32048ba9ac3068f7b3cd4ecbc3c7843804258cea Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 21 Jan 2026 23:18:51 +0800 Subject: [PATCH 35/43] chore: update gas snapshot --- .gas-snapshot | 87 +++++++++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index e63b6d2e8..39e9b64d4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -5,29 +5,36 @@ ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) ArbitrumDAOConstitutionTest:testOwnerCanSetHashTwice() (gas: 263824) -ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 16849998) -ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 19762395) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 19766168) -ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 19766113) -ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 16853331) -ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 16845313) -ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 16847662) -ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 16850081) -ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 16855450) -ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 16847561) -ArbitrumFoundationVestingWalletTest:testRelease() (gas: 16972840) +ArbitrumFoundationVestingWalletTest:testBeneficiaryCanSetBeneficiary() (gas: 17150834) +ArbitrumFoundationVestingWalletTest:testMigrateEthToNewWalletWithSlowerVesting() (gas: 20063231) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithFasterVesting() (gas: 20067004) +ArbitrumFoundationVestingWalletTest:testMigrateTokensToNewWalletWithSlowerVesting() (gas: 20066949) +ArbitrumFoundationVestingWalletTest:testMigrationTargetMustBeContract() (gas: 17154167) +ArbitrumFoundationVestingWalletTest:testOnlyBeneficiaryCanRelease() (gas: 17146149) +ArbitrumFoundationVestingWalletTest:testOnlyOwnerCanMigrate() (gas: 17148498) +ArbitrumFoundationVestingWalletTest:testOwnerCanSetBeneficiary() (gas: 17150917) +ArbitrumFoundationVestingWalletTest:testProperlyInits() (gas: 17156286) +ArbitrumFoundationVestingWalletTest:testRandomAddressCantSetBeneficiary() (gas: 17148397) +ArbitrumFoundationVestingWalletTest:testRelease() (gas: 17273676) ArbitrumVestingWalletFactoryTest:testDeploy() (gas: 4589688) ArbitrumVestingWalletFactoryTest:testOnlyOwnerCanCreateWallets() (gas: 1504286) -ArbitrumVestingWalletTest:testCastVote() (gas: 16773138) -ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 16697465) -ArbitrumVestingWalletTest:testClaim() (gas: 16550799) -ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16485838) -ArbitrumVestingWalletTest:testDelegate() (gas: 16627365) -ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16551466) -ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16489225) -ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16551680) -ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16617948) +ArbitrumVestingWalletTest:testCastVote() (gas: 17096336) +ArbitrumVestingWalletTest:testCastVoteFailsForNonBeneficiary() (gas: 17020663) +ArbitrumVestingWalletTest:testClaim() (gas: 16851636) +ArbitrumVestingWalletTest:testClaimFailsForNonBeneficiary() (gas: 16786675) +ArbitrumVestingWalletTest:testDelegate() (gas: 16928202) +ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16852303) +ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16790062) +ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16852517) +ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16918785) +Cancel:testFuzz_CancelsPendingProposal(uint256) (runs: 256, μ: 316602, ~: 316602) +Cancel:testFuzz_RevertIf_AlreadyCanceled(uint256) (runs: 256, μ: 323860, ~: 323860) +Cancel:testFuzz_RevertIf_NotProposer(uint256,address) (runs: 256, μ: 311204, ~: 311204) +Cancel:testFuzz_RevertIf_ProposalIsActive(uint256) (runs: 256, μ: 316024, ~: 316024) E2E:testE2E() (gas: 85121479) +Execute:testFuzz_EmitsExecuteEvent(uint256,address) (runs: 256, μ: 569899, ~: 569929) +Execute:testFuzz_ExecutesASucceededProposal(uint256) (runs: 256, μ: 569777, ~: 569777) +Execute:testFuzz_RevertIf_OperationNotReady(uint256,address) (runs: 256, μ: 558324, ~: 558324) FixedDelegateErc20WalletTest:testInit() (gas: 6131869) FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 6125399) FixedDelegateErc20WalletTest:testTransfer() (gas: 6290592) @@ -61,14 +68,6 @@ L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10771066) L1GovernanceFactoryTest:testSetMinDelay() (gas: 10746048) L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10799003) L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54702) -L2ArbitrumGovernorTest:testCantReinit() (gas: 14170386) -L2ArbitrumGovernorTest:testDVPQuorumAndClamping() (gas: 14425174) -L2ArbitrumGovernorTest:testExecutorPermissions() (gas: 14207447) -L2ArbitrumGovernorTest:testExecutorPermissionsFail() (gas: 14179944) -L2ArbitrumGovernorTest:testPastCirculatingSupply() (gas: 14174112) -L2ArbitrumGovernorTest:testPastCirculatingSupplyExclude() (gas: 14366232) -L2ArbitrumGovernorTest:testPastCirculatingSupplyMint() (gas: 14242703) -L2ArbitrumGovernorTest:testProperlyInitialized() (gas: 14168038) L2ArbitrumTokenTest:testCanBurn() (gas: 4375935) L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4410685) L2ArbitrumTokenTest:testCanMintLessThan2Percent() (gas: 4410687) @@ -101,20 +100,28 @@ L2ArbitrumTokenTest:testIsInitialised() (gas: 4381576) L2ArbitrumTokenTest:testNoChangeDVPOnRedelegateToSame() (gas: 4564379) L2ArbitrumTokenTest:testNoDoublePostUpgradeInit() (gas: 4418459) L2ArbitrumTokenTest:testNoLogicContractInit() (gas: 3001325) -L2GovernanceFactoryTest:testContractsDeployed() (gas: 29149632) -L2GovernanceFactoryTest:testContractsInitialized() (gas: 29186715) -L2GovernanceFactoryTest:testDeploySteps() (gas: 29161141) -L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 29158642) -L2GovernanceFactoryTest:testRoles() (gas: 29181629) -L2GovernanceFactoryTest:testSanityCheckValues() (gas: 29206037) -L2GovernanceFactoryTest:testSetMinDelay() (gas: 29154638) -L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 29207509) -L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 29498401) +L2GovernanceFactoryTest:testContractsDeployed() (gas: 29751026) +L2GovernanceFactoryTest:testContractsInitialized() (gas: 29788109) +L2GovernanceFactoryTest:testDeploySteps() (gas: 29762535) +L2GovernanceFactoryTest:testProxyAdminOwnership() (gas: 29760036) +L2GovernanceFactoryTest:testRoles() (gas: 29783023) +L2GovernanceFactoryTest:testSanityCheckValues() (gas: 29807431) +L2GovernanceFactoryTest:testSetMinDelay() (gas: 29756032) +L2GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 29808903) +L2GovernanceFactoryTest:testUpgraderCanCancel() (gas: 30122181) L2SecurityCouncilMgmtFactoryTest:testMemberElectionGovDeployment() (gas: 30767668) L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 30771899) L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 25781453) L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 30769899) L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 30788992) +MiscTests:testCantReinit() (gas: 14479294) +MiscTests:testDVPQuorumAndClamping() (gas: 14734129) +MiscTests:testExecutorPermissions() (gas: 14516409) +MiscTests:testExecutorPermissionsFail() (gas: 14488899) +MiscTests:testPastCirculatingSupply() (gas: 14483045) +MiscTests:testPastCirculatingSupplyExclude() (gas: 14675170) +MiscTests:testPastCirculatingSupplyMint() (gas: 14551636) +MiscTests:testProperlyInitialized() (gas: 14476990) NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) OfficeHoursActionTest:testConstructor() (gas: 9050) OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317091, ~: 317184) @@ -130,8 +137,14 @@ OutboxActionsTest:testCantAddEOA() (gas: 969058) OutboxActionsTest:testCantReAddOutbox() (gas: 974434) OutboxActionsTest:testRemoveAllOutboxes() (gas: 693079) OutboxActionsTest:testRemoveOutboxes() (gas: 853972) +Propose:testFuzz_EmitsProposalCreatedEvent(uint256) (runs: 256, μ: 318185, ~: 318185) +Propose:testFuzz_ProposerAboveThresholdCanPropose(uint256) (runs: 256, μ: 307797, ~: 307797) +Propose:testFuzz_ProposerBelowThresholdCannotPropose(address) (runs: 256, μ: 46178, ~: 46178) ProxyUpgradeAndCallActionTest:testUpgrade() (gas: 137140) ProxyUpgradeAndCallActionTest:testUpgradeAndCall() (gas: 143087) +Queue:testFuzz_EmitsQueueEvent(uint256) (runs: 256, μ: 502134, ~: 502134) +Queue:testFuzz_QueuesASucceededProposal(uint256) (runs: 256, μ: 521510, ~: 521510) +Queue:testFuzz_RevertIf_ProposalIsNotSucceeded(uint256) (runs: 256, μ: 420750, ~: 420750) SecurityCouncilManagerTest:testAddMemberAffordances() (gas: 249992) SecurityCouncilManagerTest:testAddMemberSpecialAddresses() (gas: 20795) SecurityCouncilManagerTest:testAddMemberToFirstCohort() (gas: 340446) From f43ade0ac6c24b017463e9ef13c966de20f5bfe0 Mon Sep 17 00:00:00 2001 From: gzeon Date: Wed, 21 Jan 2026 23:29:13 +0800 Subject: [PATCH 36/43] chore: update storage and 4bytes --- test/signatures/L2ArbitrumGovernor | 2 ++ test/storage/L2ArbitrumGovernor | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/signatures/L2ArbitrumGovernor b/test/signatures/L2ArbitrumGovernor index da71a2ea7..56ff6c7d1 100644 --- a/test/signatures/L2ArbitrumGovernor +++ b/test/signatures/L2ArbitrumGovernor @@ -10,6 +10,8 @@ |------------------------------------------------------------------------------------+------------| | EXTENDED_BALLOT_TYPEHASH() | 2fe3e261 | |------------------------------------------------------------------------------------+------------| +| cancel(address[],uint256[],bytes[],bytes32) | 452115d6 | +|------------------------------------------------------------------------------------+------------| | castVote(uint256,uint8) | 56781388 | |------------------------------------------------------------------------------------+------------| | castVoteBySig(uint256,uint8,uint8,bytes32,bytes32) | 3bccf4fd | diff --git a/test/storage/L2ArbitrumGovernor b/test/storage/L2ArbitrumGovernor index 0efda4be9..d9a66e7ce 100644 --- a/test/storage/L2ArbitrumGovernor +++ b/test/storage/L2ArbitrumGovernor @@ -70,6 +70,8 @@ |-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| | minimumQuorum | uint256 | 655 | 0 | 32 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | |-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| -| __gap | uint256[48] | 656 | 0 | 1536 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | +| proposers | mapping(uint256 => address) | 656 | 0 | 32 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | +|-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------| +| __gap | uint256[47] | 657 | 0 | 1504 | src/L2ArbitrumGovernor.sol:L2ArbitrumGovernor | ╰-------------------------+---------------------------------------------------------------------------+------+--------+-------+-----------------------------------------------╯ From ddcb51f02c3f21b1c697b07073a337305306d6ba Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 22 Jan 2026 00:14:01 +0800 Subject: [PATCH 37/43] fix: resolve test failures when running with --gas-report --- .gas-snapshot | 86 +++++++++++++++++------------------ test/L1ArbitrumTimelock.t.sol | 25 ++++++++-- test/L2ArbitrumGovernor.t.sol | 9 ++++ test/TokenDistributor.t.sol | 12 ++++- test/util/InboxMock.sol | 15 +++++- 5 files changed, 97 insertions(+), 50 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 39e9b64d4..9c82061bc 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -27,33 +27,33 @@ ArbitrumVestingWalletTest:testDelegateFailsForNonBeneficiary() (gas: 16852303) ArbitrumVestingWalletTest:testDoesDeploy() (gas: 16790062) ArbitrumVestingWalletTest:testReleaseAffordance() (gas: 16852517) ArbitrumVestingWalletTest:testVestedAmountStart() (gas: 16918785) -Cancel:testFuzz_CancelsPendingProposal(uint256) (runs: 256, μ: 316602, ~: 316602) -Cancel:testFuzz_RevertIf_AlreadyCanceled(uint256) (runs: 256, μ: 323860, ~: 323860) -Cancel:testFuzz_RevertIf_NotProposer(uint256,address) (runs: 256, μ: 311204, ~: 311204) -Cancel:testFuzz_RevertIf_ProposalIsActive(uint256) (runs: 256, μ: 316024, ~: 316024) -E2E:testE2E() (gas: 85121479) -Execute:testFuzz_EmitsExecuteEvent(uint256,address) (runs: 256, μ: 569899, ~: 569929) -Execute:testFuzz_ExecutesASucceededProposal(uint256) (runs: 256, μ: 569777, ~: 569777) -Execute:testFuzz_RevertIf_OperationNotReady(uint256,address) (runs: 256, μ: 558324, ~: 558324) +Cancel:testFuzz_CancelsPendingProposal(uint256) (runs: 256, μ: 318187, ~: 318187) +Cancel:testFuzz_RevertIf_AlreadyCanceled(uint256) (runs: 256, μ: 325445, ~: 325445) +Cancel:testFuzz_RevertIf_NotProposer(uint256,address) (runs: 256, μ: 312789, ~: 312789) +Cancel:testFuzz_RevertIf_ProposalIsActive(uint256) (runs: 256, μ: 317609, ~: 317609) +E2E:testE2E() (gas: 85172474) +Execute:testFuzz_EmitsExecuteEvent(uint256,address) (runs: 256, μ: 571528, ~: 571538) +Execute:testFuzz_ExecutesASucceededProposal(uint256) (runs: 256, μ: 571386, ~: 571386) +Execute:testFuzz_RevertIf_OperationNotReady(uint256,address) (runs: 256, μ: 559933, ~: 559933) FixedDelegateErc20WalletTest:testInit() (gas: 6131869) FixedDelegateErc20WalletTest:testInitZeroToken() (gas: 6125399) FixedDelegateErc20WalletTest:testTransfer() (gas: 6290592) FixedDelegateErc20WalletTest:testTransferNotOwner() (gas: 6253065) InboxActionsTest:testPauseAndUpauseInbox() (gas: 370544) L1AddressRegistryTest:testAddressRegistryAddress() (gas: 47009) -L1ArbitrumTimelockTest:testCancel() (gas: 5324642) -L1ArbitrumTimelockTest:testCancelFailsBadSender() (gas: 5369529) -L1ArbitrumTimelockTest:testDoesDeploy() (gas: 5273077) -L1ArbitrumTimelockTest:testDoesNotDeployZeroInbox() (gas: 4978961) -L1ArbitrumTimelockTest:testDoesNotDeployZeroL2Timelock() (gas: 4976931) -L1ArbitrumTimelockTest:testExecute() (gas: 5405352) -L1ArbitrumTimelockTest:testExecuteInbox() (gas: 5746378) -L1ArbitrumTimelockTest:testExecuteInboxBatch() (gas: 6056741) -L1ArbitrumTimelockTest:testExecuteInboxInvalidData() (gas: 5426399) -L1ArbitrumTimelockTest:testExecuteInboxNotEnoughVal() (gas: 5446210) -L1ArbitrumTimelockTest:testSchedule() (gas: 5357782) -L1ArbitrumTimelockTest:testScheduleFailsBadL2Timelock() (gas: 5286095) -L1ArbitrumTimelockTest:testScheduleFailsBadSender() (gas: 5281079) +L1ArbitrumTimelockTest:testCancel() (gas: 5349680) +L1ArbitrumTimelockTest:testCancelFailsBadSender() (gas: 5394567) +L1ArbitrumTimelockTest:testDoesDeploy() (gas: 5298115) +L1ArbitrumTimelockTest:testDoesNotDeployZeroInbox() (gas: 5003999) +L1ArbitrumTimelockTest:testDoesNotDeployZeroL2Timelock() (gas: 5001969) +L1ArbitrumTimelockTest:testExecute() (gas: 5430390) +L1ArbitrumTimelockTest:testExecuteInbox() (gas: 5784913) +L1ArbitrumTimelockTest:testExecuteInboxBatch() (gas: 6087778) +L1ArbitrumTimelockTest:testExecuteInboxInvalidData() (gas: 5472118) +L1ArbitrumTimelockTest:testExecuteInboxNotEnoughVal() (gas: 5484716) +L1ArbitrumTimelockTest:testSchedule() (gas: 5382820) +L1ArbitrumTimelockTest:testScheduleFailsBadL2Timelock() (gas: 5311133) +L1ArbitrumTimelockTest:testScheduleFailsBadSender() (gas: 5306117) L1ArbitrumTokenTest:testBridgeBurn() (gas: 3395571) L1ArbitrumTokenTest:testBridgeBurnNotGateway() (gas: 3389611) L1ArbitrumTokenTest:testBridgeMint() (gas: 3390798) @@ -64,9 +64,9 @@ L1ArbitrumTokenTest:testInitZeroNovaGateway() (gas: 3177301) L1ArbitrumTokenTest:testInitZeroNovaRouter() (gas: 3177235) L1ArbitrumTokenTest:testRegisterTokenOnL2() (gas: 4568612) L1ArbitrumTokenTest:testRegisterTokenOnL2NotEnoughVal() (gas: 4425799) -L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10771066) -L1GovernanceFactoryTest:testSetMinDelay() (gas: 10746048) -L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10799003) +L1GovernanceFactoryTest:testL1GovernanceFactory() (gas: 10796104) +L1GovernanceFactoryTest:testSetMinDelay() (gas: 10771086) +L1GovernanceFactoryTest:testSetMinDelayRevertsForCoreAddress() (gas: 10824041) L2AddressRegistryTest:testAddressRegistryAddress() (gas: 54702) L2ArbitrumTokenTest:testCanBurn() (gas: 4375935) L2ArbitrumTokenTest:testCanMint2Percent() (gas: 4410685) @@ -85,7 +85,7 @@ L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4485899) L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 4108951) L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 4108903) L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 4108994) -L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4421784, ~: 4424046) +L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4423412, ~: 4424046) L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4423454) L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4526229) L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4517654) @@ -114,17 +114,17 @@ L2SecurityCouncilMgmtFactoryTest:testNomineeElectionGovDeployment() (gas: 307718 L2SecurityCouncilMgmtFactoryTest:testOnlyOwnerCanDeploy() (gas: 25781453) L2SecurityCouncilMgmtFactoryTest:testRemovalGovDeployment() (gas: 30769899) L2SecurityCouncilMgmtFactoryTest:testSecurityCouncilManagerDeployment() (gas: 30788992) -MiscTests:testCantReinit() (gas: 14479294) -MiscTests:testDVPQuorumAndClamping() (gas: 14734129) -MiscTests:testExecutorPermissions() (gas: 14516409) -MiscTests:testExecutorPermissionsFail() (gas: 14488899) -MiscTests:testPastCirculatingSupply() (gas: 14483045) -MiscTests:testPastCirculatingSupplyExclude() (gas: 14675170) -MiscTests:testPastCirculatingSupplyMint() (gas: 14551636) -MiscTests:testProperlyInitialized() (gas: 14476990) +MiscTests:testCantReinit() (gas: 14479629) +MiscTests:testDVPQuorumAndClamping() (gas: 14734764) +MiscTests:testExecutorPermissions() (gas: 14516972) +MiscTests:testExecutorPermissionsFail() (gas: 14489330) +MiscTests:testPastCirculatingSupply() (gas: 14483380) +MiscTests:testPastCirculatingSupplyExclude() (gas: 14675601) +MiscTests:testPastCirculatingSupplyMint() (gas: 14552007) +MiscTests:testProperlyInitialized() (gas: 14477337) NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) OfficeHoursActionTest:testConstructor() (gas: 9050) -OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317091, ~: 317184) +OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317109, ~: 317184) OfficeHoursActionTest:testInvalidConstructorParameters() (gas: 235740) OfficeHoursActionTest:testPerformBeforeMinimumTimestamp() (gas: 8646) OfficeHoursActionTest:testPerformDuringOfficeHours() (gas: 9140) @@ -137,14 +137,14 @@ OutboxActionsTest:testCantAddEOA() (gas: 969058) OutboxActionsTest:testCantReAddOutbox() (gas: 974434) OutboxActionsTest:testRemoveAllOutboxes() (gas: 693079) OutboxActionsTest:testRemoveOutboxes() (gas: 853972) -Propose:testFuzz_EmitsProposalCreatedEvent(uint256) (runs: 256, μ: 318185, ~: 318185) -Propose:testFuzz_ProposerAboveThresholdCanPropose(uint256) (runs: 256, μ: 307797, ~: 307797) -Propose:testFuzz_ProposerBelowThresholdCannotPropose(address) (runs: 256, μ: 46178, ~: 46178) +Propose:testFuzz_EmitsProposalCreatedEvent(uint256) (runs: 256, μ: 319802, ~: 319802) +Propose:testFuzz_ProposerAboveThresholdCanPropose(uint256) (runs: 256, μ: 309390, ~: 309390) +Propose:testFuzz_ProposerBelowThresholdCannotPropose(address) (runs: 256, μ: 46186, ~: 46186) ProxyUpgradeAndCallActionTest:testUpgrade() (gas: 137140) ProxyUpgradeAndCallActionTest:testUpgradeAndCall() (gas: 143087) -Queue:testFuzz_EmitsQueueEvent(uint256) (runs: 256, μ: 502134, ~: 502134) -Queue:testFuzz_QueuesASucceededProposal(uint256) (runs: 256, μ: 521510, ~: 521510) -Queue:testFuzz_RevertIf_ProposalIsNotSucceeded(uint256) (runs: 256, μ: 420750, ~: 420750) +Queue:testFuzz_EmitsQueueEvent(uint256) (runs: 256, μ: 503712, ~: 503712) +Queue:testFuzz_QueuesASucceededProposal(uint256) (runs: 256, μ: 523088, ~: 523088) +Queue:testFuzz_RevertIf_ProposalIsNotSucceeded(uint256) (runs: 256, μ: 422327, ~: 422327) SecurityCouncilManagerTest:testAddMemberAffordances() (gas: 249992) SecurityCouncilManagerTest:testAddMemberSpecialAddresses() (gas: 20795) SecurityCouncilManagerTest:testAddMemberToFirstCohort() (gas: 340446) @@ -182,7 +182,7 @@ SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanProp SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32916) SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42229) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339688, ~: 339471) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339763, ~: 339762) SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) @@ -263,9 +263,9 @@ TokenDistributorTest:testSetSweepReceiverFailsNullAddress() (gas: 6059103) TokenDistributorTest:testSetSweepReceiverFailsOwner() (gas: 6060064) TokenDistributorTest:testSweep() (gas: 6132341) TokenDistributorTest:testSweepAfterClaim() (gas: 6195472) -TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 6058837) +TokenDistributorTest:testSweepFailsBeforeClaimPeriodEnd() (gas: 6058825) TokenDistributorTest:testSweepFailsForFailedTransfer() (gas: 6062536) -TokenDistributorTest:testSweepFailsTwice() (gas: 6131300) +TokenDistributorTest:testSweepFailsTwice() (gas: 6132167) TokenDistributorTest:testWithdraw() (gas: 6099572) TokenDistributorTest:testWithdrawFailsNotOwner() (gas: 6099594) TokenDistributorTest:testWithdrawFailsTransfer() (gas: 6061039) diff --git a/test/L1ArbitrumTimelock.t.sol b/test/L1ArbitrumTimelock.t.sol index b7ffc1a05..dc0502286 100644 --- a/test/L1ArbitrumTimelock.t.sol +++ b/test/L1ArbitrumTimelock.t.sol @@ -203,6 +203,9 @@ contract L1ArbitrumTimelockTest is Test { scheduleAndRoll(l1Timelock, magic, val, data, salt); vm.fee(21 gwei); + // Set defaultBaseFee to match vm.fee since block.basefee doesn't persist + // across call contexts when --gas-report is enabled + inbox.setDefaultBaseFee(21 gwei); uint256 submissionFee = inbox.calculateRetryableSubmissionFee(rData.data.length, 0); // set up the sender @@ -212,7 +215,9 @@ contract L1ArbitrumTimelockTest is Test { sender.transfer(execVal); // l2value has to come from the timelock itself - payable(address(l1Timelock)).transfer(rData.l2Value); + // Use vm.deal instead of .transfer() because the proxy's receive() function + // requires more gas than the 2300 gas stipend, especially with --gas-report + vm.deal(address(l1Timelock), rData.l2Value); vm.prank(sender); l1Timelock.execute{value: execVal}(magic, val, data, 0, salt); @@ -282,6 +287,9 @@ contract L1ArbitrumTimelockTest is Test { vm.warp(block.timestamp + minDelay); vm.fee(21 gwei); + // Set defaultBaseFee to match vm.fee since block.basefee doesn't persist + // across call contexts when --gas-report is enabled + inbox.setDefaultBaseFee(21 gwei); uint256 submissionFee = inbox.calculateRetryableSubmissionFee(rData.data.length, 0); // set up the sender @@ -291,8 +299,9 @@ contract L1ArbitrumTimelockTest is Test { sender.transfer(execVal); // l2value has to come from the timelock itself - payable(address(l1Timelock)).transfer(rData.l2Value); - payable(address(l1Timelock)).transfer(rData2.l2Value); + // Use vm.deal instead of .transfer() because the proxy's receive() function + // requires more gas than the 2300 gas stipend, especially with --gas-report + vm.deal(address(l1Timelock), rData.l2Value + rData2.l2Value); vm.prank(sender); l1Timelock.executeBatch{value: execVal}(tos, vals, payloads, 0, salt); @@ -328,6 +337,9 @@ contract L1ArbitrumTimelockTest is Test { scheduleAndRoll(l1Timelock, magic, val, data, salt); vm.fee(21 gwei); + // Set defaultBaseFee to match vm.fee since block.basefee doesn't persist + // across call contexts when --gas-report is enabled + inbox.setDefaultBaseFee(21 gwei); uint256 submissionFee = inbox.calculateRetryableSubmissionFee(rData.data.length, 0); // set up the sender @@ -337,7 +349,9 @@ contract L1ArbitrumTimelockTest is Test { sender.transfer(execVal); // l2value has to come from the timelock itself - payable(address(l1Timelock)).transfer(rData.l2Value); + // Use vm.deal instead of .transfer() because the proxy's receive() function + // requires more gas than the 2300 gas stipend, especially with --gas-report + vm.deal(address(l1Timelock), rData.l2Value); vm.expectRevert(); vm.prank(sender); @@ -367,6 +381,9 @@ contract L1ArbitrumTimelockTest is Test { scheduleAndRoll(l1Timelock, magic, val, data, salt); vm.fee(21 gwei); + // Set defaultBaseFee to match vm.fee since block.basefee doesn't persist + // across call contexts when --gas-report is enabled + inbox.setDefaultBaseFee(21 gwei); uint256 submissionFee = inbox.calculateRetryableSubmissionFee(rData.data.length, 0); // set up the sender diff --git a/test/L2ArbitrumGovernor.t.sol b/test/L2ArbitrumGovernor.t.sol index 4e21e132a..f13a85d67 100644 --- a/test/L2ArbitrumGovernor.t.sol +++ b/test/L2ArbitrumGovernor.t.sol @@ -113,6 +113,14 @@ contract L2ArbitrumGovernorTest is Test { proposalThreshold, initialVoteExtension ); + + // Grant roles to the governor if address prediction was wrong (e.g., when using --gas-report) + if (address(l2ArbitrumGovernor) != governorAddress) { + timelock.grantRole(timelock.PROPOSER_ROLE(), address(l2ArbitrumGovernor)); + timelock.grantRole(timelock.CANCELLER_ROLE(), address(l2ArbitrumGovernor)); + timelock.grantRole(timelock.EXECUTOR_ROLE(), address(l2ArbitrumGovernor)); + } + _setQuorumMinAndMax(l2ArbitrumGovernor, 0, type(uint256).max); governorProxyAdmin = abi.decode( abi.encodePacked( @@ -141,6 +149,7 @@ contract L2ArbitrumGovernorTest is Test { address proposer = address(uint160(_randomSeed)); vm.assume( proposer != address(0) && proposer != governorProxyAdmin && proposer != tokenProxyAdmin + && proposer != governor.EXCLUDE_ADDRESS() ); vm.warp(300_000_000_000_000_000); vm.startPrank(tokenOwner); diff --git a/test/TokenDistributor.t.sol b/test/TokenDistributor.t.sol index e215101b2..ac7a520d9 100644 --- a/test/TokenDistributor.t.sol +++ b/test/TokenDistributor.t.sol @@ -545,8 +545,16 @@ contract TokenDistributorTest is Test { vm.roll(claimPeriodEnd); td.sweep(); - vm.expectRevert("TokenDistributor: no leftovers"); - td.sweep(); + // After selfdestruct, calling the contract may not revert at the expected depth + // when --gas-report is enabled. Use try/catch to handle both cases. + try td.sweep() { + // If it doesn't revert, the contract was self-destructed and the call is a no-op + // Verify the contract has no code (was self-destructed) + assertEq(address(td).code.length, 0, "Contract should be self-destructed"); + } catch Error(string memory reason) { + // If it reverts with the expected message, that's also acceptable + assertEq(reason, "TokenDistributor: no leftovers"); + } } function testSweepFailsForFailedTransfer() public { diff --git a/test/util/InboxMock.sol b/test/util/InboxMock.sol index b30349aa8..9f9c7236b 100644 --- a/test/util/InboxMock.sol +++ b/test/util/InboxMock.sol @@ -31,13 +31,26 @@ contract InboxMock is IInboxSubmissionFee { bytes data ); + // Default basefee to use when block.basefee is 0 (e.g., during --gas-report) + // Tests that rely on vm.fee() should set this explicitly as vm.fee() doesn't + // persist across call contexts when --gas-report is enabled + uint256 public defaultBaseFee = 0; + + function setDefaultBaseFee(uint256 _baseFee) external { + defaultBaseFee = _baseFee; + } + function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) public view returns (uint256) { // Use current block basefee if baseFee parameter is 0 - return (1400 + 6 * dataLength) * (baseFee == 0 ? block.basefee : baseFee); + // Fall back to defaultBaseFee if block.basefee is also 0 (happens with --gas-report) + uint256 effectiveBaseFee = baseFee != 0 + ? baseFee + : (block.basefee != 0 ? block.basefee : defaultBaseFee); + return (1400 + 6 * dataLength) * effectiveBaseFee; } struct RetryableTicket { From 3672d730262060193ffdd6502e5d6dcdf3f6e52e Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 22 Jan 2026 00:20:53 +0800 Subject: [PATCH 38/43] fix: audit ci --- audit-ci.jsonc | 14 +++++++++++++- yarn.lock | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/audit-ci.jsonc b/audit-ci.jsonc index a2aaff1b2..5e001ce27 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -47,6 +47,18 @@ // js-yaml has prototype pollution in merge (<<) "GHSA-mh29-5h37-fv8m", // body-parser is vulnerable to denial of service when url encoding is used - "GHSA-wqch-xfxh-vrr4" + "GHSA-wqch-xfxh-vrr4", + // qs's arrayLimit bypass in its bracket notation allows DoS via memory exhaustion + "GHSA-6rw7-vpxm-498p", + // node-tar is Vulnerable to Arbitrary File Overwrite and Symlink Poisoning via Insufficient Path Sanitization + "GHSA-8qq5-rm4j-mr97", + // Race Condition in node-tar Path Reservations via Unicode Ligature Collisions on macOS APFS + "GHSA-r6q2-hw4h-h46w", + // jsdiff has a Denial of Service vulnerability in parsePatch and applyPatch + "GHSA-73rr-hh4g-fpgx", + // Elliptic Uses a Cryptographic Primitive with a Risky Implementation + "GHSA-848j-6mx2-7j84", + // Undici has an unbounded decompression chain in HTTP responses on Node.js Fetch API via Content-Encoding leads to resource exhaustion + "GHSA-g9mf-h72j-4rw9" ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 262939592..edb6a5341 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1471,7 +1471,7 @@ aws4@^1.8.0: axios@^1.5.1, axios@^1.6.8: version "1.13.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.2.tgz#9ada120b7b5ab24509553ec3e40123521117f687" - integrity sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA== + integrity "sha1-mtoSC3taskUJVT7D5AEjUhEX9oc= sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==" dependencies: follow-redirects "^1.15.6" form-data "^4.0.4" @@ -2269,9 +2269,9 @@ destroy@1.2.0: integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + version "4.0.4" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity "sha1-em2/2jJfJfB1F+m1GPiXwIMy4H0= sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==" diff@^5.2.0: version "5.2.0" @@ -3713,9 +3713,9 @@ js-sha3@^0.5.7: integrity sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g== js-yaml@3.x: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -4027,7 +4027,7 @@ mimic-response@^3.1.0: min-document@^2.19.0: version "2.19.1" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.1.tgz#7083ad4bc8879a6eba6516688e9f5d91d32e2d23" - integrity sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g== + integrity "sha1-cIOtS8iHmm66ZRZojp9dkdMuLSM= sha512-8lqe85PkqQJzIcs2iD7xW/WSxcncC3/DPVbTOafKNJDIMXwGfwXS350mH4SJslomntN2iYtFBuC0yNO3CEap6g==" dependencies: dom-walk "^0.1.0" @@ -4643,9 +4643,9 @@ qs@6.13.0: side-channel "^1.0.6" qs@^6.4.0: - version "6.14.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" - integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + version "6.14.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" + integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== dependencies: side-channel "^1.1.0" From f0652b62cdc40ef4129e5c3e1b1730ee1d4b7a01 Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 22 Jan 2026 00:26:53 +0800 Subject: [PATCH 39/43] docs: get rid of some excess comments --- .gas-snapshot | 6 +++--- test/L1ArbitrumTimelock.t.sol | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 9c82061bc..490fbf74a 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -85,7 +85,7 @@ L2ArbitrumTokenTest:testDecreaseDVPOnUndelegate() (gas: 4485899) L2ArbitrumTokenTest:testDoesNotInitialiseZeroInitialSup() (gas: 4108951) L2ArbitrumTokenTest:testDoesNotInitialiseZeroL1Token() (gas: 4108903) L2ArbitrumTokenTest:testDoesNotInitialiseZeroOwner() (gas: 4108994) -L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4423412, ~: 4424046) +L2ArbitrumTokenTest:testDvpAdjustment(uint64,int64) (runs: 256, μ: 4423467, ~: 4424046) L2ArbitrumTokenTest:testDvpAtBlockBeforeFirstCheckpoint() (gas: 4423454) L2ArbitrumTokenTest:testDvpDecreaseOnTransferFromDelegator() (gas: 4526229) L2ArbitrumTokenTest:testDvpIncreaseOnTransferToDelegator() (gas: 4517654) @@ -124,7 +124,7 @@ MiscTests:testPastCirculatingSupplyMint() (gas: 14552007) MiscTests:testProperlyInitialized() (gas: 14477337) NomineeGovernorV2UpgradeActionTest:testAction() (gas: 8153) OfficeHoursActionTest:testConstructor() (gas: 9050) -OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317109, ~: 317184) +OfficeHoursActionTest:testFuzzOfficeHoursDeployment(uint256,uint256,int256,uint256,uint256,uint256) (runs: 256, μ: 317050, ~: 317184) OfficeHoursActionTest:testInvalidConstructorParameters() (gas: 235740) OfficeHoursActionTest:testPerformBeforeMinimumTimestamp() (gas: 8646) OfficeHoursActionTest:testPerformDuringOfficeHours() (gas: 9140) @@ -182,7 +182,7 @@ SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanProp SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32916) SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42229) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339763, ~: 339762) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339878, ~: 339880) SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) diff --git a/test/L1ArbitrumTimelock.t.sol b/test/L1ArbitrumTimelock.t.sol index dc0502286..2cf34bc27 100644 --- a/test/L1ArbitrumTimelock.t.sol +++ b/test/L1ArbitrumTimelock.t.sol @@ -215,8 +215,6 @@ contract L1ArbitrumTimelockTest is Test { sender.transfer(execVal); // l2value has to come from the timelock itself - // Use vm.deal instead of .transfer() because the proxy's receive() function - // requires more gas than the 2300 gas stipend, especially with --gas-report vm.deal(address(l1Timelock), rData.l2Value); vm.prank(sender); @@ -299,8 +297,6 @@ contract L1ArbitrumTimelockTest is Test { sender.transfer(execVal); // l2value has to come from the timelock itself - // Use vm.deal instead of .transfer() because the proxy's receive() function - // requires more gas than the 2300 gas stipend, especially with --gas-report vm.deal(address(l1Timelock), rData.l2Value + rData2.l2Value); vm.prank(sender); @@ -349,8 +345,6 @@ contract L1ArbitrumTimelockTest is Test { sender.transfer(execVal); // l2value has to come from the timelock itself - // Use vm.deal instead of .transfer() because the proxy's receive() function - // requires more gas than the 2300 gas stipend, especially with --gas-report vm.deal(address(l1Timelock), rData.l2Value); vm.expectRevert(); From 06f816fff8e84525ab9ef1596188a1f2740c3848 Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 22 Jan 2026 15:20:18 +0800 Subject: [PATCH 40/43] chore: upgrade payload --- scripts/proposals/ActivateDvpQuorum/data.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 scripts/proposals/ActivateDvpQuorum/data.json diff --git a/scripts/proposals/ActivateDvpQuorum/data.json b/scripts/proposals/ActivateDvpQuorum/data.json new file mode 100644 index 000000000..aa697f26d --- /dev/null +++ b/scripts/proposals/ActivateDvpQuorum/data.json @@ -0,0 +1,12 @@ +{ + "actionChainIds": [ + 42161 + ], + "actionAddresses": [ + "0xf9358027f4a240d2b020af8749bc5716f5d40278" + ], + "arbSysSendTxToL1Args": { + "l1Timelock": "0xE6841D92B0C345144506576eC13ECf5103aC7f49", + "calldata": "0x8f2a0bb000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000a7b4528d1e467497f7b50fed4d1d1e5d4f9fbb25db8e4d55ab14c1afcc886f4a000000000000000000000000000000000000000000000000000000000003f4800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a723c008e76e379c55599d2e4d93879beafda79c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001800000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f000000000000000000000000cf57572261c7c2bcf21ffd220ea7d1a27d40a82700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000841cff79cd000000000000000000000000f9358027f4a240d2b020af8749bc5716f5d4027800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004b147f40c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } +} \ No newline at end of file From d2371c90fc807dc146f17c588ca7f5ecfc998fca Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 22 Jan 2026 15:24:06 +0800 Subject: [PATCH 41/43] chore: whitelist audit issue --- audit-ci.jsonc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/audit-ci.jsonc b/audit-ci.jsonc index 5e001ce27..b26f0be91 100644 --- a/audit-ci.jsonc +++ b/audit-ci.jsonc @@ -59,6 +59,8 @@ // Elliptic Uses a Cryptographic Primitive with a Risky Implementation "GHSA-848j-6mx2-7j84", // Undici has an unbounded decompression chain in HTTP responses on Node.js Fetch API via Content-Encoding leads to resource exhaustion - "GHSA-g9mf-h72j-4rw9" + "GHSA-g9mf-h72j-4rw9", + // Lodash has Prototype Pollution Vulnerability in `_.unset` and `_.omit` functions + "GHSA-xxjr-mmjv-4gpg" ] } \ No newline at end of file From 3d1f1d0c58eb0780f2e393926977cfb0afe34fc9 Mon Sep 17 00:00:00 2001 From: gzeon Date: Thu, 22 Jan 2026 16:02:19 +0800 Subject: [PATCH 42/43] fix: update payload --- scripts/proposals/ActivateDvpQuorum/data.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/proposals/ActivateDvpQuorum/data.json b/scripts/proposals/ActivateDvpQuorum/data.json index aa697f26d..643879eb7 100644 --- a/scripts/proposals/ActivateDvpQuorum/data.json +++ b/scripts/proposals/ActivateDvpQuorum/data.json @@ -3,10 +3,10 @@ 42161 ], "actionAddresses": [ - "0xf9358027f4a240d2b020af8749bc5716f5d40278" + "0x19C8Ea5F8288abF138D72a13344E699a7A71400c" ], "arbSysSendTxToL1Args": { "l1Timelock": "0xE6841D92B0C345144506576eC13ECf5103aC7f49", - "calldata": "0x8f2a0bb000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000a7b4528d1e467497f7b50fed4d1d1e5d4f9fbb25db8e4d55ab14c1afcc886f4a000000000000000000000000000000000000000000000000000000000003f4800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a723c008e76e379c55599d2e4d93879beafda79c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001800000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f000000000000000000000000cf57572261c7c2bcf21ffd220ea7d1a27d40a82700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000841cff79cd000000000000000000000000f9358027f4a240d2b020af8749bc5716f5d4027800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004b147f40c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "calldata": "0x8f2a0bb000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000b7c5875f8d6dac043d4278efb5a58e9834260314ac17df813eaf54545f5a149f000000000000000000000000000000000000000000000000000000000003f4800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a723c008e76e379c55599d2e4d93879beafda79c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001800000000000000000000000004dbd4fc535ac27206064b68ffcf827b0a60bab3f000000000000000000000000cf57572261c7c2bcf21ffd220ea7d1a27d40a82700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000841cff79cd00000000000000000000000019c8ea5f8288abf138d72a13344e699a7a71400c00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000004b147f40c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" } } \ No newline at end of file From 0968da01f8e636c88685b0577ddb6f0614f05ce5 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 23 Jan 2026 03:44:28 -0700 Subject: [PATCH 43/43] fix shadowed variables in tests (#370) --- test/L2ArbitrumGovernor.t.sol | 116 +++++++++++++++++----------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/test/L2ArbitrumGovernor.t.sol b/test/L2ArbitrumGovernor.t.sol index f13a85d67..9d9983976 100644 --- a/test/L2ArbitrumGovernor.t.sol +++ b/test/L2ArbitrumGovernor.t.sol @@ -33,11 +33,11 @@ contract L2ArbitrumGovernorTest is Test { address someRando = address(741); address executor = address(842); - L2ArbitrumGovernor governor; - L2ArbitrumToken token; - ArbitrumTimelock timelock; - address governorProxyAdmin; - address tokenProxyAdmin; + L2ArbitrumGovernor _governor; + L2ArbitrumToken _token; + ArbitrumTimelock _timelock; + address _governorProxyAdmin; + address _tokenProxyAdmin; event ProposalCreated( uint256 proposalId, @@ -70,7 +70,7 @@ contract L2ArbitrumGovernorTest is Test { } function setUp() public { - (governor, token, timelock, governorProxyAdmin, tokenProxyAdmin) = deployAndInit(); + (_governor, _token, _timelock, _governorProxyAdmin, _tokenProxyAdmin) = deployAndInit(); } function deployAndInit() @@ -80,7 +80,7 @@ contract L2ArbitrumGovernorTest is Test { L2ArbitrumToken token = L2ArbitrumToken(TestUtil.deployProxy(address(new L2ArbitrumToken()))); token.initialize(l1TokenAddress, initialTokenSupply, tokenOwner); - tokenProxyAdmin = abi.decode( + _tokenProxyAdmin = abi.decode( abi.encodePacked( vm.load( address(token), @@ -122,7 +122,7 @@ contract L2ArbitrumGovernorTest is Test { } _setQuorumMinAndMax(l2ArbitrumGovernor, 0, type(uint256).max); - governorProxyAdmin = abi.decode( + _governorProxyAdmin = abi.decode( abi.encodePacked( vm.load( address(l2ArbitrumGovernor), @@ -131,7 +131,7 @@ contract L2ArbitrumGovernorTest is Test { ), (address) ); - return (l2ArbitrumGovernor, token, timelock, governorProxyAdmin, tokenProxyAdmin); + return (l2ArbitrumGovernor, token, timelock, _governorProxyAdmin, _tokenProxyAdmin); } function _setQuorumMinAndMax(L2ArbitrumGovernor l2ArbitrumGovernor, uint256 min, uint256 max) @@ -148,15 +148,15 @@ contract L2ArbitrumGovernorTest is Test { function createAndMintToProposer(uint256 _randomSeed) internal returns (address) { address proposer = address(uint160(_randomSeed)); vm.assume( - proposer != address(0) && proposer != governorProxyAdmin && proposer != tokenProxyAdmin - && proposer != governor.EXCLUDE_ADDRESS() + proposer != address(0) && proposer != _governorProxyAdmin && proposer != _tokenProxyAdmin + && proposer != _governor.EXCLUDE_ADDRESS() ); vm.warp(300_000_000_000_000_000); vm.startPrank(tokenOwner); - token.mint(proposer, governor.proposalThreshold()); + _token.mint(proposer, _governor.proposalThreshold()); vm.stopPrank(); vm.prank(proposer); - token.delegate(proposer); + _token.delegate(proposer); vm.roll(3); return proposer; } @@ -177,9 +177,9 @@ contract L2ArbitrumGovernorTest is Test { string memory _description ) public returns (uint256 _proposalId) { vm.prank(_proposer); - _proposalId = governor.propose(_targets, _values, _calldatas, _description); + _proposalId = _governor.propose(_targets, _values, _calldatas, _description); - vm.roll(block.number + governor.votingDelay() + 1); + vm.roll(block.number + _governor.votingDelay() + 1); } function _submitAndQueueProposal( @@ -192,12 +192,12 @@ contract L2ArbitrumGovernorTest is Test { _proposalId = _submitProposal(_proposer, _targets, _values, _calldatas, _description); vm.prank(_proposer); - governor.castVote(_proposalId, uint8(VoteType.For)); - vm.roll(block.number + governor.votingPeriod() + 1); + _governor.castVote(_proposalId, uint8(VoteType.For)); + vm.roll(block.number + _governor.votingPeriod() + 1); assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Succeeded) + uint8(_governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Succeeded) ); - governor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); + _governor.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); return _proposalId; } @@ -212,8 +212,8 @@ contract L2ArbitrumGovernorTest is Test { _proposer, _targets, _values, _calldatas, _description ); - vm.warp(block.timestamp + timelock.getMinDelay() + 1); - governor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); + vm.warp(block.timestamp + _timelock.getMinDelay() + 1); + _governor.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); return _proposalId; } } @@ -429,18 +429,18 @@ contract Cancel is L2ArbitrumGovernorTest { ) = _basicProposal(); vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); + uint256 proposalId = _governor.propose(targets, values, calldatas, description); assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + uint256(_governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) ); vm.prank(_proposer); uint256 canceledId = - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + _governor.cancel(targets, values, calldatas, keccak256(bytes(description))); assertEq(canceledId, proposalId); assertEq( - uint256(governor.state(proposalId)), + uint256(_governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Canceled) ); } @@ -457,15 +457,15 @@ contract Cancel is L2ArbitrumGovernorTest { ) = _basicProposal(); vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); + uint256 proposalId = _governor.propose(targets, values, calldatas, description); assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + uint256(_governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) ); vm.expectRevert("L2ArbitrumGovernor: NOT_PROPOSER"); vm.prank(_actor); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + _governor.cancel(targets, values, calldatas, keccak256(bytes(description))); } function testFuzz_RevertIf_ProposalIsActive(uint256 _randomSeed) public { @@ -478,16 +478,16 @@ contract Cancel is L2ArbitrumGovernorTest { ) = _basicProposal(); vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); + uint256 proposalId = _governor.propose(targets, values, calldatas, description); - vm.roll(block.number + governor.votingDelay() + 1); + vm.roll(block.number + _governor.votingDelay() + 1); assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Active) + uint256(_governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Active) ); vm.prank(_proposer); vm.expectRevert("L2ArbitrumGovernor: PROPOSAL_NOT_PENDING"); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + _governor.cancel(targets, values, calldatas, keccak256(bytes(description))); } function testFuzz_RevertIf_AlreadyCanceled(uint256 _randomSeed) public { @@ -500,22 +500,22 @@ contract Cancel is L2ArbitrumGovernorTest { ) = _basicProposal(); vm.prank(_proposer); - uint256 proposalId = governor.propose(targets, values, calldatas, description); + uint256 proposalId = _governor.propose(targets, values, calldatas, description); assertEq( - uint256(governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) + uint256(_governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Pending) ); // First cancel vm.prank(_proposer); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + _governor.cancel(targets, values, calldatas, keccak256(bytes(description))); assertEq( - uint256(governor.state(proposalId)), + uint256(_governor.state(proposalId)), uint256(IGovernorUpgradeable.ProposalState.Canceled) ); vm.prank(_proposer); vm.expectRevert("L2ArbitrumGovernor: PROPOSAL_NOT_PENDING"); - governor.cancel(targets, values, calldatas, keccak256(bytes(description))); + _governor.cancel(targets, values, calldatas, keccak256(bytes(description))); } } @@ -531,7 +531,7 @@ contract Propose is L2ArbitrumGovernorTest { address _proposer = createAndMintToProposer(_randomSeed); uint256 _proposalId = _submitProposal(_proposer, targets, values, calldatas, description); assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Active) + uint8(_governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Active) ); } @@ -545,9 +545,9 @@ contract Propose is L2ArbitrumGovernorTest { address _proposer = createAndMintToProposer(_randomSeed); uint256 _expectedProposalId = - governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))); - uint256 _startBlock = block.number + governor.votingDelay(); - uint256 _endBlock = _startBlock + governor.votingPeriod(); + _governor.hashProposal(targets, values, calldatas, keccak256(bytes(description))); + uint256 _startBlock = block.number + _governor.votingDelay(); + uint256 _endBlock = _startBlock + _governor.votingPeriod(); vm.expectEmit(); emit ProposalCreated( @@ -565,8 +565,8 @@ contract Propose is L2ArbitrumGovernorTest { } function testFuzz_ProposerBelowThresholdCannotPropose(address _proposer) public { - vm.assume(governor.getVotes(_proposer, block.number - 1) < governor.proposalThreshold()); - vm.assume(_proposer != governorProxyAdmin); + vm.assume(_governor.getVotes(_proposer, block.number - 1) < _governor.proposalThreshold()); + vm.assume(_proposer != _governorProxyAdmin); ( address[] memory targets, uint256[] memory values, @@ -576,7 +576,7 @@ contract Propose is L2ArbitrumGovernorTest { vm.expectRevert("Governor: proposer votes below proposal threshold"); vm.prank(_proposer); - governor.propose(targets, values, calldatas, description); + _governor.propose(targets, values, calldatas, description); } } @@ -593,7 +593,7 @@ contract Queue is L2ArbitrumGovernorTest { uint256 _proposalId = _submitAndQueueProposal(_proposer, targets, values, calldatas, description); assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Queued) + uint8(_governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Queued) ); } @@ -606,16 +606,16 @@ contract Queue is L2ArbitrumGovernorTest { ) = _basicProposal(); address _proposer = createAndMintToProposer(_randomSeed); - uint256 _eta = block.timestamp + timelock.getMinDelay(); + uint256 _eta = block.timestamp + _timelock.getMinDelay(); uint256 _proposalId = _submitProposal(_proposer, targets, values, calldatas, description); vm.prank(_proposer); - governor.castVote(_proposalId, uint8(VoteType.For)); - vm.roll(block.number + governor.votingPeriod() + 1); + _governor.castVote(_proposalId, uint8(VoteType.For)); + vm.roll(block.number + _governor.votingPeriod() + 1); vm.expectEmit(); emit ProposalQueued(_proposalId, _eta); - governor.queue(targets, values, calldatas, keccak256(bytes(description))); + _governor.queue(targets, values, calldatas, keccak256(bytes(description))); } function testFuzz_RevertIf_ProposalIsNotSucceeded(uint256 _randomSeed) public { @@ -630,11 +630,11 @@ contract Queue is L2ArbitrumGovernorTest { uint256 _proposalId = _submitProposal(_proposer, targets, values, calldatas, description); vm.prank(_proposer); - governor.castVote(_proposalId, uint8(VoteType.Against)); - vm.roll(block.number + governor.votingPeriod() + 1); + _governor.castVote(_proposalId, uint8(VoteType.Against)); + vm.roll(block.number + _governor.votingPeriod() + 1); vm.expectRevert("Governor: proposal not successful"); - governor.queue(targets, values, calldatas, keccak256(bytes(description))); + _governor.queue(targets, values, calldatas, keccak256(bytes(description))); } } @@ -651,12 +651,12 @@ contract Execute is L2ArbitrumGovernorTest { uint256 _proposalId = _submitQueueAndExecuteProposal(_proposer, targets, values, calldatas, description); assertEq( - uint8(governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Executed) + uint8(_governor.state(_proposalId)), uint8(IGovernorUpgradeable.ProposalState.Executed) ); } function testFuzz_EmitsExecuteEvent(uint256 _randomSeed, address _actor) public { - vm.assume(_actor != governorProxyAdmin); + vm.assume(_actor != _governorProxyAdmin); ( address[] memory targets, uint256[] memory values, @@ -667,16 +667,16 @@ contract Execute is L2ArbitrumGovernorTest { address _proposer = createAndMintToProposer(_randomSeed); uint256 _proposalId = _submitAndQueueProposal(_proposer, targets, values, calldatas, description); - vm.warp(block.timestamp + timelock.getMinDelay() + 1); + vm.warp(block.timestamp + _timelock.getMinDelay() + 1); vm.expectEmit(); emit ProposalExecuted(_proposalId); vm.prank(_actor); - governor.execute(targets, values, calldatas, keccak256(bytes(description))); + _governor.execute(targets, values, calldatas, keccak256(bytes(description))); } function testFuzz_RevertIf_OperationNotReady(uint256 _randomSeed, address _actor) public { - vm.assume(_actor != governorProxyAdmin); + vm.assume(_actor != _governorProxyAdmin); ( address[] memory targets, uint256[] memory values, @@ -689,6 +689,6 @@ contract Execute is L2ArbitrumGovernorTest { vm.prank(_actor); vm.expectRevert(bytes("TimelockController: operation is not ready")); - governor.execute(targets, values, calldatas, keccak256(bytes(description))); + _governor.execute(targets, values, calldatas, keccak256(bytes(description))); } }