From 3b7adccdf4ba7558b0eee7209f2efaf880b8793f Mon Sep 17 00:00:00 2001 From: dzgoldman Date: Mon, 18 Dec 2023 15:56:36 -0500 Subject: [PATCH 1/4] add AIPNovaFeeRoutingAction --- .../AIPs/AIPNovaFeeRoutingAction.sol | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol diff --git a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol new file mode 100644 index 00000000..6a83e23d --- /dev/null +++ b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "@openzeppelin/contracts/utils/Address.sol"; + +interface IRewardDistributor { + function distributeAndUpdateRecipients( + address[] memory currentRecipients, + uint256[] memory currentWeights, + address[] memory newRecipients, + uint256[] memory newWeights + ) external; +} + +/// @notice Governance action to be deployed on Nova. Updates all l1 timelock alias recipients in all fee distribution +/// contracts to the nova-to-l1 router address; preserves all other recipients and all weights. +/// Note that the Nova L1 Base fee distributor is not updated since the timelock alias is not a recipient. +contract AIPNovaFeeRoutingAction { + address public immutable l1GovTimelockAlias = 0xf7951D92B0C345144506576eC13Ecf5103aC905a; + uint256 public immutable fullWeight = 10_000; + + address public immutable novaL1SurplusFeeDistr = 0x509386DbF5C0BE6fd68Df97A05fdB375136c32De; + + address public immutable novaL2SurplusFeeDistr = 0x3B68a689c929327224dBfCe31C1bf72Ffd2559Ce; + + address public immutable novaL2BaseFeeDistr = 0x9fCB6F75D99029f28F6F4a1d277bae49c5CAC79f; + uint256 public immutable novaL2BaseWeight0 = 8000; + uint256 public immutable novaL2BaseWeight1 = 375; + uint256 public immutable novaL2BaseWeight2 = 373; + uint256 public immutable novaL2BaseWeight3 = 373; + uint256 public immutable novaL2BaseWeight4 = 373; + uint256 public immutable novaL2BaseWeight5 = 373; + uint256 public immutable novaL2BaseWeight6 = 133; + + // novaL2BaseRecipient0 is the gov timelock alias + address public immutable novaL2BaseRecipient1 = 0xD0749b3e537Ed52DE4e6a3Ae1eB6fc26059d0895; + address public immutable novaL2BaseRecipient2 = 0x41C327d5fc9e29680CcD45e5E52446E0DB3DAdFd; + address public immutable novaL2BaseRecipient3 = 0x02C2599aa929e2509741b44F3a13029745aB1AB2; + address public immutable novaL2BaseRecipient4 = 0xA221f29236996BDEfA5C585acdD407Ec84D78447; + address public immutable novaL2BaseRecipient5 = 0x0fB1f1a31429F1A90a19Ab5486a6DFb384179641; + address public immutable novaL2BaseRecipient6 = 0xb814441ed86e98e8B83d31eEC095e4a5A36Fc3c2; + + address public immutable novaToL1Router; + + error NotAContract(address addr); + + constructor(address _novaToL1Router) { + if (!Address.isContract(_novaToL1Router)) { + revert NotAContract(_novaToL1Router); + } + novaToL1Router = _novaToL1Router; + } + + function perform() external { + // L1 surplus: replace only recipient (timelock alias) with the router + address[] memory currentNovaL1SurplusRecipients; + currentNovaL1SurplusRecipients[0] = l1GovTimelockAlias; + + uint256[] memory currentNovaL1SurplusWeights; + currentNovaL1SurplusWeights[0] = fullWeight; + + address[] memory newL1SurplusRecipients; + newL1SurplusRecipients[0] = novaToL1Router; + + // preserve current weights, update recipients + IRewardDistributor(novaL1SurplusFeeDistr).distributeAndUpdateRecipients({ + currentRecipients: currentNovaL1SurplusRecipients, + currentWeights: currentNovaL1SurplusWeights, + newRecipients: newL1SurplusRecipients, + newWeights: currentNovaL1SurplusWeights + }); + + // L2 surplus: replace only recipient (timelock alias) with the router + address[] memory currentNovaL2SurplusRecipients; + currentNovaL2SurplusRecipients[0] = l1GovTimelockAlias; + + uint256[] memory novaL2SurplusWeights; + novaL2SurplusWeights[0] = fullWeight; + + address[] memory newL2SurplusRecipients; + newL2SurplusRecipients[0] = novaToL1Router; + + // preserve current weights, update recipients + IRewardDistributor(novaL2SurplusFeeDistr).distributeAndUpdateRecipients({ + currentRecipients: currentNovaL2SurplusRecipients, + currentWeights: novaL2SurplusWeights, + newRecipients: newL2SurplusRecipients, + newWeights: novaL2SurplusWeights + }); + + // L2 base: replace first recipient (timelock alias) with router; keep other recipients the same. + address[] memory currentNovaL2BaseRecipients; + currentNovaL2BaseRecipients[0] = l1GovTimelockAlias; + currentNovaL2BaseRecipients[1] = novaL2BaseRecipient1; + currentNovaL2BaseRecipients[2] = novaL2BaseRecipient2; + currentNovaL2BaseRecipients[3] = novaL2BaseRecipient3; + currentNovaL2BaseRecipients[4] = novaL2BaseRecipient4; + currentNovaL2BaseRecipients[5] = novaL2BaseRecipient5; + currentNovaL2BaseRecipients[6] = novaL2BaseRecipient6; + + address[] memory newNovaL2BaseRecipients; + newNovaL2BaseRecipients[0] = novaToL1Router; + for (uint256 i = 1; i < currentNovaL2BaseRecipients.length; i++) { + newNovaL2BaseRecipients[i] = currentNovaL2BaseRecipients[i]; + } + + uint256[] memory currentNovaL2BaseWeights; + currentNovaL2BaseWeights[0] = novaL2BaseWeight0; + currentNovaL2BaseWeights[1] = novaL2BaseWeight1; + currentNovaL2BaseWeights[2] = novaL2BaseWeight2; + currentNovaL2BaseWeights[3] = novaL2BaseWeight3; + currentNovaL2BaseWeights[4] = novaL2BaseWeight4; + currentNovaL2BaseWeights[5] = novaL2BaseWeight5; + currentNovaL2BaseWeights[6] = novaL2BaseWeight6; + + // preserve current weights, update recipients + IRewardDistributor(novaL2BaseFeeDistr).distributeAndUpdateRecipients({ + currentRecipients: currentNovaL2BaseRecipients, + currentWeights: currentNovaL2BaseWeights, + newRecipients: newNovaL2BaseRecipients, + newWeights: currentNovaL2BaseWeights + }); + } +} From d04175efa937e00ef9a0a931b8e06017ac99ee6b Mon Sep 17 00:00:00 2001 From: Daniel Goldman Date: Wed, 17 Apr 2024 10:22:20 -0400 Subject: [PATCH 2/4] add novaToL1Router address --- .../AIPs/AIPNovaFeeRoutingAction.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol index 6a83e23d..3a18eaff 100644 --- a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol +++ b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol @@ -40,15 +40,15 @@ contract AIPNovaFeeRoutingAction { address public immutable novaL2BaseRecipient5 = 0x0fB1f1a31429F1A90a19Ab5486a6DFb384179641; address public immutable novaL2BaseRecipient6 = 0xb814441ed86e98e8B83d31eEC095e4a5A36Fc3c2; - address public immutable novaToL1Router; + address public immutable novaToL1Router = 0x47a85C0a118127F3968A6A1A61e2a326517540D4; error NotAContract(address addr); - constructor(address _novaToL1Router) { - if (!Address.isContract(_novaToL1Router)) { - revert NotAContract(_novaToL1Router); + constructor() { + // sanity check: + if (!Address.isContract(novaToL1Router)) { + revert NotAContract(novaToL1Router); } - novaToL1Router = _novaToL1Router; } function perform() external { From d394e264e61c6b9a66646d806e74cf50f4d3013e Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Fri, 3 May 2024 09:25:59 -0400 Subject: [PATCH 3/4] AIPNovaFeeRoutingAction test and new balance requirement (#279) * add fork test * gas snapshot --- .gas-snapshot | 3 +- .../AIPs/AIPNovaFeeRoutingAction.sol | 34 ++++++++--- .../gov-actions/AIPNovaFeeRoutingAction.t.sol | 61 +++++++++++++++++++ 3 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 test/gov-actions/AIPNovaFeeRoutingAction.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index 1158508a..ffe0bbe9 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,4 +1,5 @@ AIP1Point2ActionTest:testAction() (gas: 629328) +AIPNovaFeeRoutingActionTest:testAction() (gas: 3074) ArbitrumDAOConstitutionTest:testConstructor() (gas: 259383) ArbitrumDAOConstitutionTest:testMonOwnerCannotSetHash() (gas: 262836) ArbitrumDAOConstitutionTest:testOwnerCanSetHash() (gas: 261148) @@ -143,7 +144,7 @@ SecurityCouncilMemberElectionGovernorTest:testOnlyNomineeElectionGovernorCanProp SecurityCouncilMemberElectionGovernorTest:testProperInitialization() (gas: 49388) SecurityCouncilMemberElectionGovernorTest:testProposeReverts() (gas: 32916) SecurityCouncilMemberElectionGovernorTest:testRelay() (gas: 42229) -SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339956, ~: 339703) +SecurityCouncilMemberElectionGovernorTest:testSelectTopNominees(uint256) (runs: 256, μ: 339646, ~: 339587) SecurityCouncilMemberElectionGovernorTest:testSelectTopNomineesFails() (gas: 273335) SecurityCouncilMemberElectionGovernorTest:testSetFullWeightDuration() (gas: 34951) SecurityCouncilMemberElectionGovernorTest:testVotesToWeight() (gas: 152898) diff --git a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol index 3a18eaff..8e22bc58 100644 --- a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol +++ b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol @@ -10,6 +10,9 @@ interface IRewardDistributor { address[] memory newRecipients, uint256[] memory newWeights ) external; + + function currentRecipientGroup() external view returns (bytes32); + function currentRecipientWeights() external view returns (bytes32); } /// @notice Governance action to be deployed on Nova. Updates all l1 timelock alias recipients in all fee distribution @@ -52,14 +55,22 @@ contract AIPNovaFeeRoutingAction { } function perform() external { + // upgrade executor should have at least 3 * fullWeight ETH to fund the distributors + // we need each of the reward distributors to have at least fullWeight in balance + // otherwise we may get NoFundsToDistribute() errors + require(address(this).balance >= 3 * fullWeight, "AIPNovaFeeRoutingAction: insufficient balance"); + _fundDistributor(novaL1SurplusFeeDistr); + _fundDistributor(novaL2SurplusFeeDistr); + _fundDistributor(novaL2BaseFeeDistr); + // L1 surplus: replace only recipient (timelock alias) with the router - address[] memory currentNovaL1SurplusRecipients; + address[] memory currentNovaL1SurplusRecipients = new address[](1); currentNovaL1SurplusRecipients[0] = l1GovTimelockAlias; - uint256[] memory currentNovaL1SurplusWeights; + uint256[] memory currentNovaL1SurplusWeights = new uint256[](1); currentNovaL1SurplusWeights[0] = fullWeight; - address[] memory newL1SurplusRecipients; + address[] memory newL1SurplusRecipients = new address[](1); newL1SurplusRecipients[0] = novaToL1Router; // preserve current weights, update recipients @@ -71,13 +82,13 @@ contract AIPNovaFeeRoutingAction { }); // L2 surplus: replace only recipient (timelock alias) with the router - address[] memory currentNovaL2SurplusRecipients; + address[] memory currentNovaL2SurplusRecipients = new address[](1); currentNovaL2SurplusRecipients[0] = l1GovTimelockAlias; - uint256[] memory novaL2SurplusWeights; + uint256[] memory novaL2SurplusWeights = new uint256[](1); novaL2SurplusWeights[0] = fullWeight; - address[] memory newL2SurplusRecipients; + address[] memory newL2SurplusRecipients = new address[](1); newL2SurplusRecipients[0] = novaToL1Router; // preserve current weights, update recipients @@ -89,7 +100,7 @@ contract AIPNovaFeeRoutingAction { }); // L2 base: replace first recipient (timelock alias) with router; keep other recipients the same. - address[] memory currentNovaL2BaseRecipients; + address[] memory currentNovaL2BaseRecipients = new address[](7); currentNovaL2BaseRecipients[0] = l1GovTimelockAlias; currentNovaL2BaseRecipients[1] = novaL2BaseRecipient1; currentNovaL2BaseRecipients[2] = novaL2BaseRecipient2; @@ -98,13 +109,13 @@ contract AIPNovaFeeRoutingAction { currentNovaL2BaseRecipients[5] = novaL2BaseRecipient5; currentNovaL2BaseRecipients[6] = novaL2BaseRecipient6; - address[] memory newNovaL2BaseRecipients; + address[] memory newNovaL2BaseRecipients = new address[](7); newNovaL2BaseRecipients[0] = novaToL1Router; for (uint256 i = 1; i < currentNovaL2BaseRecipients.length; i++) { newNovaL2BaseRecipients[i] = currentNovaL2BaseRecipients[i]; } - uint256[] memory currentNovaL2BaseWeights; + uint256[] memory currentNovaL2BaseWeights = new uint256[](7); currentNovaL2BaseWeights[0] = novaL2BaseWeight0; currentNovaL2BaseWeights[1] = novaL2BaseWeight1; currentNovaL2BaseWeights[2] = novaL2BaseWeight2; @@ -121,4 +132,9 @@ contract AIPNovaFeeRoutingAction { newWeights: currentNovaL2BaseWeights }); } + + function _fundDistributor(address recipient) internal { + (bool b, ) = recipient.call{value: fullWeight}(""); + require(b, "AIPNovaFeeRoutingAction: funding failed"); + } } diff --git a/test/gov-actions/AIPNovaFeeRoutingAction.t.sol b/test/gov-actions/AIPNovaFeeRoutingAction.t.sol new file mode 100644 index 00000000..11480f00 --- /dev/null +++ b/test/gov-actions/AIPNovaFeeRoutingAction.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +// this test is meant to be run with a nova fork url + +import "forge-std/Test.sol"; + +import "../../src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol"; +import "../../src/UpgradeExecutor.sol"; + +contract AIPNovaFeeRoutingActionTest is Test { + UpgradeExecutor constant upExec = UpgradeExecutor(0x86a02dD71363c440b21F4c0E5B2Ad01Ffe1A7482); + + function testAction() public { + if (!isFork()) { + return; + } + + AIPNovaFeeRoutingAction action = new AIPNovaFeeRoutingAction(); + + // before we run the action, we need to make sure the upgrade executor has at least this much ETH + vm.deal(address(upExec), 3*action.fullWeight()); + + vm.prank(0xf7951D92B0C345144506576eC13Ecf5103aC905a); + upExec.execute(address(action), abi.encodeWithSignature("perform()")); + + // make sure the new recipients are set + + address[1] memory expectedL1SurplusRecipients = [0x47a85C0a118127F3968A6A1A61e2a326517540D4]; + uint256[1] memory expectedL1SurplusWeights = [uint(10_000)]; + + assertEq(IRewardDistributor(action.novaL1SurplusFeeDistr()).currentRecipientGroup(), keccak256(abi.encodePacked(expectedL1SurplusRecipients))); + assertEq(IRewardDistributor(action.novaL1SurplusFeeDistr()).currentRecipientWeights(), keccak256(abi.encodePacked(expectedL1SurplusWeights))); + assertEq(IRewardDistributor(action.novaL2SurplusFeeDistr()).currentRecipientGroup(), keccak256(abi.encodePacked(expectedL1SurplusRecipients))); + assertEq(IRewardDistributor(action.novaL2SurplusFeeDistr()).currentRecipientWeights(), keccak256(abi.encodePacked(expectedL1SurplusWeights))); + + + address[7] memory expectedBaseFeeRecipients = [ + 0x47a85C0a118127F3968A6A1A61e2a326517540D4, // nova to l1 router + 0xD0749b3e537Ed52DE4e6a3Ae1eB6fc26059d0895, // rest are same as current + 0x41C327d5fc9e29680CcD45e5E52446E0DB3DAdFd, + 0x02C2599aa929e2509741b44F3a13029745aB1AB2, + 0xA221f29236996BDEfA5C585acdD407Ec84D78447, + 0x0fB1f1a31429F1A90a19Ab5486a6DFb384179641, + 0xb814441ed86e98e8B83d31eEC095e4a5A36Fc3c2 + ]; + + uint256[7] memory expectedBaseFeeWeights = [ + uint256(8000), + uint256(375), + uint256(373), + uint256(373), + uint256(373), + uint256(373), + uint256(133) + ]; + + assertEq(IRewardDistributor(action.novaL2BaseFeeDistr()).currentRecipientGroup(), keccak256(abi.encodePacked(expectedBaseFeeRecipients))); + assertEq(IRewardDistributor(action.novaL2BaseFeeDistr()).currentRecipientWeights(), keccak256(abi.encodePacked(expectedBaseFeeWeights))); + } +} \ No newline at end of file From 78da6ad8dca593aaa7267a4b6521b95c629d4704 Mon Sep 17 00:00:00 2001 From: Henry <11198460+godzillaba@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:23:03 -0400 Subject: [PATCH 4/4] update router --- src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol | 2 +- test/gov-actions/AIPNovaFeeRoutingAction.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol index 8e22bc58..35ff9d4e 100644 --- a/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol +++ b/src/gov-action-contracts/AIPs/AIPNovaFeeRoutingAction.sol @@ -43,7 +43,7 @@ contract AIPNovaFeeRoutingAction { address public immutable novaL2BaseRecipient5 = 0x0fB1f1a31429F1A90a19Ab5486a6DFb384179641; address public immutable novaL2BaseRecipient6 = 0xb814441ed86e98e8B83d31eEC095e4a5A36Fc3c2; - address public immutable novaToL1Router = 0x47a85C0a118127F3968A6A1A61e2a326517540D4; + address public immutable novaToL1Router = 0xd9a2e0E5d7509F0BF1B2d33884F8C1b4D4490879; error NotAContract(address addr); diff --git a/test/gov-actions/AIPNovaFeeRoutingAction.t.sol b/test/gov-actions/AIPNovaFeeRoutingAction.t.sol index 11480f00..93b79753 100644 --- a/test/gov-actions/AIPNovaFeeRoutingAction.t.sol +++ b/test/gov-actions/AIPNovaFeeRoutingAction.t.sol @@ -26,7 +26,7 @@ contract AIPNovaFeeRoutingActionTest is Test { // make sure the new recipients are set - address[1] memory expectedL1SurplusRecipients = [0x47a85C0a118127F3968A6A1A61e2a326517540D4]; + address[1] memory expectedL1SurplusRecipients = [0xd9a2e0E5d7509F0BF1B2d33884F8C1b4D4490879]; uint256[1] memory expectedL1SurplusWeights = [uint(10_000)]; assertEq(IRewardDistributor(action.novaL1SurplusFeeDistr()).currentRecipientGroup(), keccak256(abi.encodePacked(expectedL1SurplusRecipients))); @@ -36,7 +36,7 @@ contract AIPNovaFeeRoutingActionTest is Test { address[7] memory expectedBaseFeeRecipients = [ - 0x47a85C0a118127F3968A6A1A61e2a326517540D4, // nova to l1 router + 0xd9a2e0E5d7509F0BF1B2d33884F8C1b4D4490879, // nova to l1 router 0xD0749b3e537Ed52DE4e6a3Ae1eB6fc26059d0895, // rest are same as current 0x41C327d5fc9e29680CcD45e5E52446E0DB3DAdFd, 0x02C2599aa929e2509741b44F3a13029745aB1AB2,