Skip to content

Commit 0545657

Browse files
authored
Use Safe MultiSend contract (#95)
* Use Safe MultiSend contract * update getCalldata * fix test * mock compound govenror * compile
1 parent fcc79ed commit 0545657

File tree

4 files changed

+64
-131
lines changed

4 files changed

+64
-131
lines changed

src/proposals/GovernorBravoProposal.sol

Lines changed: 21 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@ pragma solidity ^0.8.0;
33

44
import "@forge-std/console.sol";
55

6-
import {
7-
IGovernorBravo,
8-
ITimelockBravo,
9-
IERC20VotesComp
10-
} from "@interface/IGovernorBravo.sol";
6+
import {IERC20VotesComp, IGovernorBravo, ITimelockBravo} from "@interface/IGovernorBravo.sol";
117

128
import {Address} from "@utils/Address.sol";
139

@@ -26,18 +22,8 @@ abstract contract GovernorBravoProposal is Proposal {
2622
}
2723

2824
/// @notice Getter function for `GovernorBravoDelegate.propose()` calldata
29-
function getCalldata()
30-
public
31-
view
32-
virtual
33-
override
34-
returns (bytes memory data)
35-
{
36-
(
37-
address[] memory targets,
38-
uint256[] memory values,
39-
bytes[] memory calldatas
40-
) = getProposalActions();
25+
function getCalldata() public view virtual override returns (bytes memory data) {
26+
(address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = getProposalActions();
4127
string[] memory signatures = new string[](targets.length);
4228

4329
data = abi.encodeWithSignature(
@@ -52,21 +38,12 @@ abstract contract GovernorBravoProposal is Proposal {
5238

5339
/// @notice Check if there are any on-chain proposals that match the
5440
/// proposal calldata
55-
function getProposalId()
56-
public
57-
view
58-
override
59-
returns (uint256 proposalId)
60-
{
41+
function getProposalId() public view override returns (uint256 proposalId) {
6142
uint256 proposalCount = governor.proposalCount();
6243

6344
while (proposalCount > 0) {
64-
(
65-
address[] memory targets,
66-
uint256[] memory values,
67-
string[] memory signatures,
68-
bytes[] memory calldatas
69-
) = governor.getActions(proposalCount);
45+
(address[] memory targets, uint256[] memory values, string[] memory signatures, bytes[] memory calldatas) =
46+
governor.getActions(proposalCount);
7047

7148
bytes memory onchainCalldata = abi.encodeWithSignature(
7249
"propose(address[],uint256[],string[],bytes[],string)",
@@ -81,10 +58,7 @@ abstract contract GovernorBravoProposal is Proposal {
8158

8259
if (keccak256(proposalCalldata) == keccak256(onchainCalldata)) {
8360
if (DEBUG) {
84-
console.log(
85-
"Proposal calldata matches on-chain calldata with proposalId: ",
86-
proposalCount
87-
);
61+
console.log("Proposal calldata matches on-chain calldata with proposalId: ", proposalCount);
8862
}
8963
return proposalCount;
9064
}
@@ -98,13 +72,19 @@ abstract contract GovernorBravoProposal is Proposal {
9872
function simulate() public override {
9973
address proposerAddress = address(1);
10074
IERC20VotesComp governanceToken = governor.comp();
75+
ITimelockBravo timelock = ITimelockBravo(governor.timelock());
76+
77+
// Compound has migrated the Governor to 0x309a862bbC1A00e45506cB8A802D1ff10004c8C0,
78+
// so the timelock admin doesn't correspond to the governor address on mainnet anymore
79+
// This is a workaround since this proposal is not compatible with the new Compound governor
80+
// In Compound's timelock, admin is stored at slot 0
81+
vm.store(address(timelock), bytes32(uint256(0)), bytes32(uint256(uint160(address(governor)))));
82+
10183
{
10284
// Ensure proposer has meets minimum proposal threshold and quorum votes to pass the proposal
10385
uint256 quorumVotes = governor.quorumVotes();
10486
uint256 proposalThreshold = governor.proposalThreshold();
105-
uint256 votingPower = quorumVotes > proposalThreshold
106-
? quorumVotes
107-
: proposalThreshold;
87+
uint256 votingPower = quorumVotes > proposalThreshold ? quorumVotes : proposalThreshold;
10888
deal(address(governanceToken), proposerAddress, votingPower);
10989
// Delegate proposer's votes to itself
11090
vm.prank(proposerAddress);
@@ -120,40 +100,28 @@ abstract contract GovernorBravoProposal is Proposal {
120100
uint256 proposalId = abi.decode(data, (uint256));
121101

122102
// Check proposal is in Pending state
123-
require(
124-
governor.state(proposalId) == IGovernorBravo.ProposalState.Pending
125-
);
103+
require(governor.state(proposalId) == IGovernorBravo.ProposalState.Pending);
126104

127105
// Roll to Active state (voting period)
128106
vm.roll(block.number + governor.votingDelay() + 1);
129-
require(
130-
governor.state(proposalId) == IGovernorBravo.ProposalState.Active
131-
);
107+
require(governor.state(proposalId) == IGovernorBravo.ProposalState.Active);
132108

133109
// Vote YES
134110
vm.prank(proposerAddress);
135111
governor.castVote(proposalId, 1);
136112

137113
// Roll to allow proposal state transitions
138114
vm.roll(block.number + governor.votingPeriod());
139-
require(
140-
governor.state(proposalId) == IGovernorBravo.ProposalState.Succeeded
141-
);
115+
require(governor.state(proposalId) == IGovernorBravo.ProposalState.Succeeded);
142116

143117
// Queue the proposal
144118
governor.queue(proposalId);
145-
require(
146-
governor.state(proposalId) == IGovernorBravo.ProposalState.Queued
147-
);
119+
require(governor.state(proposalId) == IGovernorBravo.ProposalState.Queued);
148120

149-
// Warp to allow proposal execution on timelock
150-
ITimelockBravo timelock = ITimelockBravo(governor.timelock());
151121
vm.warp(block.timestamp + timelock.delay());
152122

153123
// Execute the proposal
154124
governor.execute(proposalId);
155-
require(
156-
governor.state(proposalId) == IGovernorBravo.ProposalState.Executed
157-
);
125+
require(governor.state(proposalId) == IGovernorBravo.ProposalState.Executed);
158126
}
159127
}

src/proposals/MultisigProposal.sol

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@ pragma solidity ^0.8.0;
22

33
import "@forge-std/console.sol";
44

5-
import {Address} from "@utils/Address.sol";
65
import {Proposal} from "./Proposal.sol";
6+
import {Address} from "@utils/Address.sol";
77
import {Constants} from "@utils/Constants.sol";
88

99
abstract contract MultisigProposal is Proposal {
1010
using Address for address;
1111

12-
bytes32 public constant MULTISIG_BYTECODE_HASH = bytes32(
13-
0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000
14-
);
12+
bytes32 public constant MULTISIG_BYTECODE_HASH =
13+
bytes32(0xb89c1b3bdf2cf8827818646bce9a8f6e372885f8c55e5c07acbd307cb133b000);
1514

1615
struct Call3Value {
1716
address target;
@@ -21,31 +20,24 @@ abstract contract MultisigProposal is Proposal {
2120
}
2221

2322
/// @notice return calldata, log if debug is set to true
24-
function getCalldata() public view override returns (bytes memory data) {
25-
/// get proposal actions
26-
(
27-
address[] memory targets,
28-
uint256[] memory values,
29-
bytes[] memory arguments
30-
) = getProposalActions();
31-
32-
/// create calls array with targets and arguments
33-
Call3Value[] memory calls = new Call3Value[](targets.length);
34-
35-
for (uint256 i; i < calls.length; i++) {
36-
require(targets[i] != address(0), "Invalid target for multisig");
37-
calls[i] = Call3Value({
38-
target: targets[i],
39-
allowFailure: false,
40-
value: values[i],
41-
callData: arguments[i]
42-
});
23+
function getCalldata() public view override returns (bytes memory) {
24+
(address[] memory targets, uint256[] memory values, bytes[] memory arguments) = getProposalActions();
25+
26+
require(targets.length == values.length && values.length == arguments.length, "Array lengths mismatch");
27+
28+
bytes memory encodedTxs;
29+
30+
for (uint256 i = 0; i < targets.length; i++) {
31+
uint8 operation = 0;
32+
address to = targets[i];
33+
uint256 value = values[i];
34+
bytes memory data = arguments[i];
35+
36+
encodedTxs = bytes.concat(encodedTxs, abi.encodePacked(operation, to, value, uint256(data.length), data));
4337
}
4438

45-
/// generate calldata
46-
data = abi.encodeWithSignature(
47-
"aggregate3Value((address,bool,uint256,bytes)[])", calls
48-
);
39+
// The final calldata to send to the MultiSend contract
40+
return abi.encodeWithSignature("multiSend(bytes)", encodedTxs);
4941
}
5042

5143
/// @notice Check if there are any on-chain proposal that matches the
@@ -59,7 +51,7 @@ abstract contract MultisigProposal is Proposal {
5951

6052
/// this is a hack because multisig execTransaction requires owners signatures
6153
/// so we cannot simulate it exactly as it will be executed on mainnet
62-
vm.etch(multisig, Constants.MULTICALL_BYTECODE);
54+
vm.etch(multisig, Constants.MULTISEND_BYTECODE);
6355

6456
bytes memory data = getCalldata();
6557

test/MultisigProposal.t.sol

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@ pragma solidity ^0.8.0;
44
import {Test} from "@forge-std/Test.sol";
55

66
import {Addresses} from "@addresses/Addresses.sol";
7-
import {MultisigProposal} from "@proposals/MultisigProposal.sol";
7+
88
import {MockMultisigProposal} from "@mocks/MockMultisigProposal.sol";
9+
import {MultisigProposal} from "@proposals/MultisigProposal.sol";
910

1011
contract MultisigProposalIntegrationTest is Test {
1112
Addresses public addresses;
1213
MultisigProposal public proposal;
1314

14-
struct Call3Value {
15-
address target;
16-
bool allowFailure;
17-
uint256 value;
18-
bytes callData;
19-
}
20-
2115
function setUp() public {
2216
uint256[] memory chainIds = new uint256[](1);
2317
chainIds[0] = 1;
@@ -36,15 +30,9 @@ contract MultisigProposalIntegrationTest is Test {
3630
}
3731

3832
function test_setUp() public view {
33+
assertEq(proposal.name(), string("OPTMISM_MULTISIG_MOCK"), "Wrong proposal name");
3934
assertEq(
40-
proposal.name(),
41-
string("OPTMISM_MULTISIG_MOCK"),
42-
"Wrong proposal name"
43-
);
44-
assertEq(
45-
proposal.description(),
46-
string("Mock proposal that upgrade the L1 NFT Bridge"),
47-
"Wrong proposal description"
35+
proposal.description(), string("Mock proposal that upgrade the L1 NFT Bridge"), "Wrong proposal description"
4836
);
4937
}
5038

@@ -53,9 +41,7 @@ contract MultisigProposalIntegrationTest is Test {
5341
proposal.deploy();
5442
vm.stopPrank();
5543

56-
assertTrue(
57-
addresses.isAddressSet("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION")
58-
);
44+
assertTrue(addresses.isAddressSet("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION"));
5945
}
6046

6147
function test_build() public {
@@ -66,19 +52,11 @@ contract MultisigProposalIntegrationTest is Test {
6652

6753
proposal.build();
6854

69-
(
70-
address[] memory targets,
71-
uint256[] memory values,
72-
bytes[] memory calldatas
73-
) = proposal.getProposalActions();
55+
(address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = proposal.getProposalActions();
7456

7557
// check that the proposal targets are correct
7658
assertEq(targets.length, 1, "Wrong targets length");
77-
assertEq(
78-
targets[0],
79-
addresses.getAddress("OPTIMISM_PROXY_ADMIN"),
80-
"Wrong target at index 0"
81-
);
59+
assertEq(targets[0], addresses.getAddress("OPTIMISM_PROXY_ADMIN"), "Wrong target at index 0");
8260

8361
// check that the proposal values are correct
8462
assertEq(values.length, 1, "Wrong values length");
@@ -108,30 +86,25 @@ contract MultisigProposalIntegrationTest is Test {
10886
function test_getCalldata() public {
10987
test_build();
11088

111-
(
112-
address[] memory targets,
113-
uint256[] memory values,
114-
bytes[] memory calldatas
115-
) = proposal.getProposalActions();
116-
117-
Call3Value[] memory calls = new Call3Value[](targets.length);
118-
119-
for (uint256 i; i < calls.length; i++) {
120-
calls[i] = Call3Value({
121-
target: targets[i],
122-
allowFailure: false,
123-
value: values[i],
124-
callData: calldatas[i]
125-
});
89+
(address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = proposal.getProposalActions();
90+
91+
bytes memory encodedTxs;
92+
93+
for (uint256 i = 0; i < targets.length; i++) {
94+
uint8 operation = 0;
95+
address to = targets[i];
96+
uint256 value = values[i];
97+
bytes memory callData = calldatas[i];
98+
99+
encodedTxs =
100+
bytes.concat(encodedTxs, abi.encodePacked(operation, to, value, uint256(callData.length), callData));
126101
}
127102

128-
bytes memory expectedData = abi.encodeWithSignature(
129-
"aggregate3Value((address,bool,uint256,bytes)[])", calls
130-
);
103+
bytes memory expectedData = abi.encodeWithSignature("multiSend(bytes)", encodedTxs);
131104

132105
bytes memory data = proposal.getCalldata();
133106

134-
assertEq(data, expectedData, "Wrong aggregate calldata");
107+
assertEq(data, expectedData, "Wrong multiSend calldata");
135108
}
136109

137110
function test_getProposalId() public {

0 commit comments

Comments
 (0)