Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 21 additions & 53 deletions src/proposals/GovernorBravoProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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(
Expand All @@ -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)",
Expand All @@ -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;
}
Expand All @@ -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);
Expand All @@ -120,40 +100,28 @@ 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);
governor.castVote(proposalId, 1);

// 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);
}
}
48 changes: 20 additions & 28 deletions src/proposals/MultisigProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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();

Expand Down
69 changes: 21 additions & 48 deletions test/MultisigProposal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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"
);
}

Expand All @@ -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 {
Expand All @@ -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");
Expand Down Expand Up @@ -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 {
Expand Down
Loading
Loading