Skip to content

Commit

Permalink
Multisig Proposal example using Optmism
Browse files Browse the repository at this point in the history
  • Loading branch information
anajuliabit committed May 13, 2024
1 parent 812f3b1 commit 630be09
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 128 deletions.
30 changes: 30 additions & 0 deletions addresses/Addresses.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
"chainId": 31337,
"isContract": false
},
{
"addr": "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739",
"name": "DEPLOYER_EOA",
"chainId": 1,
"isContract": false
},
{
"addr": "0xA454F330A728389b87A255b3D59aB10aEABa0C84",
"name": "DEV_MULTISIG",
Expand Down Expand Up @@ -118,5 +124,29 @@
"chainId": 11155111,
"name": "ExampleTypeCheck_02",
"isContract": true
},
{
"addr": "0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A",
"chainId": 1,
"name": "OPTIMISM_MULTISIG",
"isContract": true
},
{
"addr": "0x543bA4AADBAb8f9025686Bd03993043599c6fB04",
"chainId": 1,
"name": "OPTIMISM_PROXY_ADMIN",
"isContract": true
},
{
"addr": "0x5a7749f83b81b301cab5f48eb8516b986daef23d",
"chainId": 1,
"name": "OPTIMISM_L1_NFT_BRIDGE_PROXY",
"isContract": true
},
{
"addr": "0xAE2AF01232a6c4a4d3012C5eC5b1b35059caF10d",
"chainId": 1,
"name": "OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION",
"isContract": true
}
]
80 changes: 34 additions & 46 deletions mocks/MockMultisigProposal.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.0;

import {console} from "@forge-std/console.sol";

Check failure on line 4 in mocks/MockMultisigProposal.sol

View workflow job for this annotation

GitHub Actions / lint

imported name console is not used
import {Addresses} from "@addresses/Addresses.sol";

import {MultisigProposal} from "@proposals/MultisigProposal.sol";

import {Vault} from "@mocks/Vault.sol";
import {Token} from "@mocks/Token.sol";

interface ProxyAdmin {
function upgrade(address proxy, address implementation) external;
}

interface Proxy {
function implementation() external view returns (address);
}

contract MockMultisigProposal is MultisigProposal {
function name() public pure override returns (string memory) {
return "MULTISIG_MOCK";
return "OPTMISM_MULTISIG_MOCK";
}

function description() public pure override returns (string memory) {
return "Multisig proposal mock";
return "Mock proposal that upgrade the L1 NFT Bridge";
}

function run() public override {
Expand All @@ -27,68 +35,48 @@ contract MockMultisigProposal is MultisigProposal {
}

function deploy() public override {
address multisig = addresses.getAddress("DEV_MULTISIG");
if (!addresses.isAddressSet("MULTISIG_VAULT")) {
Vault timelockVault = new Vault();
if (!addresses.isAddressSet("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION")) {
address l1NFTBridgeImplementation = address(new Vault());

addresses.addAddress(
"MULTISIG_VAULT",
address(timelockVault),
"OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION",
l1NFTBridgeImplementation,
true
);
}

if (!addresses.isAddressSet("MULTISIG_TOKEN")) {
Token token = new Token();
addresses.addAddress("MULTISIG_TOKEN", address(token), true);

// During forge script execution, the deployer of the contracts is
// the DEPLOYER_EOA. However, when running through forge test, the deployer of the contracts is this contract.
uint256 balance = token.balanceOf(address(this)) > 0
? token.balanceOf(address(this))
: token.balanceOf(addresses.getAddress("DEPLOYER_EOA"));

token.transfer(multisig, balance);
}
}

function build()
public
override
buildModifier(addresses.getAddress("DEV_MULTISIG"))
buildModifier(addresses.getAddress("OPTIMISM_MULTISIG"))
{
address multisig = addresses.getAddress("DEV_MULTISIG");

/// STATICCALL -- not recorded for the run stage
address timelockVault = addresses.getAddress("MULTISIG_VAULT");
address token = addresses.getAddress("MULTISIG_TOKEN");
uint256 balance = Token(token).balanceOf(address(multisig));

Vault(timelockVault).whitelistToken(token, true);
ProxyAdmin proxy = ProxyAdmin(
addresses.getAddress("OPTIMISM_PROXY_ADMIN")
);

/// CALLS -- mutative and recorded
Token(token).approve(timelockVault, balance);
Vault(timelockVault).deposit(token, balance);
proxy.upgrade(
addresses.getAddress("OPTIMISM_L1_NFT_BRIDGE_PROXY"),
addresses.getAddress("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION")
);
}

function simulate() public override {
address multisig = addresses.getAddress("DEV_MULTISIG");
address multisig = addresses.getAddress("OPTIMISM_MULTISIG");

/// Dev is proposer and executor
_simulateActions(multisig);
}

function validate() public view override {
Vault timelockVault = Vault(addresses.getAddress("MULTISIG_VAULT"));
Token token = Token(addresses.getAddress("MULTISIG_TOKEN"));
address multisig = addresses.getAddress("DEV_MULTISIG");

uint256 balance = token.balanceOf(address(timelockVault));
(uint256 amount, ) = timelockVault.deposits(address(token), multisig);
assertEq(amount, balance);

assertTrue(timelockVault.tokenWhitelist(address(token)));
function validate() public override {
Proxy proxy = Proxy(
addresses.getAddress("OPTIMISM_L1_NFT_BRIDGE_PROXY")
);

assertEq(token.balanceOf(address(timelockVault)), token.totalSupply());
vm.prank(addresses.getAddress("OPTIMISM_PROXY_ADMIN"));
require(
proxy.implementation() ==
addresses.getAddress("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION"),
"Proxy implementation not set"
);
}
}
4 changes: 0 additions & 4 deletions src/proposals/MultisigProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ abstract contract MultisigProposal is Proposal {
}

function _simulateActions(address multisig) internal {
require(
multisig.getContractHash() == MULTISIG_BYTECODE_HASH,
"Multisig address doesn't match Gnosis Safe contract bytecode"
);
vm.startPrank(multisig);

/// this is a hack because multisig execTransaction requires owners signatures
Expand Down
91 changes: 13 additions & 78 deletions test/MultisigProposal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ contract MultisigProposalIntegrationTest is Test {
function test_setUp() public view {
assertEq(
proposal.name(),
string("MULTISIG_MOCK"),
string("OPTMISM_MULTISIG_MOCK"),
"Wrong proposal name"
);
assertEq(
proposal.description(),
string("Multisig proposal mock"),
string("Mock proposal that upgrade the L1 NFT Bridge"),
"Wrong proposal description"
);
}
Expand All @@ -48,16 +48,8 @@ contract MultisigProposalIntegrationTest is Test {
proposal.deploy();
vm.stopPrank();

// check that the vault was deployed
assertTrue(addresses.isAddressSet("MULTISIG_VAULT"));

// check that the token was deployed
assertTrue(addresses.isAddressSet("MULTISIG_TOKEN"));
Token token = Token(addresses.getAddress("MULTISIG_TOKEN"));
assertEq(
token.balanceOf(addresses.getAddress("DEV_MULTISIG")),
token.totalSupply(),
"Wrong token balance"
assertTrue(
addresses.isAddressSet("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION")
);
}

Expand All @@ -67,12 +59,6 @@ contract MultisigProposalIntegrationTest is Test {
vm.expectRevert("No actions found");
proposal.getProposalActions();

Token token = Token(addresses.getAddress("MULTISIG_TOKEN"));

uint256 expectedBalance = token.balanceOf(
addresses.getAddress("DEV_MULTISIG")
);

proposal.build();

(
Expand All @@ -82,81 +68,36 @@ contract MultisigProposalIntegrationTest is Test {
) = proposal.getProposalActions();

// check that the proposal targets are correct
assertEq(targets.length, 3, "Wrong targets length");
assertEq(targets.length, 1, "Wrong targets length");
assertEq(
targets[0],
addresses.getAddress("MULTISIG_VAULT"),
addresses.getAddress("OPTIMISM_PROXY_ADMIN"),
"Wrong target at index 0"
);
assertEq(
targets[1],
addresses.getAddress("MULTISIG_TOKEN"),
"Wrong target at index 1"
);
assertEq(
targets[2],
addresses.getAddress("MULTISIG_VAULT"),
"Wrong target at index 2"
);

// check that the proposal values are correct
assertEq(values.length, 3, "Wrong values length");
assertEq(values.length, 1, "Wrong values length");
assertEq(values[0], 0, "Wrong value at index 0");
assertEq(values[1], 0, "Wrong value at index 1");
assertEq(values[2], 0, "Wrong value at index 2");

// check that the proposal calldatas are correct
assertEq(calldatas.length, 3);
assertEq(calldatas.length, 1);
assertEq(
calldatas[0],
abi.encodeWithSignature(
"whitelistToken(address,bool)",
addresses.getAddress("MULTISIG_TOKEN"),
true
"upgrade(address,address)",
addresses.getAddress("OPTIMISM_L1_NFT_BRIDGE_PROXY"),
addresses.getAddress("OPTIMISM_L1_NFT_BRIDGE_IMPLEMENTATION")
),
"Wrong calldata at index 0"
);
assertEq(
calldatas[1],
abi.encodeWithSignature(
"approve(address,uint256)",
addresses.getAddress("MULTISIG_VAULT"),
expectedBalance
),
"Wrong calldata at index 1"
);
assertEq(
calldatas[2],
abi.encodeWithSignature(
"deposit(address,uint256)",
addresses.getAddress("MULTISIG_TOKEN"),
expectedBalance
),
"Wrong calldata at index 2"
);
}

function test_simulate() public {
test_build();

proposal.simulate();

// check that the proposal actions were executed
Vault timelockVault = Vault(addresses.getAddress("MULTISIG_VAULT"));
Token token = Token(addresses.getAddress("MULTISIG_TOKEN"));

assertTrue(
timelockVault.tokenWhitelist(
addresses.getAddress("MULTISIG_TOKEN")
),
"Token not whitelisted"
);

assertEq(
token.balanceOf(addresses.getAddress("MULTISIG_VAULT")),
token.totalSupply(),
"Wrong token balance"
);
proposal.validate();
}

function test_getCalldata() public {
Expand All @@ -178,17 +119,11 @@ contract MultisigProposalIntegrationTest is Test {

bytes memory data = proposal.getCalldata();

assertEq(data, expectedData, "Wrong scheduleBatch calldata");
assertEq(data, expectedData, "Wrong aggregate calldata");
}

function test_checkOnChainCalldata() public {
vm.expectRevert("Not implemented");
proposal.checkOnChainCalldata();
}

function test_validate() public {
test_simulate();

proposal.validate();
}
}

0 comments on commit 630be09

Please sign in to comment.