diff --git a/src/proposals/GovernorBravoProposal.sol b/src/proposals/GovernorBravoProposal.sol index 270e6296..a49ee3de 100644 --- a/src/proposals/GovernorBravoProposal.sol +++ b/src/proposals/GovernorBravoProposal.sol @@ -3,11 +3,7 @@ pragma solidity ^0.8.0; import "@forge-std/console.sol"; -import { - IGovernorBravo, - ITimelockBravo, - IERC20VotesComp -} from "@interface/IGovernorBravo.sol"; +import {IERC20VotesComp, IGovernorBravo, ITimelockBravo} from "@interface/IGovernorBravo.sol"; import {Address} from "@utils/Address.sol"; @@ -26,18 +22,8 @@ abstract contract GovernorBravoProposal is Proposal { } /// @notice Getter function for `GovernorBravoDelegate.propose()` calldata - function getCalldata() - public - view - virtual - override - returns (bytes memory data) - { - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas - ) = getProposalActions(); + function getCalldata() public view virtual override returns (bytes memory data) { + (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getProposalActions(); string[] memory signatures = new string[](targets.length); data = abi.encodeWithSignature( @@ -52,21 +38,12 @@ abstract contract GovernorBravoProposal is Proposal { /// @notice Check if there are any on-chain proposals that match the /// proposal calldata - function getProposalId() - public - view - override - returns (uint256 proposalId) - { + function getProposalId() public view override returns (uint256 proposalId) { uint256 proposalCount = governor.proposalCount(); while (proposalCount > 0) { - ( - address[] memory targets, - uint256[] memory values, - string[] memory signatures, - bytes[] memory calldatas - ) = governor.getActions(proposalCount); + (address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas) = + governor.getActions(proposalCount); bytes memory onchainCalldata = abi.encodeWithSignature( "propose(address[],uint256[],string[],bytes[],string)", @@ -81,10 +58,7 @@ abstract contract GovernorBravoProposal is Proposal { if (keccak256(proposalCalldata) == keccak256(onchainCalldata)) { if (DEBUG) { - console.log( - "Proposal calldata matches on-chain calldata with proposalId: ", - proposalCount - ); + console.log("Proposal calldata matches on-chain calldata with proposalId: ", proposalCount); } return proposalCount; } @@ -98,13 +72,19 @@ abstract contract GovernorBravoProposal is Proposal { function simulate() public override { address proposerAddress = address(1); IERC20VotesComp governanceToken = governor.comp(); + ITimelockBravo timelock = ITimelockBravo(governor.timelock()); + + // Compound has migrated the Governor to 0x309a862bbC1A00e45506cB8A802D1ff10004c8C0, + // so the timelock admin doesn't correspond to the governor address on mainnet anymore + // This is a workaround since this proposal is not compatible with the new Compound governor + // In Compound's timelock, admin is stored at slot 0 + vm.store(address(timelock), bytes32(uint256(0)), bytes32(uint256(uint160(address(governor))))); + { // Ensure proposer has meets minimum proposal threshold and quorum votes to pass the proposal uint256 quorumVotes = governor.quorumVotes(); uint256 proposalThreshold = governor.proposalThreshold(); - uint256 votingPower = quorumVotes > proposalThreshold - ? quorumVotes - : proposalThreshold; + uint256 votingPower = quorumVotes > proposalThreshold ? quorumVotes : proposalThreshold; deal(address(governanceToken), proposerAddress, votingPower); // Delegate proposer's votes to itself vm.prank(proposerAddress); @@ -120,15 +100,11 @@ abstract contract GovernorBravoProposal is Proposal { uint256 proposalId = abi.decode(data, (uint256)); // Check proposal is in Pending state - require( - governor.state(proposalId) == IGovernorBravo.ProposalState.Pending - ); + require(governor.state(proposalId) == IGovernorBravo.ProposalState.Pending); // Roll to Active state (voting period) vm.roll(block.number + governor.votingDelay() + 1); - require( - governor.state(proposalId) == IGovernorBravo.ProposalState.Active - ); + require(governor.state(proposalId) == IGovernorBravo.ProposalState.Active); // Vote YES vm.prank(proposerAddress); @@ -136,24 +112,16 @@ abstract contract GovernorBravoProposal is Proposal { // Roll to allow proposal state transitions vm.roll(block.number + governor.votingPeriod()); - require( - governor.state(proposalId) == IGovernorBravo.ProposalState.Succeeded - ); + require(governor.state(proposalId) == IGovernorBravo.ProposalState.Succeeded); // Queue the proposal governor.queue(proposalId); - require( - governor.state(proposalId) == IGovernorBravo.ProposalState.Queued - ); + require(governor.state(proposalId) == IGovernorBravo.ProposalState.Queued); - // Warp to allow proposal execution on timelock - ITimelockBravo timelock = ITimelockBravo(governor.timelock()); vm.warp(block.timestamp + timelock.delay()); // Execute the proposal governor.execute(proposalId); - require( - governor.state(proposalId) == IGovernorBravo.ProposalState.Executed - ); + require(governor.state(proposalId) == IGovernorBravo.ProposalState.Executed); } } diff --git a/src/proposals/MultisigProposal.sol b/src/proposals/MultisigProposal.sol index b2bc552d..e508bba5 100644 --- a/src/proposals/MultisigProposal.sol +++ b/src/proposals/MultisigProposal.sol @@ -2,16 +2,15 @@ pragma solidity ^0.8.0; import "@forge-std/console.sol"; -import {Address} from "@utils/Address.sol"; import {Proposal} from "./Proposal.sol"; +import {Address} from "@utils/Address.sol"; import {Constants} from "@utils/Constants.sol"; abstract contract MultisigProposal is Proposal { using Address for address; - bytes32 public constant MULTISIG_BYTECODE_HASH = bytes32( - 0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000 - ); + bytes32 public constant MULTISIG_BYTECODE_HASH = + bytes32(0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000); struct Call3Value { address target; @@ -21,31 +20,24 @@ abstract contract MultisigProposal is Proposal { } /// @notice return calldata, log if debug is set to true - function getCalldata() public view override returns (bytes memory data) { - /// get proposal actions - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory arguments - ) = getProposalActions(); - - /// create calls array with targets and arguments - Call3Value[] memory calls = new Call3Value[](targets.length); - - for (uint256 i; i < calls.length; i++) { - require(targets[i] != address(0), "Invalid target for multisig"); - calls[i] = Call3Value({ - target: targets[i], - allowFailure: false, - value: values[i], - callData: arguments[i] - }); + function getCalldata() public view override returns (bytes memory) { + (address[] memory targets, uint256[] memory values, bytes[] memory arguments) = getProposalActions(); + + require(targets.length == values.length && values.length == arguments.length, "Array lengths mismatch"); + + bytes memory encodedTxs; + + for (uint256 i = 0; i < targets.length; i++) { + uint8 operation = 0; + address to = targets[i]; + uint256 value = values[i]; + bytes memory data = arguments[i]; + + encodedTxs = bytes.concat(encodedTxs, abi.encodePacked(operation, to, value, uint256(data.length), data)); } - /// generate calldata - data = abi.encodeWithSignature( - "aggregate3Value((address,bool,uint256,bytes)[])", calls - ); + // The final calldata to send to the MultiSend contract + return abi.encodeWithSignature("multiSend(bytes)", encodedTxs); } /// @notice Check if there are any on-chain proposal that matches the @@ -59,7 +51,7 @@ abstract contract MultisigProposal is Proposal { /// this is a hack because multisig execTransaction requires owners signatures /// so we cannot simulate it exactly as it will be executed on mainnet - vm.etch(multisig, Constants.MULTICALL_BYTECODE); + vm.etch(multisig, Constants.MULTISEND_BYTECODE); bytes memory data = getCalldata(); diff --git a/test/MultisigProposal.t.sol b/test/MultisigProposal.t.sol index a7d667b1..93be2a14 100644 --- a/test/MultisigProposal.t.sol +++ b/test/MultisigProposal.t.sol @@ -4,20 +4,14 @@ pragma solidity ^0.8.0; import {Test} from "@forge-std/Test.sol"; import {Addresses} from "@addresses/Addresses.sol"; -import {MultisigProposal} from "@proposals/MultisigProposal.sol"; + import {MockMultisigProposal} from "@mocks/MockMultisigProposal.sol"; +import {MultisigProposal} from "@proposals/MultisigProposal.sol"; contract MultisigProposalIntegrationTest is Test { Addresses public addresses; MultisigProposal public proposal; - struct Call3Value { - address target; - bool allowFailure; - uint256 value; - bytes callData; - } - function setUp() public { uint256[] memory chainIds = new uint256[](1); chainIds[0] = 1; @@ -36,15 +30,9 @@ contract MultisigProposalIntegrationTest is Test { } function test_setUp() public view { + assertEq(proposal.name(), string("OPTMISM_MULTISIG_MOCK"), "Wrong proposal name"); assertEq( - proposal.name(), - string("OPTMISM_MULTISIG_MOCK"), - "Wrong proposal name" - ); - assertEq( - proposal.description(), - string("Mock proposal that upgrade the L1 NFT Bridge"), - "Wrong proposal description" + proposal.description(), string("Mock proposal that upgrade the L1 NFT Bridge"), "Wrong proposal description" ); } @@ -53,9 +41,7 @@ contract MultisigProposalIntegrationTest is Test { proposal.deploy(); vm.stopPrank(); - assertTrue( - addresses.isAddressSet("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION") - ); + assertTrue(addresses.isAddressSet("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION")); } function test_build() public { @@ -66,19 +52,11 @@ contract MultisigProposalIntegrationTest is Test { proposal.build(); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas - ) = proposal.getProposalActions(); + (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = proposal.getProposalActions(); // check that the proposal targets are correct assertEq(targets.length, 1, "Wrong targets length"); - assertEq( - targets[0], - addresses.getAddress("OPTIMISM_PROXY_ADMIN"), - "Wrong target at index 0" - ); + assertEq(targets[0], addresses.getAddress("OPTIMISM_PROXY_ADMIN"), "Wrong target at index 0"); // check that the proposal values are correct assertEq(values.length, 1, "Wrong values length"); @@ -108,30 +86,25 @@ contract MultisigProposalIntegrationTest is Test { function test_getCalldata() public { test_build(); - ( - address[] memory targets, - uint256[] memory values, - bytes[] memory calldatas - ) = proposal.getProposalActions(); - - Call3Value[] memory calls = new Call3Value[](targets.length); - - for (uint256 i; i < calls.length; i++) { - calls[i] = Call3Value({ - target: targets[i], - allowFailure: false, - value: values[i], - callData: calldatas[i] - }); + (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = proposal.getProposalActions(); + + bytes memory encodedTxs; + + for (uint256 i = 0; i < targets.length; i++) { + uint8 operation = 0; + address to = targets[i]; + uint256 value = values[i]; + bytes memory callData = calldatas[i]; + + encodedTxs = + bytes.concat(encodedTxs, abi.encodePacked(operation, to, value, uint256(callData.length), callData)); } - bytes memory expectedData = abi.encodeWithSignature( - "aggregate3Value((address,bool,uint256,bytes)[])", calls - ); + bytes memory expectedData = abi.encodeWithSignature("multiSend(bytes)", encodedTxs); bytes memory data = proposal.getCalldata(); - assertEq(data, expectedData, "Wrong aggregate calldata"); + assertEq(data, expectedData, "Wrong multiSend calldata"); } function test_getProposalId() public { diff --git a/utils/Constants.sol b/utils/Constants.sol index 349a3e65..1c7025fd 100644 --- a/utils/Constants.sol +++ b/utils/Constants.sol @@ -1,8 +1,8 @@ pragma solidity ^0.8.0; library Constants { - bytes public constant MULTICALL_BYTECODE = - hex"6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033"; + bytes public constant MULTISEND_BYTECODE = + hex"60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b805160205b8181101561015f578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461012857600181146101385761013d565b6000808585888a5af1915061013d565b600080fd5b50600081141561014c57600080fd5b82605501870196505050505050506100e3565b50505056fea264697066735822122035246402746c96964495cae5b36461fd44dfb89f8e6cf6f6b8d60c0aa89f414864736f6c63430007060033"; bytes public constant SAFE_BYTECODE = hex"608060405273ffffffffffffffffffffffffffffffffffffffff600054167fa619486e0000000000000000000000000000000000000000000000000000000060003514156050578060005260206000f35b3660008037600080366000845af43d6000803e60008114156070573d6000fd5b3d6000f3fea2646970667358221220d1429297349653a4918076d650332de1a1068c5f3e07c5c82360c277770b955264736f6c63430007060033";