From 47a14e2702a3e93de5850ee65c4f2a02bbe76797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 6 Jun 2024 10:27:54 +0200 Subject: [PATCH 1/5] Indexing optimistic proposal ID's --- src/OptimisticTokenVotingPlugin.sol | 7 ++++- test/OptimisticTokenVotingPlugin.t.sol | 39 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/OptimisticTokenVotingPlugin.sol b/src/OptimisticTokenVotingPlugin.sol index b220c1a..7e27507 100644 --- a/src/OptimisticTokenVotingPlugin.sol +++ b/src/OptimisticTokenVotingPlugin.sol @@ -99,6 +99,9 @@ contract OptimisticTokenVotingPlugin is /// @notice A mapping between proposal IDs and proposal information. mapping(uint256 => Proposal) internal proposals; + /// @notice A mapping to enumerate proposal ID's by index + mapping(uint256 => uint256) public proposalIds; + /// @notice Emitted when the vetoing settings are updated. /// @param minVetoRatio The minimum veto ratio needed to defeat the proposal, as a fraction of 1_000_000. /// @param minDuration The minimum duration of the proposal vote in seconds. @@ -358,6 +361,8 @@ contract OptimisticTokenVotingPlugin is _actions: _actions, _allowFailureMap: _allowFailureMap }); + // Index the ID to make it enumerable. Proposal ID's contain timestamps and cannot be iterated + proposalIds[proposalCount() - 1] = proposalId; // Store proposal related information Proposal storage proposal_ = proposals[proposalId]; @@ -549,5 +554,5 @@ contract OptimisticTokenVotingPlugin is } /// @notice This empty reserved space is put in place to allow future versions to add new variables without shifting down storage in the inheritance chain (see [OpenZeppelin's guide about storage gaps](https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps)). - uint256[45] private __gap; + uint256[44] private __gap; } diff --git a/test/OptimisticTokenVotingPlugin.t.sol b/test/OptimisticTokenVotingPlugin.t.sol index 872ce20..c57b141 100644 --- a/test/OptimisticTokenVotingPlugin.t.sol +++ b/test/OptimisticTokenVotingPlugin.t.sol @@ -862,6 +862,45 @@ contract OptimisticTokenVotingPluginTest is AragonTest { assertEq(proposalId, expectedPid, "Should have created proposal 2"); } + function test_CreateProposalIncrementsTheProposalCounter() public { + IDAO.Action[] memory actions = new IDAO.Action[](0); + assertEq(optimisticPlugin.proposalCount(), 0); + optimisticPlugin.createProposal("", actions, 0, 10 days); + assertEq(optimisticPlugin.proposalCount(), 1); + optimisticPlugin.createProposal("ipfs://", actions, 0, 10 days); + assertEq(optimisticPlugin.proposalCount(), 2); + optimisticPlugin.createProposal("", actions, 255, 15 days); + assertEq(optimisticPlugin.proposalCount(), 3); + optimisticPlugin.createProposal("", actions, 127, 20 days); + assertEq(optimisticPlugin.proposalCount(), 4); + optimisticPlugin.createProposal("ipfs://meta", actions, 0, 10 days); + assertEq(optimisticPlugin.proposalCount(), 5); + optimisticPlugin.createProposal("", actions, 0, 100 days); + assertEq(optimisticPlugin.proposalCount(), 6); + } + + function test_CreateProposalIndexesThePid() public { + uint256 expectedPid = uint256(block.timestamp) << 128 | uint256(block.timestamp + 10 days) << 64; + + IDAO.Action[] memory actions = new IDAO.Action[](0); + // 1 + assertEq(optimisticPlugin.proposalIds(0), 0); + optimisticPlugin.createProposal("", actions, 0, 10 days); + assertEq(optimisticPlugin.proposalIds(0), expectedPid); + + // 2 + expectedPid = uint256(block.timestamp) << 128 | uint256(block.timestamp + 100 days) << 64 | 1; + assertEq(optimisticPlugin.proposalIds(1), 0); + optimisticPlugin.createProposal("ipfs://meta", actions, 0, 100 days); + assertEq(optimisticPlugin.proposalIds(1), expectedPid); + + // 3 + expectedPid = uint256(block.timestamp) << 128 | uint256(block.timestamp + 50 days) << 64 | 2; + assertEq(optimisticPlugin.proposalIds(2), 0); + optimisticPlugin.createProposal("", actions, 0, 50 days); + assertEq(optimisticPlugin.proposalIds(2), expectedPid); + } + function test_CreateProposalEmitsAnEvent() public { uint256 expectedPid = uint256(block.timestamp) << 128 | uint256(block.timestamp + 10 days) << 64; From 118b5b6578b51139a5ab871eddacf61b0fb5531a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 6 Jun 2024 14:19:32 +0100 Subject: [PATCH 2/5] Making the registry and wall iterable --- DEPLOYMENTS.md | 11 +++++ README.md | 2 +- src/DelegationWall.sol | 8 ++++ src/PublicKeyRegistry.sol | 17 ++++++-- test/DelegationWall.t.sol | 18 ++++++++ test/PublicKeyRegistry.t.sol | 83 +++++++++++++++++++++++++++++++----- 6 files changed, 124 insertions(+), 15 deletions(-) create mode 100644 DEPLOYMENTS.md diff --git a/DEPLOYMENTS.md b/DEPLOYMENTS.md new file mode 100644 index 0000000..2960764 --- /dev/null +++ b/DEPLOYMENTS.md @@ -0,0 +1,11 @@ +# Deployment list + +## Mainnet + +## Sepolia + +On June 6th 2024: + +``` +``` + diff --git a/README.md b/README.md index 1dbdc4d..2e41a47 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The Security Council has a standard multisig plugin and an emergency variant. Th [Learn more about Aragon OSx](#protocol-overview). -See [Deploying the DAO](#deploying-the-dao) below. +See [Deploying the DAO](#deploying-the-dao) below and check out the [latest deployments](./DEPLOYMENTS.md). ## Optimistic Token Voting plugin diff --git a/src/DelegationWall.sol b/src/DelegationWall.sol index bbe7f0f..8da9301 100644 --- a/src/DelegationWall.sol +++ b/src/DelegationWall.sol @@ -12,6 +12,7 @@ contract DelegationWall { /// @dev Stores the data registered by the delegate candidates mapping(address => Candidate) public candidates; + /// @dev Keeps track of the addresses that have been already registered, used to enumerate. address[] public candidateAddresses; @@ -33,6 +34,13 @@ contract DelegationWall { emit CandidateRegistered(msg.sender, _contentUrl); } + /// @notice Returns the list of candidate addresses registered + /// @dev Use this function to get all addresses in a single call. You can still call candidateAddresses[idx] to resolve them one by one. + function getCandidateAddresses() public view returns (address[] memory) { + return candidateAddresses; + } + + /// @notice Returns the number of candidate entries available function candidateCount() public view returns (uint256) { return candidateAddresses.length; } diff --git a/src/PublicKeyRegistry.sol b/src/PublicKeyRegistry.sol index f9df687..581ff4a 100644 --- a/src/PublicKeyRegistry.sol +++ b/src/PublicKeyRegistry.sol @@ -6,7 +6,10 @@ pragma solidity ^0.8.17; /// @author Aragon Association - 2024 /// @notice A smart contract where any wallet can register its own libsodium public key for encryption purposes contract PublicKeyRegistry { - mapping(address => bytes32) internal publicKeys; + mapping(address => bytes32) public publicKeys; + + /// @dev Allows to enumerate the wallets that have a public key registered + address[] public registeredWallets; /// @notice Emitted when a public key is registered event PublicKeyRegistered(address wallet, bytes32 publicKey); @@ -19,9 +22,17 @@ contract PublicKeyRegistry { publicKeys[msg.sender] = _publicKey; emit PublicKeyRegistered(msg.sender, _publicKey); + registeredWallets.push(msg.sender); + } + + /// @notice Returns the list of wallets that have registered a public key + /// @dev Use this function to get all addresses in a single call. You can still call registeredWallets[idx] to resolve them one by one. + function getRegisteredWallets() public view returns (address[] memory) { + return registeredWallets; } - function getPublicKey(address _wallet) public view returns (bytes32) { - return publicKeys[_wallet]; + /// @notice Returns the number of publicKey entries available + function registeredWalletCount() public view returns (uint256) { + return registeredWallets.length; } } diff --git a/test/DelegationWall.t.sol b/test/DelegationWall.t.sol index 2a73f2c..72082cb 100644 --- a/test/DelegationWall.t.sol +++ b/test/DelegationWall.t.sol @@ -216,6 +216,24 @@ contract EmergencyMultisigTest is AragonTest { assertEq(wall.candidateAddresses(3), david, "Incorrect candidate address"); } + function test_ShouldLoadTheRegisteredAddresses() public { + vm.startPrank(alice); + wall.register("https://"); + vm.startPrank(bob); + wall.register("https://taiko.xyz"); + vm.startPrank(carol); + wall.register("https://x.com/carol"); + vm.startPrank(david); + wall.register("https://defeat-goliath.org"); + + address[] memory candidates = wall.getCandidateAddresses(); + assertEq(candidates.length, 4); + assertEq(candidates[0], alice); + assertEq(candidates[1], bob); + assertEq(candidates[2], carol); + assertEq(candidates[3], david); + } + function test_ShouldEmitAnEventWhenRegistering() public { // Alice vm.startPrank(alice); diff --git a/test/PublicKeyRegistry.t.sol b/test/PublicKeyRegistry.t.sol index 9841c5e..f7fbd7f 100644 --- a/test/PublicKeyRegistry.t.sol +++ b/test/PublicKeyRegistry.t.sol @@ -18,37 +18,37 @@ contract EmergencyMultisigTest is AragonTest { } function test_ShouldRegisterAPublicKey() public { - assertEq(registry.getPublicKey(alice), 0x0000000000000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(alice), 0x0000000000000000000000000000000000000000000000000000000000000000); // Alice vm.startPrank(alice); registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); // Bob vm.startPrank(bob); registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(carol), 0x0000000090ab0000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(carol), 0x0000000090ab0000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(carol), 0x0000000090ab0000000000000000000000000000000000000000000000000000); - assertEq(registry.getPublicKey(david), 0x000000000000cdef000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(carol), 0x0000000090ab0000000000000000000000000000000000000000000000000000); + assertEq(registry.publicKeys(david), 0x000000000000cdef000000000000000000000000000000000000000000000000); } function test_ShouldEmitARegistrationEvent() public { @@ -102,4 +102,65 @@ contract EmergencyMultisigTest is AragonTest { vm.expectRevert(abi.encodeWithSelector(AlreadySet.selector)); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); } + + function test_ShouldCountRegisteredCandidates() public { + assertEq(registry.registeredWalletCount(), 0, "Incorrect count"); + + // Alice + vm.startPrank(alice); + registry.setPublicKey(bytes32(uint256(1234))); + assertEq(registry.registeredWalletCount(), 1, "Incorrect count"); + + // Bob + vm.startPrank(bob); + registry.setPublicKey(bytes32(uint256(2345))); + assertEq(registry.registeredWalletCount(), 2, "Incorrect count"); + + // Carol + vm.startPrank(carol); + registry.setPublicKey(bytes32(uint256(3456))); + assertEq(registry.registeredWalletCount(), 3, "Incorrect count"); + + // David + vm.startPrank(david); + registry.setPublicKey(bytes32(uint256(4567))); + assertEq(registry.registeredWalletCount(), 4, "Incorrect count"); + } + + function test_ShouldEnumerateRegisteredCandidates() public { + // Register + vm.startPrank(alice); + registry.setPublicKey(bytes32(uint256(1234))); + vm.startPrank(bob); + registry.setPublicKey(bytes32(uint256(2345))); + vm.startPrank(carol); + registry.setPublicKey(bytes32(uint256(3456))); + vm.startPrank(david); + registry.setPublicKey(bytes32(uint256(4567))); + + assertEq(registry.registeredWalletCount(), 4, "Incorrect count"); + + assertEq(registry.registeredWallets(0), alice); + assertEq(registry.registeredWallets(1), bob); + assertEq(registry.registeredWallets(2), carol); + assertEq(registry.registeredWallets(3), david); + } + + function test_ShouldLoadTheRegisteredAddresses() public { + vm.startPrank(alice); + registry.setPublicKey(bytes32(uint256(1234))); + vm.startPrank(bob); + registry.setPublicKey(bytes32(uint256(2345))); + vm.startPrank(carol); + registry.setPublicKey(bytes32(uint256(3456))); + vm.startPrank(david); + registry.setPublicKey(bytes32(uint256(4567))); + + address[] memory candidates = registry.getRegisteredWallets(); + assertEq(candidates.length, 4); + assertEq(candidates[0], alice); + assertEq(candidates[1], bob); + assertEq(candidates[2], carol); + assertEq(candidates[3], david); + } } From c3982f33a8a3ac0ff2e485244364bad50f9de648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 6 Jun 2024 16:54:12 +0100 Subject: [PATCH 3/5] Sepolia deployment info --- DEPLOYMENTS.md | 18 ++++++++++++++++++ script/Deploy.s.sol | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/DEPLOYMENTS.md b/DEPLOYMENTS.md index 2960764..a7498aa 100644 --- a/DEPLOYMENTS.md +++ b/DEPLOYMENTS.md @@ -7,5 +7,23 @@ On June 6th 2024: ``` +Chain ID: 11155111 + +Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab +Test voting token: 0xf7A8F99a1d0AFB3C95f80770223b00e062C6Ec19 +Factory contract: 0x8c99CDb5567206660CA9b77C3B26C13F6C674952 + +DAO contract: 0x6C477915CC803518723d4Bdd5B2170cf38A57203 + +- Multisig plugin: 0x0fC611670228A61824c317926f30e8a2615aa1A3 +- Emergency multisig plugin: 0x619d6661eA06b917e26694f23c5Bb32fa0456773 +- Optimistic token voting plugin: 0xC9304930f6a4fB2DAe74A17032426Aa1E817897A + +- Multisig plugin repository: 0x841E3dA30697C8FC7224a43952041001545a2443 +- Emergency multisig plugin repository: 0x6E8578B1519a04BA9262CB633B06624f636D4795 +- Optimistic token voting plugin repository: 0x58CA6f90edB98f9213353f456c685ABF253edAA7 + +Public key registry 0xadAb459A189AAaa17D4807805e6Fab55d3fb5C44 +Delegation wall 0x0cE7f031BA69abFB404fE148dD09F597db8AB3a0 ``` diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6e120c1..430e700 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -36,8 +36,8 @@ contract Deploy is Script { ); console.log("Chain ID:", block.chainid); - console.log("Deploying from:", vm.addr(vm.envUint("DEPLOYMENT_PRIVATE_KEY"))); console.log(""); + console.log("Deploying from:", vm.addr(vm.envUint("DEPLOYMENT_PRIVATE_KEY"))); TaikoDaoFactory.DeploymentSettings memory settings; if (block.chainid == 1) { @@ -55,6 +55,7 @@ contract Deploy is Script { // Print summary console.log("Factory contract:", address(factory)); + console.log(""); console.log("DAO contract:", address(daoDeployment.dao)); console.log(""); @@ -110,7 +111,6 @@ contract Deploy is Script { address votingToken = createTestToken(multisigMembers, taikoBridgeAddress); console.log("Test voting token:", votingToken); - console.log(""); settings = TaikoDaoFactory.DeploymentSettings({ // Taiko contract settings From 6e7afce4b4ce7a95b599730e8b92d23252c0f5bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 6 Jun 2024 14:19:32 +0100 Subject: [PATCH 4/5] Making the registry and wall iterable --- src/DelegationWall.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DelegationWall.sol b/src/DelegationWall.sol index 8da9301..6de6748 100644 --- a/src/DelegationWall.sol +++ b/src/DelegationWall.sol @@ -22,6 +22,7 @@ contract DelegationWall { /// @notice Raised when a delegate registers with an empty contentUrl error EmptyContent(); + /// @notice Registers the given data as a new delegation candidate function register(bytes memory _contentUrl) public { if (_contentUrl.length == 0) revert EmptyContent(); From f6541c3035bd2ec678a4a30a55aaa52aee5f3a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 7 Jun 2024 22:26:06 +0100 Subject: [PATCH 5/5] Adding tests for all plugin setup's --- .vscode/settings.json | 2 +- src/setup/EmergencyMultisigPluginSetup.sol | 8 +- src/setup/MultisigPluginSetup.sol | 61 ++----- test/EmergencyMultisigPluginSetup.t.sol | 179 +++++++++++++++++++++ test/MultisigPluginSetup.t.sol | 160 ++++++++++++++++++ 5 files changed, 361 insertions(+), 49 deletions(-) create mode 100644 test/EmergencyMultisigPluginSetup.t.sol create mode 100644 test/MultisigPluginSetup.t.sol diff --git a/.vscode/settings.json b/.vscode/settings.json index e33cfb7..e4d66f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "solidity.packageDefaultDependenciesDirectory": "lib", "editor.formatOnSave": true, "[solidity]": { - "editor.defaultFormatter": "NomicFoundation.hardhat-solidity" + "editor.defaultFormatter": "NomicFoundation.hardhat-solidity" }, "solidity.formatter": "forge", "solidity.compileUsingRemoteVersion": "v0.8.17" diff --git a/src/setup/EmergencyMultisigPluginSetup.sol b/src/setup/EmergencyMultisigPluginSetup.sol index bb49b71..3139b6b 100644 --- a/src/setup/EmergencyMultisigPluginSetup.sol +++ b/src/setup/EmergencyMultisigPluginSetup.sol @@ -25,8 +25,8 @@ contract EmergencyMultisigPluginSetup is PluginSetup { external returns (address plugin, PreparedSetupData memory preparedSetupData) { - // Decode `_data` to extract the params needed for deploying and initializing `EmergencyMultisig` plugin. - (EmergencyMultisig.MultisigSettings memory multisigSettings) = decodeInstallationParams(_data); + // Decode `_data` to extract the parameters needed for deploying and initializing `EmergencyMultisig` plugin. + (EmergencyMultisig.MultisigSettings memory multisigSettings) = decodeInstallationParameters(_data); // Prepare and Deploy the plugin proxy. plugin = createERC1967Proxy( @@ -34,7 +34,7 @@ contract EmergencyMultisigPluginSetup is PluginSetup { ); // Prepare permissions - PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[](3); + PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[](2); // Set permissions to be granted. // Grant the list of permissions of the plugin to the DAO. @@ -107,7 +107,7 @@ contract EmergencyMultisigPluginSetup is PluginSetup { } /// @notice Decodes the given byte array into the original installation parameters - function decodeInstallationParams(bytes memory _data) + function decodeInstallationParameters(bytes memory _data) public pure returns (EmergencyMultisig.MultisigSettings memory _multisigSettings) diff --git a/src/setup/MultisigPluginSetup.sol b/src/setup/MultisigPluginSetup.sol index 4a65dbb..9bf35bb 100644 --- a/src/setup/MultisigPluginSetup.sol +++ b/src/setup/MultisigPluginSetup.sol @@ -21,31 +21,21 @@ contract MultisigPluginSetup is PluginSetup { } /// @inheritdoc IPluginSetup - function prepareInstallation( - address _dao, - bytes calldata _data - ) + function prepareInstallation(address _dao, bytes calldata _data) external returns (address plugin, PreparedSetupData memory preparedSetupData) { - // Decode `_data` to extract the params needed for deploying and initializing `Multisig` plugin. - ( - address[] memory members, - Multisig.MultisigSettings memory multisigSettings - ) = decodeInstallationParams(_data); + // Decode `_data` to extract the parameters needed for deploying and initializing `Multisig` plugin. + (address[] memory members, Multisig.MultisigSettings memory multisigSettings) = + decodeInstallationParameters(_data); // Prepare and Deploy the plugin proxy. plugin = createERC1967Proxy( - address(multisigBase), - abi.encodeCall( - Multisig.initialize, - (IDAO(_dao), members, multisigSettings) - ) + address(multisigBase), abi.encodeCall(Multisig.initialize, (IDAO(_dao), members, multisigSettings)) ); // Prepare permissions - PermissionLib.MultiTargetPermission[] - memory permissions = new PermissionLib.MultiTargetPermission[](3); + PermissionLib.MultiTargetPermission[] memory permissions = new PermissionLib.MultiTargetPermission[](2); // Set permissions to be granted. // Grant the list of permissions of the plugin to the DAO. @@ -69,25 +59,15 @@ contract MultisigPluginSetup is PluginSetup { } /// @inheritdoc IPluginSetup - function prepareUpdate( - address _dao, - uint16 _currentBuild, - SetupPayload calldata _payload - ) + function prepareUpdate(address _dao, uint16 _currentBuild, SetupPayload calldata _payload) external pure override - returns ( - bytes memory initData, - PreparedSetupData memory preparedSetupData - ) + returns (bytes memory initData, PreparedSetupData memory preparedSetupData) {} /// @inheritdoc IPluginSetup - function prepareUninstallation( - address _dao, - SetupPayload calldata _payload - ) + function prepareUninstallation(address _dao, SetupPayload calldata _payload) external view returns (PermissionLib.MultiTargetPermission[] memory permissions) @@ -119,27 +99,20 @@ contract MultisigPluginSetup is PluginSetup { } /// @notice Encodes the given installation parameters into a byte array - function encodeInstallationParameters( - address[] memory _members, - Multisig.MultisigSettings memory _multisigSettings - ) external pure returns (bytes memory) { + function encodeInstallationParameters(address[] memory _members, Multisig.MultisigSettings memory _multisigSettings) + external + pure + returns (bytes memory) + { return abi.encode(_members, _multisigSettings); } /// @notice Decodes the given byte array into the original installation parameters - function decodeInstallationParams( - bytes memory _data - ) + function decodeInstallationParameters(bytes memory _data) public pure - returns ( - address[] memory _members, - Multisig.MultisigSettings memory _multisigSettings - ) + returns (address[] memory _members, Multisig.MultisigSettings memory _multisigSettings) { - (_members, _multisigSettings) = abi.decode( - _data, - (address[], Multisig.MultisigSettings) - ); + (_members, _multisigSettings) = abi.decode(_data, (address[], Multisig.MultisigSettings)); } } diff --git a/test/EmergencyMultisigPluginSetup.t.sol b/test/EmergencyMultisigPluginSetup.t.sol new file mode 100644 index 0000000..169172f --- /dev/null +++ b/test/EmergencyMultisigPluginSetup.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; +import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; +import {Multisig} from "../src/Multisig.sol"; +import {EmergencyMultisigPluginSetup} from "../src/setup/EmergencyMultisigPluginSetup.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {RATIO_BASE} from "@aragon/osx/plugins/utils/Ratio.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ERC20Mock} from "./mocks/ERC20Mock.sol"; +import {TaikoL1} from "../src/adapted-dependencies/TaikoL1.sol"; + +contract EmergencyMultisigPluginSetupTest is Test { + EmergencyMultisigPluginSetup public pluginSetup; + GovernanceERC20 governanceERC20Base; + GovernanceWrappedERC20 governanceWrappedERC20Base; + address immutable daoBase = address(new DAO()); + address immutable stdMultisigBase = address(new Multisig()); + DAO dao; + + // Recycled installation parameters + EmergencyMultisig.MultisigSettings eMultisigSettings; + address[] stdMembers; + Multisig stdMultisig; + + address alice = address(0xa11ce); + address bob = address(0xb0b); + address carol = address(0xc4601); + address dave = address(0xd473); + + error Unimplemented(); + + function setUp() public { + pluginSetup = new EmergencyMultisigPluginSetup(); + + // Address list source (std multisig) + stdMembers = new address[](4); + stdMembers[0] = alice; + stdMembers[1] = bob; + stdMembers[2] = carol; + stdMembers[3] = dave; + Multisig.MultisigSettings memory stdSettings = + Multisig.MultisigSettings({onlyListed: true, minApprovals: 3, destinationProposalDuration: 10 days}); + stdMultisig = Multisig( + createProxyAndCall( + stdMultisigBase, abi.encodeCall(Multisig.initialize, (IDAO(dao), stdMembers, stdSettings)) + ) + ); + + // Default params + eMultisigSettings = + EmergencyMultisig.MultisigSettings({onlyListed: true, minApprovals: 3, addresslistSource: stdMultisig}); + } + + function test_ShouldEncodeInstallationParameters_1() public view { + // 1 + bytes memory output = pluginSetup.encodeInstallationParameters(eMultisigSettings); + + bytes memory expected = + hex"000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000005991a2df15a8f6a256d3ec51e99254cd3fb576a9"; + assertEq(output, expected, "Incorrect encoded bytes"); + } + + function test_ShouldEncodeInstallationParameters_2() public { + // 2 + stdMembers = new address[](2); + stdMembers[0] = alice; + stdMembers[1] = bob; + Multisig.MultisigSettings memory stdSettings = + Multisig.MultisigSettings({onlyListed: true, minApprovals: 1, destinationProposalDuration: 10 days}); + stdMultisig = Multisig( + createProxyAndCall( + stdMultisigBase, abi.encodeCall(Multisig.initialize, (IDAO(dao), stdMembers, stdSettings)) + ) + ); + + eMultisigSettings = + EmergencyMultisig.MultisigSettings({onlyListed: true, minApprovals: 1, addresslistSource: stdMultisig}); + + bytes memory output = pluginSetup.encodeInstallationParameters(eMultisigSettings); + bytes memory expected = + hex"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c7183455a4c133ae270771860664b6b7ec320bb1"; + assertEq(output, expected, "Incorrect encoded bytes"); + } + + function test_ShouldDecodeInstallationParameters_1() public view { + // 1 + bytes memory installationParams = pluginSetup.encodeInstallationParameters(eMultisigSettings); + + // Decode + (EmergencyMultisig.MultisigSettings memory outSettings) = + pluginSetup.decodeInstallationParameters(installationParams); + + assertEq(outSettings.onlyListed, true, "Should be true"); + assertEq(outSettings.minApprovals, 3, "Should be 3"); + assertEq( + address(outSettings.addresslistSource), + address(eMultisigSettings.addresslistSource), + "Incorrect address list source" + ); + } + + function test_ShouldDecodeInstallationParameters_2() public { + // 2 + + stdMembers = new address[](2); + stdMembers[0] = alice; + stdMembers[1] = bob; + Multisig.MultisigSettings memory stdSettings = + Multisig.MultisigSettings({onlyListed: true, minApprovals: 1, destinationProposalDuration: 10 days}); + stdMultisig = Multisig( + createProxyAndCall( + stdMultisigBase, abi.encodeCall(Multisig.initialize, (IDAO(dao), stdMembers, stdSettings)) + ) + ); + eMultisigSettings = + EmergencyMultisig.MultisigSettings({onlyListed: false, minApprovals: 1, addresslistSource: stdMultisig}); + + bytes memory installationParams = pluginSetup.encodeInstallationParameters(eMultisigSettings); + + // Decode + (EmergencyMultisig.MultisigSettings memory outSettings) = + pluginSetup.decodeInstallationParameters(installationParams); + + assertEq(outSettings.onlyListed, false, "Should be false"); + assertEq(outSettings.minApprovals, 1, "Should be 1"); + assertEq(address(outSettings.addresslistSource), address(stdMultisig), "Incorrect address list source"); + } + + function test_PrepareInstallationReturnsTheProperPermissions() public { + bytes memory installationParams = pluginSetup.encodeInstallationParameters(eMultisigSettings); + + (address _plugin, IPluginSetup.PreparedSetupData memory _preparedSetupData) = + pluginSetup.prepareInstallation(address(dao), installationParams); + + assertEq(_plugin != address(0), true, "Plugin address should not be zero"); + assertEq(_preparedSetupData.helpers.length, 0, "Zero helpers expected"); + assertEq( + _preparedSetupData.permissions.length, + 2, // permissions + "Incorrect permission length" + ); + // 1 + assertEq( + uint256(_preparedSetupData.permissions[0].operation), + uint256(PermissionLib.Operation.Grant), + "Incorrect operation" + ); + assertEq(_preparedSetupData.permissions[0].where, _plugin, "Incorrect where"); + assertEq(_preparedSetupData.permissions[0].who, address(dao), "Incorrect who"); + assertEq(_preparedSetupData.permissions[0].condition, address(0), "Incorrect condition"); + assertEq( + _preparedSetupData.permissions[0].permissionId, + keccak256("UPDATE_MULTISIG_SETTINGS_PERMISSION"), + "Incorrect permission id" + ); + // 2 + assertEq(_preparedSetupData.permissions[1].where, _plugin, "Incorrect where"); + assertEq(_preparedSetupData.permissions[1].who, address(dao), "Incorrect who"); + assertEq(_preparedSetupData.permissions[1].condition, address(0), "Incorrect condition"); + assertEq( + _preparedSetupData.permissions[1].permissionId, + keccak256("UPGRADE_PLUGIN_PERMISSION"), + "Incorrect permission id" + ); + } + + // HELPERS + function createProxyAndCall(address _logic, bytes memory _data) private returns (address) { + return address(new ERC1967Proxy(_logic, _data)); + } +} diff --git a/test/MultisigPluginSetup.t.sol b/test/MultisigPluginSetup.t.sol new file mode 100644 index 0000000..5c5d4c6 --- /dev/null +++ b/test/MultisigPluginSetup.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; +import {Multisig} from "../src/Multisig.sol"; +import {MultisigPluginSetup} from "../src/setup/MultisigPluginSetup.sol"; +import {GovernanceERC20} from "@aragon/osx/token/ERC20/governance/GovernanceERC20.sol"; +import {GovernanceWrappedERC20} from "@aragon/osx/token/ERC20/governance/GovernanceWrappedERC20.sol"; +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {RATIO_BASE} from "@aragon/osx/plugins/utils/Ratio.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IPluginSetup} from "@aragon/osx/framework/plugin/setup/PluginSetup.sol"; +import {PermissionLib} from "@aragon/osx/core/permission/PermissionLib.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ERC20Mock} from "./mocks/ERC20Mock.sol"; +import {TaikoL1} from "../src/adapted-dependencies/TaikoL1.sol"; + +contract MultisigPluginSetupTest is Test { + MultisigPluginSetup public pluginSetup; + GovernanceERC20 governanceERC20Base; + GovernanceWrappedERC20 governanceWrappedERC20Base; + address immutable daoBase = address(new DAO()); + DAO dao; + + // Recycled installation parameters + Multisig.MultisigSettings multisigSettings; + address[] members; + + address alice = address(0xa11ce); + address bob = address(0xb0b); + address carol = address(0xc4601); + address dave = address(0xd473); + + error Unimplemented(); + + function setUp() public { + pluginSetup = new MultisigPluginSetup(); + + // Default params + multisigSettings = + Multisig.MultisigSettings({onlyListed: true, minApprovals: 3, destinationProposalDuration: 10 days}); + + members = new address[](4); + members[0] = alice; + members[1] = bob; + members[2] = carol; + members[3] = dave; + } + + function test_ShouldEncodeInstallationParameters_1() public view { + // 1 + bytes memory output = pluginSetup.encodeInstallationParameters(members, multisigSettings); + + bytes memory expected = + hex"00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000d2f00000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000a11ce0000000000000000000000000000000000000000000000000000000000000b0b00000000000000000000000000000000000000000000000000000000000c4601000000000000000000000000000000000000000000000000000000000000d473"; + assertEq(output, expected, "Incorrect encoded bytes"); + } + + function test_ShouldEncodeInstallationParameters_2() public { + // 2 + multisigSettings = + Multisig.MultisigSettings({onlyListed: true, minApprovals: 1, destinationProposalDuration: 5 days}); + + members = new address[](2); + members[0] = alice; + members[1] = bob; + + bytes memory output = pluginSetup.encodeInstallationParameters(members, multisigSettings); + bytes memory expected = + hex"0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000069780000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000a11ce0000000000000000000000000000000000000000000000000000000000000b0b"; + assertEq(output, expected, "Incorrect encoded bytes"); + } + + function test_ShouldDecodeInstallationParameters_1() public view { + // 1 + bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + + // Decode + (address[] memory outMembers, Multisig.MultisigSettings memory outSettings) = + pluginSetup.decodeInstallationParameters(installationParams); + + assertEq(outMembers.length, 4, "Incorrect length"); + assertEq(outMembers[0], alice, "Incorrect member"); + assertEq(outMembers[1], bob, "Incorrect member"); + assertEq(outMembers[2], carol, "Incorrect member"); + assertEq(outMembers[3], dave, "Incorrect member"); + + assertEq(outSettings.onlyListed, true, "Should be true"); + assertEq(outSettings.minApprovals, 3, "Should be 3"); + assertEq(outSettings.destinationProposalDuration, 10 days, "Should be 10 days"); + } + + function test_ShouldDecodeInstallationParameters_2() public { + // 2 + multisigSettings = + Multisig.MultisigSettings({onlyListed: false, minApprovals: 1, destinationProposalDuration: 5 days}); + + members = new address[](2); + members[0] = alice; + members[1] = bob; + + bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + + // Decode + (address[] memory outMembers, Multisig.MultisigSettings memory outSettings) = + pluginSetup.decodeInstallationParameters(installationParams); + + assertEq(outMembers.length, 2, "Incorrect length"); + assertEq(outMembers[0], alice, "Incorrect member"); + assertEq(outMembers[1], bob, "Incorrect member"); + + assertEq(outSettings.onlyListed, false, "Should be false"); + assertEq(outSettings.minApprovals, 1, "Should be 1"); + assertEq(outSettings.destinationProposalDuration, 5 days, "Should be 5 days"); + } + + function test_PrepareInstallationReturnsTheProperPermissions() public { + bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + + (address _plugin, IPluginSetup.PreparedSetupData memory _preparedSetupData) = + pluginSetup.prepareInstallation(address(dao), installationParams); + + assertEq(_plugin != address(0), true, "Plugin address should not be zero"); + assertEq(_preparedSetupData.helpers.length, 0, "Zero helpers expected"); + assertEq( + _preparedSetupData.permissions.length, + 2, // permissions + "Incorrect permission length" + ); + // 1 + assertEq( + uint256(_preparedSetupData.permissions[0].operation), + uint256(PermissionLib.Operation.Grant), + "Incorrect operation" + ); + assertEq(_preparedSetupData.permissions[0].where, _plugin, "Incorrect where"); + assertEq(_preparedSetupData.permissions[0].who, address(dao), "Incorrect who"); + assertEq(_preparedSetupData.permissions[0].condition, address(0), "Incorrect condition"); + assertEq( + _preparedSetupData.permissions[0].permissionId, + keccak256("UPDATE_MULTISIG_SETTINGS_PERMISSION"), + "Incorrect permission id" + ); + // 2 + assertEq(_preparedSetupData.permissions[1].where, _plugin, "Incorrect where"); + assertEq(_preparedSetupData.permissions[1].who, address(dao), "Incorrect who"); + assertEq(_preparedSetupData.permissions[1].condition, address(0), "Incorrect condition"); + assertEq( + _preparedSetupData.permissions[1].permissionId, + keccak256("UPGRADE_PLUGIN_PERMISSION"), + "Incorrect permission id" + ); + } + + // HELPERS + function createProxyAndCall(address _logic, bytes memory _data) private returns (address) { + return address(new ERC1967Proxy(_logic, _data)); + } +}