From e02aa0b92a2ab531331751e6bf6147365e1c08b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Mon, 14 Oct 2024 11:26:48 +0200 Subject: [PATCH 01/45] Encryption registry --- script/Deploy.s.sol | 4 +-- ...KeyRegistry.sol => EncryptionRegistry.sol} | 4 +-- src/factory/TaikoDaoFactory.sol | 10 +++--- ...egistry.t.sol => EncryptionRegistry.t.sol} | 36 +++++++++---------- test/integration/TaikoDaoFactory.t.sol | 8 ++--- 5 files changed, 31 insertions(+), 31 deletions(-) rename src/{PublicKeyRegistry.sol => EncryptionRegistry.sol} (96%) rename test/{PublicKeyRegistry.t.sol => EncryptionRegistry.t.sol} (87%) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 43bfaee..6132c47 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -25,7 +25,7 @@ contract Deploy is Script { vm.startBroadcast(vm.envUint("DEPLOYMENT_PRIVATE_KEY")); // NOTE: Deploying the plugin setup's separately because of the code size limit - // PublicKeyRegistry and DelegationWall are deployed by the TaikoDaoFactory + // EncryptionRegistry and DelegationWall are deployed by the TaikoDaoFactory // Deploy the plugin setup's multisigPluginSetup = new MultisigPluginSetup(); @@ -77,7 +77,7 @@ contract Deploy is Script { console.log(""); console.log("Helpers"); - console.log("- Public key registry", address(daoDeployment.publicKeyRegistry)); + console.log("- Public key registry", address(daoDeployment.encryptionRegistry)); console.log("- Delegation wall", address(delegationWall)); } diff --git a/src/PublicKeyRegistry.sol b/src/EncryptionRegistry.sol similarity index 96% rename from src/PublicKeyRegistry.sol rename to src/EncryptionRegistry.sol index 25ecdbd..23ac5f4 100644 --- a/src/PublicKeyRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -5,10 +5,10 @@ pragma solidity ^0.8.17; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -/// @title PublicKeyRegistry - Release 1, Build 1 +/// @title EncryptionRegistry - Release 1, Build 1 /// @author Aragon Association - 2024 /// @notice A smart contract where any wallet can register its own libsodium public key for encryption purposes -contract PublicKeyRegistry { +contract EncryptionRegistry { mapping(address => bytes32) public publicKeys; /// @dev Allows to enumerate the wallets that have a public key registered diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol index 00f2f06..72425ee 100644 --- a/src/factory/TaikoDaoFactory.sol +++ b/src/factory/TaikoDaoFactory.sol @@ -5,7 +5,7 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; import {Multisig} from "../Multisig.sol"; import {EmergencyMultisig} from "../EmergencyMultisig.sol"; -import {PublicKeyRegistry} from "../PublicKeyRegistry.sol"; +import {EncryptionRegistry} from "../EncryptionRegistry.sol"; import {OptimisticTokenVotingPlugin} from "../OptimisticTokenVotingPlugin.sol"; import {OptimisticTokenVotingPluginSetup} from "../setup/OptimisticTokenVotingPluginSetup.sol"; import {MultisigPluginSetup} from "../setup/MultisigPluginSetup.sol"; @@ -86,7 +86,7 @@ contract TaikoDaoFactory { PluginRepo emergencyMultisigPluginRepo; PluginRepo optimisticTokenVotingPluginRepo; // Other - PublicKeyRegistry publicKeyRegistry; + EncryptionRegistry encryptionRegistry; } /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. @@ -151,7 +151,7 @@ contract TaikoDaoFactory { revokeOwnerPermission(deployment.dao); // DEPLOY OTHER CONTRACTS - deployment.publicKeyRegistry = deployPublicKeyRegistry(); + deployment.encryptionRegistry = deployEncryptionRegistry(); } function prepareDao() internal returns (DAO dao) { @@ -302,8 +302,8 @@ contract TaikoDaoFactory { return (OptimisticTokenVotingPlugin(plugin), pluginRepo, preparedSetupData); } - function deployPublicKeyRegistry() internal returns (PublicKeyRegistry) { - return new PublicKeyRegistry(deployment.multisigPlugin); + function deployEncryptionRegistry() internal returns (EncryptionRegistry) { + return new EncryptionRegistry(deployment.multisigPlugin); } function applyPluginInstallation( diff --git a/test/PublicKeyRegistry.t.sol b/test/EncryptionRegistry.t.sol similarity index 87% rename from test/PublicKeyRegistry.t.sol rename to test/EncryptionRegistry.t.sol index fcea597..3b8ec2f 100644 --- a/test/PublicKeyRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.17; import {AragonTest} from "./base/AragonTest.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {PublicKeyRegistry} from "../src/PublicKeyRegistry.sol"; +import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../src/Multisig.sol"; contract EmergencyMultisigTest is AragonTest { - PublicKeyRegistry registry; + EncryptionRegistry registry; DaoBuilder builder; Multisig multisig; @@ -21,7 +21,7 @@ contract EmergencyMultisigTest is AragonTest { (,, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember(carol) .withMultisigMember(david).build(); - registry = new PublicKeyRegistry(multisig); + registry = new EncryptionRegistry(multisig); } function test_ShouldRegisterAPublicKey() public { @@ -61,7 +61,7 @@ contract EmergencyMultisigTest is AragonTest { function test_ShouldRevertIfNotASigner() public { (,, multisig,,,) = new DaoBuilder().withMultisigMember(alice).build(); - registry = new PublicKeyRegistry(multisig); + registry = new EncryptionRegistry(multisig); // OK assertEq(registry.publicKeys(alice), 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -77,7 +77,7 @@ contract EmergencyMultisigTest is AragonTest { // Bob vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); @@ -85,7 +85,7 @@ contract EmergencyMultisigTest is AragonTest { // Carol vm.startPrank(carol); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); @@ -94,7 +94,7 @@ contract EmergencyMultisigTest is AragonTest { // David vm.startPrank(david); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); @@ -128,30 +128,30 @@ contract EmergencyMultisigTest is AragonTest { function test_ShouldRevertIfReRegistering() public { vm.startPrank(alice); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); vm.startPrank(bob); registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); vm.startPrank(carol); registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); vm.startPrank(david); registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.AlreadySet.selector)); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); } @@ -218,12 +218,12 @@ contract EmergencyMultisigTest is AragonTest { function test_TheConstructorShouldRevertIfInvalidAddressList() public { // Fail - vm.expectRevert(abi.encodeWithSelector(PublicKeyRegistry.InvalidAddressList.selector)); - new PublicKeyRegistry(Addresslist(address(this))); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.InvalidAddressList.selector)); + new EncryptionRegistry(Addresslist(address(this))); // OK (,, multisig,,,) = new DaoBuilder().withMultisigMember(alice).build(); - new PublicKeyRegistry(multisig); + new EncryptionRegistry(multisig); } /// @dev mock function for test_TheConstructorShouldRevertIfInvalidAddressList() diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index 5b8a681..1feb048 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -517,8 +517,8 @@ contract TaikoDaoFactoryTest is AragonTest { ); // PUBLIC KEY REGISTRY - assertNotEq(address(deployment.publicKeyRegistry), address(0), "Empty publicKeyRegistry field"); - assertEq(deployment.publicKeyRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); + assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); + assertEq(deployment.encryptionRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); } function test_StandardDeployment_2() public { @@ -754,8 +754,8 @@ contract TaikoDaoFactoryTest is AragonTest { ); // PUBLIC KEY REGISTRY - assertNotEq(address(deployment.publicKeyRegistry), address(0), "Empty publicKeyRegistry field"); - assertEq(deployment.publicKeyRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); + assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); + assertEq(deployment.encryptionRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); } function test_MultipleDeploysDoNothing() public { From ee6b44995cbe593dd5ae710ee787bcede2c9af8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Mon, 14 Oct 2024 16:00:46 +0200 Subject: [PATCH 02/45] Encryption registry with partial tests --- src/EncryptionRegistry.sol | 95 ++++-- test/EncryptionRegistry.t.sol | 419 +++++++++++++++++++------ test/integration/TaikoDaoFactory.t.sol | 8 +- 3 files changed, 408 insertions(+), 114 deletions(-) diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index 23ac5f4..11f4968 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -4,24 +4,39 @@ pragma solidity ^0.8.17; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; /// @title EncryptionRegistry - Release 1, Build 1 /// @author Aragon Association - 2024 -/// @notice A smart contract where any wallet can register its own libsodium public key for encryption purposes +/// @notice A smart contract where addresses can register their libsodium public key for encryption purposes, as well as appointing an EOA contract EncryptionRegistry { - mapping(address => bytes32) public publicKeys; + struct RegistryEntry { + address appointedWallet; + bytes32 publicKey; + } + + /// @dev Allows to enumerate the addresses that have a public key registered + address[] public registeredAddresses; - /// @dev Allows to enumerate the wallets that have a public key registered - address[] public registeredWallets; + mapping(address => RegistryEntry) public members; /// @dev The contract to check whether the caller is a multisig member Addresslist addresslistSource; - /// @notice Emitted when a public key is registered - event PublicKeyRegistered(address wallet, bytes32 publicKey); + /// @notice Emitted when a public key is defined + event PublicKeySet(address member, bytes32 publicKey); + + /// @notice Emitted when an externally owned wallet is appointed + event WalletAppointed(address member, address appointedWallet); + + /// @notice Raised when attempting to register a contract instead of a wallet + error CannotAppointContracts(); + + /// @notice Raised when a non appointed wallet tried to define the public key + error NotAppointed(); - /// @notice Raised when the public key of the given user has already been set - error AlreadySet(); + /// @notice Raised when the member attempts to define the public key of the appointed wallet + error OnlyAppointed(); /// @notice Raised when the caller is not a multisig member error RegistrationForbidden(); @@ -37,23 +52,61 @@ contract EncryptionRegistry { addresslistSource = _addresslistSource; } - function setPublicKey(bytes32 _publicKey) public { - if (publicKeys[msg.sender] != 0) revert AlreadySet(); - else if (!addresslistSource.isListed(msg.sender)) revert RegistrationForbidden(); + /// @notice Registers the externally owned wallet's address to use for encryption. This allows smart contracts to appoint an EOA that can decrypt data. + function appointWallet(address _newAddress) public { + if (!addresslistSource.isListed(msg.sender)) revert RegistrationForbidden(); + else if (Address.isContract(_newAddress)) revert CannotAppointContracts(); + + if (members[msg.sender].appointedWallet == address(0) && members[msg.sender].publicKey == bytes32(0)) { + registeredAddresses.push(msg.sender); + } + + if (members[msg.sender].publicKey != bytes32(0)) { + // The old member should no longer be able to see new content + members[msg.sender].publicKey = bytes32(0); + } + members[msg.sender].appointedWallet = _newAddress; + emit WalletAppointed(msg.sender, _newAddress); + } + + /// @notice Registers the given public key as its own target for decrypting messages + function setOwnPublicKey(bytes32 _publicKey) public { + if (!addresslistSource.isListed(msg.sender)) { + revert RegistrationForbidden(); + } else if ( + members[msg.sender].appointedWallet != msg.sender && members[msg.sender].appointedWallet != address(0) + ) { + revert OnlyAppointed(); + } + + _setPublicKey(msg.sender, _publicKey); + } + + /// @notice Registers the given public key as the member's target for decrypting messages. Only if the sender is appointed. + function setPublicKey(address _memberAddress, bytes32 _publicKey) public { + if (!addresslistSource.isListed(_memberAddress)) revert RegistrationForbidden(); + else if (members[_memberAddress].appointedWallet != msg.sender) revert NotAppointed(); + + _setPublicKey(_memberAddress, _publicKey); + } + + function _setPublicKey(address _memberAddress, bytes32 _publicKey) internal { + if (members[_memberAddress].appointedWallet == address(0) && members[_memberAddress].publicKey == bytes32(0)) { + registeredAddresses.push(_memberAddress); + } - publicKeys[msg.sender] = _publicKey; - emit PublicKeyRegistered(msg.sender, _publicKey); - registeredWallets.push(msg.sender); + members[_memberAddress].publicKey = _publicKey; + emit PublicKeySet(_memberAddress, _publicKey); } - /// @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; + /// @notice Returns the list of addresses on the registry + /// @dev Use this function to get all addresses in a single call. You can still call registeredAddresses[idx] to resolve them one by one. + function getRegisteredAddresses() public view returns (address[] memory) { + return registeredAddresses; } - /// @notice Returns the number of publicKey entries available - function registeredWalletCount() public view returns (uint256) { - return registeredWallets.length; + /// @notice Returns the number of addresses registered + function getRegisteredAddressesLength() public view returns (uint256) { + return registeredAddresses.length; } } diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index 3b8ec2f..cefcd45 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -8,7 +8,7 @@ import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../src/Multisig.sol"; -contract EmergencyMultisigTest is AragonTest { +contract EncryptionRegistryTest is AragonTest { EncryptionRegistry registry; DaoBuilder builder; Multisig multisig; @@ -24,191 +24,428 @@ contract EmergencyMultisigTest is AragonTest { registry = new EncryptionRegistry(multisig); } - function test_ShouldRegisterAPublicKey() public { - assertEq(registry.publicKeys(alice), 0x0000000000000000000000000000000000000000000000000000000000000000); + function test_ShouldAppointWallets() public { + address addrValue; + bytes32 bytesValue; + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Alice vm.startPrank(alice); - registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); + registry.appointWallet(address(0x1234000000000000000000000000000000000000)); - assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(alice); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); // Bob vm.startPrank(bob); - registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + registry.appointWallet(address(0x0000567800000000000000000000000000000000)); - assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(alice); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); + (addrValue, bytesValue) = registry.members(bob); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x0000567800000000000000000000000000000000)); // Carol vm.startPrank(carol); - registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); + registry.appointWallet(address(0x0000000090aB0000000000000000000000000000)); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); + (addrValue, bytesValue) = registry.members(bob); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x0000567800000000000000000000000000000000)); + (addrValue, bytesValue) = registry.members(carol); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x0000000090aB0000000000000000000000000000)); + + // David + vm.startPrank(david); + registry.appointWallet(address(0x000000000000cdEf000000000000000000000000)); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); + (addrValue, bytesValue) = registry.members(bob); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x0000567800000000000000000000000000000000)); + (addrValue, bytesValue) = registry.members(carol); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x0000000090aB0000000000000000000000000000)); + (addrValue, bytesValue) = registry.members(david); + assertEq(bytesValue, 0); + assertEq(addrValue, address(0x000000000000cdEf000000000000000000000000)); + } + + function test_ShouldRegisterOwnPublicKeys() public { + address addrValue; + bytes32 bytesValue; + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Alice + vm.startPrank(alice); + registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + + // Bob + vm.startPrank(bob); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(carol), 0x0000000090ab0000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + + // Carol + vm.startPrank(carol); + registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(david); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); + } - assertEq(registry.publicKeys(alice), 0x1234000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(bob), 0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(carol), 0x0000000090ab0000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(david), 0x000000000000cdef000000000000000000000000000000000000000000000000); + function testFuzz_ShouldRegisterMemberPublicKeys(address appointedWallet) public { + if (appointedWallet == address(0)) return; + + address addrValue; + bytes32 bytesValue; + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Alice + vm.startPrank(alice); + registry.appointWallet(address(appointedWallet)); + vm.startPrank(appointedWallet); + registry.setPublicKey(alice, 0x1234000000000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + + // Bob + vm.startPrank(bob); + registry.appointWallet(address(appointedWallet)); + vm.startPrank(appointedWallet); + registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + + // Carol + vm.startPrank(carol); + registry.appointWallet(address(appointedWallet)); + vm.startPrank(appointedWallet); + registry.setPublicKey(carol, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + + // David + vm.startPrank(david); + registry.appointWallet(address(appointedWallet)); + vm.startPrank(appointedWallet); + registry.setPublicKey(david, 0x000000000000cdef000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(david); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } - function test_ShouldRevertIfNotASigner() public { + function test_ShouldWipePublicKeyAfterAppointing(address appointedWallet) public { + if (appointedWallet == address(0)) return; + + address addrValue; + bytes32 bytesValue; + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Alice + vm.startPrank(alice); + registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); + registry.appointWallet(address(appointedWallet)); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + + // Bob + vm.startPrank(bob); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + registry.appointWallet(address(appointedWallet)); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + + // Carol + vm.startPrank(carol); + registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); + registry.appointWallet(address(appointedWallet)); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + + // David + vm.startPrank(david); + registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + registry.appointWallet(address(appointedWallet)); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(david); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); + } + + function test_ShouldRevertWhenAppointingContracts() public { + revert(""); + } + + function test_ShouldRevertIfNotAppointed() public { + revert(""); + } + + function test_ShouldRevertIfNotListed_PublicKeySelf() public { + address addrValue; + bytes32 bytesValue; + (,, multisig,,,) = new DaoBuilder().withMultisigMember(alice).build(); registry = new EncryptionRegistry(multisig); // OK - assertEq(registry.publicKeys(alice), 0x0000000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Alice vm.startPrank(alice); assertEq(multisig.isMember(alice), true); - registry.setPublicKey(0x5678000000000000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x5678000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); // NOT OK // Bob vm.startPrank(bob); vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); - registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(bob), 0x0000000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, bob); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); - registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - - assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(bob), 0x0000000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(carol), 0x0000000000000000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, bob); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, carol); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, bob); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(carol); + assertEq(addrValue, carol); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + (addrValue, bytesValue) = registry.members(david); + assertEq(addrValue, david); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + } + + function test_ShouldRevertIfNotListed_PublicKeyAppointee() public { + revert(""); + } + + function test_ShouldRevertIfNotListed_AppointWallet() public { + revert(""); + } - assertEq(registry.publicKeys(alice), 0x5678000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(bob), 0x0000000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(carol), 0x0000000000000000000000000000000000000000000000000000000000000000); - assertEq(registry.publicKeys(david), 0x0000000000000000000000000000000000000000000000000000000000000000); + function test_PublicKeyShouldBeEmptyAfterAppointing() public { + revert(""); } - function test_ShouldEmitARegistrationEvent() public { + function test_ShouldEmitPublicKeyDefinedEvents() public { + // For itself vm.startPrank(alice); vm.expectEmit(); emit PublicKeyRegistered(alice, 0x000000000000cdef000000000000000000000000000000000000000000000000); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); vm.startPrank(bob); vm.expectEmit(); emit PublicKeyRegistered(bob, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); vm.startPrank(carol); vm.expectEmit(); emit PublicKeyRegistered(carol, 0x0000567800000000000000000000000000000000000000000000000000000000); - registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); vm.startPrank(david); vm.expectEmit(); emit PublicKeyRegistered(david, 0x1234000000000000000000000000000000000000000000000000000000000000); - registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - } - - function test_ShouldRevertIfReRegistering() public { - vm.startPrank(alice); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - - vm.startPrank(bob); - registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - - vm.startPrank(carol); - registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - vm.startPrank(david); - registry.setPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.AlreadySet.selector)); - registry.setPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + // As the appointee + revert("to do"); } function test_ShouldCountRegisteredCandidates() public { - assertEq(registry.registeredWalletCount(), 0, "Incorrect count"); + assertEq(registry.getRegisteredAddressesLength(), 0, "Incorrect count"); // Alice vm.startPrank(alice); - registry.setPublicKey(bytes32(uint256(1234))); - assertEq(registry.registeredWalletCount(), 1, "Incorrect count"); + registry.setOwnPublicKey(bytes32(uint256(1234))); + assertEq(registry.getRegisteredAddressesLength(), 1, "Incorrect count"); // Bob vm.startPrank(bob); - registry.setPublicKey(bytes32(uint256(2345))); - assertEq(registry.registeredWalletCount(), 2, "Incorrect count"); + registry.setOwnPublicKey(bytes32(uint256(2345))); + assertEq(registry.getRegisteredAddressesLength(), 2, "Incorrect count"); // Carol vm.startPrank(carol); - registry.setPublicKey(bytes32(uint256(3456))); - assertEq(registry.registeredWalletCount(), 3, "Incorrect count"); + registry.setOwnPublicKey(bytes32(uint256(3456))); + assertEq(registry.getRegisteredAddressesLength(), 3, "Incorrect count"); // David vm.startPrank(david); - registry.setPublicKey(bytes32(uint256(4567))); - assertEq(registry.registeredWalletCount(), 4, "Incorrect count"); + registry.setOwnPublicKey(bytes32(uint256(4567))); + assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); } function test_ShouldEnumerateRegisteredCandidates() public { // Register vm.startPrank(alice); - registry.setPublicKey(bytes32(uint256(1234))); + registry.setOwnPublicKey(bytes32(uint256(1234))); vm.startPrank(bob); - registry.setPublicKey(bytes32(uint256(2345))); + registry.setOwnPublicKey(bytes32(uint256(2345))); vm.startPrank(carol); - registry.setPublicKey(bytes32(uint256(3456))); + registry.setOwnPublicKey(bytes32(uint256(3456))); vm.startPrank(david); - registry.setPublicKey(bytes32(uint256(4567))); + registry.setOwnPublicKey(bytes32(uint256(4567))); - assertEq(registry.registeredWalletCount(), 4, "Incorrect count"); + assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); - assertEq(registry.registeredWallets(0), alice); - assertEq(registry.registeredWallets(1), bob); - assertEq(registry.registeredWallets(2), carol); - assertEq(registry.registeredWallets(3), david); + assertEq(registry.registeredAddresses(0), alice); + assertEq(registry.registeredAddresses(1), bob); + assertEq(registry.registeredAddresses(2), carol); + assertEq(registry.registeredAddresses(3), david); } function test_ShouldLoadTheRegisteredAddresses() public { vm.startPrank(alice); - registry.setPublicKey(bytes32(uint256(1234))); + registry.setOwnPublicKey(bytes32(uint256(1234))); vm.startPrank(bob); - registry.setPublicKey(bytes32(uint256(2345))); + registry.setOwnPublicKey(bytes32(uint256(2345))); vm.startPrank(carol); - registry.setPublicKey(bytes32(uint256(3456))); + registry.setOwnPublicKey(bytes32(uint256(3456))); vm.startPrank(david); - registry.setPublicKey(bytes32(uint256(4567))); + registry.setOwnPublicKey(bytes32(uint256(4567))); - address[] memory candidates = registry.getRegisteredWallets(); + address[] memory candidates = registry.getRegisteredAddresses(); assertEq(candidates.length, 4); assertEq(candidates[0], alice); assertEq(candidates[1], bob); diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index 1feb048..d18f777 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -518,7 +518,9 @@ contract TaikoDaoFactoryTest is AragonTest { // PUBLIC KEY REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); - assertEq(deployment.encryptionRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); + assertEq( + deployment.encryptionRegistry.getRegisteredAddressesLength(), 0, "Invalid getRegisteredAddressesLength" + ); } function test_StandardDeployment_2() public { @@ -755,7 +757,9 @@ contract TaikoDaoFactoryTest is AragonTest { // PUBLIC KEY REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); - assertEq(deployment.encryptionRegistry.registeredWalletCount(), 0, "Invalid registeredWalletCount"); + assertEq( + deployment.encryptionRegistry.getRegisteredAddressesLength(), 0, "Invalid getRegisteredAddressesLength" + ); } function test_MultipleDeploysDoNothing() public { From 3d51a8e1d0454d4d08772d1fc0f1a4ccfaf65729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Mon, 14 Oct 2024 23:57:54 +0200 Subject: [PATCH 03/45] Tests WIP --- test/EncryptionRegistry.t.sol | 251 ++++++++++++++++++++++++++-------- 1 file changed, 194 insertions(+), 57 deletions(-) diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index cefcd45..1bb8861 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -7,10 +7,12 @@ import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../src/Multisig.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; contract EncryptionRegistryTest is AragonTest { EncryptionRegistry registry; DaoBuilder builder; + DAO dao; Multisig multisig; // Events/errors to be tested here (duplicate) @@ -18,7 +20,7 @@ contract EncryptionRegistryTest is AragonTest { function setUp() public { builder = new DaoBuilder(); - (,, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember(carol) + (dao,, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember(carol) .withMultisigMember(david).build(); registry = new EncryptionRegistry(multisig); @@ -154,7 +156,7 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); - registry.appointWallet(address(appointedWallet)); + registry.appointWallet(appointedWallet); vm.startPrank(appointedWallet); registry.setPublicKey(alice, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -164,7 +166,7 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); - registry.appointWallet(address(appointedWallet)); + registry.appointWallet(appointedWallet); vm.startPrank(appointedWallet); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); @@ -177,7 +179,7 @@ contract EncryptionRegistryTest is AragonTest { // Carol vm.startPrank(carol); - registry.appointWallet(address(appointedWallet)); + registry.appointWallet(appointedWallet); vm.startPrank(appointedWallet); registry.setPublicKey(carol, 0x0000000090ab0000000000000000000000000000000000000000000000000000); @@ -193,7 +195,7 @@ contract EncryptionRegistryTest is AragonTest { // David vm.startPrank(david); - registry.appointWallet(address(appointedWallet)); + registry.appointWallet(appointedWallet); vm.startPrank(appointedWallet); registry.setPublicKey(david, 0x000000000000cdef000000000000000000000000000000000000000000000000); @@ -211,8 +213,9 @@ contract EncryptionRegistryTest is AragonTest { assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } - function test_ShouldWipePublicKeyAfterAppointing(address appointedWallet) public { + function test_ShouldClearPublicKeyAfterAppointing(address appointedWallet) public { if (appointedWallet == address(0)) return; + else if (Address.isContract(appointedWallet)) return; address addrValue; bytes32 bytesValue; @@ -224,146 +227,255 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(appointedWallet)); + (addrValue, bytesValue) = registry.members(alice); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(appointedWallet); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Bob vm.startPrank(bob); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(appointedWallet)); + (addrValue, bytesValue) = registry.members(bob); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(appointedWallet); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(bob); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(appointedWallet)); + (addrValue, bytesValue) = registry.members(carol); + assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + + registry.appointWallet(appointedWallet); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(bob); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(carol); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - registry.appointWallet(address(appointedWallet)); + (addrValue, bytesValue) = registry.members(david); + assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); + + registry.appointWallet(appointedWallet); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(bob); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(carol); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(david); assertEq(addrValue, appointedWallet); - assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } function test_ShouldRevertWhenAppointingContracts() public { - revert(""); - } + address addrValue; + bytes32 bytesValue; - function test_ShouldRevertIfNotAppointed() public { - revert(""); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + vm.startPrank(alice); + + // OK + registry.appointWallet(address(0x1234)); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0x1234)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // OK + registry.appointWallet(bob); + registry.appointWallet(carol); + registry.appointWallet(david); + + // KO + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.CannotAppointContracts.selector)); + registry.appointWallet(address(dao)); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, david); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // KO + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.CannotAppointContracts.selector)); + registry.appointWallet(address(multisig)); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, david); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // KO + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.CannotAppointContracts.selector)); + registry.appointWallet(address(registry)); + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, david); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } - function test_ShouldRevertIfNotListed_PublicKeySelf() public { + function test_ShouldRevertIfNotListed(address appointedWallet) public { + if (Address.isContract(appointedWallet)) return; + address addrValue; bytes32 bytesValue; + // Only Alice (,, multisig,,,) = new DaoBuilder().withMultisigMember(alice).build(); - registry = new EncryptionRegistry(multisig); - // OK (addrValue, bytesValue) = registry.members(alice); - assertEq(addrValue, alice); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + // OK + // Alice vm.startPrank(alice); assertEq(multisig.isMember(alice), true); registry.setOwnPublicKey(0x5678000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(alice); - assertEq(addrValue, alice); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + // Appoint self + registry.appointWallet(alice); + vm.startPrank(alice); + registry.setPublicKey(alice, 0x1234000000000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + // NOT OK // Bob vm.startPrank(bob); vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + registry.appointWallet(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + vm.startPrank(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + registry.setPublicKey(bob, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, alice); - assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(bob); - assertEq(addrValue, bob); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); - registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); + registry.appointWallet(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + vm.startPrank(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + registry.setPublicKey(carol, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, alice); - assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(bob); - assertEq(addrValue, bob); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(carol); - assertEq(addrValue, carol); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); - registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); + registry.appointWallet(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + vm.startPrank(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + registry.setPublicKey(david, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(alice); assertEq(addrValue, alice); - assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(bob); - assertEq(addrValue, bob); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(carol); - assertEq(addrValue, carol); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.members(david); - assertEq(addrValue, david); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } - function test_ShouldRevertIfNotListed_PublicKeyAppointee() public { - revert(""); - } + function test_ShouldRevertIfNotAppointed(address appointedWallet) public { + if (Address.isContract(appointedWallet)) return; - function test_ShouldRevertIfNotListed_AppointWallet() public { - revert(""); - } + address addrValue; + bytes32 bytesValue; - function test_PublicKeyShouldBeEmptyAfterAppointing() public { - revert(""); + // Alice + vm.startPrank(alice); + + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.NotAppointed.selector)); + registry.setPublicKey(alice, 0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(appointedWallet); + + // Appointed + vm.startPrank(appointedWallet); + registry.setPublicKey(alice, 0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + + // Bob + vm.startPrank(bob); + + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.NotAppointed.selector)); + registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(appointedWallet); + + // Appointed + vm.startPrank(appointedWallet); + registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); } function test_ShouldEmitPublicKeyDefinedEvents() public { @@ -389,10 +501,33 @@ contract EncryptionRegistryTest is AragonTest { registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); // As the appointee - revert("to do"); + vm.startPrank(alice); + registry.appointWallet(alice); // Self + vm.expectEmit(); + emit PublicKeyRegistered(alice, 0x0000000000000000cdef00000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000000000000000cdef00000000000000000000000000000000000000000000); + + vm.startPrank(bob); + registry.appointWallet(bob); // Self + vm.expectEmit(); + emit PublicKeyRegistered(bob, 0x00000000000090ab000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x00000000000090ab000000000000000000000000000000000000000000000000); + + vm.startPrank(carol); + registry.appointWallet(carol); // Self + vm.expectEmit(); + emit PublicKeyRegistered(carol, 0x0000000056780000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000000056780000000000000000000000000000000000000000000000000000); + + vm.startPrank(david); + registry.appointWallet(david); // Self + vm.expectEmit(); + emit PublicKeyRegistered(david, 0x0000123400000000000000000000000000000000000000000000000000000000); + registry.setOwnPublicKey(0x0000123400000000000000000000000000000000000000000000000000000000); } - function test_ShouldCountRegisteredCandidates() public { + function test_ShouldCountRegisteredAddresses() public { + vm.skip(true); assertEq(registry.getRegisteredAddressesLength(), 0, "Incorrect count"); // Alice @@ -416,7 +551,8 @@ contract EncryptionRegistryTest is AragonTest { assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); } - function test_ShouldEnumerateRegisteredCandidates() public { + function test_ShouldEnumerateRegisteredAddresses() public { + vm.skip(true); // Register vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); @@ -436,6 +572,7 @@ contract EncryptionRegistryTest is AragonTest { } function test_ShouldLoadTheRegisteredAddresses() public { + vm.skip(true); vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); vm.startPrank(bob); @@ -445,12 +582,12 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(david); registry.setOwnPublicKey(bytes32(uint256(4567))); - address[] memory candidates = registry.getRegisteredAddresses(); - assertEq(candidates.length, 4); - assertEq(candidates[0], alice); - assertEq(candidates[1], bob); - assertEq(candidates[2], carol); - assertEq(candidates[3], david); + address[] memory addresses = registry.getRegisteredAddresses(); + assertEq(addresses.length, 4); + assertEq(addresses[0], alice); + assertEq(addresses[1], bob); + assertEq(addresses[2], carol); + assertEq(addresses[3], david); } function test_TheConstructorShouldRevertIfInvalidAddressList() public { From cebb7a5aaeee3872175cff61436363ea58d1ad9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 15 Oct 2024 00:26:54 +0200 Subject: [PATCH 04/45] Encryption registry tests ok --- src/EncryptionRegistry.sol | 4 +- test/EmergencyMultisig.t.sol | 6 +- test/EncryptionRegistry.t.sol | 147 +++++++++++++++++++++++++++++----- 3 files changed, 131 insertions(+), 26 deletions(-) diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index 11f4968..db5edac 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -36,7 +36,7 @@ contract EncryptionRegistry { error NotAppointed(); /// @notice Raised when the member attempts to define the public key of the appointed wallet - error OnlyAppointed(); + error OwnerNotAppointed(); /// @notice Raised when the caller is not a multisig member error RegistrationForbidden(); @@ -76,7 +76,7 @@ contract EncryptionRegistry { } else if ( members[msg.sender].appointedWallet != msg.sender && members[msg.sender].appointedWallet != address(0) ) { - revert OnlyAppointed(); + revert OwnerNotAppointed(); } _setPublicKey(msg.sender, _publicKey); diff --git a/test/EmergencyMultisig.t.sol b/test/EmergencyMultisig.t.sol index f4f63ab..08a2eea 100644 --- a/test/EmergencyMultisig.t.sol +++ b/test/EmergencyMultisig.t.sol @@ -968,7 +968,7 @@ contract EmergencyMultisigTest is AragonTest { proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); address[] memory signers = new address[](1); - signers[0] = address(0x0); + signers[0] = alice; stdMultisig = Multisig( createProxyAndCall( @@ -994,12 +994,12 @@ contract EmergencyMultisigTest is AragonTest { uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); // ko - if (randomWallet != address(0x0)) { + if (randomWallet != alice) { assertEq(eMultisig.canApprove(pid, randomWallet), false, "Should be false"); } // static ok - assertEq(eMultisig.canApprove(pid, address(0)), true, "Should be true"); + assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); } function test_CanApproveReturnsFalseIfApproved() public { diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index 1bb8861..d74073b 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -16,7 +16,8 @@ contract EncryptionRegistryTest is AragonTest { Multisig multisig; // Events/errors to be tested here (duplicate) - event PublicKeyRegistered(address wallet, bytes32 publicKey); + event PublicKeySet(address member, bytes32 publicKey); + event WalletAppointed(address member, address appointedWallet); function setUp() public { builder = new DaoBuilder(); @@ -145,7 +146,7 @@ contract EncryptionRegistryTest is AragonTest { } function testFuzz_ShouldRegisterMemberPublicKeys(address appointedWallet) public { - if (appointedWallet == address(0)) return; + if (Address.isContract(appointedWallet)) return; address addrValue; bytes32 bytesValue; @@ -431,7 +432,7 @@ contract EncryptionRegistryTest is AragonTest { assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } - function test_ShouldRevertIfNotAppointed(address appointedWallet) public { + function test_ShouldRevertOnSetPublicKeyIfNotAppointed(address appointedWallet) public { if (Address.isContract(appointedWallet)) return; address addrValue; @@ -478,90 +479,170 @@ contract EncryptionRegistryTest is AragonTest { assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); } + function test_ShouldRevertIfOwnerNotAppointed(address appointedWallet) public { + if (appointedWallet == address(0)) return; + else if (Address.isContract(appointedWallet)) return; + + address addrValue; + bytes32 bytesValue; + + // Alice + vm.startPrank(alice); + registry.appointWallet(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.OwnerNotAppointed.selector)); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Appointed + registry.appointWallet(alice); + registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(alice); + assertEq(addrValue, alice); + assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); + + // Bob + vm.startPrank(bob); + registry.appointWallet(appointedWallet); + vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.OwnerNotAppointed.selector)); + registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, appointedWallet); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Appointed + registry.appointWallet(bob); + registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); + + (addrValue, bytesValue) = registry.members(bob); + assertEq(addrValue, bob); + assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); + } + function test_ShouldEmitPublicKeyDefinedEvents() public { // For itself vm.startPrank(alice); vm.expectEmit(); - emit PublicKeyRegistered(alice, 0x000000000000cdef000000000000000000000000000000000000000000000000); + emit PublicKeySet(alice, 0x000000000000cdef000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); vm.startPrank(bob); vm.expectEmit(); - emit PublicKeyRegistered(bob, 0x0000000090ab0000000000000000000000000000000000000000000000000000); + emit PublicKeySet(bob, 0x0000000090ab0000000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); vm.startPrank(carol); vm.expectEmit(); - emit PublicKeyRegistered(carol, 0x0000567800000000000000000000000000000000000000000000000000000000); + emit PublicKeySet(carol, 0x0000567800000000000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); vm.startPrank(david); vm.expectEmit(); - emit PublicKeyRegistered(david, 0x1234000000000000000000000000000000000000000000000000000000000000); + emit PublicKeySet(david, 0x1234000000000000000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); // As the appointee vm.startPrank(alice); registry.appointWallet(alice); // Self vm.expectEmit(); - emit PublicKeyRegistered(alice, 0x0000000000000000cdef00000000000000000000000000000000000000000000); + emit PublicKeySet(alice, 0x0000000000000000cdef00000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x0000000000000000cdef00000000000000000000000000000000000000000000); vm.startPrank(bob); registry.appointWallet(bob); // Self vm.expectEmit(); - emit PublicKeyRegistered(bob, 0x00000000000090ab000000000000000000000000000000000000000000000000); + emit PublicKeySet(bob, 0x00000000000090ab000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x00000000000090ab000000000000000000000000000000000000000000000000); vm.startPrank(carol); registry.appointWallet(carol); // Self vm.expectEmit(); - emit PublicKeyRegistered(carol, 0x0000000056780000000000000000000000000000000000000000000000000000); + emit PublicKeySet(carol, 0x0000000056780000000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x0000000056780000000000000000000000000000000000000000000000000000); vm.startPrank(david); registry.appointWallet(david); // Self vm.expectEmit(); - emit PublicKeyRegistered(david, 0x0000123400000000000000000000000000000000000000000000000000000000); + emit PublicKeySet(david, 0x0000123400000000000000000000000000000000000000000000000000000000); registry.setOwnPublicKey(0x0000123400000000000000000000000000000000000000000000000000000000); } function test_ShouldCountRegisteredAddresses() public { - vm.skip(true); assertEq(registry.getRegisteredAddressesLength(), 0, "Incorrect count"); + // Set public key first + // Alice vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); assertEq(registry.getRegisteredAddressesLength(), 1, "Incorrect count"); + registry.appointWallet(address(0x1234)); + assertEq(registry.getRegisteredAddressesLength(), 1, "Incorrect count"); // Bob vm.startPrank(bob); registry.setOwnPublicKey(bytes32(uint256(2345))); assertEq(registry.getRegisteredAddressesLength(), 2, "Incorrect count"); + registry.appointWallet(address(0x5678)); + assertEq(registry.getRegisteredAddressesLength(), 2, "Incorrect count"); + + // Appoint first // Carol vm.startPrank(carol); - registry.setOwnPublicKey(bytes32(uint256(3456))); + registry.appointWallet(address(0x90ab)); + assertEq(registry.getRegisteredAddressesLength(), 3, "Incorrect count"); + registry.appointWallet(carol); + registry.setPublicKey(carol, bytes32(uint256(3456))); assertEq(registry.getRegisteredAddressesLength(), 3, "Incorrect count"); // David vm.startPrank(david); - registry.setOwnPublicKey(bytes32(uint256(4567))); + registry.appointWallet(address(0xcdef)); + assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); + registry.appointWallet(david); + registry.setPublicKey(david, bytes32(uint256(4567))); assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); } function test_ShouldEnumerateRegisteredAddresses() public { - vm.skip(true); - // Register + // Set public key first + + // Alice vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); + assertEq(registry.registeredAddresses(0), alice); + registry.appointWallet(address(0x1234)); + assertEq(registry.registeredAddresses(0), alice); + + // Bob vm.startPrank(bob); registry.setOwnPublicKey(bytes32(uint256(2345))); + assertEq(registry.registeredAddresses(1), bob); + registry.appointWallet(address(0x5678)); + assertEq(registry.registeredAddresses(1), bob); + + // Appoint first + + // Carol vm.startPrank(carol); - registry.setOwnPublicKey(bytes32(uint256(3456))); + registry.appointWallet(address(0x90ab)); + assertEq(registry.registeredAddresses(2), carol); + registry.appointWallet(carol); + registry.setPublicKey(carol, bytes32(uint256(3456))); + assertEq(registry.registeredAddresses(2), carol); + + // David vm.startPrank(david); - registry.setOwnPublicKey(bytes32(uint256(4567))); + registry.appointWallet(address(0xcdef)); + assertEq(registry.registeredAddresses(3), david); + registry.appointWallet(david); + registry.setPublicKey(david, bytes32(uint256(4567))); + assertEq(registry.registeredAddresses(3), david); assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); @@ -572,15 +653,39 @@ contract EncryptionRegistryTest is AragonTest { } function test_ShouldLoadTheRegisteredAddresses() public { - vm.skip(true); + // Set public key first + + // Alice vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); + assertEq(registry.registeredAddresses(0), alice); + registry.appointWallet(address(0x1234)); + assertEq(registry.registeredAddresses(0), alice); + + // Bob vm.startPrank(bob); registry.setOwnPublicKey(bytes32(uint256(2345))); + assertEq(registry.registeredAddresses(1), bob); + registry.appointWallet(address(0x5678)); + assertEq(registry.registeredAddresses(1), bob); + + // Appoint first + + // Carol vm.startPrank(carol); - registry.setOwnPublicKey(bytes32(uint256(3456))); + registry.appointWallet(address(0x90ab)); + assertEq(registry.registeredAddresses(2), carol); + registry.appointWallet(carol); + registry.setPublicKey(carol, bytes32(uint256(3456))); + assertEq(registry.registeredAddresses(2), carol); + + // David vm.startPrank(david); - registry.setOwnPublicKey(bytes32(uint256(4567))); + registry.appointWallet(address(0xcdef)); + assertEq(registry.registeredAddresses(3), david); + registry.appointWallet(david); + registry.setPublicKey(david, bytes32(uint256(4567))); + assertEq(registry.registeredAddresses(3), david); address[] memory addresses = registry.getRegisteredAddresses(); assertEq(addresses.length, 4); From 1b8cfac6f58b0ec42d74ac8ed45e405e2521988d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 15 Oct 2024 10:53:21 +0200 Subject: [PATCH 05/45] Final touches --- README.md | 10 ++++------ script/Deploy.s.sol | 2 +- src/EncryptionRegistry.sol | 2 +- test/integration/TaikoDaoFactory.t.sol | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5d756b4..2e85300 100644 --- a/README.md +++ b/README.md @@ -69,15 +69,13 @@ The Emergency Multisig settings are the same as for the standard Multisig. - The plugin can only create proposals on the [Optimistic Token Voting plugin](#optimistic-token-voting-plugin) provided that the `duration` is equal or greater than the minimum defined - The DAO can update the plugin settings -## Public Key Registry +## Encryption Registry -This is a helper contract that allows Security Council members to register the public key of their deterministic ephemeral wallet. The available public keys will be used to encrypt the proposal metadata and actions. Refer to the UI repository for the encryption details. +This is a helper contract that allows Security Council members to register the public key of their deterministic ephemeral wallet. The available public keys will be used to encrypt the proposal metadata and actions. -NOTE: A published public key cannot be changed once published. +Given that smart contracts cannot possibly sign or decrypt data, the encryption registry allows to appoint an EOA as the end target for encryption purposes. This is useful for organizations not wanting to rely on just a single wallet. -- A wallet can only generate one derived key pair. -- Public key registration is an automated process. No human error should be possible. -- Altering an encryption key is a strange edge case of which the rest of signers should be aware of. +Refer to the UI repository for the encryption details. ## Delegation Wall diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 6132c47..f5066ca 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -77,7 +77,7 @@ contract Deploy is Script { console.log(""); console.log("Helpers"); - console.log("- Public key registry", address(daoDeployment.encryptionRegistry)); + console.log("- Encryption registry", address(daoDeployment.encryptionRegistry)); console.log("- Delegation wall", address(delegationWall)); } diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index db5edac..b3b0351 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -15,7 +15,7 @@ contract EncryptionRegistry { bytes32 publicKey; } - /// @dev Allows to enumerate the addresses that have a public key registered + /// @dev Allows to enumerate the addresses that have a encryption registered address[] public registeredAddresses; mapping(address => RegistryEntry) public members; diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index d18f777..403eac0 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -516,7 +516,7 @@ contract TaikoDaoFactoryTest is AragonTest { "Invalid optimisticTokenVotingPluginSetup" ); - // PUBLIC KEY REGISTRY + // ENCRYPTION REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); assertEq( deployment.encryptionRegistry.getRegisteredAddressesLength(), 0, "Invalid getRegisteredAddressesLength" @@ -755,7 +755,7 @@ contract TaikoDaoFactoryTest is AragonTest { "Invalid optimisticTokenVotingPluginSetup" ); - // PUBLIC KEY REGISTRY + // ENCRYPTION REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); assertEq( deployment.encryptionRegistry.getRegisteredAddressesLength(), 0, "Invalid getRegisteredAddressesLength" From 056d02fd721e7f01a0f84e712a798dfa826d64c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 17 Oct 2024 12:05:11 +0200 Subject: [PATCH 06/45] Deployment info --- DEPLOYMENTS.md | 109 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/DEPLOYMENTS.md b/DEPLOYMENTS.md index 682d4d7..b5ea6e7 100644 --- a/DEPLOYMENTS.md +++ b/DEPLOYMENTS.md @@ -4,6 +4,39 @@ ## Holesly +### October 16th 2024 + +Deployment for internal testing: +- Exit window of 2h +- L2 disabled +- Using a pre-release voting token + +``` +Chain ID: 17000 +Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab +Using production settings + +Factory: 0xFC84a8516Cc08F7cAB9633C900eB7E54811533Cd + +DAO: 0x7A1a8393678cFB7C72d9C3Ed0Db69F7A336224b7 +Voting token: 0x7dbcF74e44EFc5eC635f40c962d90F2EeD81069a +Taiko Bridge: 0xA098b76a3Dd499D3F6D58D8AcCaFC8efBFd06807 + +Plugins +- Multisig plugin: 0x3952b0de6537866d872331d529357C23427cf364 +- Emergency multisig plugin: 0x38aC34F55A0712C101697360118fEC35AeC777C9 +- Optimistic token voting plugin: 0xd0E3fC86DD0AdA97aC2a3432b75BE31b0e1E900F + +Plugin repositories +- Multisig plugin repository: 0xa77DDA30b1a0AbAa837212C458C46a1Ae8a60Cc6 +- Emergency multisig plugin repository: 0x875A8BBac6880c965844f4d3935fD892C8f3F931 +- Optimistic token voting plugin repository: 0xF03e700D8C08c8c50BB5e7C7165342858172E65a + +Helpers +- Encryption registry 0xD0D409d0048F998fb58a6b352Cf58239c5168d53 +- Delegation wall 0x0470d887b19cf877949A5Bc227042DFfAa3d7752 +``` + ### August 1st 2024 Deployment for internal testing, with L2 voting disabled and using a pre-release voting token. @@ -12,23 +45,23 @@ Deployment for internal testing, with L2 voting disabled and using a pre-release Chain ID: 17000 Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Using production settings - + Factory: 0xC06F1a08fBacF5895aDe3EFB137Dc2Cc2dA7b3B9 - + DAO: 0xC38fFd23688cF6f70b61C7FD6ca6D7D2C84Ef252 Voting token: 0x7dbcF74e44EFc5eC635f40c962d90F2EeD81069a Taiko Bridge: 0xA098b76a3Dd499D3F6D58D8AcCaFC8efBFd06807 - + Plugins - Multisig plugin: 0x038FdE3344EfFe37A4575cA1276f1982A43ce9dF - Emergency multisig plugin: 0x0fC611670228A61824c317926f30e8a2615aa1A3 - Optimistic token voting plugin: 0x619d6661eA06b917e26694f23c5Bb32fa0456773 - + Plugin repositories - Multisig plugin repository: 0xcba5780F2054BB9FAEA4f55047bdcD5828704829 - Emergency multisig plugin repository: 0x175749Dec3157ADFf45D20abF61F8Cf9c17D16Af - Optimistic token voting plugin repository: 0x8D762BdEb9582b782D2955C3C6701Fc1a89fe8FD - + Helpers - Public key registry 0x9695520e32F85eF403f6B18b8a94e44A90D5cBF0 - Delegation wall 0x15B379C5c9115e645Cdf1EF9fA03389586AfEa2A @@ -43,18 +76,18 @@ Deployment for internal testing, with L2 voting disabled and using a test voting Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Using internal testing settings Minting test tokens for the multisig members and the bridge - + Factory: 0xF9Be929F990F9C8bF9ed355Ddd29Af7bd9995890 - + DAO: 0xeB4586617089270Fe042F69Bf799590AF224807a Voting token: 0x12b2574840dB17C2278d9725a2679E97FE266075 Taiko Bridge: 0x0000000000000000000000000000001234567890 - + Plugins - Multisig plugin: 0xd8Fe1194Cf90eF38b54A110EcfeAE8F2AA5Dfe86 - Emergency multisig plugin: 0xeCBa720A8645B198b2637f6559B9155E4bc3B566 - Optimistic token voting plugin: 0xd9F6A2533efab98bA016Cb1D3001b6Ec1C246485 - + Plugin repositories - Multisig plugin repository: 0xa51B2d7b7847cFB666919301e03f48b596A15871 - Emergency multisig plugin repository: 0x2ce4e91D1a00c42736730B494Ab9BFfbfEDdF2ac @@ -74,23 +107,23 @@ Deployment for internal testing, targeting test dependencies. Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Using internal testing settings Minting test tokens for the multisig members and the bridge - + Factory: 0x151dB38A460F3c4F9F377cf040A5Ed5D9958940D - + DAO: 0x192206aA5807ADef5C6C32ffBA2C6dA8e4473e9e Voting token: 0xA8888c98205B146804798B4dA1411288B5E8bb1C Taiko Bridge: 0x0000000000000000000000000000001234567890 - + Plugins - Multisig plugin: 0xd3e68dB8B60120D79032E8eb84c620CE6D9D6258 - Emergency multisig plugin: 0x155f75684Ed220D78634432F892D61b8B7D592B5 - Optimistic token voting plugin: 0x4f438847492002FF84B3735e1da8E65fADD18271 - + Plugin repositories - Multisig plugin repository: 0xC16d70743046b3478728eE22Ca3110515Fa05718 - Emergency multisig plugin repository: 0x20235f476181a8C3b5121e36EAb13e4Bf6A65cD4 - Optimistic token voting plugin repository: 0xa03ef51E9cCBe245BF2A7bF431eE0A81908d1e84 - + Helpers - Public key registry 0xB96057cC9A2bb13C837d88d10370A804Efe68396 - Delegation wall 0xE1A79CCd6d5Dda5dCfCC4B2aaCfE458A82B2F914 @@ -105,23 +138,23 @@ Deployment for internal testing. Targetting Taiko's deployment. Chain ID: 17000 Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Using production settings - + Factory: 0x30435F686dA174f5B646E75684A0795F6A06d0C8 - + DAO: 0xcB10AB2E59Ac73e202adE31531462F7a75cfe74C Voting token: 0x6490E12d480549D333499236fF2Ba6676C296011 Taiko Bridge: 0xA098b76a3Dd499D3F6D58D8AcCaFC8efBFd06807 - + Plugins - Multisig plugin: 0x9d2f62109CE2fDb3FaE58f14D2c1CedFdc7939f9 - Emergency multisig plugin: 0x2198F07F02b2D7365C7Df8C488741B43EE076f83 - Optimistic token voting plugin: 0x799A3D93DB762A838F41Dd956857463AC9D245d7 - + Plugin repositories - Multisig plugin repository: 0xA16B5FD427EA11f171104945B6360793C801766B - Emergency multisig plugin repository: 0x5644C0B88a571B35C0AaA2F9378A06F60f04A927 - Optimistic token voting plugin repository: 0x48309dCFc32eBB1CB6DbA9169F8259f35d4fE993 - + Helpers - Public key registry 0x054098E107FCd07d1C3D0F97Ba8217CE85AaC3ca - Delegation wall 0x9A118b78dE4b3c91706f45Bb8686f678d5600500 @@ -135,23 +168,23 @@ Deployment intended for staging purposes. Chain ID: 17000 Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Minting test tokens for the multisig members and the bridge - + Factory: 0x2799EBD75fA793b93c4feBdb134b3b6Cbbb32124 - + DAO: 0xa0FDC6b2bf9FFd48D4F86b697761F13b32D0b7A1 Voting token: 0x01aeE1a16C8807DF52f2DA9191Cec8058e747F4A Taiko Bridge: 0x0000000000000000000000000000001234567890 - + Plugins - Multisig plugin: 0x284F47A42f1Eb96f0F1540931F8Ef04F4243Fb33 - Emergency multisig plugin: 0x0E09bFDA087cf60Bd03A767A03bf88e9E3824c39 - Optimistic token voting plugin: 0xf52B4681F1eB88C5b028510a3F365b5d04fa3295 - + Plugin repositories - Multisig plugin repository: 0x00fD4E0093a885F20208308C996461dbD93d3604 - Emergency multisig plugin repository: 0xb17469b843Ec56Bd75b118b461C07BA520f792d1 - Optimistic token voting plugin repository: 0xd49028E41E941296A48e5b1733bBDA857509FD1b - + Helpers - Public key registry 0x3b1a9c9198eF98d987A6361219FC59c3F805537d - Delegation wall 0xfdFd89FA33B92Cd1c49A2Ae452294Bc2C89f810D @@ -166,21 +199,21 @@ Used for internal development, using a different Taiko Bridge address. Chain ID: 17000 Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Minting test tokens for the multisig members and the bridge - + Factory contract: 0x57B11BfBEEc6935b307abF8a9c8Ce0DE8DB1868C DAO contract: 0xfCb5AC35C8Ab27c8f6B277a2963e7352f71ca993 Voting token: 0xD2275fEdcE5defbCccA4C29EE058455288248F84 Taiko Bridge: 0x0000000000000000000000000000001234567890 - + - Multisig plugin: 0x9cBDcae87CBE9bdbb9A882A551F4A3F20D007033 - Emergency multisig plugin: 0x456349f1F6621604536E99dB591EBD94e00d94F6 - Optimistic token voting plugin: 0xF9b68bD4a57281f3Ae8FE9A4600BD516fc7938c5 - + - Multisig plugin repository: 0xF5625F767D06814Becd2e4d224629dBA589c905E - Emergency multisig plugin repository: 0x920adce1a42A07E6A167A39a94194739e7602e55 - Optimistic token voting plugin repository: 0xd26d960b2BbfD0efcC16659f804A636c6B46bBce - + Helpers: - Public key registry 0x71D886c82694828f223136d6db18A3603ed8110e - Delegation wall 0xdeb0377b711DbA11d4f6B90EC2153256B8E17fd8 @@ -191,22 +224,22 @@ Used for internal development. ``` Chain ID: 17000 - + Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Minting test tokens for the multisig members and the bridge Test voting token: 0x53bbA0e878a73013AA0B1Dc6e6c4ea9691182E04 Factory contract: 0x06D323915f7057e32B0560b95A298c5a2Fe80C8d - + DAO contract: 0xC373851C8a42D0c9120f5bd6c218693CFED068C1 - + - Multisig plugin: 0x754C929002d09d09610831F81263Bb5A43Ea0865 - Emergency multisig plugin: 0x21B1eeb7A9ff58e4422eB2a06A8b2b2ceb0aC581 - Optimistic token voting plugin: 0x14DCBE5aAF3Ce2998E93f98DcFAB1cbd198D1257 - + - Multisig plugin repository: 0x494d47d419c2b48e3f888066FAf210DD32BFA1b6 - Emergency multisig plugin repository: 0xcA7404c1dDD5cb817E94F970256972b277F82f80 - Optimistic token voting plugin repository: 0xAe66318a5941712A80eA7B6e2F96C23B071816E5 - + Public key registry 0x683C6B9c550870423cEc58f6cedd78BCE36Fd7f1 Delegation wall 0x291aAE5fCAbBbD19A1b64F93338B71343E2AD740 ``` @@ -216,22 +249,22 @@ Used as a staging deployment. ``` Chain ID: 17000 - + Deploying from: 0x424797Ed6d902E17b9180BFcEF452658e148e0Ab Minting test tokens for the multisig members and the bridge Test voting token: 0xa95BADd91beB92F364905187eCB08B80220d5FA3 Factory contract: 0xFbA94606d10e807Bf6542C19a68DfEa815a4eeC3 - + DAO contract: 0xdA69Bd97278c409574AdC39295465A848C82CD16 - + - Multisig plugin: 0x2a22Fc29dE8944E62227bf75C89cA2e8CE9BA274 - Emergency multisig plugin: 0x7C36a0F03c27880C23f5704296Bc18Bfc33A7f59 - Optimistic token voting plugin: 0x40CD85d43B883C83290ed5D18400C640176A9679 - + - Multisig plugin repository: 0x307d009483C1b8Ef3C91F6ae748385Bf0936C59e - Emergency multisig plugin repository: 0x8181da2e9b1a428a4cF60fF6CEFc0098c1298aaA - Optimistic token voting plugin repository: 0x0847F2531e070353297fc3D7fFDB4656C1664c6d - + Public key registry 0x7A9577A02608446022F52984435ce1ca632BA629 Delegation wall 0xE917426E10a54FbF22FDAF32A4151c90550e1cA5 ``` From 91613c28f93cf2be6cc17e4a10cb3bc487f16be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 17 Oct 2024 18:45:18 +0200 Subject: [PATCH 07/45] WIP: Swapping the address list source to the emergency multisig --- src/EmergencyMultisig.sol | 86 ++++++++++++++++------ src/EncryptionRegistry.sol | 72 ++++++++++++------ src/Multisig.sol | 77 +++++++------------ src/interfaces/IEmergencyMultisig.sol | 8 ++ src/interfaces/IMultisig.sol | 8 -- src/setup/EmergencyMultisigPluginSetup.sol | 20 ++--- src/setup/MultisigPluginSetup.sol | 13 ++-- 7 files changed, 160 insertions(+), 124 deletions(-) diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index eabf88d..11453fb 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -12,12 +12,18 @@ import {ProposalUpgradeable} from "@aragon/osx/core/plugin/proposal/ProposalUpgr import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IEmergencyMultisig} from "./interfaces/IEmergencyMultisig.sol"; import {OptimisticTokenVotingPlugin} from "./OptimisticTokenVotingPlugin.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {IAppointer} from "./EncryptionRegistry.sol"; /// @title Multisig - Release 1, Build 1 /// @author Aragon Association - 2022-2024 /// @notice The on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. -contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgradeable { +contract EmergencyMultisig is + IEmergencyMultisig, + IMembership, + PluginUUPSUpgradeable, + ProposalUpgradeable, + Addresslist +{ using SafeCastUpgradeable for uint256; /// @notice A container for proposal-related information. @@ -53,13 +59,13 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade /// @notice A container for the plugin settings. /// @param onlyListed Whether only listed addresses can create a proposal or not. /// @param minApprovals The minimal number of approvals required for a proposal to pass. - /// @param addresslistSource The contract where the list of signers is defined. /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. + /// @param encryptionRegistry The contract defining who is the appointed wallet for smart contract based accounts struct MultisigSettings { bool onlyListed; uint16 minApprovals; - Addresslist addresslistSource; uint64 proposalExpirationPeriod; + IAppointer encryptionRegistry; } /// @notice The ID of the permission required to call the `addAddresses` and `removeAddresses` functions. @@ -104,9 +110,10 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade /// @param actual The actual value. error MinApprovalsOutOfBounds(uint16 limit, uint16 actual); - /// @notice Thrown if the address list source is empty. - /// @param givenContract The received address that doesn't conform to Addresslist. - error InvalidAddressListSource(address givenContract); + /// @notice Thrown if the address list length is out of bounds. + /// @param limit The limit value. + /// @param actual The actual value. + error AddresslistLengthOutOfBounds(uint16 limit, uint256 actual); /// @notice Emitted when a proposal is created. /// @param proposalId The ID of the proposal. @@ -126,19 +133,30 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade /// @notice Emitted when the plugin settings are set. /// @param onlyListed Whether only listed addresses can create a proposal. /// @param minApprovals The minimum amount of approvals needed to pass a proposal. - /// @param addresslistSource The address of the contract holding the address list to use. + /// @param encryptionRegistry The contract defining who is the appointed wallet for smart contract based accounts /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. event MultisigSettingsUpdated( - bool onlyListed, uint16 indexed minApprovals, Addresslist addresslistSource, uint64 proposalExpirationPeriod + bool onlyListed, uint16 indexed minApprovals, IAppointer encryptionRegistry, uint64 proposalExpirationPeriod ); /// @notice Initializes Release 1, Build 1. /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). /// @param _dao The IDAO interface of the associated DAO. + /// @param _members The addresses of the initial members to be added. /// @param _multisigSettings The multisig settings. - function initialize(IDAO _dao, MultisigSettings calldata _multisigSettings) external initializer { + function initialize(IDAO _dao, address[] calldata _members, MultisigSettings calldata _multisigSettings) + external + initializer + { __PluginUUPSUpgradeable_init(_dao); + if (_members.length > type(uint16).max) { + revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: _members.length}); + } + + _addAddresses(_members); + emit MembersAdded({members: _members}); + _updateMultisigSettings(_multisigSettings); } @@ -152,8 +170,36 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade override(PluginUUPSUpgradeable, ProposalUpgradeable) returns (bool) { - return _interfaceId == type(IEmergencyMultisig).interfaceId || _interfaceId == type(IMembership).interfaceId - || super.supportsInterface(_interfaceId); + return _interfaceId == type(IEmergencyMultisig).interfaceId || _interfaceId == type(Addresslist).interfaceId + || _interfaceId == type(IMembership).interfaceId || super.supportsInterface(_interfaceId); + } + + /// @inheritdoc IEmergencyMultisig + function addAddresses(address[] calldata _members) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { + uint256 newAddresslistLength = addresslistLength() + _members.length; + + // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + if (newAddresslistLength > type(uint16).max) { + revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: newAddresslistLength}); + } + + _addAddresses(_members); + + emit MembersAdded({members: _members}); + } + + /// @inheritdoc IEmergencyMultisig + function removeAddresses(address[] calldata _members) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { + uint16 newAddresslistLength = uint16(addresslistLength() - _members.length); + + // Check if the new address list length would become less than the current minimum number of approvals required. + if (newAddresslistLength < multisigSettings.minApprovals) { + revert MinApprovalsOutOfBounds({limit: multisigSettings.minApprovals, actual: newAddresslistLength}); + } + + _removeAddresses(_members); + + emit MembersRemoved({members: _members}); } /// @notice Updates the plugin settings. @@ -179,7 +225,7 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade OptimisticTokenVotingPlugin _destinationPlugin, bool _approveProposal ) external returns (uint256 proposalId) { - if (multisigSettings.onlyListed && !multisigSettings.addresslistSource.isListed(msg.sender)) { + if (multisigSettings.onlyListed && !isListed(msg.sender)) { revert ProposalCreationForbidden(msg.sender); } @@ -318,7 +364,7 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade /// @inheritdoc IMembership function isMember(address _account) external view returns (bool) { - return multisigSettings.addresslistSource.isListed(_account); + return isListed(_account); } /// @notice Internal function to execute a vote. It assumes the queried proposal exists. @@ -349,7 +395,7 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade return false; } - if (!multisigSettings.addresslistSource.isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { + if (!isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { // The approver has no voting power. return false; } @@ -387,13 +433,7 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade /// @notice Internal function to update the plugin settings. /// @param _multisigSettings The new settings. function _updateMultisigSettings(MultisigSettings calldata _multisigSettings) internal { - if (!IERC165(address(_multisigSettings.addresslistSource)).supportsInterface(type(Addresslist).interfaceId)) { - revert InvalidAddressListSource(address(_multisigSettings.addresslistSource)); - } else if (_multisigSettings.minApprovals < 1) { - revert MinApprovalsOutOfBounds({limit: 1, actual: _multisigSettings.minApprovals}); - } - - uint16 addresslistLength_ = uint16(_multisigSettings.addresslistSource.addresslistLength()); + uint16 addresslistLength_ = uint16(addresslistLength()); if (_multisigSettings.minApprovals > addresslistLength_) { revert MinApprovalsOutOfBounds({limit: addresslistLength_, actual: _multisigSettings.minApprovals}); @@ -405,7 +445,7 @@ contract EmergencyMultisig is IEmergencyMultisig, IMembership, PluginUUPSUpgrade emit MultisigSettingsUpdated({ onlyListed: _multisigSettings.onlyListed, minApprovals: _multisigSettings.minApprovals, - addresslistSource: _multisigSettings.addresslistSource, + encryptionRegistry: _multisigSettings.encryptionRegistry, proposalExpirationPeriod: _multisigSettings.proposalExpirationPeriod }); } diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index b3b0351..c6b344c 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -6,10 +6,27 @@ import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +interface IAppointer { + /// @notice Raised when attempting to register a contract instead of a wallet + error CannotAppointContracts(); + + /// @notice Raised when a non appointed wallet tried to define the public key + error NotAppointed(); + + /// @notice Raised when the member attempts to define the public key of the appointed wallet + error OwnerNotAppointed(); + + /// @notice Registers the externally owned wallet's address to use for encryption. This allows smart contracts to appoint an EOA that can decrypt data. + function appointWallet(address _newAddress) external; + + /// @notice Returns the address of the wallet appointed for encryption purposes + function getAppointedAddress(address member) external returns (address); +} + /// @title EncryptionRegistry - Release 1, Build 1 /// @author Aragon Association - 2024 /// @notice A smart contract where addresses can register their libsodium public key for encryption purposes, as well as appointing an EOA -contract EncryptionRegistry { +contract EncryptionRegistry is IAppointer { struct RegistryEntry { address appointedWallet; bytes32 publicKey; @@ -29,15 +46,6 @@ contract EncryptionRegistry { /// @notice Emitted when an externally owned wallet is appointed event WalletAppointed(address member, address appointedWallet); - /// @notice Raised when attempting to register a contract instead of a wallet - error CannotAppointContracts(); - - /// @notice Raised when a non appointed wallet tried to define the public key - error NotAppointed(); - - /// @notice Raised when the member attempts to define the public key of the appointed wallet - error OwnerNotAppointed(); - /// @notice Raised when the caller is not a multisig member error RegistrationForbidden(); @@ -52,10 +60,13 @@ contract EncryptionRegistry { addresslistSource = _addresslistSource; } - /// @notice Registers the externally owned wallet's address to use for encryption. This allows smart contracts to appoint an EOA that can decrypt data. + /// @inheritdoc IAppointer function appointWallet(address _newAddress) public { - if (!addresslistSource.isListed(msg.sender)) revert RegistrationForbidden(); - else if (Address.isContract(_newAddress)) revert CannotAppointContracts(); + if (!addresslistSource.isListed(msg.sender)) { + revert RegistrationForbidden(); + } else if (Address.isContract(_newAddress)) { + revert CannotAppointContracts(); + } if (members[msg.sender].appointedWallet == address(0) && members[msg.sender].publicKey == bytes32(0)) { registeredAddresses.push(msg.sender); @@ -84,19 +95,13 @@ contract EncryptionRegistry { /// @notice Registers the given public key as the member's target for decrypting messages. Only if the sender is appointed. function setPublicKey(address _memberAddress, bytes32 _publicKey) public { - if (!addresslistSource.isListed(_memberAddress)) revert RegistrationForbidden(); - else if (members[_memberAddress].appointedWallet != msg.sender) revert NotAppointed(); - - _setPublicKey(_memberAddress, _publicKey); - } - - function _setPublicKey(address _memberAddress, bytes32 _publicKey) internal { - if (members[_memberAddress].appointedWallet == address(0) && members[_memberAddress].publicKey == bytes32(0)) { - registeredAddresses.push(_memberAddress); + if (!addresslistSource.isListed(_memberAddress)) { + revert RegistrationForbidden(); + } else if (members[_memberAddress].appointedWallet != msg.sender) { + revert NotAppointed(); } - members[_memberAddress].publicKey = _publicKey; - emit PublicKeySet(_memberAddress, _publicKey); + _setPublicKey(_memberAddress, _publicKey); } /// @notice Returns the list of addresses on the registry @@ -109,4 +114,23 @@ contract EncryptionRegistry { function getRegisteredAddressesLength() public view returns (uint256) { return registeredAddresses.length; } + + /// @inheritdoc IAppointer + function getAppointedAddress(address _member) public view returns (address) { + if (members[_member].appointedWallet != address(0)) { + return members[_member].appointedWallet; + } + return _member; + } + + // Internal helpers + + function _setPublicKey(address _memberAddress, bytes32 _publicKey) internal { + if (members[_memberAddress].appointedWallet == address(0) && members[_memberAddress].publicKey == bytes32(0)) { + registeredAddresses.push(_memberAddress); + } + + members[_memberAddress].publicKey = _publicKey; + emit PublicKeySet(_memberAddress, _publicKey); + } } diff --git a/src/Multisig.sol b/src/Multisig.sol index c19c8dc..11b0b79 100644 --- a/src/Multisig.sol +++ b/src/Multisig.sol @@ -12,11 +12,12 @@ import {ProposalUpgradeable} from "@aragon/osx/core/plugin/proposal/ProposalUpgr import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IMultisig} from "./interfaces/IMultisig.sol"; import {OptimisticTokenVotingPlugin} from "./OptimisticTokenVotingPlugin.sol"; +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /// @title Multisig - Release 1, Build 1 /// @author Aragon Association - 2022-2024 /// @notice The on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. -contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgradeable, Addresslist { +contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgradeable { using SafeCastUpgradeable for uint256; /// @notice A container for proposal-related information. @@ -50,11 +51,13 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice A container for the plugin settings. /// @param onlyListed Whether only listed addresses can create a proposal or not. /// @param minApprovals The minimal number of approvals required for a proposal to pass. + /// @param addresslistSource The contract where the list of signers is defined. /// @param destinationProposalDuration The minimum duration that the destination plugin will enforce. /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. struct MultisigSettings { bool onlyListed; uint16 minApprovals; + Addresslist addresslistSource; uint64 destinationProposalDuration; uint64 proposalExpirationPeriod; } @@ -93,10 +96,9 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @param actual The actual value. error MinApprovalsOutOfBounds(uint16 limit, uint16 actual); - /// @notice Thrown if the address list length is out of bounds. - /// @param limit The limit value. - /// @param actual The actual value. - error AddresslistLengthOutOfBounds(uint16 limit, uint256 actual); + /// @notice Thrown if the address list source is empty. + /// @param givenContract The received address that doesn't conform to Addresslist. + error InvalidAddressListSource(address givenContract); /// @notice Emitted when a proposal is approve by an approver. /// @param proposalId The ID of the proposal. @@ -110,11 +112,13 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice Emitted when the plugin settings are set. /// @param onlyListed Whether only listed addresses can create a proposal. /// @param minApprovals The minimum amount of approvals needed to pass a proposal. + /// @param addresslistSource The address of the contract holding the address list to use. /// @param destinationProposalDuration The minimum duration (in seconds) that will be required on the destination plugin /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. event MultisigSettingsUpdated( bool onlyListed, uint16 indexed minApprovals, + Addresslist addresslistSource, uint64 destinationProposalDuration, uint64 proposalExpirationPeriod ); @@ -122,21 +126,10 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice Initializes Release 1, Build 1. /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). /// @param _dao The IDAO interface of the associated DAO. - /// @param _members The addresses of the initial members to be added. /// @param _multisigSettings The multisig settings. - function initialize(IDAO _dao, address[] calldata _members, MultisigSettings calldata _multisigSettings) - external - initializer - { + function initialize(IDAO _dao, MultisigSettings calldata _multisigSettings) external initializer { __PluginUUPSUpgradeable_init(_dao); - if (_members.length > type(uint16).max) { - revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: _members.length}); - } - - _addAddresses(_members); - emit MembersAdded({members: _members}); - _updateMultisigSettings(_multisigSettings); } @@ -150,36 +143,8 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr override(PluginUUPSUpgradeable, ProposalUpgradeable) returns (bool) { - return _interfaceId == type(IMultisig).interfaceId || _interfaceId == type(Addresslist).interfaceId - || _interfaceId == type(IMembership).interfaceId || super.supportsInterface(_interfaceId); - } - - /// @inheritdoc IMultisig - function addAddresses(address[] calldata _members) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { - uint256 newAddresslistLength = addresslistLength() + _members.length; - - // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. - if (newAddresslistLength > type(uint16).max) { - revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: newAddresslistLength}); - } - - _addAddresses(_members); - - emit MembersAdded({members: _members}); - } - - /// @inheritdoc IMultisig - function removeAddresses(address[] calldata _members) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { - uint16 newAddresslistLength = uint16(addresslistLength() - _members.length); - - // Check if the new address list length would become less than the current minimum number of approvals required. - if (newAddresslistLength < multisigSettings.minApprovals) { - revert MinApprovalsOutOfBounds({limit: multisigSettings.minApprovals, actual: newAddresslistLength}); - } - - _removeAddresses(_members); - - emit MembersRemoved({members: _members}); + return _interfaceId == type(IMultisig).interfaceId || _interfaceId == type(IMembership).interfaceId + || super.supportsInterface(_interfaceId); } /// @notice Updates the plugin settings. @@ -203,7 +168,7 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr OptimisticTokenVotingPlugin _destinationPlugin, bool _approveProposal ) external returns (uint256 proposalId) { - if (multisigSettings.onlyListed && !isListed(msg.sender)) { + if (multisigSettings.onlyListed && !multisigSettings.addresslistSource.isListed(msg.sender)) { revert ProposalCreationForbidden(msg.sender); } @@ -219,6 +184,7 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr } uint64 _expirationDate = block.timestamp.toUint64() + multisigSettings.proposalExpirationPeriod; + proposalId = _createProposal({ _creator: msg.sender, _metadata: _metadataURI, @@ -329,7 +295,7 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @inheritdoc IMembership function isMember(address _account) external view returns (bool) { - return isListed(_account); + return multisigSettings.addresslistSource.isListed(_account); } /// @notice Internal function to execute a vote. It assumes the queried proposal exists. @@ -360,7 +326,7 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr return false; } - if (!isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { + if (!multisigSettings.addresslistSource.isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { // The approver has no voting power. return false; } @@ -398,7 +364,13 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice Internal function to update the plugin settings. /// @param _multisigSettings The new settings. function _updateMultisigSettings(MultisigSettings calldata _multisigSettings) internal { - uint16 addresslistLength_ = uint16(addresslistLength()); + if (!IERC165(address(_multisigSettings.addresslistSource)).supportsInterface(type(Addresslist).interfaceId)) { + revert InvalidAddressListSource(address(_multisigSettings.addresslistSource)); + } else if (_multisigSettings.minApprovals < 1) { + revert MinApprovalsOutOfBounds({limit: 1, actual: _multisigSettings.minApprovals}); + } + + uint16 addresslistLength_ = uint16(_multisigSettings.addresslistSource.addresslistLength()); if (_multisigSettings.minApprovals > addresslistLength_) { revert MinApprovalsOutOfBounds({limit: addresslistLength_, actual: _multisigSettings.minApprovals}); @@ -414,6 +386,7 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr emit MultisigSettingsUpdated({ onlyListed: _multisigSettings.onlyListed, minApprovals: _multisigSettings.minApprovals, + addresslistSource: _multisigSettings.addresslistSource, destinationProposalDuration: _multisigSettings.destinationProposalDuration, proposalExpirationPeriod: _multisigSettings.proposalExpirationPeriod }); @@ -422,5 +395,5 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @dev This empty reserved space is put in place to allow future versions to add new /// variables without shifting down storage in the inheritance chain. /// https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - uint256[47] private __gap; + uint256[46] private __gap; } diff --git a/src/interfaces/IEmergencyMultisig.sol b/src/interfaces/IEmergencyMultisig.sol index b57f8c8..03de5e0 100644 --- a/src/interfaces/IEmergencyMultisig.sol +++ b/src/interfaces/IEmergencyMultisig.sol @@ -8,6 +8,14 @@ import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; /// @author Aragon Association - 2023 /// @notice An interface for an on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. interface IEmergencyMultisig { + /// @notice Adds new members to the address list. Previously, it checks if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + /// @param _members The addresses of the members to be added. + function addAddresses(address[] calldata _members) external; + + /// @notice Removes existing members from the address list. Previously, it checks if the new address list length is at least as long as the minimum approvals parameter requires. Note that `minApprovals` is must be at least 1 so the address list cannot become empty. + /// @param _members The addresses of the members to be removed. + function removeAddresses(address[] calldata _members) external; + /// @notice Approves and, optionally, executes the proposal. /// @param _proposalId The ID of the proposal. function approve(uint256 _proposalId) external; diff --git a/src/interfaces/IMultisig.sol b/src/interfaces/IMultisig.sol index ba73eff..79e9e66 100644 --- a/src/interfaces/IMultisig.sol +++ b/src/interfaces/IMultisig.sol @@ -8,14 +8,6 @@ import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; /// @author Aragon Association - 2023 /// @notice An interface for an on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. interface IMultisig { - /// @notice Adds new members to the address list. Previously, it checks if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. - /// @param _members The addresses of the members to be added. - function addAddresses(address[] calldata _members) external; - - /// @notice Removes existing members from the address list. Previously, it checks if the new address list length is at least as long as the minimum approvals parameter requires. Note that `minApprovals` is must be at least 1 so the address list cannot become empty. - /// @param _members The addresses of the members to be removed. - function removeAddresses(address[] calldata _members) external; - /// @notice Approves and, optionally, executes the proposal. /// @param _proposalId The ID of the proposal. /// @param _tryExecution If `true`, execution is tried after the approval cast. The call does not revert if execution is not possible. diff --git a/src/setup/EmergencyMultisigPluginSetup.sol b/src/setup/EmergencyMultisigPluginSetup.sol index a823e13..557b9f5 100644 --- a/src/setup/EmergencyMultisigPluginSetup.sol +++ b/src/setup/EmergencyMultisigPluginSetup.sol @@ -26,11 +26,12 @@ contract EmergencyMultisigPluginSetup is PluginSetup { returns (address plugin, PreparedSetupData memory preparedSetupData) { // Decode `_data` to extract the parameters needed for deploying and initializing `EmergencyMultisig` plugin. - (EmergencyMultisig.MultisigSettings memory multisigSettings) = decodeInstallationParameters(_data); + (address[] memory members, EmergencyMultisig.MultisigSettings memory multisigSettings) = + decodeInstallationParameters(_data); // Prepare and Deploy the plugin proxy. plugin = createERC1967Proxy( - address(multisigBase), abi.encodeCall(EmergencyMultisig.initialize, (IDAO(_dao), multisigSettings)) + address(multisigBase), abi.encodeCall(EmergencyMultisig.initialize, (IDAO(_dao), members, multisigSettings)) ); // Prepare permissions @@ -98,20 +99,19 @@ contract EmergencyMultisigPluginSetup is PluginSetup { } /// @notice Encodes the given installation parameters into a byte array - function encodeInstallationParameters(EmergencyMultisig.MultisigSettings memory _multisigSettings) - external - pure - returns (bytes memory) - { - return abi.encode(_multisigSettings); + function encodeInstallationParameters( + address[] memory _members, + EmergencyMultisig.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 decodeInstallationParameters(bytes memory _data) public pure - returns (EmergencyMultisig.MultisigSettings memory _multisigSettings) + returns (address[] memory _members, EmergencyMultisig.MultisigSettings memory _multisigSettings) { - (_multisigSettings) = abi.decode(_data, (EmergencyMultisig.MultisigSettings)); + (_members, _multisigSettings) = abi.decode(_data, (address[], EmergencyMultisig.MultisigSettings)); } } diff --git a/src/setup/MultisigPluginSetup.sol b/src/setup/MultisigPluginSetup.sol index c4af62c..e19c892 100644 --- a/src/setup/MultisigPluginSetup.sol +++ b/src/setup/MultisigPluginSetup.sol @@ -26,12 +26,11 @@ contract MultisigPluginSetup is PluginSetup { returns (address plugin, PreparedSetupData memory preparedSetupData) { // Decode `_data` to extract the parameters needed for deploying and initializing `Multisig` plugin. - (address[] memory members, Multisig.MultisigSettings memory multisigSettings) = - decodeInstallationParameters(_data); + (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), multisigSettings)) ); // Prepare permissions @@ -99,20 +98,20 @@ contract MultisigPluginSetup is PluginSetup { } /// @notice Encodes the given installation parameters into a byte array - function encodeInstallationParameters(address[] memory _members, Multisig.MultisigSettings memory _multisigSettings) + function encodeInstallationParameters(Multisig.MultisigSettings memory _multisigSettings) external pure returns (bytes memory) { - return abi.encode(_members, _multisigSettings); + return abi.encode(_multisigSettings); } /// @notice Decodes the given byte array into the original installation parameters function decodeInstallationParameters(bytes memory _data) public pure - returns (address[] memory _members, Multisig.MultisigSettings memory _multisigSettings) + returns (Multisig.MultisigSettings memory _multisigSettings) { - (_members, _multisigSettings) = abi.decode(_data, (address[], Multisig.MultisigSettings)); + (_multisigSettings) = abi.decode(_data, (Multisig.MultisigSettings)); } } From 0cf968da85d2ce98954bf07496fa1ded44f9ec17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Sat, 19 Oct 2024 00:11:02 +0200 Subject: [PATCH 08/45] WIP: Splitting the address list into a separate contract, abstracting encryption account as well --- src/EmergencyMultisig.sol | 141 +++++++--------- src/EncryptionRegistry.sol | 153 ++++++++--------- src/Multisig.sol | 84 ++++++---- src/SignerList.sol | 221 +++++++++++++++++++++++++ src/interfaces/IEmergencyMultisig.sol | 8 - src/interfaces/IEncryptionRegistry.sol | 53 ++++++ src/interfaces/ISignerList.sol | 44 +++++ 7 files changed, 497 insertions(+), 207 deletions(-) create mode 100644 src/SignerList.sol create mode 100644 src/interfaces/IEncryptionRegistry.sol create mode 100644 src/interfaces/ISignerList.sol diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index 11453fb..015d4e3 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -5,25 +5,19 @@ pragma solidity ^0.8.17; import {SafeCastUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {IMembership} from "@aragon/osx/core/plugin/membership/IMembership.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; - import {ProposalUpgradeable} from "@aragon/osx/core/plugin/proposal/ProposalUpgradeable.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IEmergencyMultisig} from "./interfaces/IEmergencyMultisig.sol"; import {OptimisticTokenVotingPlugin} from "./OptimisticTokenVotingPlugin.sol"; -import {IAppointer} from "./EncryptionRegistry.sol"; +import {SignerList} from "./SignerList.sol"; +import {ISignerList} from "./interfaces/ISignerList.sol"; /// @title Multisig - Release 1, Build 1 /// @author Aragon Association - 2022-2024 /// @notice The on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. -contract EmergencyMultisig is - IEmergencyMultisig, - IMembership, - PluginUUPSUpgradeable, - ProposalUpgradeable, - Addresslist -{ +contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { using SafeCastUpgradeable for uint256; /// @notice A container for proposal-related information. @@ -60,12 +54,12 @@ contract EmergencyMultisig is /// @param onlyListed Whether only listed addresses can create a proposal or not. /// @param minApprovals The minimal number of approvals required for a proposal to pass. /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. - /// @param encryptionRegistry The contract defining who is the appointed wallet for smart contract based accounts + /// @param signerList The contract defining who is a member and/or who is appointed as a decryption wallet struct MultisigSettings { bool onlyListed; uint16 minApprovals; uint64 proposalExpirationPeriod; - IAppointer encryptionRegistry; + SignerList signerList; } /// @notice The ID of the permission required to call the `addAddresses` and `removeAddresses` functions. @@ -105,16 +99,15 @@ contract EmergencyMultisig is /// @param proposalId The ID of the proposal. error InvalidMetadataUri(uint256 proposalId); + /// @notice Thrown if the SignerList contract is not compatible. + /// @param signerList The given address + error InvalidSignerList(SignerList signerList); + /// @notice Thrown if the minimal approvals value is out of bounds (less than 1 or greater than the number of members in the address list). /// @param limit The maximal value. /// @param actual The actual value. error MinApprovalsOutOfBounds(uint16 limit, uint16 actual); - /// @notice Thrown if the address list length is out of bounds. - /// @param limit The limit value. - /// @param actual The actual value. - error AddresslistLengthOutOfBounds(uint16 limit, uint256 actual); - /// @notice Emitted when a proposal is created. /// @param proposalId The ID of the proposal. /// @param creator The creator of the proposal. @@ -133,30 +126,19 @@ contract EmergencyMultisig is /// @notice Emitted when the plugin settings are set. /// @param onlyListed Whether only listed addresses can create a proposal. /// @param minApprovals The minimum amount of approvals needed to pass a proposal. - /// @param encryptionRegistry The contract defining who is the appointed wallet for smart contract based accounts + /// @param signerList The contract defining who is a member and/or who is appointed as a decryption wallet /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. event MultisigSettingsUpdated( - bool onlyListed, uint16 indexed minApprovals, IAppointer encryptionRegistry, uint64 proposalExpirationPeriod + bool onlyListed, uint16 indexed minApprovals, SignerList signerList, uint64 proposalExpirationPeriod ); /// @notice Initializes Release 1, Build 1. /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). /// @param _dao The IDAO interface of the associated DAO. - /// @param _members The addresses of the initial members to be added. /// @param _multisigSettings The multisig settings. - function initialize(IDAO _dao, address[] calldata _members, MultisigSettings calldata _multisigSettings) - external - initializer - { + function initialize(IDAO _dao, MultisigSettings calldata _multisigSettings) external initializer { __PluginUUPSUpgradeable_init(_dao); - if (_members.length > type(uint16).max) { - revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: _members.length}); - } - - _addAddresses(_members); - emit MembersAdded({members: _members}); - _updateMultisigSettings(_multisigSettings); } @@ -170,36 +152,7 @@ contract EmergencyMultisig is override(PluginUUPSUpgradeable, ProposalUpgradeable) returns (bool) { - return _interfaceId == type(IEmergencyMultisig).interfaceId || _interfaceId == type(Addresslist).interfaceId - || _interfaceId == type(IMembership).interfaceId || super.supportsInterface(_interfaceId); - } - - /// @inheritdoc IEmergencyMultisig - function addAddresses(address[] calldata _members) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { - uint256 newAddresslistLength = addresslistLength() + _members.length; - - // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. - if (newAddresslistLength > type(uint16).max) { - revert AddresslistLengthOutOfBounds({limit: type(uint16).max, actual: newAddresslistLength}); - } - - _addAddresses(_members); - - emit MembersAdded({members: _members}); - } - - /// @inheritdoc IEmergencyMultisig - function removeAddresses(address[] calldata _members) external auth(UPDATE_MULTISIG_SETTINGS_PERMISSION_ID) { - uint16 newAddresslistLength = uint16(addresslistLength() - _members.length); - - // Check if the new address list length would become less than the current minimum number of approvals required. - if (newAddresslistLength < multisigSettings.minApprovals) { - revert MinApprovalsOutOfBounds({limit: multisigSettings.minApprovals, actual: newAddresslistLength}); - } - - _removeAddresses(_members); - - emit MembersRemoved({members: _members}); + return _interfaceId == type(IEmergencyMultisig).interfaceId || super.supportsInterface(_interfaceId); } /// @notice Updates the plugin settings. @@ -225,8 +178,13 @@ contract EmergencyMultisig is OptimisticTokenVotingPlugin _destinationPlugin, bool _approveProposal ) external returns (uint256 proposalId) { - if (multisigSettings.onlyListed && !isListed(msg.sender)) { - revert ProposalCreationForbidden(msg.sender); + if (multisigSettings.onlyListed) { + (bool ownerIsListed,) = multisigSettings.signerList.resolveEncryptionAccountStatus(msg.sender); + + // Only the account or its appointed address may create proposals + if (!ownerIsListed) { + revert ProposalCreationForbidden(msg.sender); + } } uint64 snapshotBlock; @@ -267,9 +225,9 @@ contract EmergencyMultisig is /// @inheritdoc IEmergencyMultisig function approve(uint256 _proposalId) public { - address approver = msg.sender; - if (!_canApprove(_proposalId, approver)) { - revert ApprovalCastForbidden(_proposalId, approver); + address _sender = msg.sender; + if (!_canApprove(_proposalId, _sender)) { + revert ApprovalCastForbidden(_proposalId, _sender); } Proposal storage proposal_ = proposals[_proposalId]; @@ -280,9 +238,11 @@ contract EmergencyMultisig is proposal_.approvals += 1; } - proposal_.approvers[approver] = true; + // Register the approval as being made by the owner, the one who isListed() relates to + address _owner = multisigSettings.signerList.resolveEncryptionOwner(_sender); + proposal_.approvers[_owner] = true; - emit Approved({proposalId: _proposalId, approver: approver}); + emit Approved({proposalId: _proposalId, approver: _owner}); // Automatic execution is intentionally omitted in order to prevent // private actions from accidentally leaving the local computer before being executed @@ -333,7 +293,9 @@ contract EmergencyMultisig is /// @inheritdoc IEmergencyMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { - return proposals[_proposalId].approvers[_account]; + address _owner = multisigSettings.signerList.resolveEncryptionOwner(_account); + + return proposals[_proposalId].approvers[_owner]; } /// @inheritdoc IEmergencyMultisig @@ -362,11 +324,6 @@ contract EmergencyMultisig is actionsHash = keccak256(abi.encode(_actions)); } - /// @inheritdoc IMembership - function isMember(address _account) external view returns (bool) { - return isListed(_account); - } - /// @notice Internal function to execute a vote. It assumes the queried proposal exists. /// @param _proposalId The ID of the proposal. function _execute(uint256 _proposalId, bytes memory _metadataUri, IDAO.Action[] calldata _actions) internal { @@ -385,9 +342,9 @@ contract EmergencyMultisig is /// @notice Internal function to check if an account can approve. It assumes the queried proposal exists. /// @param _proposalId The ID of the proposal. - /// @param _account The account to check. + /// @param _approver The account to check. /// @return Returns `true` if the given account can approve on a certain proposal and `false` otherwise. - function _canApprove(uint256 _proposalId, address _account) internal view returns (bool) { + function _canApprove(uint256 _proposalId, address _approver) internal view returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; if (!_isProposalOpen(proposal_)) { @@ -395,13 +352,25 @@ contract EmergencyMultisig is return false; } - if (!isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { - // The approver has no voting power. + (address _owner, address _appointedWallet) = multisigSettings.signerList.resolveEncryptionAccount(_approver); + + if (_owner == address(0)) { + // Not resolved return false; + } else if (!multisigSettings.signerList.isListedAtBlock(_owner, proposal_.parameters.snapshotBlock)) { + // The owner account had no voting power + return false; + } + // If there is an appointed wallet, only that wallet can approve + else if (_appointedWallet != address(0)) { + // Someone else is appointed + if (_approver != _appointedWallet) return false; } - if (proposal_.approvers[_account]) { - // The approver has already approved + // If _appointedWallet == address(0), then _owner == _approver. No need to check. + + if (proposal_.approvers[_owner]) { + // The account already approved return false; } @@ -433,10 +402,16 @@ contract EmergencyMultisig is /// @notice Internal function to update the plugin settings. /// @param _multisigSettings The new settings. function _updateMultisigSettings(MultisigSettings calldata _multisigSettings) internal { - uint16 addresslistLength_ = uint16(addresslistLength()); + if (!IERC165(address(_multisigSettings.signerList)).supportsInterface(type(ISignerList).interfaceId)) { + revert InvalidSignerList(_multisigSettings.signerList); + } + + uint16 addresslistLength_ = uint16(_multisigSettings.signerList.addresslistLength()); if (_multisigSettings.minApprovals > addresslistLength_) { revert MinApprovalsOutOfBounds({limit: addresslistLength_, actual: _multisigSettings.minApprovals}); + } else if (_multisigSettings.minApprovals < 1) { + revert MinApprovalsOutOfBounds({limit: 1, actual: _multisigSettings.minApprovals}); } multisigSettings = _multisigSettings; @@ -445,8 +420,8 @@ contract EmergencyMultisig is emit MultisigSettingsUpdated({ onlyListed: _multisigSettings.onlyListed, minApprovals: _multisigSettings.minApprovals, - encryptionRegistry: _multisigSettings.encryptionRegistry, - proposalExpirationPeriod: _multisigSettings.proposalExpirationPeriod + proposalExpirationPeriod: _multisigSettings.proposalExpirationPeriod, + signerList: _multisigSettings.signerList }); } diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index c6b344c..559df62 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -5,132 +5,119 @@ pragma solidity ^0.8.17; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; - -interface IAppointer { - /// @notice Raised when attempting to register a contract instead of a wallet - error CannotAppointContracts(); - - /// @notice Raised when a non appointed wallet tried to define the public key - error NotAppointed(); - - /// @notice Raised when the member attempts to define the public key of the appointed wallet - error OwnerNotAppointed(); - - /// @notice Registers the externally owned wallet's address to use for encryption. This allows smart contracts to appoint an EOA that can decrypt data. - function appointWallet(address _newAddress) external; - - /// @notice Returns the address of the wallet appointed for encryption purposes - function getAppointedAddress(address member) external returns (address); -} +import {IEncryptionRegistry} from "./interfaces/IEncryptionRegistry.sol"; /// @title EncryptionRegistry - Release 1, Build 1 /// @author Aragon Association - 2024 -/// @notice A smart contract where addresses can register their libsodium public key for encryption purposes, as well as appointing an EOA -contract EncryptionRegistry is IAppointer { - struct RegistryEntry { +/// @notice A smart contract where accounts can register their libsodium public key for encryption purposes, as well as appointing an EOA +contract EncryptionRegistry is IEncryptionRegistry { + struct AccountEntry { address appointedWallet; bytes32 publicKey; } - /// @dev Allows to enumerate the addresses that have a encryption registered - address[] public registeredAddresses; - - mapping(address => RegistryEntry) public members; - - /// @dev The contract to check whether the caller is a multisig member - Addresslist addresslistSource; - - /// @notice Emitted when a public key is defined - event PublicKeySet(address member, bytes32 publicKey); + /// @notice Allows to enumerate the addresses on the registry + address[] public registeredAccounts; - /// @notice Emitted when an externally owned wallet is appointed - event WalletAppointed(address member, address appointedWallet); + /// @notice The database of appointed wallets and their public key + mapping(address => AccountEntry) public accounts; - /// @notice Raised when the caller is not a multisig member - error RegistrationForbidden(); + /// @notice A reference to the account that appointed each wallet + mapping(address => address) public appointedBy; - /// @notice Raised when the caller is not a multisig member - error InvalidAddressList(); + /// @dev The contract to check whether the caller is a multisig member + Addresslist addresslist; - constructor(Addresslist _addresslistSource) { - if (!IERC165(address(_addresslistSource)).supportsInterface(type(Addresslist).interfaceId)) { + constructor(Addresslist _addresslist) { + if (!IERC165(address(_addresslist)).supportsInterface(type(Addresslist).interfaceId)) { revert InvalidAddressList(); } - addresslistSource = _addresslistSource; + addresslist = _addresslist; } - /// @inheritdoc IAppointer - function appointWallet(address _newAddress) public { - if (!addresslistSource.isListed(msg.sender)) { - revert RegistrationForbidden(); - } else if (Address.isContract(_newAddress)) { + /// @inheritdoc IEncryptionRegistry + function appointWallet(address _newWallet) public { + if (!addresslist.isListed(msg.sender)) { + revert MustBeListed(); + } else if (Address.isContract(_newWallet)) { revert CannotAppointContracts(); + } else if (appointedBy[_newWallet] != address(0)) { + revert AlreadyAppointed(); } - if (members[msg.sender].appointedWallet == address(0) && members[msg.sender].publicKey == bytes32(0)) { - registeredAddresses.push(msg.sender); + // New account? + if (accounts[msg.sender].appointedWallet == address(0) && accounts[msg.sender].publicKey == bytes32(0)) { + registeredAccounts.push(msg.sender); } - - if (members[msg.sender].publicKey != bytes32(0)) { - // The old member should no longer be able to see new content - members[msg.sender].publicKey = bytes32(0); + // Existing account + else { + // Clear the old appointedBy[], if needed + if (accounts[msg.sender].appointedWallet != address(0)) { + appointedBy[accounts[msg.sender].appointedWallet] = address(0); + } + // Clear the old public key, if needed + if (accounts[msg.sender].publicKey != bytes32(0)) { + // The old appointed wallet should no longer be able to see new content + accounts[msg.sender].publicKey = bytes32(0); + } } - members[msg.sender].appointedWallet = _newAddress; - emit WalletAppointed(msg.sender, _newAddress); + + accounts[msg.sender].appointedWallet = _newWallet; + appointedBy[_newWallet] = msg.sender; + emit WalletAppointed(msg.sender, _newWallet); } - /// @notice Registers the given public key as its own target for decrypting messages + /// @inheritdoc IEncryptionRegistry function setOwnPublicKey(bytes32 _publicKey) public { - if (!addresslistSource.isListed(msg.sender)) { - revert RegistrationForbidden(); - } else if ( - members[msg.sender].appointedWallet != msg.sender && members[msg.sender].appointedWallet != address(0) + if (!addresslist.isListed(msg.sender)) { + revert MustBeListed(); + } + // If someone else if appointed, the public key cannot be overriden. + // The appointed value should be set to address(0) or msg.sender first. + else if ( + accounts[msg.sender].appointedWallet != address(0) && accounts[msg.sender].appointedWallet != msg.sender ) { - revert OwnerNotAppointed(); + revert CannotSetPubKeyForAppointedWallets(); } _setPublicKey(msg.sender, _publicKey); + emit PublicKeySet(msg.sender, _publicKey); } - /// @notice Registers the given public key as the member's target for decrypting messages. Only if the sender is appointed. - function setPublicKey(address _memberAddress, bytes32 _publicKey) public { - if (!addresslistSource.isListed(_memberAddress)) { - revert RegistrationForbidden(); - } else if (members[_memberAddress].appointedWallet != msg.sender) { - revert NotAppointed(); + /// @inheritdoc IEncryptionRegistry + function setPublicKey(address _account, bytes32 _publicKey) public { + if (!addresslist.isListed(_account)) { + revert MustBeListed(); + } else if (accounts[_account].appointedWallet != msg.sender) { + revert MustBeAppointed(); } - _setPublicKey(_memberAddress, _publicKey); - } - - /// @notice Returns the list of addresses on the registry - /// @dev Use this function to get all addresses in a single call. You can still call registeredAddresses[idx] to resolve them one by one. - function getRegisteredAddresses() public view returns (address[] memory) { - return registeredAddresses; + _setPublicKey(_account, _publicKey); + emit PublicKeySet(_account, _publicKey); } - /// @notice Returns the number of addresses registered - function getRegisteredAddressesLength() public view returns (uint256) { - return registeredAddresses.length; + /// @inheritdoc IEncryptionRegistry + function getRegisteredAccounts() public view returns (address[] memory) { + return registeredAccounts; } - /// @inheritdoc IAppointer - function getAppointedAddress(address _member) public view returns (address) { - if (members[_member].appointedWallet != address(0)) { - return members[_member].appointedWallet; + /// @inheritdoc IEncryptionRegistry + function getAppointedWallet(address _member) public view returns (address) { + if (accounts[_member].appointedWallet != address(0)) { + return accounts[_member].appointedWallet; } return _member; } // Internal helpers - function _setPublicKey(address _memberAddress, bytes32 _publicKey) internal { - if (members[_memberAddress].appointedWallet == address(0) && members[_memberAddress].publicKey == bytes32(0)) { - registeredAddresses.push(_memberAddress); + function _setPublicKey(address _account, bytes32 _publicKey) internal { + if (accounts[_account].appointedWallet == address(0) && accounts[_account].publicKey == bytes32(0)) { + // New member + registeredAccounts.push(_account); } - members[_memberAddress].publicKey = _publicKey; - emit PublicKeySet(_memberAddress, _publicKey); + accounts[_account].publicKey = _publicKey; } } diff --git a/src/Multisig.sol b/src/Multisig.sol index 11b0b79..4e135f0 100644 --- a/src/Multisig.sol +++ b/src/Multisig.sol @@ -9,15 +9,16 @@ import {IMembership} from "@aragon/osx/core/plugin/membership/IMembership.sol"; import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; import {ProposalUpgradeable} from "@aragon/osx/core/plugin/proposal/ProposalUpgradeable.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IMultisig} from "./interfaces/IMultisig.sol"; +import {SignerList} from "./SignerList.sol"; +import {ISignerList} from "./interfaces/ISignerList.sol"; import {OptimisticTokenVotingPlugin} from "./OptimisticTokenVotingPlugin.sol"; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /// @title Multisig - Release 1, Build 1 /// @author Aragon Association - 2022-2024 /// @notice The on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. -contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgradeable { +contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { using SafeCastUpgradeable for uint256; /// @notice A container for proposal-related information. @@ -53,12 +54,13 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @param minApprovals The minimal number of approvals required for a proposal to pass. /// @param addresslistSource The contract where the list of signers is defined. /// @param destinationProposalDuration The minimum duration that the destination plugin will enforce. + /// @param signerList The contract defining who is a member and/or who is appointed as a decryption wallet /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. struct MultisigSettings { bool onlyListed; uint16 minApprovals; - Addresslist addresslistSource; uint64 destinationProposalDuration; + SignerList signerList; uint64 proposalExpirationPeriod; } @@ -91,6 +93,10 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @param proposalId The ID of the proposal. error ProposalExecutionForbidden(uint256 proposalId); + /// @notice Thrown if the SignerList contract is not compatible. + /// @param signerList The given address + error InvalidSignerList(SignerList signerList); + /// @notice Thrown if the minimal approvals value is out of bounds (less than 1 or greater than the number of members in the address list). /// @param limit The maximal value. /// @param actual The actual value. @@ -112,13 +118,13 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice Emitted when the plugin settings are set. /// @param onlyListed Whether only listed addresses can create a proposal. /// @param minApprovals The minimum amount of approvals needed to pass a proposal. - /// @param addresslistSource The address of the contract holding the address list to use. + /// @param signerList The contract defining who is a member and/or who is appointed as a decryption wallet /// @param destinationProposalDuration The minimum duration (in seconds) that will be required on the destination plugin /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. event MultisigSettingsUpdated( bool onlyListed, uint16 indexed minApprovals, - Addresslist addresslistSource, + SignerList signerList, uint64 destinationProposalDuration, uint64 proposalExpirationPeriod ); @@ -168,8 +174,13 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr OptimisticTokenVotingPlugin _destinationPlugin, bool _approveProposal ) external returns (uint256 proposalId) { - if (multisigSettings.onlyListed && !multisigSettings.addresslistSource.isListed(msg.sender)) { - revert ProposalCreationForbidden(msg.sender); + if (multisigSettings.onlyListed) { + (bool ownerIsListed,) = multisigSettings.signerList.resolveEncryptionAccountStatus(msg.sender); + + // Only the account or its appointed address may create proposals + if (!ownerIsListed) { + revert ProposalCreationForbidden(msg.sender); + } } uint64 snapshotBlock; @@ -217,9 +228,9 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @inheritdoc IMultisig function approve(uint256 _proposalId, bool _tryExecution) public { - address approver = msg.sender; - if (!_canApprove(_proposalId, approver)) { - revert ApprovalCastForbidden(_proposalId, approver); + address _sender = msg.sender; + if (!_canApprove(_proposalId, _sender)) { + revert ApprovalCastForbidden(_proposalId, _sender); } Proposal storage proposal_ = proposals[_proposalId]; @@ -230,9 +241,11 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr proposal_.approvals += 1; } - proposal_.approvers[approver] = true; + // Register the approval as being made by the owner, the one who isListed() relates to + address _owner = multisigSettings.signerList.resolveEncryptionOwner(_sender); + proposal_.approvers[_owner] = true; - emit Approved({proposalId: _proposalId, approver: approver}); + emit Approved({proposalId: _proposalId, approver: _owner}); if (_tryExecution && _canExecute(_proposalId)) { _execute(_proposalId); @@ -281,7 +294,9 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @inheritdoc IMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { - return proposals[_proposalId].approvers[_account]; + address _owner = multisigSettings.signerList.resolveEncryptionOwner(_account); + + return proposals[_proposalId].approvers[_owner]; } /// @inheritdoc IMultisig @@ -293,11 +308,6 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr _execute(_proposalId); } - /// @inheritdoc IMembership - function isMember(address _account) external view returns (bool) { - return multisigSettings.addresslistSource.isListed(_account); - } - /// @notice Internal function to execute a vote. It assumes the queried proposal exists. /// @param _proposalId The ID of the proposal. function _execute(uint256 _proposalId) internal { @@ -316,9 +326,9 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice Internal function to check if an account can approve. It assumes the queried proposal exists. /// @param _proposalId The ID of the proposal. - /// @param _account The account to check. + /// @param _approver The account to check. /// @return Returns `true` if the given account can approve on a certain proposal and `false` otherwise. - function _canApprove(uint256 _proposalId, address _account) internal view returns (bool) { + function _canApprove(uint256 _proposalId, address _approver) internal view returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; if (!_isProposalOpen(proposal_)) { @@ -326,13 +336,25 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr return false; } - if (!multisigSettings.addresslistSource.isListedAtBlock(_account, proposal_.parameters.snapshotBlock)) { - // The approver has no voting power. + (address _owner, address _appointedWallet) = multisigSettings.signerList.resolveEncryptionAccount(_approver); + + if (_owner == address(0)) { + // Not resolved return false; + } else if (!multisigSettings.signerList.isListedAtBlock(_owner, proposal_.parameters.snapshotBlock)) { + // The owner account had no voting power + return false; + } + // If there is an appointed wallet, only that wallet can approve + else if (_appointedWallet != address(0)) { + // Someone else is appointed + if (_approver != _appointedWallet) return false; } - if (proposal_.approvers[_account]) { - // The approver has already approved + // If _appointedWallet == address(0), then _owner == _approver. No need to check. + + if (proposal_.approvers[_owner]) { + // The account already approved return false; } @@ -364,19 +386,15 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr /// @notice Internal function to update the plugin settings. /// @param _multisigSettings The new settings. function _updateMultisigSettings(MultisigSettings calldata _multisigSettings) internal { - if (!IERC165(address(_multisigSettings.addresslistSource)).supportsInterface(type(Addresslist).interfaceId)) { - revert InvalidAddressListSource(address(_multisigSettings.addresslistSource)); - } else if (_multisigSettings.minApprovals < 1) { - revert MinApprovalsOutOfBounds({limit: 1, actual: _multisigSettings.minApprovals}); + if (!IERC165(address(_multisigSettings.signerList)).supportsInterface(type(ISignerList).interfaceId)) { + revert InvalidSignerList(_multisigSettings.signerList); } - uint16 addresslistLength_ = uint16(_multisigSettings.addresslistSource.addresslistLength()); + uint16 addresslistLength_ = uint16(_multisigSettings.signerList.addresslistLength()); if (_multisigSettings.minApprovals > addresslistLength_) { revert MinApprovalsOutOfBounds({limit: addresslistLength_, actual: _multisigSettings.minApprovals}); - } - - if (_multisigSettings.minApprovals < 1) { + } else if (_multisigSettings.minApprovals < 1) { revert MinApprovalsOutOfBounds({limit: 1, actual: _multisigSettings.minApprovals}); } @@ -386,7 +404,7 @@ contract Multisig is IMultisig, IMembership, PluginUUPSUpgradeable, ProposalUpgr emit MultisigSettingsUpdated({ onlyListed: _multisigSettings.onlyListed, minApprovals: _multisigSettings.minApprovals, - addresslistSource: _multisigSettings.addresslistSource, + signerList: _multisigSettings.signerList, destinationProposalDuration: _multisigSettings.destinationProposalDuration, proposalExpirationPeriod: _multisigSettings.proposalExpirationPeriod }); diff --git a/src/SignerList.sol b/src/SignerList.sol new file mode 100644 index 0000000..8a3838d --- /dev/null +++ b/src/SignerList.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.17; + +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; +import {ISignerList} from "./interfaces/ISignerList.sol"; +import {EncryptionRegistry} from "./EncryptionRegistry.sol"; +import {DaoAuthorizableUpgradeable} from "@aragon/osx/core/plugin/dao-authorizable/DaoAuthorizableUpgradeable.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; +import {IEncryptionRegistry} from "./interfaces/IEncryptionRegistry.sol"; + +// ID of the permission required to call the `addAddresses` and `removeAddresses` functions. +bytes32 constant UPDATE_SIGNER_LIST_PERMISSION_ID = keccak256("UPDATE_SIGNER_LIST_PERMISSION"); + +// ID of the permission required to update the SignerList settings. +bytes32 constant UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID = keccak256("UPDATE_SIGNER_LIST_SETTINGS_PERMISSION"); + +/// @title SignerList - Release 1, Build 1 +/// @author Aragon Association - 2024 +/// @notice A smart contract acting as the source of truth for multisig censuses, as well as defining who is appointed as an EOA for decryption purposes. +contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthorizableUpgradeable { + /// @notice Thrown if the signer list length is out of bounds. + /// @param limit The limit value. + /// @param actual The actual value. + error SignerListLengthOutOfBounds(uint16 limit, uint256 actual); + + /// @notice Thrown when attempting to define an invalid EncryptionRegistry + error InvalidEncryptionRegitry(address givenAddress); + + /// @notice Emitted when the SignerList settings are updated + event SignerListSettingsUpdated(EncryptionRegistry encryptionRegistry, uint16 minSignerListLength); + + struct Settings { + /// @notice The contract where current signers can appoint wallets for decryption purposes + EncryptionRegistry encryptionRegistry; + /// @notice The minimum amount of addresses required. + /// @notice Set this value to at least the `minApprovals` of the EmergencyMultisig contract. + uint16 minSignerListLength; + } + + Settings public settings; + + /// @notice Disables the initializers on the implementation contract to prevent it from being left uninitialized. + constructor() { + _disableInitializers(); + } + + /// @notice Initializes Release 1, Build 1. + /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). + /// @param _dao The IDAO interface of the associated DAO. + /// @param _signers The addresses of the initial signers to be added. + /// @param _settings The settings to define on the new instance. + function initialize(IDAO _dao, address[] calldata _signers, Settings calldata _settings) external initializer { + __DaoAuthorizableUpgradeable_init(_dao); + + // Validating _signers[] + if (_signers.length > type(uint16).max) { + revert SignerListLengthOutOfBounds({limit: type(uint16).max, actual: _signers.length}); + } + + _addAddresses(_signers); + emit SignersAdded({signers: _signers}); + + // Settings (validated within _updateSettings) + _updateSettings(_settings); + emit SignerListSettingsUpdated({ + encryptionRegistry: _settings.encryptionRegistry, + minSignerListLength: _settings.minSignerListLength + }); + } + + /// @inheritdoc ISignerList + function addSigners(address[] calldata _signers) external auth(UPDATE_SIGNER_LIST_PERMISSION_ID) { + uint256 newAddresslistLength = addresslistLength() + _signers.length; + + // Check if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + if (newAddresslistLength > type(uint16).max) { + revert SignerListLengthOutOfBounds({limit: type(uint16).max, actual: newAddresslistLength}); + } + + _addAddresses(_signers); + emit SignersAdded({signers: _signers}); + } + + /// @inheritdoc ISignerList + function removeSigners(address[] calldata _signers) external auth(UPDATE_SIGNER_LIST_PERMISSION_ID) { + uint16 newAddresslistLength = uint16(addresslistLength() - _signers.length); + + // Check if the new address list length would become less than the current minimum number of approvals required. + if (newAddresslistLength < settings.minSignerListLength) { + revert SignerListLengthOutOfBounds({limit: settings.minSignerListLength, actual: newAddresslistLength}); + } + + _removeAddresses(_signers); + emit SignersRemoved({signers: _signers}); + } + + /// @notice Updates the plugin settings. + /// @param _newSettings The new settings. + function updateSettings(Settings calldata _newSettings) external auth(UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID) { + // Values validated within _updateSettings + _updateSettings(_newSettings); + + emit SignerListSettingsUpdated({ + encryptionRegistry: _newSettings.encryptionRegistry, + minSignerListLength: _newSettings.minSignerListLength + }); + } + + /// @inheritdoc ISignerList + function resolveEncryptionAccountStatus(address _sender) + public + view + returns (bool ownerIsListed, bool isAppointed) + { + if (this.isListed(_sender)) { + ownerIsListed = true; + } else if (this.isListed(settings.encryptionRegistry.appointedBy(_sender))) { + ownerIsListed = true; + isAppointed = true; + } + + // Not found, return blank values + } + + /// @inheritdoc ISignerList + function resolveEncryptionOwner(address _sender) public view returns (address owner) { + (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_sender); + + if (!ownerIsListed) return address(0); + else if (isAppointed) return settings.encryptionRegistry.appointedBy(_sender); + return _sender; + } + + /// @inheritdoc ISignerList + function resolveEncryptionAccount(address _sender) public view returns (address owner, address appointedWallet) { + (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_sender); + + if (ownerIsListed) { + if (isAppointed) { + owner = settings.encryptionRegistry.appointedBy(_sender); + appointedWallet = _sender; + } else { + owner = _sender; + appointedWallet = settings.encryptionRegistry.getAppointedWallet(_sender); + } + } + + // Not found, return blank values + } + + /// @inheritdoc ISignerList + function getEncryptionRecipients() external view returns (address[] memory result) { + address[] memory _encryptionAccounts = settings.encryptionRegistry.getRegisteredAccounts(); + + // Allocating the full length. + // If any member is no longer listed, the size will be decreased. + result = new address[](_encryptionAccounts.length); + + uint256 rIdx; // Result iterator. Will never be greater than erIdx. + uint256 erIdx; // EncryptionRegistry iterator + address appointed; + for (erIdx = 0; erIdx < _encryptionAccounts.length;) { + if (isListed(_encryptionAccounts[erIdx])) { + // Add it to the result array if listed + appointed = settings.encryptionRegistry.getAppointedWallet(_encryptionAccounts[erIdx]); + // Use the appointed address if non-zero + if (appointed != address(0)) { + result[rIdx] = appointed; + } else { + result[rIdx] = _encryptionAccounts[erIdx]; + } + + unchecked { + rIdx++; + } + } + // Skip non-listed accounts othersise + + unchecked { + erIdx++; + } + } + + if (rIdx < erIdx) { + // Decrease the array size to return listed accounts without blank entries + uint256 diff = erIdx - rIdx; + assembly { + mstore(result, sub(mload(result), diff)) + } + } + } + + /// @notice Checks if this or the parent contract supports an interface by its ID. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return _interfaceId == type(ISignerList).interfaceId || _interfaceId == type(Addresslist).interfaceId + || super.supportsInterface(_interfaceId); + } + + // Internal helpers + + /// @notice Internal function to update the plugin settings. + /// @param _newSettings The new settings. + function _updateSettings(Settings calldata _newSettings) internal { + if (!IERC165(address(_newSettings.encryptionRegistry)).supportsInterface(type(IEncryptionRegistry).interfaceId)) + { + revert InvalidEncryptionRegitry(address(_newSettings.encryptionRegistry)); + } + + uint16 _currentLength = uint16(addresslistLength()); + if (_newSettings.minSignerListLength > _currentLength) { + revert SignerListLengthOutOfBounds({limit: _currentLength, actual: _newSettings.minSignerListLength}); + } + + settings = _newSettings; + } +} diff --git a/src/interfaces/IEmergencyMultisig.sol b/src/interfaces/IEmergencyMultisig.sol index 03de5e0..b57f8c8 100644 --- a/src/interfaces/IEmergencyMultisig.sol +++ b/src/interfaces/IEmergencyMultisig.sol @@ -8,14 +8,6 @@ import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; /// @author Aragon Association - 2023 /// @notice An interface for an on-chain multisig governance plugin in which a proposal passes if X out of Y approvals are met. interface IEmergencyMultisig { - /// @notice Adds new members to the address list. Previously, it checks if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. - /// @param _members The addresses of the members to be added. - function addAddresses(address[] calldata _members) external; - - /// @notice Removes existing members from the address list. Previously, it checks if the new address list length is at least as long as the minimum approvals parameter requires. Note that `minApprovals` is must be at least 1 so the address list cannot become empty. - /// @param _members The addresses of the members to be removed. - function removeAddresses(address[] calldata _members) external; - /// @notice Approves and, optionally, executes the proposal. /// @param _proposalId The ID of the proposal. function approve(uint256 _proposalId) external; diff --git a/src/interfaces/IEncryptionRegistry.sol b/src/interfaces/IEncryptionRegistry.sol new file mode 100644 index 0000000..4a5c2ac --- /dev/null +++ b/src/interfaces/IEncryptionRegistry.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.17; + +interface IEncryptionRegistry { + /// @notice Emitted when a public key is defined + event PublicKeySet(address account, bytes32 publicKey); + + /// @notice Emitted when an externally owned wallet is appointed + event WalletAppointed(address account, address appointedWallet); + + /// @notice Raised when the caller is not an addresslist member + error MustBeListed(); + + /// @notice Raised when attempting to register a contract instead of a wallet + error CannotAppointContracts(); + + /// @notice Raised when attempting to appoint an already appointed address + error AlreadyAppointed(); + + /// @notice Raised when a non appointed wallet tries to define the public key + error MustBeAppointed(); + + /// @notice Raised when an account attempts to define the public key of the appointed wallet + error CannotSetPubKeyForAppointedWallets(); + + /// @notice Raised when the caller is not an addresslist compatible contract + error InvalidAddressList(); + + /// @notice Registers the externally owned wallet's address to use for encryption. This allows smart contracts to appoint an EOA that can decrypt data. + /// @dev NOTE: calling this function will wipe any existing public key previously registered. + function appointWallet(address newWallet) external; + + /// @notice Registers the given public key as the account's target for decrypting messages. + /// @dev NOTE: Calling this function from a smart contracts will revert. + function setOwnPublicKey(bytes32 publicKey) external; + + /// @notice Registers the given public key as the member's target for decrypting messages. Only if the sender is appointed. + /// @param account The address of the account to set the public key for. The sender must be appointed or the transaction will revert. + /// @param publicKey The libsodium public key to register + function setPublicKey(address account, bytes32 publicKey) external; + + /// @notice Returns the address of the account that appointed the given wallet, if any. + /// @return appointerAddress The address of the appointer account or zero. + function appointedBy(address wallet) external returns (address appointerAddress); + + /// @notice Returns the list of addresses on the registry + /// @dev Use this function to get all addresses in a single call. You can still call registeredAccounts[idx] to resolve them one by one. + function getRegisteredAccounts() external view returns (address[] memory); + + /// @notice Returns the address of the wallet appointed for encryption purposes + function getAppointedWallet(address member) external view returns (address); +} diff --git a/src/interfaces/ISignerList.sol b/src/interfaces/ISignerList.sol new file mode 100644 index 0000000..eb25e63 --- /dev/null +++ b/src/interfaces/ISignerList.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +pragma solidity ^0.8.17; + +interface ISignerList { + /// @notice Emitted when signers are added to the DAO plugin. + /// @param signers The list of new signers being added. + event SignersAdded(address[] signers); + + /// @notice Emitted when signers are removed from the DAO plugin. + /// @param signers The list of existing signers being removed. + event SignersRemoved(address[] signers); + + /// @notice Adds new signers to the address list. Previously, it checks if the new address list length would be greater than `type(uint16).max`, the maximal number of approvals. + /// @param signers The addresses of the signers to be added. + function addSigners(address[] calldata signers) external; + + /// @notice Removes existing signers from the address list. Previously, it checks if the new address list length is at least as long as the minimum approvals parameter requires. Note that `minApprovals` is must be at least 1 so the address list cannot become empty. + /// @param signers The addresses of the signers to be removed. + function removeSigners(address[] calldata signers) external; + + /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. + /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. + /// @return ownerIsListed If resolved, whether the given address is currently listed as a member. False otherwise. + /// @return isAppointed If resolved, whether the given address is appointed by the owner. False otherwise. + function resolveEncryptionAccountStatus(address _sender) + external + view + returns (bool ownerIsListed, bool isAppointed); + + /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. + /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. + /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. + function resolveEncryptionOwner(address _sender) external view returns (address owner); + + /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. + /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. + /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. + /// @return appointedWallet If resolved, it contains the wallet address appointed for decryption, if any. Returns address(0) otherwise. + function resolveEncryptionAccount(address sender) external view returns (address owner, address appointedWallet); + + /// @notice Among the SignerList's members registered on the EncryptionRegistry, return the effective address they use for encryption + function getEncryptionRecipients() external view returns (address[] memory); +} From fff9944bd6742004afc66b03d8dfd0d029fd2794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Mon, 21 Oct 2024 12:01:18 +0200 Subject: [PATCH 09/45] Factory and setup adaptations --- src/EmergencyMultisig.sol | 6 ++-- src/SignerList.sol | 11 ++++-- src/factory/TaikoDaoFactory.sol | 41 +++++++++++++++------- src/setup/EmergencyMultisigPluginSetup.sol | 20 +++++------ 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index 015d4e3..df1ca17 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -53,13 +53,13 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa /// @notice A container for the plugin settings. /// @param onlyListed Whether only listed addresses can create a proposal or not. /// @param minApprovals The minimal number of approvals required for a proposal to pass. - /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. /// @param signerList The contract defining who is a member and/or who is appointed as a decryption wallet + /// @param proposalExpirationPeriod The amount of seconds after which a non executed proposal expires. struct MultisigSettings { bool onlyListed; uint16 minApprovals; - uint64 proposalExpirationPeriod; SignerList signerList; + uint64 proposalExpirationPeriod; } /// @notice The ID of the permission required to call the `addAddresses` and `removeAddresses` functions. @@ -405,7 +405,7 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa if (!IERC165(address(_multisigSettings.signerList)).supportsInterface(type(ISignerList).interfaceId)) { revert InvalidSignerList(_multisigSettings.signerList); } - + uint16 addresslistLength_ = uint16(_multisigSettings.signerList.addresslistLength()); if (_multisigSettings.minApprovals > addresslistLength_) { diff --git a/src/SignerList.sol b/src/SignerList.sol index 8a3838d..eff9ae3 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -206,8 +206,15 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza /// @notice Internal function to update the plugin settings. /// @param _newSettings The new settings. function _updateSettings(Settings calldata _newSettings) internal { - if (!IERC165(address(_newSettings.encryptionRegistry)).supportsInterface(type(IEncryptionRegistry).interfaceId)) - { + // Avoid writing if not needed + if ( + _newSettings.encryptionRegistry == settings.encryptionRegistry + && _newSettings.minSignerListLength == settings.minSignerListLength + ) { + return; + } else if ( + !IERC165(address(_newSettings.encryptionRegistry)).supportsInterface(type(IEncryptionRegistry).interfaceId) + ) { revert InvalidEncryptionRegitry(address(_newSettings.encryptionRegistry)); } diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol index 72425ee..8e247fe 100644 --- a/src/factory/TaikoDaoFactory.sol +++ b/src/factory/TaikoDaoFactory.sol @@ -5,6 +5,7 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; import {Multisig} from "../Multisig.sol"; import {EmergencyMultisig} from "../EmergencyMultisig.sol"; +import {SignerList} from "../SignerList.sol"; import {EncryptionRegistry} from "../EncryptionRegistry.sol"; import {OptimisticTokenVotingPlugin} from "../OptimisticTokenVotingPlugin.sol"; import {OptimisticTokenVotingPluginSetup} from "../setup/OptimisticTokenVotingPluginSetup.sol"; @@ -81,12 +82,13 @@ contract TaikoDaoFactory { Multisig multisigPlugin; EmergencyMultisig emergencyMultisigPlugin; OptimisticTokenVotingPlugin optimisticTokenVotingPlugin; + // Helpers + SignerList signerList; + EncryptionRegistry encryptionRegistry; // Plugin repo's PluginRepo multisigPluginRepo; PluginRepo emergencyMultisigPluginRepo; PluginRepo optimisticTokenVotingPluginRepo; - // Other - EncryptionRegistry encryptionRegistry; } /// @notice Thrown when attempting to call deployOnce() when the DAO is already deployed. @@ -112,11 +114,20 @@ contract TaikoDaoFactory { DAO dao = prepareDao(); deployment.dao = dao; + // DEPLOY THE SIGNER LIST AND REGISTRY + deployment.signerList = deploySignerListNoSettings(dao); + deployment.encryptionRegistry = deployEncryptionRegistry(); + // Link them together + deployment.signerList.updateSettings( + SignerList.Settings(deployment.encryptionRegistry, uint16(settings.multisigMembers.length)) + ); + // DEPLOY THE PLUGINS - (deployment.multisigPlugin, deployment.multisigPluginRepo, preparedMultisigSetupData) = prepareMultisig(dao); + (deployment.multisigPlugin, deployment.multisigPluginRepo, preparedMultisigSetupData) = + prepareMultisig(dao, deployment.signerList); (deployment.emergencyMultisigPlugin, deployment.emergencyMultisigPluginRepo, preparedEmergencyMultisigSetupData) - = prepareEmergencyMultisig(dao, deployment.multisigPlugin); + = prepareEmergencyMultisig(dao, deployment.signerList); ( deployment.optimisticTokenVotingPlugin, @@ -149,9 +160,6 @@ contract TaikoDaoFactory { // REMOVE THIS CONTRACT AS OWNER revokeOwnerPermission(deployment.dao); - - // DEPLOY OTHER CONTRACTS - deployment.encryptionRegistry = deployEncryptionRegistry(); } function prepareDao() internal returns (DAO dao) { @@ -188,18 +196,21 @@ contract TaikoDaoFactory { dao.applySingleTargetPermissions(address(dao), items); } - function prepareMultisig(DAO dao) internal returns (Multisig, PluginRepo, IPluginSetup.PreparedSetupData memory) { + function prepareMultisig(DAO dao, SignerList signerList) + internal + returns (Multisig, PluginRepo, IPluginSetup.PreparedSetupData memory) + { // Publish repo PluginRepo pluginRepo = PluginRepoFactory(settings.pluginRepoFactory).createPluginRepoWithFirstVersion( settings.stdMultisigEnsDomain, address(settings.multisigPluginSetup), address(dao), " ", " " ); bytes memory settingsData = settings.multisigPluginSetup.encodeInstallationParameters( - settings.multisigMembers, Multisig.MultisigSettings( true, // onlyListed settings.minStdApprovals, settings.minStdProposalDuration, // destination minDuration + signerList, settings.multisigExpirationPeriod ) ); @@ -216,7 +227,7 @@ contract TaikoDaoFactory { return (Multisig(plugin), pluginRepo, preparedSetupData); } - function prepareEmergencyMultisig(DAO dao, Addresslist multisigPlugin) + function prepareEmergencyMultisig(DAO dao, SignerList signerList) internal returns (EmergencyMultisig, PluginRepo, IPluginSetup.PreparedSetupData memory) { @@ -229,7 +240,7 @@ contract TaikoDaoFactory { EmergencyMultisig.MultisigSettings( true, // onlyListed settings.minEmergencyApprovals, // minAppovals - Addresslist(multisigPlugin), + signerList, settings.multisigExpirationPeriod ) ); @@ -302,8 +313,14 @@ contract TaikoDaoFactory { return (OptimisticTokenVotingPlugin(plugin), pluginRepo, preparedSetupData); } + function deploySignerListNoSettings(DAO dao) internal returns (SignerList helper) { + helper = new SignerList(); + + helper.initialize(dao, settings.multisigMembers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + } + function deployEncryptionRegistry() internal returns (EncryptionRegistry) { - return new EncryptionRegistry(deployment.multisigPlugin); + return new EncryptionRegistry(deployment.signerList); } function applyPluginInstallation( diff --git a/src/setup/EmergencyMultisigPluginSetup.sol b/src/setup/EmergencyMultisigPluginSetup.sol index 557b9f5..a823e13 100644 --- a/src/setup/EmergencyMultisigPluginSetup.sol +++ b/src/setup/EmergencyMultisigPluginSetup.sol @@ -26,12 +26,11 @@ contract EmergencyMultisigPluginSetup is PluginSetup { returns (address plugin, PreparedSetupData memory preparedSetupData) { // Decode `_data` to extract the parameters needed for deploying and initializing `EmergencyMultisig` plugin. - (address[] memory members, EmergencyMultisig.MultisigSettings memory multisigSettings) = - decodeInstallationParameters(_data); + (EmergencyMultisig.MultisigSettings memory multisigSettings) = decodeInstallationParameters(_data); // Prepare and Deploy the plugin proxy. plugin = createERC1967Proxy( - address(multisigBase), abi.encodeCall(EmergencyMultisig.initialize, (IDAO(_dao), members, multisigSettings)) + address(multisigBase), abi.encodeCall(EmergencyMultisig.initialize, (IDAO(_dao), multisigSettings)) ); // Prepare permissions @@ -99,19 +98,20 @@ contract EmergencyMultisigPluginSetup is PluginSetup { } /// @notice Encodes the given installation parameters into a byte array - function encodeInstallationParameters( - address[] memory _members, - EmergencyMultisig.MultisigSettings memory _multisigSettings - ) external pure returns (bytes memory) { - return abi.encode(_members, _multisigSettings); + function encodeInstallationParameters(EmergencyMultisig.MultisigSettings memory _multisigSettings) + external + pure + returns (bytes memory) + { + return abi.encode(_multisigSettings); } /// @notice Decodes the given byte array into the original installation parameters function decodeInstallationParameters(bytes memory _data) public pure - returns (address[] memory _members, EmergencyMultisig.MultisigSettings memory _multisigSettings) + returns (EmergencyMultisig.MultisigSettings memory _multisigSettings) { - (_members, _multisigSettings) = abi.decode(_data, (address[], EmergencyMultisig.MultisigSettings)); + (_multisigSettings) = abi.decode(_data, (EmergencyMultisig.MultisigSettings)); } } From a275b20147d5e2ef7b91ac0bfda53d63cb6693e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 22 Oct 2024 11:29:37 +0200 Subject: [PATCH 10/45] Testing WIP --- src/EncryptionRegistry.sol | 2 +- src/interfaces/IEncryptionRegistry.sol | 7 +- test/EncryptionRegistry.t.sol | 263 ++++++++++++------------- test/SignerList.t.sol | 144 ++++++++++++++ test/helpers/DaoBuilder.sol | 40 ++-- 5 files changed, 308 insertions(+), 148 deletions(-) create mode 100644 test/SignerList.t.sol diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index 559df62..4454ba8 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -78,7 +78,7 @@ contract EncryptionRegistry is IEncryptionRegistry { else if ( accounts[msg.sender].appointedWallet != address(0) && accounts[msg.sender].appointedWallet != msg.sender ) { - revert CannotSetPubKeyForAppointedWallets(); + revert MustResetAppointment(); } _setPublicKey(msg.sender, _publicKey); diff --git a/src/interfaces/IEncryptionRegistry.sol b/src/interfaces/IEncryptionRegistry.sol index 4a5c2ac..e096e46 100644 --- a/src/interfaces/IEncryptionRegistry.sol +++ b/src/interfaces/IEncryptionRegistry.sol @@ -21,8 +21,8 @@ interface IEncryptionRegistry { /// @notice Raised when a non appointed wallet tries to define the public key error MustBeAppointed(); - /// @notice Raised when an account attempts to define the public key of the appointed wallet - error CannotSetPubKeyForAppointedWallets(); + /// @notice Raised when someone else is appointed and the account owner tries to override the public key of the appointed wallet. The appointed value should be set to address(0) or msg.sender first. + error MustResetAppointment(); /// @notice Raised when the caller is not an addresslist compatible contract error InvalidAddressList(); @@ -44,6 +44,9 @@ interface IEncryptionRegistry { /// @return appointerAddress The address of the appointer account or zero. function appointedBy(address wallet) external returns (address appointerAddress); + /// @notice Returns the address of the account registered at the given index + function registeredAccounts(uint256) external view returns (address); + /// @notice Returns the list of addresses on the registry /// @dev Use this function to get all addresses in a single call. You can still call registeredAccounts[idx] to resolve them one by one. function getRegisteredAccounts() external view returns (address[] memory); diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index d74073b..40698e2 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.17; import {AragonTest} from "./base/AragonTest.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; +import {EncryptionRegistry, IEncryptionRegistry} from "../src/EncryptionRegistry.sol"; +import {SignerList} from "../src/SignerList.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../src/Multisig.sol"; @@ -21,17 +22,16 @@ contract EncryptionRegistryTest is AragonTest { function setUp() public { builder = new DaoBuilder(); - (dao,, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember(carol) - .withMultisigMember(david).build(); - - registry = new EncryptionRegistry(multisig); + (dao,, multisig,,,, registry,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( + carol + ).withMultisigMember(david).build(); } function test_ShouldAppointWallets() public { address addrValue; bytes32 bytesValue; - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -39,7 +39,7 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(alice); registry.appointWallet(address(0x1234000000000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(bytesValue, 0); assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); @@ -47,10 +47,10 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(bob); registry.appointWallet(address(0x0000567800000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(bytesValue, 0); assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0); assertEq(addrValue, address(0x0000567800000000000000000000000000000000)); @@ -58,13 +58,13 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(carol); registry.appointWallet(address(0x0000000090aB0000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(bytesValue, 0); assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0); assertEq(addrValue, address(0x0000567800000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(bytesValue, 0); assertEq(addrValue, address(0x0000000090aB0000000000000000000000000000)); @@ -72,16 +72,16 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(david); registry.appointWallet(address(0x000000000000cdEf000000000000000000000000)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(bytesValue, 0); assertEq(addrValue, address(0x1234000000000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0); assertEq(addrValue, address(0x0000567800000000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(bytesValue, 0); assertEq(addrValue, address(0x0000000090aB0000000000000000000000000000)); - (addrValue, bytesValue) = registry.members(david); + (addrValue, bytesValue) = registry.accounts(david); assertEq(bytesValue, 0); assertEq(addrValue, address(0x000000000000cdEf000000000000000000000000)); } @@ -90,7 +90,7 @@ contract EncryptionRegistryTest is AragonTest { address addrValue; bytes32 bytesValue; - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -98,7 +98,7 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(alice); registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -106,10 +106,10 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(bob); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); @@ -117,13 +117,13 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(carol); registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); @@ -131,16 +131,16 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(david); registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(david); + (addrValue, bytesValue) = registry.accounts(david); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } @@ -151,7 +151,7 @@ contract EncryptionRegistryTest is AragonTest { address addrValue; bytes32 bytesValue; - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -161,7 +161,7 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(appointedWallet); registry.setPublicKey(alice, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -171,10 +171,10 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(appointedWallet); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); @@ -184,13 +184,13 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(appointedWallet); registry.setPublicKey(carol, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); @@ -200,16 +200,16 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(appointedWallet); registry.setPublicKey(david, 0x000000000000cdef000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(david); + (addrValue, bytesValue) = registry.accounts(david); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } @@ -221,73 +221,73 @@ contract EncryptionRegistryTest is AragonTest { address addrValue; bytes32 bytesValue; - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Alice vm.startPrank(alice); registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); registry.appointWallet(appointedWallet); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Bob vm.startPrank(bob); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); registry.appointWallet(appointedWallet); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); registry.setOwnPublicKey(0x0000000090ab0000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); registry.appointWallet(appointedWallet); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); registry.setOwnPublicKey(0x000000000000cdef000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(david); + (addrValue, bytesValue) = registry.accounts(david); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); registry.appointWallet(appointedWallet); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(david); + (addrValue, bytesValue) = registry.accounts(david); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } @@ -296,7 +296,7 @@ contract EncryptionRegistryTest is AragonTest { address addrValue; bytes32 bytesValue; - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -304,7 +304,7 @@ contract EncryptionRegistryTest is AragonTest { // OK registry.appointWallet(address(0x1234)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0x1234)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -314,23 +314,23 @@ contract EncryptionRegistryTest is AragonTest { registry.appointWallet(david); // KO - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.CannotAppointContracts.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.CannotAppointContracts.selector)); registry.appointWallet(address(dao)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, david); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // KO - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.CannotAppointContracts.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.CannotAppointContracts.selector)); registry.appointWallet(address(multisig)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, david); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // KO - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.CannotAppointContracts.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.CannotAppointContracts.selector)); registry.appointWallet(address(registry)); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, david); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } @@ -338,14 +338,14 @@ contract EncryptionRegistryTest is AragonTest { function test_ShouldRevertIfNotListed(address appointedWallet) public { if (Address.isContract(appointedWallet)) return; + SignerList signerList; address addrValue; bytes32 bytesValue; // Only Alice - (,, multisig,,,) = new DaoBuilder().withMultisigMember(alice).build(); - registry = new EncryptionRegistry(multisig); + (,, multisig,,, signerList, registry,) = new DaoBuilder().withMultisigMember(alice).build(); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -353,10 +353,10 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); - assertEq(multisig.isMember(alice), true); + assertEq(signerList.isListed(alice), true); registry.setOwnPublicKey(0x5678000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); @@ -365,7 +365,7 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(alice); registry.setPublicKey(alice, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, alice); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -373,61 +373,61 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.appointWallet(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); vm.startPrank(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(bob, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, alice); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.appointWallet(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); vm.startPrank(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(carol, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, alice); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.appointWallet(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); vm.startPrank(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.RegistrationForbidden.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(david, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, alice); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(carol); + (addrValue, bytesValue) = registry.accounts(carol); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(david); + (addrValue, bytesValue) = registry.accounts(david); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } @@ -441,10 +441,10 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.NotAppointed.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeAppointed.selector)); registry.setPublicKey(alice, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -454,17 +454,17 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(appointedWallet); registry.setPublicKey(alice, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); // Bob vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.NotAppointed.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeAppointed.selector)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -474,7 +474,7 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(appointedWallet); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); } @@ -489,10 +489,10 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); registry.appointWallet(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.OwnerNotAppointed.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustResetAppointment.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -500,17 +500,17 @@ contract EncryptionRegistryTest is AragonTest { registry.appointWallet(alice); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(alice); + (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, alice); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); // Bob vm.startPrank(bob); registry.appointWallet(appointedWallet); - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.OwnerNotAppointed.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustResetAppointment.selector)); registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); @@ -518,7 +518,7 @@ contract EncryptionRegistryTest is AragonTest { registry.appointWallet(bob); registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); - (addrValue, bytesValue) = registry.members(bob); + (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, bob); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); } @@ -571,42 +571,42 @@ contract EncryptionRegistryTest is AragonTest { registry.setOwnPublicKey(0x0000123400000000000000000000000000000000000000000000000000000000); } - function test_ShouldCountRegisteredAddresses() public { - assertEq(registry.getRegisteredAddressesLength(), 0, "Incorrect count"); + function test_RegisteredAddressShouldHaveTheRightLength() public { + assertEq(registry.getRegisteredAccounts().length, 0, "Incorrect length"); // Set public key first // Alice vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); - assertEq(registry.getRegisteredAddressesLength(), 1, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect length"); registry.appointWallet(address(0x1234)); - assertEq(registry.getRegisteredAddressesLength(), 1, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect length"); // Bob vm.startPrank(bob); registry.setOwnPublicKey(bytes32(uint256(2345))); - assertEq(registry.getRegisteredAddressesLength(), 2, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect length"); registry.appointWallet(address(0x5678)); - assertEq(registry.getRegisteredAddressesLength(), 2, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect length"); // Appoint first // Carol vm.startPrank(carol); registry.appointWallet(address(0x90ab)); - assertEq(registry.getRegisteredAddressesLength(), 3, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 3, "Incorrect length"); registry.appointWallet(carol); registry.setPublicKey(carol, bytes32(uint256(3456))); - assertEq(registry.getRegisteredAddressesLength(), 3, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 3, "Incorrect length"); // David vm.startPrank(david); registry.appointWallet(address(0xcdef)); - assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 4, "Incorrect length"); registry.appointWallet(david); registry.setPublicKey(david, bytes32(uint256(4567))); - assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 4, "Incorrect length"); } function test_ShouldEnumerateRegisteredAddresses() public { @@ -615,41 +615,41 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); - assertEq(registry.registeredAddresses(0), alice); + assertEq(registry.registeredAccounts(0), alice); registry.appointWallet(address(0x1234)); - assertEq(registry.registeredAddresses(0), alice); + assertEq(registry.registeredAccounts(0), alice); // Bob vm.startPrank(bob); registry.setOwnPublicKey(bytes32(uint256(2345))); - assertEq(registry.registeredAddresses(1), bob); + assertEq(registry.registeredAccounts(1), bob); registry.appointWallet(address(0x5678)); - assertEq(registry.registeredAddresses(1), bob); + assertEq(registry.registeredAccounts(1), bob); // Appoint first // Carol vm.startPrank(carol); registry.appointWallet(address(0x90ab)); - assertEq(registry.registeredAddresses(2), carol); + assertEq(registry.registeredAccounts(2), carol); registry.appointWallet(carol); registry.setPublicKey(carol, bytes32(uint256(3456))); - assertEq(registry.registeredAddresses(2), carol); + assertEq(registry.registeredAccounts(2), carol); // David vm.startPrank(david); registry.appointWallet(address(0xcdef)); - assertEq(registry.registeredAddresses(3), david); + assertEq(registry.registeredAccounts(3), david); registry.appointWallet(david); registry.setPublicKey(david, bytes32(uint256(4567))); - assertEq(registry.registeredAddresses(3), david); + assertEq(registry.registeredAccounts(3), david); - assertEq(registry.getRegisteredAddressesLength(), 4, "Incorrect count"); + assertEq(registry.getRegisteredAccounts().length, 4, "Incorrect length"); - assertEq(registry.registeredAddresses(0), alice); - assertEq(registry.registeredAddresses(1), bob); - assertEq(registry.registeredAddresses(2), carol); - assertEq(registry.registeredAddresses(3), david); + assertEq(registry.registeredAccounts(0), alice); + assertEq(registry.registeredAccounts(1), bob); + assertEq(registry.registeredAccounts(2), carol); + assertEq(registry.registeredAccounts(3), david); } function test_ShouldLoadTheRegisteredAddresses() public { @@ -658,36 +658,36 @@ contract EncryptionRegistryTest is AragonTest { // Alice vm.startPrank(alice); registry.setOwnPublicKey(bytes32(uint256(1234))); - assertEq(registry.registeredAddresses(0), alice); + assertEq(registry.registeredAccounts(0), alice); registry.appointWallet(address(0x1234)); - assertEq(registry.registeredAddresses(0), alice); + assertEq(registry.registeredAccounts(0), alice); // Bob vm.startPrank(bob); registry.setOwnPublicKey(bytes32(uint256(2345))); - assertEq(registry.registeredAddresses(1), bob); + assertEq(registry.registeredAccounts(1), bob); registry.appointWallet(address(0x5678)); - assertEq(registry.registeredAddresses(1), bob); + assertEq(registry.registeredAccounts(1), bob); // Appoint first // Carol vm.startPrank(carol); registry.appointWallet(address(0x90ab)); - assertEq(registry.registeredAddresses(2), carol); + assertEq(registry.registeredAccounts(2), carol); registry.appointWallet(carol); registry.setPublicKey(carol, bytes32(uint256(3456))); - assertEq(registry.registeredAddresses(2), carol); + assertEq(registry.registeredAccounts(2), carol); // David vm.startPrank(david); registry.appointWallet(address(0xcdef)); - assertEq(registry.registeredAddresses(3), david); + assertEq(registry.registeredAccounts(3), david); registry.appointWallet(david); registry.setPublicKey(david, bytes32(uint256(4567))); - assertEq(registry.registeredAddresses(3), david); + assertEq(registry.registeredAccounts(3), david); - address[] memory addresses = registry.getRegisteredAddresses(); + address[] memory addresses = registry.getRegisteredAccounts(); assertEq(addresses.length, 4); assertEq(addresses[0], alice); assertEq(addresses[1], bob); @@ -697,12 +697,11 @@ contract EncryptionRegistryTest is AragonTest { function test_TheConstructorShouldRevertIfInvalidAddressList() public { // Fail - vm.expectRevert(abi.encodeWithSelector(EncryptionRegistry.InvalidAddressList.selector)); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.InvalidAddressList.selector)); new EncryptionRegistry(Addresslist(address(this))); // OK - (,, multisig,,,) = new DaoBuilder().withMultisigMember(alice).build(); - new EncryptionRegistry(multisig); + (,, multisig,,,,,) = new DaoBuilder().withMultisigMember(alice).build(); } /// @dev mock function for test_TheConstructorShouldRevertIfInvalidAddressList() diff --git a/test/SignerList.t.sol b/test/SignerList.t.sol new file mode 100644 index 0000000..873413e --- /dev/null +++ b/test/SignerList.t.sol @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import {AragonTest} from "./base/AragonTest.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; +import {SignerList} from "../src/SignerList.sol"; +import {DaoBuilder} from "./helpers/DaoBuilder.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {Multisig} from "../src/Multisig.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +contract EncryptionRegistryTest is AragonTest { + SignerList signerList; + EncryptionRegistry encryptionRegistry; + DaoBuilder builder; + DAO dao; + Multisig multisig; + address[] signers; + + // Events/errors to be tested here (duplicate) + error SignerListLengthOutOfBounds(uint16 limit, uint256 actual); + error InvalidEncryptionRegitry(address givenAddress); + + function setUp() public { + builder = new DaoBuilder(); + (dao,, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) + .withMultisigMember(carol).withMultisigMember(david).build(); + + signers = new address[](4); + signers[0] = alice; + signers[1] = bob; + signers[2] = carol; + signers[3] = david; + } + + // Initialize + function test_InitializeRevertsIfInitialized() public { + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + + vm.expectRevert(bytes("Initializable: contract is already initialized")); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + } + + function test_InitializeSetsTheRightValues() public { + // 1 + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + + (EncryptionRegistry reg, uint16 minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(0), "Incorrect address"); + vm.assertEq(minSignerListLength, 0); + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + + // 2 + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 0)); + + (reg, minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(minSignerListLength, 0); + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + + // 3 + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2)); + + (reg, minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(minSignerListLength, 2); + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + + // 4 + signers = new address[](2); + signers[0] = address(100); + signers[0] = address(200); + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 1)); + + (reg, minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(minSignerListLength, 1); + vm.assertEq(signerList.addresslistLength(), 2, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(bob), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(carol), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(david), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); + } + + function test_InitializingWithAnInvalidRegistryShouldRevert() public { + // 1 + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(alice)), 2)); + + vm.expectRevert(InvalidEncryptionRegitry.selector); + + // 2 + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(bob)), 3)); + + vm.expectRevert(InvalidEncryptionRegitry.selector); + + // OK + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2)); + } + + function test_InitializingWithTooManySignersReverts() public { + // 1 + signers = new address[](type(uint16).max + 1); + + signerList = new SignerList(); + vm.expectRevert( + abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 1) + ); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + + // 2 + signers = new address[](type(uint16).max + 10); + + signerList = new SignerList(); + vm.expectRevert( + abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 10) + ); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + } +} diff --git a/test/helpers/DaoBuilder.sol b/test/helpers/DaoBuilder.sol index 94b8fc9..f1896e1 100644 --- a/test/helpers/DaoBuilder.sol +++ b/test/helpers/DaoBuilder.sol @@ -6,6 +6,8 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../../src/Multisig.sol"; import {EmergencyMultisig} from "../../src/EmergencyMultisig.sol"; import {OptimisticTokenVotingPlugin} from "../../src/OptimisticTokenVotingPlugin.sol"; +import {SignerList} from "../../src/SignerList.sol"; +import {EncryptionRegistry} from "../../src/EncryptionRegistry.sol"; import {createProxyAndCall} from "../../src/helpers/proxy.sol"; import {RATIO_BASE} from "@aragon/osx/plugins/utils/Ratio.sol"; import {TaikoL1Mock, TaikoL1PausedMock, TaikoL1WithOldLastBlock, TaikoL1Incompatible} from "../mocks/TaikoL1Mock.sol"; @@ -147,7 +149,9 @@ contract DaoBuilder is Test { } function withMinApprovals(uint16 newMinApprovals) public returns (DaoBuilder) { - if (newMinApprovals > multisigMembers.length) revert("You should add enough multisig members first"); + if (newMinApprovals > multisigMembers.length) { + revert("You should add enough multisig members first"); + } minApprovals = newMinApprovals; return this; } @@ -162,6 +166,8 @@ contract DaoBuilder is Test { Multisig multisig, EmergencyMultisig emergencyMultisig, GovernanceERC20Mock votingToken, + SignerList signerList, + EncryptionRegistry encryptionRegistry, ITaikoL1 taikoL1 ) { @@ -217,15 +223,8 @@ contract DaoBuilder is Test { ); } - // Standard multisig + // Encryption registry and signer list { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: onlyListed, - minApprovals: minApprovals, - destinationProposalDuration: stdProposalDuration, - proposalExpirationPeriod: multisigProposalExpirationPeriod - }); - address[] memory signers; if (multisigMembers.length > 0) { signers = multisigMembers; @@ -234,10 +233,25 @@ contract DaoBuilder is Test { signers = new address[](1); signers[0] = owner; } + + signerList = new SignerList(); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + encryptionRegistry = new EncryptionRegistry(signerList); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, uint16(signers.length))); + } + + // Standard multisig + { + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: onlyListed, + minApprovals: minApprovals, + destinationProposalDuration: stdProposalDuration, + signerList: signerList, + proposalExpirationPeriod: multisigProposalExpirationPeriod + }); + multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) + createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings))) ); } @@ -246,7 +260,7 @@ contract DaoBuilder is Test { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: onlyListed, minApprovals: minApprovals, - addresslistSource: multisig, + signerList: signerList, proposalExpirationPeriod: multisigProposalExpirationPeriod }); From b5353ae5aee49012e8084af5812216b44484bf0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 22 Oct 2024 19:00:05 +0400 Subject: [PATCH 11/45] Work in progress --- src/EmergencyMultisig.sol | 1 - test/EmergencyMultisig.t.sol | 339 ++++++++----------------- test/integration/TaikoDaoFactory.t.sol | 4 +- 3 files changed, 113 insertions(+), 231 deletions(-) diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index df1ca17..ce3d02d 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -8,7 +8,6 @@ import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {PluginUUPSUpgradeable} from "@aragon/osx/core/plugin/PluginUUPSUpgradeable.sol"; import {ProposalUpgradeable} from "@aragon/osx/core/plugin/proposal/ProposalUpgradeable.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IEmergencyMultisig} from "./interfaces/IEmergencyMultisig.sol"; import {OptimisticTokenVotingPlugin} from "./OptimisticTokenVotingPlugin.sol"; import {SignerList} from "./SignerList.sol"; diff --git a/test/EmergencyMultisig.t.sol b/test/EmergencyMultisig.t.sol index 08a2eea..6a0ba3f 100644 --- a/test/EmergencyMultisig.t.sol +++ b/test/EmergencyMultisig.t.sol @@ -7,13 +7,13 @@ import {StandardProposalCondition} from "../src/conditions/StandardProposalCondi import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; import {Multisig} from "../src/Multisig.sol"; import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; +import {SignerList} from "../src/SignerList.sol"; import {IEmergencyMultisig} from "../src/interfaces/IEmergencyMultisig.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {PermissionManager} from "@aragon/osx/core/permission/PermissionManager.sol"; import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; -import {IMembership} from "@aragon/osx/core/plugin/membership/IMembership.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; @@ -28,10 +28,11 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig eMultisig; Multisig stdMultisig; OptimisticTokenVotingPlugin optimisticPlugin; + SignerList signerList; // Events/errors to be tested here (duplicate) event MultisigSettingsUpdated( - bool onlyListed, uint16 indexed minApprovals, Addresslist addresslistSource, uint64 expiration + bool onlyListed, uint16 indexed minApprovals, SignerList signerList, uint64 proposalExpirationPeriod ); event MembersAdded(address[] members); event MembersRemoved(address[] members); @@ -62,8 +63,10 @@ contract EmergencyMultisigTest is AragonTest { vm.roll(100); builder = new DaoBuilder(); - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).withMinDuration(0).build(); + (dao, optimisticPlugin, stdMultisig, eMultisig,, signerList,,) = builder.withMultisigMember(alice) + .withMultisigMember(bob).withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).withMinDuration( + 0 + ).build(); } function test_RevertsIfTryingToReinitialize() public { @@ -71,7 +74,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -91,7 +94,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 2, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -122,7 +125,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -153,7 +156,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory emSettings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -163,13 +166,13 @@ contract EmergencyMultisigTest is AragonTest { ) ); - (,, Addresslist givenAddressListSource,) = eMultisig.multisigSettings(); - assertEq(address(givenAddressListSource), address(stdMultisig), "Incorrect addresslistSource"); + (,, Addresslist givenSignerList,) = eMultisig.multisigSettings(); + assertEq(address(givenSignerList), address(stdMultisig), "Incorrect addresslistSource"); // Redeploy with a new addresslist source - (,, Multisig newMultisig,,,) = builder.build(); + (,,,,, signerList,,) = builder.build(); - emSettings.addresslistSource = newMultisig; + emSettings.signerList = signerList; eMultisig = EmergencyMultisig( createProxyAndCall( @@ -177,8 +180,8 @@ contract EmergencyMultisigTest is AragonTest { ) ); - (,, givenAddressListSource,) = eMultisig.multisigSettings(); - assertEq(address(givenAddressListSource), address(emSettings.addresslistSource), "Incorrect addresslistSource"); + (,, signerList,) = eMultisig.multisigSettings(); + assertEq(address(signerList), address(emSettings.signerList), "Incorrect addresslistSource"); } function test_InitializeSetsProposalExpiration() public { @@ -186,7 +189,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 15 days }); address[] memory signers = new address[](4); @@ -222,12 +225,12 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 5 days }); vm.expectEmit(); - emit MultisigSettingsUpdated(true, uint16(3), stdMultisig, 5 days); + emit MultisigSettingsUpdated(true, uint16(3), signerList, 5 days); eMultisig = EmergencyMultisig( createProxyAndCall( @@ -237,16 +240,16 @@ contract EmergencyMultisigTest is AragonTest { // Deploy with false/2/new - (,, Multisig newMultisig,,,) = builder.build(); + (,, Multisig newMultisig,,, SignerList newSignerList,,) = builder.build(); settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 2, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: 15 days }); vm.expectEmit(); - emit MultisigSettingsUpdated(false, uint16(2), newMultisig, 15 days); + emit MultisigSettingsUpdated(false, uint16(2), newSignerList, 15 days); eMultisig = EmergencyMultisig( createProxyAndCall( @@ -277,11 +280,6 @@ contract EmergencyMultisigTest is AragonTest { assertEq(supported, true, "Should support IProposal"); } - function test_SupportsIMembership() public view { - bool supported = eMultisig.supportsInterface(type(IMembership).interfaceId); - assertEq(supported, true, "Should support IMembership"); - } - function test_SupportsIEmergencyMultisig() public view { bool supported = eMultisig.supportsInterface(type(IEmergencyMultisig).interfaceId); assertEq(supported, true, "Should support IEmergencyMultisig"); @@ -289,11 +287,11 @@ contract EmergencyMultisigTest is AragonTest { // UPDATE MULTISIG SETTINGS - function test_ShouldntAllowMinApprovalsHigherThenAddrListLength() public { + function test_ShouldNotAllowMinApprovalsGreaterThanSignerListLength() public { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 5, - addresslistSource: stdMultisig, // Greater than 4 members + signerList: signerList, // Greater than 4 members proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -309,7 +307,7 @@ contract EmergencyMultisigTest is AragonTest { settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 6, - addresslistSource: stdMultisig, // Greater than 4 members + signerList: signerList, // Greater than 4 members proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 6)); @@ -318,32 +316,9 @@ contract EmergencyMultisigTest is AragonTest { address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) ) ); - } - - function test_ShouldNotAllowMinApprovalsZero() public { - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 0, - addresslistSource: stdMultisig, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 1, 0)); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - // Retry with onlyListed false - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 0, - addresslistSource: stdMultisig, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 1, 0)); + // OK + settings.minApprovals = 4; eMultisig = EmergencyMultisig( createProxyAndCall( address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) @@ -358,98 +333,96 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 1, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); vm.expectEmit(); - emit MultisigSettingsUpdated(true, 1, stdMultisig, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + emit MultisigSettingsUpdated(true, 1, signerList, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); eMultisig.updateMultisigSettings(settings); // 2 - (,, Multisig newMultisig,,,) = builder.build(); + (,, Multisig newMultisig,,, SignerList newSignerList,,) = builder.build(); settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 2, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1 }); vm.expectEmit(); - emit MultisigSettingsUpdated(true, 2, newMultisig, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); + emit MultisigSettingsUpdated(true, 2, newSignerList, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); eMultisig.updateMultisigSettings(settings); // 3 - (,, newMultisig,,,) = builder.build(); + (,, newMultisig,,, newSignerList,,) = builder.build(); settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 3, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: 4 days }); vm.expectEmit(); - emit MultisigSettingsUpdated(false, 3, newMultisig, 4 days); + emit MultisigSettingsUpdated(false, 3, newSignerList, 4 days); eMultisig.updateMultisigSettings(settings); // 4 settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 4, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 8 days }); vm.expectEmit(); - emit MultisigSettingsUpdated(false, 4, stdMultisig, 8 days); + emit MultisigSettingsUpdated(false, 4, signerList, 8 days); eMultisig.updateMultisigSettings(settings); } - function test_UpdateSettingsShouldRevertWithInvalidAddressSource() public { + function test_UpdateSettingsShouldRevertWithInvalidSignerList() public { dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); // ko EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 1, - addresslistSource: Multisig(address(dao)), + signerList: SignerList(address(dao)), proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidAddressListSource.selector, address(dao))); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidSignerList.selector, address(dao))); eMultisig.updateMultisigSettings(settings); // ko 2 settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 1, - addresslistSource: Multisig(address(optimisticPlugin)), + signerList: SignerList(address(optimisticPlugin)), proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - vm.expectRevert( - abi.encodeWithSelector(EmergencyMultisig.InvalidAddressListSource.selector, address(optimisticPlugin)) - ); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidSignerList.selector, address(optimisticPlugin))); eMultisig.updateMultisigSettings(settings); // ok - (,, Multisig newMultisig,,,) = builder.build(); + (,,,,, SignerList newSignerList,,) = builder.build(); settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 1, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); eMultisig.updateMultisigSettings(settings); } function test_onlyWalletWithPermissionsCanUpdateSettings() public { - (,, Multisig newMultisig,,,) = builder.build(); + (,,,,, SignerList newSignerList,,) = builder.build(); EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 1, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: 3 days }); vm.expectRevert( @@ -475,174 +448,85 @@ contract EmergencyMultisigTest is AragonTest { dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); vm.expectEmit(); - emit MultisigSettingsUpdated(false, 1, newMultisig, 3 days); + emit MultisigSettingsUpdated(false, 1, newSignerList, 3 days); eMultisig.updateMultisigSettings(settings); } - function test_IsMemberShouldReturnWhenApropriate() public { - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - dao.grant(address(stdMultisig), alice, stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - address[] memory signers = new address[](1); - signers[0] = bob; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), false, "Should not be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - // 2 - stdMultisig.addAddresses(signers); // Add Bob back - signers[0] = alice; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), false, "Should not be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - // 3 - stdMultisig.addAddresses(signers); // Add Alice back - signers[0] = carol; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), false, "Should not be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - // 4 - stdMultisig.addAddresses(signers); // Add Carol back - signers[0] = david; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), false, "Should not be a member"); - } - - function test_IsMemberIsListedShouldReturnTheSameValue() public { - assertEq(stdMultisig.isListed(alice), eMultisig.isMember(alice), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(bob), eMultisig.isMember(bob), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(carol), eMultisig.isMember(carol), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(david), eMultisig.isMember(david), "isMember isListed should be equal"); - - dao.grant(address(stdMultisig), alice, stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - address[] memory signers = new address[](1); - signers[0] = alice; - stdMultisig.removeAddresses(signers); - - assertEq(stdMultisig.isListed(alice), eMultisig.isMember(alice), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(bob), eMultisig.isMember(bob), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(carol), eMultisig.isMember(carol), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(david), eMultisig.isMember(david), "isMember isListed should be equal"); - - // 2 - stdMultisig.addAddresses(signers); // Add Alice back - signers[0] = bob; - stdMultisig.removeAddresses(signers); - - assertEq(stdMultisig.isListed(alice), eMultisig.isMember(alice), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(bob), eMultisig.isMember(bob), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(carol), eMultisig.isMember(carol), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(david), eMultisig.isMember(david), "isMember isListed should be equal"); - - // 3 - stdMultisig.addAddresses(signers); // Add Bob back - signers[0] = carol; - stdMultisig.removeAddresses(signers); - - assertEq(stdMultisig.isListed(alice), eMultisig.isMember(alice), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(bob), eMultisig.isMember(bob), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(carol), eMultisig.isMember(carol), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(david), eMultisig.isMember(david), "isMember isListed should be equal"); - - // 4 - stdMultisig.addAddresses(signers); // Add Carol back - signers[0] = david; - stdMultisig.removeAddresses(signers); - - assertEq(stdMultisig.isListed(alice), eMultisig.isMember(alice), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(bob), eMultisig.isMember(bob), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(carol), eMultisig.isMember(carol), "isMember isListed should be equal"); - assertEq(stdMultisig.isListed(david), eMultisig.isMember(david), "isMember isListed should be equal"); - } + function test_MinApprovalsBiggerThanTheListReverts() public { + // MinApprovals should be within the boundaries of the list + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public { - // Deploy a new stdMultisig instance - Multisig.MultisigSettings memory mSettings = Multisig.MultisigSettings({ + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, + minApprovals: 5, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); + eMultisig.updateMultisigSettings(settings); + + // More signers + address[] memory signers = new address[](1); - signers[0] = address(0x0); // 0x0... would be a member but the chance is negligible + signers[0] = randomWallet; + signerList.addSigners(signers); - stdMultisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, mSettings))) - ); - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + // should not fail now + eMultisig.updateMultisigSettings(settings); + + // More than that, should fail again + settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, - minApprovals: 1, - addresslistSource: stdMultisig, + minApprovals: 6, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 5, 6)); + eMultisig.updateMultisigSettings(settings); - assertEq( - eMultisig.isMember(vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy))))), false, "Should be false" - ); + // OK + settings.minApprovals = 5; + eMultisig.updateMultisigSettings(settings); } function testFuzz_PermissionedUpdateSettings(address randomAccount) public { dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - (bool onlyListed, uint16 minApprovals, Addresslist addresslistSource, uint64 expiration) = + (bool onlyListed, uint16 minApprovals, SignerList givenSignerList, uint64 expiration) = eMultisig.multisigSettings(); assertEq(minApprovals, 3, "Should be 3"); assertEq(onlyListed, true, "Should be true"); - assertEq(address(addresslistSource), address(stdMultisig), "Incorrect addresslistSource"); + assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); assertEq(expiration, 10 days, "Should be 10"); // in - (,, Multisig newMultisig,,,) = builder.build(); + (,,,,, SignerList newSignerList,,) = builder.build(); EmergencyMultisig.MultisigSettings memory newSettings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 2, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: 4 days }); eMultisig.updateMultisigSettings(newSettings); - Addresslist givenAddresslistSource; - (onlyListed, minApprovals, givenAddresslistSource, expiration) = eMultisig.multisigSettings(); + (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); assertEq(minApprovals, 2, "Should be 2"); assertEq(onlyListed, false, "Should be false"); - assertEq(address(givenAddresslistSource), address(newMultisig), "Incorrect addresslistSource"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect signerList"); assertEq(expiration, 4 days, "Should be 4"); // out newSettings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 1, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 1 days }); eMultisig.updateMultisigSettings(newSettings); - (onlyListed, minApprovals, givenAddresslistSource, expiration) = eMultisig.multisigSettings(); + (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); assertEq(minApprovals, 1, "Should be 1"); assertEq(onlyListed, true, "Should be true"); - assertEq(address(givenAddresslistSource), address(stdMultisig), "Incorrect addresslistSource"); + assertEq(address(givenSignerList), address(signerList), "Incorrect signerList"); assertEq(expiration, 1 days, "Should be 1"); vm.roll(block.number + 1); @@ -651,11 +535,11 @@ contract EmergencyMultisigTest is AragonTest { if (randomAccount != alice && randomAccount != address(0)) { vm.startPrank(randomAccount); - (,, newMultisig,,,) = builder.build(); + (,,,,, newSignerList,,) = builder.build(); newSettings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 4, - addresslistSource: newMultisig, + signerList: newSignerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -670,10 +554,10 @@ contract EmergencyMultisigTest is AragonTest { ); eMultisig.updateMultisigSettings(newSettings); - (onlyListed, minApprovals, givenAddresslistSource, expiration) = eMultisig.multisigSettings(); + (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); assertEq(minApprovals, 1, "Should still be 1"); assertEq(onlyListed, true, "Should still be true"); - assertEq(address(givenAddresslistSource), address(stdMultisig), "Should still be stdMultisig"); + assertEq(address(givenSignerList), address(signerList), "Should still be signerList"); assertEq(expiration, 1 days, "Should still be 1"); } } @@ -778,7 +662,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -802,7 +686,7 @@ contract EmergencyMultisigTest is AragonTest { // creates a proposal when unlisted accounts are allowed // Deploy a new instance with custom settings - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withoutOnlyListed().build(); + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withoutOnlyListed().build(); vm.startPrank(randomWallet); eMultisig.createProposal("", 0, 0, optimisticPlugin, false); @@ -829,23 +713,23 @@ contract EmergencyMultisigTest is AragonTest { function test_RevertsWhenCreatorWasListedBeforeButNotNow() public { // reverts if `msg.sender` is not listed although she was listed in the last block - dao.grant(address(stdMultisig), alice, stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); // Remove address[] memory addrs = new address[](1); addrs[0] = alice; - stdMultisig.removeAddresses(addrs); + signerList.removeSigners(addrs); vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, alice)); eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - stdMultisig.addAddresses(addrs); // Add Alice back + signerList.addSigners(addrs); // Add Alice back vm.roll(block.number + 1); eMultisig.createProposal("", 0, 0, optimisticPlugin, false); // Add+remove addrs[0] = bob; - stdMultisig.removeAddresses(addrs); + signerList.removeSigners(addrs); vm.startPrank(bob); @@ -856,7 +740,7 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(alice); // Bob can create now - stdMultisig.addAddresses(addrs); // Add Bob back + signerList.addSigners(addrs); // Add Bob back vm.startPrank(alice); @@ -965,21 +849,20 @@ contract EmergencyMultisigTest is AragonTest { onlyListed: false, minApprovals: 1, destinationProposalDuration: 4 days, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); address[] memory signers = new address[](1); signers[0] = alice; stdMultisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, mSettings)) - ) + createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, mSettings))) ); // New emergency stdMultisig using the above EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 1, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); eMultisig = EmergencyMultisig( @@ -1005,7 +888,7 @@ contract EmergencyMultisigTest is AragonTest { function test_CanApproveReturnsFalseIfApproved() public { // returns `false` if the approver has already approved builder = new DaoBuilder(); - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withMultisigMember(alice).withMultisigMember(bob) + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMultisigMember(alice).withMultisigMember(bob) .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(4).build(); uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); @@ -1112,7 +995,7 @@ contract EmergencyMultisigTest is AragonTest { // new setup builder = new DaoBuilder(); - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMultisigMember(randomWallet).withMinApprovals(1).withMinDuration(0).build(); // now ko @@ -1206,7 +1089,7 @@ contract EmergencyMultisigTest is AragonTest { // Reverts if the signer is not listed builder = new DaoBuilder(); - (,,, eMultisig,,) = builder.withMultisigMember(alice).withMinApprovals(1).build(); + (,,, eMultisig,,,,) = builder.withMultisigMember(alice).withMinApprovals(1).build(); uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); if (randomSigner == alice) { @@ -1368,7 +1251,7 @@ contract EmergencyMultisigTest is AragonTest { function test_CanExecuteReturnsFalseIfBelowMinApprovals() public { // returns `false` if the proposal has not reached the minimum approvals yet - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withMinApprovals(2).build(); + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(2).build(); uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); @@ -1384,7 +1267,7 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(alice); // More approvals required (4) - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withMinApprovals(4).build(); + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(4).build(); pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); @@ -1508,7 +1391,7 @@ contract EmergencyMultisigTest is AragonTest { function test_ExecuteRevertsIfBelowMinApprovals() public { // reverts if minApprovals is not met yet - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withMinApprovals(2).build(); + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(2).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); bytes32 metadataUriHash = keccak256("ipfs://"); @@ -1528,7 +1411,7 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(alice); // More approvals required (4) - (dao, optimisticPlugin, stdMultisig, eMultisig,,) = builder.withMinApprovals(4).build(); + (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(4).build(); pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1648,7 +1531,7 @@ contract EmergencyMultisigTest is AragonTest { vm.expectEmit(); emit Executed(pid); vm.expectEmit(); - uint256 targetPid = 5 days << 128 | 5 days << 64; + uint256 targetPid = (5 days << 128) | (5 days << 64); emit ProposalCreated(targetPid, address(eMultisig), 5 days, 5 days, "ipfs://", actions, 0); eMultisig.execute(pid, "ipfs://", actions); @@ -1678,7 +1561,7 @@ contract EmergencyMultisigTest is AragonTest { vm.expectEmit(); emit Executed(pid); vm.expectEmit(); - targetPid = (20 days << 128 | 20 days << 64) + 1; + targetPid = ((20 days << 128) | (20 days << 64)) + 1; emit ProposalCreated(targetPid, address(eMultisig), 20 days, 20 days, "ipfs://more-metadata-here", actions, 0); eMultisig.execute(pid, "ipfs://more-metadata-here", actions); } @@ -1969,7 +1852,7 @@ contract EmergencyMultisigTest is AragonTest { function test_ExecutesSuccessfullyDespiteIncompatibleTaikoL1() public { // executes even if the TaikoL1 contract reverts - (dao, optimisticPlugin,, eMultisig,,) = builder.withIncompatibleTaikoL1().build(); + (dao, optimisticPlugin,, eMultisig,,,,) = builder.withIncompatibleTaikoL1().build(); vm.deal(address(dao), 4 ether); @@ -2040,7 +1923,7 @@ contract EmergencyMultisigTest is AragonTest { // 2 new proposal OptimisticTokenVotingPlugin newOptimisticPlugin; - (dao, newOptimisticPlugin, stdMultisig, eMultisig,,) = builder.build(); + (dao, newOptimisticPlugin, stdMultisig, eMultisig,,,,) = builder.build(); vm.deal(address(dao), 1 ether); metadataUriHash = keccak256("ipfs://another-public-metadata"); @@ -2188,7 +2071,7 @@ contract EmergencyMultisigTest is AragonTest { // Check round (open, executed, parameters, vetoTally, metadataUri, retrievedActions, allowFailureMap) = - optimisticPlugin.getProposal((uint256(block.timestamp) << 128 | uint256(block.timestamp) << 64)); + optimisticPlugin.getProposal(((uint256(block.timestamp) << 128) | (uint256(block.timestamp) << 64))); assertEq(open, false, "Should not be open"); assertEq(executed, true, "Should be executed"); @@ -2241,7 +2124,7 @@ contract EmergencyMultisigTest is AragonTest { // Check round (open, executed, parameters, vetoTally, metadataUri, retrievedActions, allowFailureMap) = - optimisticPlugin.getProposal((uint256(block.timestamp) << 128 | uint256(block.timestamp) << 64) + 1); + optimisticPlugin.getProposal(((uint256(block.timestamp) << 128) | (uint256(block.timestamp) << 64)) + 1); assertEq(open, false, "Should not be open"); assertEq(executed, true, "Should be executed"); @@ -2293,7 +2176,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); @@ -2338,7 +2221,7 @@ contract EmergencyMultisigTest is AragonTest { EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); eMultisig.upgradeToAndCall( diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index 403eac0..3304233 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -519,7 +519,7 @@ contract TaikoDaoFactoryTest is AragonTest { // ENCRYPTION REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); assertEq( - deployment.encryptionRegistry.getRegisteredAddressesLength(), 0, "Invalid getRegisteredAddressesLength" + deployment.encryptionRegistry.getRegisteredAddresses().length, 0, "Invalid getRegisteredAddresses().length" ); } @@ -758,7 +758,7 @@ contract TaikoDaoFactoryTest is AragonTest { // ENCRYPTION REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); assertEq( - deployment.encryptionRegistry.getRegisteredAddressesLength(), 0, "Invalid getRegisteredAddressesLength" + deployment.encryptionRegistry.getRegisteredAddresses().length, 0, "Invalid getRegisteredAddresses().length" ); } From 1332f2d6663420cf0e16019fa500b905c1717b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 24 Oct 2024 17:11:14 +0400 Subject: [PATCH 12/45] Defining the signer list tests --- Makefile | 2 +- TEST_TREE.md | 98 +++ src/SignerList.sol | 26 +- test/EmergencyMultisig.t.sol | 2 +- test/Multisig.t.sol | 779 +++--------------------- test/SignerList.t.sol | 1115 +++++++++++++++++++++++++++++++++- test/SignerListTree.t.sol | 257 ++++++++ test/SignerListTree.t.yaml | 154 +++++ 8 files changed, 1705 insertions(+), 728 deletions(-) create mode 100644 test/SignerListTree.t.sol create mode 100644 test/SignerListTree.t.yaml diff --git a/Makefile b/Makefile index 805ace6..9cd4f8f 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ $(TREE_FILES): $(SOURCE_FILES) %.tree: %.t.yaml @for file in $^; do \ - echo "[Convert] $$file -> $${file%.t.yaml}.tree" ; \ + echo "[Convert] $$file -> $${file%.t.yaml}.tree" ; \ cat $$file | $(MAKE_TEST_TREE) > $${file%.t.yaml}.tree ; \ done diff --git a/TEST_TREE.md b/TEST_TREE.md index bf268b3..8c86679 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -1,3 +1,101 @@ # Test tree definitions Below is the graphical definition of the contract tests implemented on [the test folder](./test) + +``` +SignerListTest +├── When deploying the contract +│ └── It should initialize normally +├── Given a deployed contract +│ └── It should fail to initialize again +├── Given a new instance +│ └── Given calling initialize +│ ├── It should set the DAO address +│ ├── It should set the addresses as signers +│ ├── It settings should match the given ones +│ ├── It should emit the SignersAdded event +│ ├── It should emit the SignerListSettingsUpdated event +│ └── Given passing more addresses than supported +│ └── It should revert +├── When calling addSigners +│ ├── When addSigners without the permission +│ │ └── It should revert +│ ├── Given passing more addresses than allowed +│ │ └── It should revert +│ ├── Given duplicate addresses +│ │ └── It should revert +│ ├── It should append the new addresses to the list +│ └── It should emit the SignersAddedEvent +├── When calling removeSigners +│ ├── When removeSigners without the permission +│ │ └── It should revert +│ ├── Given removing too many addresses // The new list will be smaller than minSignerListLength +│ │ └── It should revert +│ ├── It should more the given addresses +│ └── It should emit the SignersRemovedEvent +├── When calling isListed +│ ├── Given the member is listed +│ │ └── It returns true +│ └── Given the member is not listed +│ └── It returns false +├── When calling isListedAtBlock +│ ├── Given the member was listed +│ │ ├── Given the member is not listed now +│ │ │ └── It returns true +│ │ └── Given the member is listed now +│ │ └── It returns true +│ └── Given the member was not listed +│ ├── Given the member is delisted now +│ │ └── It returns false +│ └── Given the member is enlisted now +│ └── It returns false +├── When calling updateSettings +│ ├── When updateSettings without the permission +│ │ └── It should revert +│ ├── When encryptionRegistry is not compatible +│ │ └── It should revert +│ ├── When setting a minSignerListLength lower than the current list size +│ │ └── It should revert +│ ├── It set the new encryption registry +│ ├── It set the new minSignerListLength +│ └── It should emit a SignerListSettingsUpdated event +├── When calling resolveEncryptionAccountStatus +│ ├── Given the caller is a listed signer +│ │ ├── It ownerIsListed should be true +│ │ └── It isAppointed should be false +│ ├── Given the caller is appointed by a signer +│ │ ├── It ownerIsListed should be true +│ │ └── It isAppointed should be true +│ └── Given the caller is not listed or appointed +│ ├── It ownerIsListed should be false +│ └── It isAppointed should be false +├── When calling resolveEncryptionOwner +│ ├── Given the resolved owner is listed +│ │ ├── When the given address is appointed +│ │ │ ├── It owner should be the resolved owner +│ │ │ └── It appointedWallet should be the caller +│ │ └── When the given address is not appointed +│ │ ├── It owner should be the caller +│ │ └── It appointedWallet should be resolved appointed wallet +│ └── Given the resolved owner is not listed +│ ├── It should return a zero owner +│ └── It should return a zero appointedWallet +├── When calling getEncryptionRecipients +│ ├── Given the encryption registry has no accounts +│ │ ├── It returns an empty list, even with signers +│ │ └── It returns an empty list, without signers +│ └── Given the encryption registry has accounts +│ ├── Given no overlap between registry and signerList // Some are on the encryption registry only and some are on the signerList only +│ │ └── It returns an empty list +│ └── Given some addresses are registered everywhere +│ ├── It returns a list containing the overlapping addresses +│ ├── It the result has the correct resolved addresses // appointed wallets are present, not the owner +│ ├── It result does not contain unregistered addresses +│ ├── It result does not contain unlisted addresses +│ └── It result does not contain non appointed addresses +└── When calling supportsInterface + ├── It supports ISignerList + ├── It supports Addresslist + └── It supports the parents interfaces +``` + diff --git a/src/SignerList.sol b/src/SignerList.sol index eff9ae3..228ec02 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -110,14 +110,14 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza } /// @inheritdoc ISignerList - function resolveEncryptionAccountStatus(address _sender) + function resolveEncryptionAccountStatus(address _address) public view returns (bool ownerIsListed, bool isAppointed) { - if (this.isListed(_sender)) { + if (this.isListed(_address)) { ownerIsListed = true; - } else if (this.isListed(settings.encryptionRegistry.appointedBy(_sender))) { + } else if (this.isListed(settings.encryptionRegistry.appointedBy(_address))) { ownerIsListed = true; isAppointed = true; } @@ -126,25 +126,25 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza } /// @inheritdoc ISignerList - function resolveEncryptionOwner(address _sender) public view returns (address owner) { - (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_sender); + function resolveEncryptionOwner(address _address) public view returns (address owner) { + (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_address); if (!ownerIsListed) return address(0); - else if (isAppointed) return settings.encryptionRegistry.appointedBy(_sender); - return _sender; + else if (isAppointed) return settings.encryptionRegistry.appointedBy(_address); + return _address; } /// @inheritdoc ISignerList - function resolveEncryptionAccount(address _sender) public view returns (address owner, address appointedWallet) { - (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_sender); + function resolveEncryptionAccount(address _address) public view returns (address owner, address appointedWallet) { + (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_address); if (ownerIsListed) { if (isAppointed) { - owner = settings.encryptionRegistry.appointedBy(_sender); - appointedWallet = _sender; + owner = settings.encryptionRegistry.appointedBy(_address); + appointedWallet = _address; } else { - owner = _sender; - appointedWallet = settings.encryptionRegistry.getAppointedWallet(_sender); + owner = _address; + appointedWallet = settings.encryptionRegistry.getAppointedWallet(_address); } } diff --git a/test/EmergencyMultisig.t.sol b/test/EmergencyMultisig.t.sol index 6a0ba3f..bbdc03c 100644 --- a/test/EmergencyMultisig.t.sol +++ b/test/EmergencyMultisig.t.sol @@ -7,7 +7,7 @@ import {StandardProposalCondition} from "../src/conditions/StandardProposalCondi import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; import {Multisig} from "../src/Multisig.sol"; import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; -import {SignerList} from "../src/SignerList.sol"; +import {SignerList, UPDATE_SIGNER_LIST_PERMISSION_ID} from "../src/SignerList.sol"; import {IEmergencyMultisig} from "../src/interfaces/IEmergencyMultisig.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; diff --git a/test/Multisig.t.sol b/test/Multisig.t.sol index bc66372..149849b 100644 --- a/test/Multisig.t.sol +++ b/test/Multisig.t.sol @@ -7,12 +7,12 @@ import {StandardProposalCondition} from "../src/conditions/StandardProposalCondi import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; import {Multisig} from "../src/Multisig.sol"; import {IMultisig} from "../src/interfaces/IMultisig.sol"; +import {SignerList} from "../src/SignerList.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {PermissionManager} from "@aragon/osx/core/permission/PermissionManager.sol"; import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; -import {IMembership} from "@aragon/osx/core/plugin/membership/IMembership.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; @@ -26,12 +26,14 @@ contract MultisigTest is AragonTest { DAO dao; Multisig multisig; OptimisticTokenVotingPlugin optimisticPlugin; + SignerList signerList; // Events/errors to be tested here (duplicate) event MultisigSettingsUpdated( bool onlyListed, uint16 indexed minApprovals, uint64 destinationProposalDuration, + SignerList signerList, uint64 proposalExpirationPeriod ); event MembersAdded(address[] members); @@ -58,7 +60,7 @@ contract MultisigTest is AragonTest { vm.roll(100); builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob) + (dao, optimisticPlugin, multisig,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob) .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); } @@ -68,67 +70,16 @@ contract MultisigTest is AragonTest { onlyListed: true, minApprovals: 3, destinationProposalDuration: 4 days, + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); // Reinitialize should fail vm.expectRevert("Initializable: contract is already initialized"); - multisig.initialize(dao, signers, settings); - } - - function test_InitializeAddsInitialAddresses() public { - // Deploy with 4 signers - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - - assertEq(multisig.isListed(alice), true, "Should be a member"); - assertEq(multisig.isListed(bob), true, "Should be a member"); - assertEq(multisig.isListed(carol), true, "Should be a member"); - assertEq(multisig.isListed(david), true, "Should be a member"); - assertEq(multisig.isListed(randomWallet), false, "Should not be a member"); - - // Redeploy with just 2 signers - builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob).build(); - - assertEq(multisig.isListed(alice), true, "Should be a member"); - assertEq(multisig.isListed(bob), true, "Should be a member"); - assertEq(multisig.isListed(carol), false, "Should not be a member"); - assertEq(multisig.isListed(david), false, "Should not be a member"); - assertEq(multisig.isListed(randomWallet), false, "Should not be a member"); - - // Redeploy with 5 signers - builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMultisigMember(randomWallet).build(); - - assertEq(multisig.isListed(alice), true, "Should be a member"); - assertEq(multisig.isListed(bob), true, "Should be a member"); - assertEq(multisig.isListed(carol), true, "Should be a member"); - assertEq(multisig.isListed(david), true, "Should be a member"); - assertEq(multisig.isListed(randomWallet), true, "Should be a member"); + multisig.initialize(dao, settings); } function test_InitializeSetsMinApprovals() public { @@ -137,29 +88,22 @@ contract MultisigTest is AragonTest { onlyListed: true, minApprovals: 2, destinationProposalDuration: 4 days, + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (, uint16 minApprovals,,) = multisig.multisigSettings(); + (, uint16 minApprovals,,,,,) = multisig.multisigSettings(); assertEq(minApprovals, uint16(2), "Incorrect minApprovals"); // Redeploy with 1 settings.minApprovals = 1; - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (, minApprovals,,) = multisig.multisigSettings(); + (, minApprovals,,,,,) = multisig.multisigSettings(); assertEq(minApprovals, uint16(1), "Incorrect minApprovals"); } @@ -169,29 +113,22 @@ contract MultisigTest is AragonTest { onlyListed: true, minApprovals: 3, destinationProposalDuration: 4 days, + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (bool onlyListed,,,) = multisig.multisigSettings(); + (bool onlyListed,,,,,) = multisig.multisigSettings(); assertEq(onlyListed, true, "Incorrect onlyListed"); // Redeploy with false settings.onlyListed = false; - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (onlyListed,,,) = multisig.multisigSettings(); + (onlyListed,,,,,) = multisig.multisigSettings(); assertEq(onlyListed, false, "Incorrect onlyListed"); } @@ -201,29 +138,22 @@ contract MultisigTest is AragonTest { onlyListed: true, minApprovals: 3, destinationProposalDuration: 5 days, + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (,, uint64 minDuration,) = multisig.multisigSettings(); + (,, uint64 minDuration,,,) = multisig.multisigSettings(); assertEq(minDuration, 5 days, "Incorrect minDuration"); // Redeploy with 3 days settings.destinationProposalDuration = 3 days; - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (,, minDuration,) = multisig.multisigSettings(); + (,, minDuration,,,) = multisig.multisigSettings(); assertEq(minDuration, 3 days, "Incorrect minDuration"); } @@ -235,27 +165,19 @@ contract MultisigTest is AragonTest { destinationProposalDuration: 5 days, proposalExpirationPeriod: 15 days }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (,,, uint64 expirationPeriod) = multisig.multisigSettings(); + (,,, uint64 expirationPeriod,) = multisig.multisigSettings(); assertEq(expirationPeriod, 15 days, "Incorrect expirationPeriod"); // Redeploy with 3 days settings.proposalExpirationPeriod = 3 days; - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - (,,, expirationPeriod) = multisig.multisigSettings(); + (,,, expirationPeriod,) = multisig.multisigSettings(); assertEq(expirationPeriod, 3 days, "Incorrect expirationPeriod"); } @@ -267,18 +189,11 @@ contract MultisigTest is AragonTest { destinationProposalDuration: 4 days, proposalExpirationPeriod: 6 days }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - vm.expectEmit(); emit MultisigSettingsUpdated(true, uint16(3), 4 days, 6 days); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_InitializeEmitsMultisigSettingsUpdatedOnInstall2() public { @@ -289,33 +204,11 @@ contract MultisigTest is AragonTest { destinationProposalDuration: 7 days, proposalExpirationPeriod: 8 days }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - vm.expectEmit(); emit MultisigSettingsUpdated(false, uint16(2), 7 days, 8 days); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - } - - function test_InitializeRevertsIfMembersListIsTooLong() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](65537); - - vm.expectRevert(abi.encodeWithSelector(Multisig.AddresslistLengthOutOfBounds.selector, 65535, 65537)); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } // INTERFACES @@ -340,16 +233,6 @@ contract MultisigTest is AragonTest { assertEq(supported, true, "Should support IProposal"); } - function test_SupportsIMembership() public view { - bool supported = multisig.supportsInterface(type(IMembership).interfaceId); - assertEq(supported, true, "Should support IMembership"); - } - - function test_SupportsAddresslist() public view { - bool supported = multisig.supportsInterface(type(Addresslist).interfaceId); - assertEq(supported, true, "Should support Addresslist"); - } - function test_SupportsIMultisig() public view { bool supported = multisig.supportsInterface(type(IMultisig).interfaceId); assertEq(supported, true, "Should support IMultisig"); @@ -357,36 +240,35 @@ contract MultisigTest is AragonTest { // UPDATE MULTISIG SETTINGS - function test_ShouldntAllowMinApprovalsHigherThenAddrListLength() public { + function test_ShouldNotAllowMinApprovalsGreaterThanSignerListLength() public { Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ onlyListed: true, minApprovals: 5, - destinationProposalDuration: 4 days, // Greater than 4 members below + destinationProposalDuration: 4 days, // Greater than 4 members + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); // Retry with onlyListed false settings = Multisig.MultisigSettings({ onlyListed: false, minApprovals: 6, - destinationProposalDuration: 4 days, // Greater than 4 members below + destinationProposalDuration: 4 days, // Greater than 4 members + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 6)); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + + // OK + settings.minApprovals = 4; + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_ShouldNotAllowMinApprovalsZero() public { @@ -396,17 +278,10 @@ contract MultisigTest is AragonTest { destinationProposalDuration: 4 days, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); // Retry with onlyListed false settings = Multisig.MultisigSettings({ @@ -416,9 +291,8 @@ contract MultisigTest is AragonTest { proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_EmitsMultisigSettingsUpdated() public { @@ -492,7 +366,7 @@ contract MultisigTest is AragonTest { multisig.updateMultisigSettings(settings); // Nothing changed - (bool onlyListed, uint16 minApprovals, uint64 destinationProposalDuration, uint64 expiration) = + (bool onlyListed, uint16 minApprovals, uint64 destinationProposalDuration, uint64 expiration,) = multisig.multisigSettings(); assertEq(onlyListed, true); assertEq(minApprovals, 3); @@ -507,322 +381,6 @@ contract MultisigTest is AragonTest { multisig.updateMultisigSettings(settings); } - function test_IsMemberShouldReturnWhenApropriate() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = alice; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), false, "Should not be a member"); - assertEq(multisig.isMember(carol), false, "Should not be a member"); - assertEq(multisig.isMember(david), false, "Should not be a member"); - - // More members - settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - signers = new address[](3); - signers[0] = bob; - signers[1] = carol; - signers[2] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - - assertEq(multisig.isMember(alice), false, "Should not be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - } - - function test_IsMemberIsListedShouldReturnTheSameValue() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = alice; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - - assertEq(multisig.isListed(alice), multisig.isMember(alice), "isMember isListed should be equal"); - assertEq(multisig.isListed(bob), multisig.isMember(bob), "isMember isListed should be equal"); - assertEq(multisig.isListed(carol), multisig.isMember(carol), "isMember isListed should be equal"); - assertEq(multisig.isListed(david), multisig.isMember(david), "isMember isListed should be equal"); - - // More members - settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - signers = new address[](3); - signers[0] = bob; - signers[1] = carol; - signers[2] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - - assertEq(multisig.isListed(alice), multisig.isMember(alice), "isMember isListed should be equal"); - assertEq(multisig.isListed(bob), multisig.isMember(bob), "isMember isListed should be equal"); - assertEq(multisig.isListed(carol), multisig.isMember(carol), "isMember isListed should be equal"); - assertEq(multisig.isListed(david), multisig.isMember(david), "isMember isListed should be equal"); - } - - function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); // 0x0... would be a member but the chance is negligible - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); - - assertEq(multisig.isListed(randomWallet), false, "Should be false"); - assertEq( - multisig.isListed(vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy))))), false, "Should be false" - ); - } - - function test_AddsNewMembersAndEmits() public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // No - assertEq(multisig.isMember(randomWallet), false, "Should not be a member"); - - address[] memory addrs = new address[](1); - addrs[0] = randomWallet; - - vm.expectEmit(); - emit MembersAdded({members: addrs}); - multisig.addAddresses(addrs); - - // Yes - assertEq(multisig.isMember(randomWallet), true, "Should be a member"); - - // Next - addrs = new address[](3); - addrs[0] = vm.addr(1234); - addrs[1] = vm.addr(2345); - addrs[2] = vm.addr(3456); - - // No - assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); - assertEq(multisig.isMember(addrs[1]), false, "Should not be a member"); - assertEq(multisig.isMember(addrs[2]), false, "Should not be a member"); - - vm.expectEmit(); - emit MembersAdded({members: addrs}); - multisig.addAddresses(addrs); - - // Yes - assertEq(multisig.isMember(addrs[0]), true, "Should be a member"); - assertEq(multisig.isMember(addrs[1]), true, "Should be a member"); - assertEq(multisig.isMember(addrs[2]), true, "Should be a member"); - } - - function test_RemovesMembersAndEmits() public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig.updateMultisigSettings(settings); - - // Before - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - address[] memory addrs = new address[](2); - addrs[0] = alice; - addrs[1] = bob; - - vm.expectEmit(); - emit MembersRemoved({members: addrs}); - multisig.removeAddresses(addrs); - - // After - assertEq(multisig.isMember(alice), false, "Should not be a member"); - assertEq(multisig.isMember(bob), false, "Should not be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // Next - addrs = new address[](3); - addrs[0] = vm.addr(1234); - addrs[1] = vm.addr(2345); - addrs[2] = vm.addr(3456); - multisig.addAddresses(addrs); - - // Remove - addrs = new address[](2); - addrs[0] = carol; - addrs[1] = david; - - vm.expectEmit(); - emit MembersRemoved({members: addrs}); - multisig.removeAddresses(addrs); - - // Yes - assertEq(multisig.isMember(carol), false, "Should not be a member"); - assertEq(multisig.isMember(david), false, "Should not be a member"); - } - - function test_RevertsIfAddingTooManyMembers() public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - address[] memory addrs = new address[](type(uint16).max); - addrs[0] = address(12345678); - - assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); - vm.expectRevert( - abi.encodeWithSelector( - Multisig.AddresslistLengthOutOfBounds.selector, type(uint16).max, uint256(type(uint16).max) + 4 - ) - ); - multisig.addAddresses(addrs); - - assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); - } - - function test_ShouldRevertIfEmptySignersList() public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig.updateMultisigSettings(settings); - - // Before - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // ok - address[] memory addrs = new address[](1); - addrs[0] = alice; - multisig.removeAddresses(addrs); - - addrs[0] = bob; - multisig.removeAddresses(addrs); - - addrs[0] = carol; - multisig.removeAddresses(addrs); - - assertEq(multisig.isMember(alice), false, "Should not be a member"); - assertEq(multisig.isMember(bob), false, "Should not be a member"); - assertEq(multisig.isMember(carol), false, "Should not be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // ko - addrs[0] = david; - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); - multisig.removeAddresses(addrs); - - // Next - addrs = new address[](1); - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - // Retry removing David - addrs = new address[](1); - addrs[0] = david; - - multisig.removeAddresses(addrs); - - // Yes - assertEq(multisig.isMember(david), false, "Should not be a member"); - } - - function test_ShouldRevertIfLessThanMinApproval() public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // Before - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // ok - address[] memory addrs = new address[](1); - addrs[0] = alice; - multisig.removeAddresses(addrs); - - // ko - addrs[0] = bob; - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 3, 2)); - multisig.removeAddresses(addrs); - - // ko - addrs[0] = carol; - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 3, 2)); - multisig.removeAddresses(addrs); - - // ko - addrs[0] = david; - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 3, 2)); - multisig.removeAddresses(addrs); - - // Add and retry removing - - addrs = new address[](1); - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - addrs = new address[](1); - addrs[0] = bob; - multisig.removeAddresses(addrs); - - // 2 - addrs = new address[](1); - addrs[0] = vm.addr(2345); - multisig.addAddresses(addrs); - - addrs = new address[](1); - addrs[0] = carol; - multisig.removeAddresses(addrs); - - // 3 - addrs = new address[](1); - addrs[0] = vm.addr(3456); - multisig.addAddresses(addrs); - - addrs = new address[](1); - addrs[0] = david; - multisig.removeAddresses(addrs); - } - function test_MinApprovalsBiggerThanTheListReverts() public { // MinApprovals should be within the boundaries of the list dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); @@ -831,6 +389,7 @@ contract MultisigTest is AragonTest { onlyListed: true, minApprovals: 5, destinationProposalDuration: 4 days, // More than 4 + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); @@ -850,161 +409,21 @@ contract MultisigTest is AragonTest { onlyListed: true, minApprovals: 6, destinationProposalDuration: 4 days, // More than 5 + signerList: signerList, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 5, 6)); multisig.updateMultisigSettings(settings); - } - - function test_ShouldRevertIfDuplicatingAddresses() public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // ok - address[] memory addrs = new address[](1); - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - // ko - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.addAddresses(addrs); - - // 1 - addrs[0] = alice; - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.addAddresses(addrs); - - // 2 - addrs[0] = bob; - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.addAddresses(addrs); - - // 3 - addrs[0] = carol; - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.addAddresses(addrs); - - // 4 - addrs[0] = david; - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.addAddresses(addrs); - - // ok - addrs[0] = vm.addr(1234); - multisig.removeAddresses(addrs); - - // ko - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.removeAddresses(addrs); - - addrs[0] = vm.addr(2345); - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.removeAddresses(addrs); - - addrs[0] = vm.addr(3456); - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.removeAddresses(addrs); - - addrs[0] = vm.addr(4567); - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.removeAddresses(addrs); - - addrs[0] = randomWallet; - vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0])); - multisig.removeAddresses(addrs); - } - - function test_onlyWalletWithPermissionsCanAddRemove() public { - // ko - address[] memory addrs = new address[](1); - addrs[0] = vm.addr(1234); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.addAddresses(addrs); - - // ko - addrs[0] = alice; - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.removeAddresses(addrs); - - // Permission - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // ok - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - addrs[0] = alice; - multisig.removeAddresses(addrs); - } - - function testFuzz_PermissionedAddRemoveMembers(address randomAccount) public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - assertEq(multisig.isMember(randomWallet), false, "Should be false"); - - // in - address[] memory addrs = new address[](1); - addrs[0] = randomWallet; - multisig.addAddresses(addrs); - assertEq(multisig.isMember(randomWallet), true, "Should be true"); - - // out - multisig.removeAddresses(addrs); - assertEq(multisig.isMember(randomWallet), false, "Should be false"); - - // someone else - if (randomAccount != alice) { - vm.startPrank(randomAccount); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - randomAccount, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.addAddresses(addrs); - assertEq(multisig.isMember(randomWallet), false, "Should be false"); - - addrs[0] = carol; - assertEq(multisig.isMember(carol), true, "Should be true"); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - randomAccount, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.removeAddresses(addrs); - assertEq(multisig.isMember(carol), true, "Should be true"); - } - - vm.startPrank(alice); + // OK + settings.minApprovals = 5; + multisig.updateMultisigSettings(settings); } function testFuzz_PermissionedUpdateSettings(address randomAccount) public { dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - (bool onlyListed, uint16 minApprovals, uint64 destMinDuration, uint64 expiration) = multisig.multisigSettings(); + (bool onlyListed, uint16 minApprovals, uint64 destMinDuration, uint64 expiration,) = multisig.multisigSettings(); assertEq(minApprovals, 3, "Should be 3"); assertEq(onlyListed, true, "Should be true"); assertEq(destMinDuration, 10 days, "Incorrect destMinDuration A"); @@ -1160,15 +579,8 @@ contract MultisigTest is AragonTest { destinationProposalDuration: 4 days, proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings))) - ); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); // 1 IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1203,43 +615,40 @@ contract MultisigTest is AragonTest { } function test_RevertsWhenCreatorWasListedBeforeButNotNow() public { - // reverts if `_msgSender` is not listed although she was listed in the last block + // reverts if `msg.sender` is not listed although she was listed in the last block - // Deploy a new multisig instance - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); + dao.grant(address(signerList), alice, signerList.UPDATE_SIGNER_LIST_PERMISSION_ID()); + + // Remove address[] memory addrs = new address[](1); addrs[0] = alice; + signerList.removeSigners(addrs); - multisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, addrs, settings))) - ); - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, alice)); + multisig.createProposal("", 0, 0, optimisticPlugin, false); + + signerList.addSigners(addrs); // Add Alice back vm.roll(block.number + 1); + multisig.createProposal("", 0, 0, optimisticPlugin, false); // Add+remove addrs[0] = bob; - multisig.addAddresses(addrs); + signerList.removeSigners(addrs); - addrs[0] = alice; - multisig.removeAddresses(addrs); + vm.startPrank(bob); - // Alice cannot create now - IDAO.Action[] memory actions = new IDAO.Action[](0); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, alice)); - multisig.createProposal("", actions, optimisticPlugin, false); + // Bob cannot create now + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, bob)); + multisig.createProposal("", 0, 0, optimisticPlugin, false); + + vm.startPrank(alice); // Bob can create now - vm.startPrank(bob); + signerList.addSigners(addrs); // Add Bob back - multisig.createProposal("", actions, optimisticPlugin, false); + vm.startPrank(alice); - assertEq(multisig.isListed(alice), false, "Should not be listed"); - assertEq(multisig.isListed(bob), true, "Should be listed"); + multisig.createProposal("", 0, 0, optimisticPlugin, false); } function test_CreatesProposalWithoutApprovingIfUnspecified() public { @@ -1968,7 +1377,7 @@ contract MultisigTest is AragonTest { // event vm.expectEmit(); emit Executed(pid); - uint256 targetPid = uint256(block.timestamp) << 128 | uint256(block.timestamp + 10 days) << 64; + uint256 targetPid = (uint256(block.timestamp) << 128) | (uint256(block.timestamp + 10 days) << 64); vm.expectEmit(); emit ProposalCreated( targetPid, address(multisig), uint64(block.timestamp), uint64(block.timestamp) + 10 days, "", actions, 0 @@ -2000,7 +1409,7 @@ contract MultisigTest is AragonTest { // events vm.expectEmit(); emit Executed(pid); - targetPid = (uint256(block.timestamp) << 128 | uint256(block.timestamp + 50 days) << 64); + targetPid = ((uint256(block.timestamp) << 128) | (uint256(block.timestamp + 50 days) << 64)); vm.expectEmit(); emit ProposalCreated( targetPid, address(multisig), uint64(block.timestamp), 20 days + 50 days, "ipfs://", actions, 0 @@ -2055,7 +1464,7 @@ contract MultisigTest is AragonTest { vm.expectEmit(); emit Executed(pid); - uint256 targetPid = (uint256(block.timestamp) << 128 | uint256(block.timestamp + 10 days) << 64); + uint256 targetPid = ((uint256(block.timestamp) << 128) | (uint256(block.timestamp + 10 days) << 64)); vm.expectEmit(); emit ProposalCreated( targetPid, address(multisig), uint64(block.timestamp), uint64(block.timestamp) + 10 days, "", actions, 0 @@ -2085,7 +1494,7 @@ contract MultisigTest is AragonTest { vm.expectEmit(); emit Executed(pid); - targetPid = (uint256(5 days) << 128 | uint256(5 days + 10 days) << 64) + 1; + targetPid = ((uint256(5 days) << 128) | (uint256(5 days + 10 days) << 64)) + 1; vm.expectEmit(); emit ProposalCreated( targetPid, // foreign pid @@ -2123,7 +1532,7 @@ contract MultisigTest is AragonTest { vm.expectEmit(); emit Executed(pid); - targetPid = (uint256(7 days) << 128 | uint256(7 days + 50 days) << 64); + targetPid = ((uint256(7 days) << 128) | (uint256(7 days + 50 days) << 64)); vm.expectEmit(); emit ProposalCreated(targetPid, address(multisig), 7 days, 7 days + 50 days, "ipfs://...", actions, 0); multisig.approve(pid, true); @@ -2566,7 +1975,7 @@ contract MultisigTest is AragonTest { // Check round // start=1d, end=10d, counter=0 (open, executed, parameters, vetoTally, metadataUri, actions, allowFailureMap) = - optimisticPlugin.getProposal(uint256(2 days) << 128 | uint256(2 days + 10 days) << 64); + optimisticPlugin.getProposal((uint256(2 days) << 128) | (uint256(2 days + 10 days) << 64)); assertEq(open, true, "Should be open"); assertEq(executed, false, "Should not be executed"); @@ -2614,7 +2023,7 @@ contract MultisigTest is AragonTest { // Check round (open, executed, parameters, vetoTally, metadataUri, actions, allowFailureMap) = - optimisticPlugin.getProposal((uint256(3 days) << 128 | uint256(3 days + 10 days) << 64) + 1); + optimisticPlugin.getProposal(((uint256(3 days) << 128) | (uint256(3 days + 10 days) << 64)) + 1); assertEq(open, true, "Should be open"); assertEq(executed, false, "Should not be executed"); diff --git a/test/SignerList.t.sol b/test/SignerList.t.sol index 873413e..450dfb6 100644 --- a/test/SignerList.t.sol +++ b/test/SignerList.t.sol @@ -10,7 +10,7 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../src/Multisig.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -contract EncryptionRegistryTest is AragonTest { +contract SignerListTestTemp is AragonTest { SignerList signerList; EncryptionRegistry encryptionRegistry; DaoBuilder builder; @@ -24,8 +24,12 @@ contract EncryptionRegistryTest is AragonTest { function setUp() public { builder = new DaoBuilder(); - (dao,, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).build(); + (dao, , multisig, , , signerList, encryptionRegistry, ) = builder + .withMultisigMember(alice) + .withMultisigMember(bob) + .withMultisigMember(carol) + .withMultisigMember(david) + .build(); signers = new address[](4); signers[0] = alice; @@ -37,18 +41,33 @@ contract EncryptionRegistryTest is AragonTest { // Initialize function test_InitializeRevertsIfInitialized() public { signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(0)), 0) + ); - vm.expectRevert(bytes("Initializable: contract is already initialized")); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + vm.expectRevert( + bytes("Initializable: contract is already initialized") + ); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(0)), 0) + ); } function test_InitializeSetsTheRightValues() public { // 1 signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(0)), 0) + ); - (EncryptionRegistry reg, uint16 minSignerListLength) = signerList.settings(); + (EncryptionRegistry reg, uint16 minSignerListLength) = signerList + .settings(); vm.assertEq(address(reg), address(0), "Incorrect address"); vm.assertEq(minSignerListLength, 0); vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); @@ -56,70 +75,154 @@ contract EncryptionRegistryTest is AragonTest { vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq( + signerList.isListed(address(100)), + false, + "Should not be a signer" + ); + vm.assertEq( + signerList.isListed(address(200)), + false, + "Should not be a signer" + ); // 2 signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 0)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(encryptionRegistry), 0) + ); (reg, minSignerListLength) = signerList.settings(); - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq( + address(reg), + address(encryptionRegistry), + "Incorrect address" + ); vm.assertEq(minSignerListLength, 0); vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq( + signerList.isListed(address(100)), + false, + "Should not be a signer" + ); + vm.assertEq( + signerList.isListed(address(200)), + false, + "Should not be a signer" + ); // 3 signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2) + ); (reg, minSignerListLength) = signerList.settings(); - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq( + address(reg), + address(encryptionRegistry), + "Incorrect address" + ); vm.assertEq(minSignerListLength, 2); vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); vm.assertEq(signerList.isListed(david), true, "Should be a signer"); - vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); - vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + vm.assertEq( + signerList.isListed(address(100)), + false, + "Should not be a signer" + ); + vm.assertEq( + signerList.isListed(address(200)), + false, + "Should not be a signer" + ); // 4 signers = new address[](2); signers[0] = address(100); signers[0] = address(200); signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 1)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(encryptionRegistry), 1) + ); (reg, minSignerListLength) = signerList.settings(); - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq( + address(reg), + address(encryptionRegistry), + "Incorrect address" + ); vm.assertEq(minSignerListLength, 1); vm.assertEq(signerList.addresslistLength(), 2, "Incorrect length"); - vm.assertEq(signerList.isListed(alice), false, "Should not be a signer"); + vm.assertEq( + signerList.isListed(alice), + false, + "Should not be a signer" + ); vm.assertEq(signerList.isListed(bob), false, "Should not be a signer"); - vm.assertEq(signerList.isListed(carol), false, "Should not be a signer"); - vm.assertEq(signerList.isListed(david), false, "Should not be a signer"); - vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); - vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); + vm.assertEq( + signerList.isListed(carol), + false, + "Should not be a signer" + ); + vm.assertEq( + signerList.isListed(david), + false, + "Should not be a signer" + ); + vm.assertEq( + signerList.isListed(address(100)), + true, + "Should be a signer" + ); + vm.assertEq( + signerList.isListed(address(200)), + true, + "Should be a signer" + ); } function test_InitializingWithAnInvalidRegistryShouldRevert() public { // 1 signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(alice)), 2)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(alice)), 2) + ); vm.expectRevert(InvalidEncryptionRegitry.selector); // 2 signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(bob)), 3)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(bob)), 3) + ); vm.expectRevert(InvalidEncryptionRegitry.selector); // OK signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2)); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2) + ); } function test_InitializingWithTooManySignersReverts() public { @@ -128,17 +231,973 @@ contract EncryptionRegistryTest is AragonTest { signerList = new SignerList(); vm.expectRevert( - abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 1) + abi.encodeWithSelector( + SignerListLengthOutOfBounds.selector, + type(uint16).max, + type(uint16).max + 1 + ) + ); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(0)), 0) ); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); // 2 signers = new address[](type(uint16).max + 10); signerList = new SignerList(); vm.expectRevert( - abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 10) + abi.encodeWithSelector( + SignerListLengthOutOfBounds.selector, + type(uint16).max, + type(uint16).max + 10 + ) + ); + signerList.initialize( + dao, + signers, + SignerList.Settings(EncryptionRegistry(address(0)), 0) + ); + } + + // function test_SupportsIMembership() public view { + // bool supported = multisig.supportsInterface(type(IMembership).interfaceId); + // assertEq(supported, true, "Should support IMembership"); + // } + + function test_SupportsAddresslist() public view { + bool supported = multisig.supportsInterface( + type(Addresslist).interfaceId + ); + assertEq(supported, true, "Should support Addresslist"); + } + + function test_DoesntSupportTheEmptyInterface() public view { + bool supported = multisig.supportsInterface(0); + assertEq(supported, false, "Should not support the empty interface"); + } + + function test_SupportsIERC165Upgradeable() public view { + bool supported = multisig.supportsInterface( + type(IERC165Upgradeable).interfaceId + ); + assertEq(supported, true, "Should support IERC165Upgradeable"); + } + + function test_IsMemberShouldReturnWhenApropriate() public { + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + address[] memory signers = new address[](1); + signers[0] = alice; + + multisig = Multisig( + createProxyAndCall( + address(MULTISIG_BASE), + abi.encodeCall(Multisig.initialize, (dao, signers, settings)) + ) + ); + + assertEq(multisig.isMember(alice), true, "Should be a member"); + assertEq(multisig.isMember(bob), false, "Should not be a member"); + assertEq(multisig.isMember(carol), false, "Should not be a member"); + assertEq(multisig.isMember(david), false, "Should not be a member"); + + // More members + settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + signers = new address[](3); + signers[0] = bob; + signers[1] = carol; + signers[2] = david; + + multisig = Multisig( + createProxyAndCall( + address(MULTISIG_BASE), + abi.encodeCall(Multisig.initialize, (dao, signers, settings)) + ) + ); + + assertEq(multisig.isMember(alice), false, "Should not be a member"); + assertEq(multisig.isMember(bob), true, "Should be a member"); + assertEq(multisig.isMember(carol), true, "Should be a member"); + assertEq(multisig.isMember(david), true, "Should be a member"); + } + + function test_IsMemberIsListedShouldReturnTheSameValue() public { + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + address[] memory signers = new address[](1); + signers[0] = alice; + + multisig = Multisig( + createProxyAndCall( + address(MULTISIG_BASE), + abi.encodeCall(Multisig.initialize, (dao, signers, settings)) + ) + ); + + assertEq( + multisig.isListed(alice), + multisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + multisig.isListed(bob), + multisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + multisig.isListed(carol), + multisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + multisig.isListed(david), + multisig.isMember(david), + "isMember isListed should be equal" + ); + + // More members + settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + signers = new address[](3); + signers[0] = bob; + signers[1] = carol; + signers[2] = david; + + multisig = Multisig( + createProxyAndCall( + address(MULTISIG_BASE), + abi.encodeCall(Multisig.initialize, (dao, signers, settings)) + ) + ); + + assertEq( + multisig.isListed(alice), + multisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + multisig.isListed(bob), + multisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + multisig.isListed(carol), + multisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + multisig.isListed(david), + multisig.isMember(david), + "isMember isListed should be equal" + ); + } + + function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public { + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + address[] memory signers = new address[](1); // 0x0... would be a member but the chance is negligible + + multisig = Multisig( + createProxyAndCall( + address(MULTISIG_BASE), + abi.encodeCall(Multisig.initialize, (dao, signers, settings)) + ) + ); + + assertEq(multisig.isListed(randomWallet), false, "Should be false"); + assertEq( + multisig.isListed( + vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy)))) + ), + false, + "Should be false" + ); + } + + function test_AddsNewMembersAndEmits() public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + // No + assertEq( + multisig.isMember(randomWallet), + false, + "Should not be a member" + ); + + address[] memory addrs = new address[](1); + addrs[0] = randomWallet; + + vm.expectEmit(); + emit MembersAdded({members: addrs}); + multisig.addAddresses(addrs); + + // Yes + assertEq(multisig.isMember(randomWallet), true, "Should be a member"); + + // Next + addrs = new address[](3); + addrs[0] = vm.addr(1234); + addrs[1] = vm.addr(2345); + addrs[2] = vm.addr(3456); + + // No + assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); + assertEq(multisig.isMember(addrs[1]), false, "Should not be a member"); + assertEq(multisig.isMember(addrs[2]), false, "Should not be a member"); + + vm.expectEmit(); + emit MembersAdded({members: addrs}); + multisig.addAddresses(addrs); + + // Yes + assertEq(multisig.isMember(addrs[0]), true, "Should be a member"); + assertEq(multisig.isMember(addrs[1]), true, "Should be a member"); + assertEq(multisig.isMember(addrs[2]), true, "Should be a member"); + } + + function test_RemovesMembersAndEmits() public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig.updateMultisigSettings(settings); + + // Before + assertEq(multisig.isMember(alice), true, "Should be a member"); + assertEq(multisig.isMember(bob), true, "Should be a member"); + assertEq(multisig.isMember(carol), true, "Should be a member"); + assertEq(multisig.isMember(david), true, "Should be a member"); + + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + + vm.expectEmit(); + emit MembersRemoved({members: addrs}); + multisig.removeAddresses(addrs); + + // After + assertEq(multisig.isMember(alice), false, "Should not be a member"); + assertEq(multisig.isMember(bob), false, "Should not be a member"); + assertEq(multisig.isMember(carol), true, "Should be a member"); + assertEq(multisig.isMember(david), true, "Should be a member"); + + // Next + addrs = new address[](3); + addrs[0] = vm.addr(1234); + addrs[1] = vm.addr(2345); + addrs[2] = vm.addr(3456); + multisig.addAddresses(addrs); + + // Remove + addrs = new address[](2); + addrs[0] = carol; + addrs[1] = david; + + vm.expectEmit(); + emit MembersRemoved({members: addrs}); + multisig.removeAddresses(addrs); + + // Yes + assertEq(multisig.isMember(carol), false, "Should not be a member"); + assertEq(multisig.isMember(david), false, "Should not be a member"); + } + + function test_RevertsIfAddingTooManyMembers() public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + address[] memory addrs = new address[](type(uint16).max); + addrs[0] = address(12345678); + + assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); + vm.expectRevert( + abi.encodeWithSelector( + Multisig.AddresslistLengthOutOfBounds.selector, + type(uint16).max, + uint256(type(uint16).max) + 4 + ) + ); + multisig.addAddresses(addrs); + + assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); + } + + function test_ShouldRevertIfEmptySignersList() public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig.updateMultisigSettings(settings); + + // Before + assertEq(multisig.isMember(alice), true, "Should be a member"); + assertEq(multisig.isMember(bob), true, "Should be a member"); + assertEq(multisig.isMember(carol), true, "Should be a member"); + assertEq(multisig.isMember(david), true, "Should be a member"); + + // ok + address[] memory addrs = new address[](1); + addrs[0] = alice; + multisig.removeAddresses(addrs); + + addrs[0] = bob; + multisig.removeAddresses(addrs); + + addrs[0] = carol; + multisig.removeAddresses(addrs); + + assertEq(multisig.isMember(alice), false, "Should not be a member"); + assertEq(multisig.isMember(bob), false, "Should not be a member"); + assertEq(multisig.isMember(carol), false, "Should not be a member"); + assertEq(multisig.isMember(david), true, "Should be a member"); + + // ko + addrs[0] = david; + vm.expectRevert( + abi.encodeWithSelector( + Multisig.MinApprovalsOutOfBounds.selector, + 1, + 0 + ) + ); + multisig.removeAddresses(addrs); + + // Next + addrs = new address[](1); + addrs[0] = vm.addr(1234); + multisig.addAddresses(addrs); + + // Retry removing David + addrs = new address[](1); + addrs[0] = david; + + multisig.removeAddresses(addrs); + + // Yes + assertEq(multisig.isMember(david), false, "Should not be a member"); + } + + function test_ShouldRevertIfLessThanMinApproval() public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + // Before + assertEq(multisig.isMember(alice), true, "Should be a member"); + assertEq(multisig.isMember(bob), true, "Should be a member"); + assertEq(multisig.isMember(carol), true, "Should be a member"); + assertEq(multisig.isMember(david), true, "Should be a member"); + + // ok + address[] memory addrs = new address[](1); + addrs[0] = alice; + multisig.removeAddresses(addrs); + + // ko + addrs[0] = bob; + vm.expectRevert( + abi.encodeWithSelector( + Multisig.MinApprovalsOutOfBounds.selector, + 3, + 2 + ) + ); + multisig.removeAddresses(addrs); + + // ko + addrs[0] = carol; + vm.expectRevert( + abi.encodeWithSelector( + Multisig.MinApprovalsOutOfBounds.selector, + 3, + 2 + ) + ); + multisig.removeAddresses(addrs); + + // ko + addrs[0] = david; + vm.expectRevert( + abi.encodeWithSelector( + Multisig.MinApprovalsOutOfBounds.selector, + 3, + 2 + ) + ); + multisig.removeAddresses(addrs); + + // Add and retry removing + + addrs = new address[](1); + addrs[0] = vm.addr(1234); + multisig.addAddresses(addrs); + + addrs = new address[](1); + addrs[0] = bob; + multisig.removeAddresses(addrs); + + // 2 + addrs = new address[](1); + addrs[0] = vm.addr(2345); + multisig.addAddresses(addrs); + + addrs = new address[](1); + addrs[0] = carol; + multisig.removeAddresses(addrs); + + // 3 + addrs = new address[](1); + addrs[0] = vm.addr(3456); + multisig.addAddresses(addrs); + + addrs = new address[](1); + addrs[0] = david; + multisig.removeAddresses(addrs); + } + + function test_IsMemberShouldReturnWhenApropriate() public { + assertEq(eMultisig.isMember(alice), true, "Should be a member"); + assertEq(eMultisig.isMember(bob), true, "Should be a member"); + assertEq(eMultisig.isMember(carol), true, "Should be a member"); + assertEq(eMultisig.isMember(david), true, "Should be a member"); + + dao.grant( + address(stdMultisig), + alice, + stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + address[] memory signers = new address[](1); + signers[0] = bob; + stdMultisig.removeAddresses(signers); + + assertEq(eMultisig.isMember(alice), true, "Should be a member"); + assertEq(eMultisig.isMember(bob), false, "Should not be a member"); + assertEq(eMultisig.isMember(carol), true, "Should be a member"); + assertEq(eMultisig.isMember(david), true, "Should be a member"); + + // 2 + stdMultisig.addAddresses(signers); // Add Bob back + signers[0] = alice; + stdMultisig.removeAddresses(signers); + + assertEq(eMultisig.isMember(alice), false, "Should not be a member"); + assertEq(eMultisig.isMember(bob), true, "Should be a member"); + assertEq(eMultisig.isMember(carol), true, "Should be a member"); + assertEq(eMultisig.isMember(david), true, "Should be a member"); + + // 3 + stdMultisig.addAddresses(signers); // Add Alice back + signers[0] = carol; + stdMultisig.removeAddresses(signers); + + assertEq(eMultisig.isMember(alice), true, "Should be a member"); + assertEq(eMultisig.isMember(bob), true, "Should be a member"); + assertEq(eMultisig.isMember(carol), false, "Should not be a member"); + assertEq(eMultisig.isMember(david), true, "Should be a member"); + + // 4 + stdMultisig.addAddresses(signers); // Add Carol back + signers[0] = david; + stdMultisig.removeAddresses(signers); + + assertEq(eMultisig.isMember(alice), true, "Should be a member"); + assertEq(eMultisig.isMember(bob), true, "Should be a member"); + assertEq(eMultisig.isMember(carol), true, "Should be a member"); + assertEq(eMultisig.isMember(david), false, "Should not be a member"); + } + + function test_IsMemberIsListedShouldReturnTheSameValue() public { + assertEq( + stdMultisig.isListed(alice), + eMultisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(bob), + eMultisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(carol), + eMultisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(david), + eMultisig.isMember(david), + "isMember isListed should be equal" + ); + + dao.grant( + address(stdMultisig), + alice, + stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + address[] memory signers = new address[](1); + signers[0] = alice; + stdMultisig.removeAddresses(signers); + + assertEq( + stdMultisig.isListed(alice), + eMultisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(bob), + eMultisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(carol), + eMultisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(david), + eMultisig.isMember(david), + "isMember isListed should be equal" + ); + + // 2 + stdMultisig.addAddresses(signers); // Add Alice back + signers[0] = bob; + stdMultisig.removeAddresses(signers); + + assertEq( + stdMultisig.isListed(alice), + eMultisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(bob), + eMultisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(carol), + eMultisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(david), + eMultisig.isMember(david), + "isMember isListed should be equal" + ); + + // 3 + stdMultisig.addAddresses(signers); // Add Bob back + signers[0] = carol; + stdMultisig.removeAddresses(signers); + + assertEq( + stdMultisig.isListed(alice), + eMultisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(bob), + eMultisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(carol), + eMultisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(david), + eMultisig.isMember(david), + "isMember isListed should be equal" + ); + + // 4 + stdMultisig.addAddresses(signers); // Add Carol back + signers[0] = david; + stdMultisig.removeAddresses(signers); + + assertEq( + stdMultisig.isListed(alice), + eMultisig.isMember(alice), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(bob), + eMultisig.isMember(bob), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(carol), + eMultisig.isMember(carol), + "isMember isListed should be equal" + ); + assertEq( + stdMultisig.isListed(david), + eMultisig.isMember(david), + "isMember isListed should be equal" + ); + } + + function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public { + // Deploy a new stdMultisig instance + Multisig.MultisigSettings memory mSettings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + address[] memory signers = new address[](1); + signers[0] = address(0x0); // 0x0... would be a member but the chance is negligible + + stdMultisig = Multisig( + createProxyAndCall( + address(MULTISIG_BASE), + abi.encodeCall(Multisig.initialize, (dao, signers, mSettings)) + ) + ); + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig + .MultisigSettings({ + onlyListed: true, + minApprovals: 1, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), + abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + + assertEq( + eMultisig.isMember( + vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy)))) + ), + false, + "Should be false" + ); + } + + function test_ShouldRevertIfDuplicatingAddresses() public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + // ok + address[] memory addrs = new address[](1); + addrs[0] = vm.addr(1234); + multisig.addAddresses(addrs); + + // ko + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.addAddresses(addrs); + + // 1 + addrs[0] = alice; + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.addAddresses(addrs); + + // 2 + addrs[0] = bob; + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.addAddresses(addrs); + + // 3 + addrs[0] = carol; + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.addAddresses(addrs); + + // 4 + addrs[0] = david; + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) ); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + multisig.addAddresses(addrs); + + // ok + addrs[0] = vm.addr(1234); + multisig.removeAddresses(addrs); + + // ko + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.removeAddresses(addrs); + + addrs[0] = vm.addr(2345); + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.removeAddresses(addrs); + + addrs[0] = vm.addr(3456); + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.removeAddresses(addrs); + + addrs[0] = vm.addr(4567); + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.removeAddresses(addrs); + + addrs[0] = randomWallet; + vm.expectRevert( + abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) + ); + multisig.removeAddresses(addrs); + } + + function test_onlyWalletWithPermissionsCanAddRemove() public { + // ko + address[] memory addrs = new address[](1); + addrs[0] = vm.addr(1234); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + multisig.addAddresses(addrs); + + // ko + addrs[0] = alice; + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + multisig.removeAddresses(addrs); + + // Permission + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + // ok + addrs[0] = vm.addr(1234); + multisig.addAddresses(addrs); + + addrs[0] = alice; + multisig.removeAddresses(addrs); + } + + function testFuzz_PermissionedAddRemoveMembers( + address randomAccount + ) public { + dao.grant( + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ); + + assertEq(multisig.isMember(randomWallet), false, "Should be false"); + + // in + address[] memory addrs = new address[](1); + addrs[0] = randomWallet; + multisig.addAddresses(addrs); + assertEq(multisig.isMember(randomWallet), true, "Should be true"); + + // out + multisig.removeAddresses(addrs); + assertEq(multisig.isMember(randomWallet), false, "Should be false"); + + // someone else + if (randomAccount != alice) { + vm.startPrank(randomAccount); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + randomAccount, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + multisig.addAddresses(addrs); + assertEq(multisig.isMember(randomWallet), false, "Should be false"); + + addrs[0] = carol; + assertEq(multisig.isMember(carol), true, "Should be true"); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + randomAccount, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + multisig.removeAddresses(addrs); + + assertEq(multisig.isMember(carol), true, "Should be true"); + } + + vm.startPrank(alice); } } + +/* +1. Constructor Tests + + Default Values: Ensure the constructor initializes settings correctly. + Invalid Encryption Registry: Verify that passing an invalid encryption registry address reverts. + +2. Settings Update Tests + + Update Settings: Test updating the settings with valid values. + No Change: Ensure no change is made if the new settings are the same as the current ones. + Invalid Encryption Registry: Verify that updating with an invalid encryption registry address reverts. + Min Signer List Length Out of Bounds: Ensure updating with a min signer list length greater than the current length reverts. + +3. Encryption Account Status Tests + + Listed Owner: Test resolveEncryptionAccountStatus when the sender is listed directly. + Appointed Owner: Test resolveEncryptionAccountStatus when the sender is appointed by another address. + Unlisted Sender: Test resolveEncryptionAccountStatus when the sender is not listed. + +4. Encryption Owner Tests + + Listed Owner: Test resolveEncryptionOwner when the sender is listed directly. + Appointed Owner: Test resolveEncryptionOwner when the sender is appointed by another address. + Unlisted Sender: Test resolveEncryptionOwner when the sender is not listed. + +5. Encryption Account Tests + + Listed Owner: Test resolveEncryptionAccount when the sender is listed directly. + Appointed Owner: Test resolveEncryptionAccount when the sender is appointed by another address. + Unlisted Sender: Test resolveEncryptionAccount when the sender is not listed. + +6. Get Encryption Recipients Tests + + All Listed Accounts: Test getEncryptionRecipients when all accounts are listed. + Mixed Listed and Unlisted Accounts: Test getEncryptionRecipients when some accounts are listed and others are unlisted. + No Listed Accounts: Test getEncryptionRecipients when no accounts are listed. + +7. Interface Support Tests + + ISignerList Interface: Ensure the contract supports the ISignerList interface. + Addresslist Interface: Ensure the contract supports the Addresslist interface. + Other Interfaces: Ensure the contract does not support other interfaces it should not. + +8. Edge Cases and Error Handling + + Zero Address: Test with zero addresses for both sender and encryption registry. + Max Int16 Value: Test with the maximum value of uint16 for min signer list length. + Min Int16 Value: Test with the minimum value of int16 for min signer list length. + +9. Gas Optimization + + Performance: Measure and optimize gas usage, especially in loops and conditional statements. + Memory Management: Ensure memory management is efficient to avoid out-of-gas errors. + +10. Reentrancy Tests + + Reentrant Calls: Test for reentrancy vulnerabilities by calling functions that modify state from within a callback or event handler. + +11. Access Control + + Role-Based Access: Ensure only authorized roles can update settings. + Unauthorized Access: Verify that unauthorized addresses cannot update settings. + +12. State Consistency + + Consistent State: Ensure the state remains consistent after each function call, especially in multi-step operations. + +13. Documentation and Comments + + Code Documentation: Ensure all functions have clear documentation explaining their purpose and parameters. + Comments: Ensure critical logic has comments for clarity. + +14. Unit Tests + + Isolated Tests: Write unit tests that isolate each function to ensure they work independently. + Edge Case Coverage: Cover edge cases in unit tests to ensure robustness. + +15. Integration Tests + + Contract Interactions: Test interactions between SignerList and other contracts it depends on, such as EncryptionRegistry. + Complex Scenarios: Simulate complex scenarios involving multiple contract calls and state changes. + +16. Security Audits + + Code Review: Have the code reviewed by security experts. + Formal Verification: Use formal verification tools to check for bugs and vulnerabilities. + + +EDGE CASES: +- Multisig: lower minApprovals than addressListLength() +- SignerList: removing members without lowering the minSize + +- Multisig's check settings values + - On initialize + - When updating + +*/ diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol new file mode 100644 index 0000000..39659eb --- /dev/null +++ b/test/SignerListTree.t.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.17; + +import {Test} from "forge-std/Test.sol"; + +contract SignerListTest is Test { + function test_WhenDeployingTheContract() external { + // It should initialize normally + vm.skip(true); + } + + function test_GivenADeployedContract() external { + // It should fail to initialize again + vm.skip(true); + } + + modifier givenANewInstance() { + _; + } + + modifier givenCallingInitialize() { + _; + } + + function test_GivenCallingInitialize() external givenANewInstance givenCallingInitialize { + // It should set the DAO address + // It should set the addresses as signers + // It settings should match the given ones + // It should emit the SignersAdded event + // It should emit the SignerListSettingsUpdated event + vm.skip(true); + } + + function test_RevertGiven_PassingMoreAddressesThanSupported() external givenANewInstance givenCallingInitialize { + // It should revert + vm.skip(true); + } + + modifier whenCallingAddSigners() { + _; + } + + function test_WhenCallingAddSigners() external whenCallingAddSigners { + // It should append the new addresses to the list + // It should emit the SignersAddedEvent + vm.skip(true); + } + + function test_RevertWhen_AddSignersWithoutThePermission() external whenCallingAddSigners { + // It should revert + vm.skip(true); + } + + function test_RevertGiven_PassingMoreAddressesThanAllowed() external whenCallingAddSigners { + // It should revert + vm.skip(true); + } + + function test_RevertGiven_DuplicateAddresses() external whenCallingAddSigners { + // It should revert + vm.skip(true); + } + + modifier whenCallingRemoveSigners() { + _; + } + + function test_WhenCallingRemoveSigners() external whenCallingRemoveSigners { + // It should more the given addresses + // It should emit the SignersRemovedEvent + vm.skip(true); + } + + function test_RevertWhen_RemoveSignersWithoutThePermission() external whenCallingRemoveSigners { + // It should revert + vm.skip(true); + } + + function test_RevertGiven_RemovingTooManyAddresses() external whenCallingRemoveSigners { + // It should revert + vm.skip(true); + } + + modifier whenCallingIsListed() { + _; + } + + function test_GivenTheMemberIsListed() external whenCallingIsListed { + // It returns true + vm.skip(true); + } + + function test_GivenTheMemberIsNotListed() external whenCallingIsListed { + // It returns false + vm.skip(true); + } + + modifier whenCallingIsListedAtBlock() { + _; + } + + modifier givenTheMemberWasListed() { + _; + } + + function test_GivenTheMemberIsNotListedNow() external whenCallingIsListedAtBlock givenTheMemberWasListed { + // It returns true + vm.skip(true); + } + + function test_GivenTheMemberIsListedNow() external whenCallingIsListedAtBlock givenTheMemberWasListed { + // It returns true + vm.skip(true); + } + + modifier givenTheMemberWasNotListed() { + _; + } + + function test_GivenTheMemberIsDelistedNow() external whenCallingIsListedAtBlock givenTheMemberWasNotListed { + // It returns false + vm.skip(true); + } + + function test_GivenTheMemberIsEnlistedNow() external whenCallingIsListedAtBlock givenTheMemberWasNotListed { + // It returns false + vm.skip(true); + } + + modifier whenCallingUpdateSettings() { + _; + } + + function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { + // It set the new encryption registry + // It set the new minSignerListLength + // It should emit a SignerListSettingsUpdated event + vm.skip(true); + } + + function test_RevertWhen_UpdateSettingsWithoutThePermission() external whenCallingUpdateSettings { + // It should revert + vm.skip(true); + } + + function test_RevertWhen_EncryptionRegistryIsNotCompatible() external whenCallingUpdateSettings { + // It should revert + vm.skip(true); + } + + function test_RevertWhen_SettingAMinSignerListLengthLowerThanTheCurrentListSize() + external + whenCallingUpdateSettings + { + // It should revert + vm.skip(true); + } + + modifier whenCallingResolveEncryptionAccountStatus() { + _; + } + + function test_GivenTheCallerIsAListedSigner() external whenCallingResolveEncryptionAccountStatus { + // It ownerIsListed should be true + // It isAppointed should be false + vm.skip(true); + } + + function test_GivenTheCallerIsAppointedByASigner() external whenCallingResolveEncryptionAccountStatus { + // It ownerIsListed should be true + // It isAppointed should be true + vm.skip(true); + } + + function test_GivenTheCallerIsNotListedOrAppointed() external whenCallingResolveEncryptionAccountStatus { + // It ownerIsListed should be false + // It isAppointed should be false + vm.skip(true); + } + + modifier whenCallingResolveEncryptionOwner() { + _; + } + + modifier givenTheResolvedOwnerIsListed() { + _; + } + + function test_WhenTheGivenAddressIsAppointed() + external + whenCallingResolveEncryptionOwner + givenTheResolvedOwnerIsListed + { + // It owner should be the resolved owner + // It appointedWallet should be the caller + vm.skip(true); + } + + function test_WhenTheGivenAddressIsNotAppointed() + external + whenCallingResolveEncryptionOwner + givenTheResolvedOwnerIsListed + { + // It owner should be the caller + // It appointedWallet should be resolved appointed wallet + vm.skip(true); + } + + function test_GivenTheResolvedOwnerIsNotListed() external whenCallingResolveEncryptionOwner { + // It should return a zero owner + // It should return a zero appointedWallet + vm.skip(true); + } + + modifier whenCallingGetEncryptionRecipients() { + _; + } + + function test_GivenTheEncryptionRegistryHasNoAccounts() external whenCallingGetEncryptionRecipients { + // It returns an empty list, even with signers + // It returns an empty list, without signers + vm.skip(true); + } + + modifier givenTheEncryptionRegistryHasAccounts() { + _; + } + + function test_GivenNoOverlapBetweenRegistryAndSignerList() + external + whenCallingGetEncryptionRecipients + givenTheEncryptionRegistryHasAccounts + { + // It returns an empty list + vm.skip(true); + } + + function test_GivenSomeAddressesAreRegisteredEverywhere() + external + whenCallingGetEncryptionRecipients + givenTheEncryptionRegistryHasAccounts + { + // It returns a list containing the overlapping addresses + // It the result has the correct resolved addresses + // It result does not contain unregistered addresses + // It result does not contain unlisted addresses + // It result does not contain non appointed addresses + vm.skip(true); + } + + function test_WhenCallingSupportsInterface() external { + // It supports ISignerList + // It supports Addresslist + // It supports the parents interfaces + vm.skip(true); + } +} diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml new file mode 100644 index 0000000..768ab70 --- /dev/null +++ b/test/SignerListTree.t.yaml @@ -0,0 +1,154 @@ +SignerListTest: +- when: deploying the contract + then: + - it: should initialize normally +- given: a deployed contract + then: + - it: should fail to initialize again +- given: a new instance + and: + - given: calling initialize + and: + - it: should set the DAO address + - it: should set the addresses as signers + - it: settings should match the given ones + - it: should emit the SignersAdded event + - it: should emit the SignerListSettingsUpdated event + - given: passing more addresses than supported + then: + - it: should revert + +- when: calling addSigners + and: + - when: addSigners without the permission + then: + - it: should revert + - given: passing more addresses than allowed + then: + - it: should revert + - given: duplicate addresses + then: + - it: should revert + - it: should append the new addresses to the list + - it: should emit the SignersAddedEvent + +- when: calling removeSigners + and: + - when: removeSigners without the permission + then: + - it: should revert + - given: removing too many addresses + comment: The new list will be smaller than minSignerListLength + then: + - it: should revert + - it: should more the given addresses + - it: should emit the SignersRemovedEvent + +- when: calling isListed + and: + - given: the member is listed + then: + - it: returns true + - given: the member is not listed + then: + - it: returns false + +- when: calling isListedAtBlock + and: + - given: the member was listed + and: + - given: the member is not listed now + then: + - it: returns true + - given: the member is listed now + then: + - it: returns true + - given: the member was not listed + and: + - given: the member is delisted now + then: + - it: returns false + - given: the member is enlisted now + then: + - it: returns false + +- when: calling updateSettings + and: + - when: updateSettings without the permission + then: + - it: should revert + - when: encryptionRegistry is not compatible + then: + - it: should revert + - when: setting a minSignerListLength lower than the current list size + then: + - it: should revert + - it: set the new encryption registry + - it: set the new minSignerListLength + - it: should emit a SignerListSettingsUpdated event + +- when: calling resolveEncryptionAccountStatus + and: + - given: the caller is a listed signer + then: + - it: ownerIsListed should be true + - it: isAppointed should be false + - given: the caller is appointed by a signer + then: + - it: ownerIsListed should be true + - it: isAppointed should be true + - given: the caller is not listed or appointed + then: + - it: ownerIsListed should be false + - it: isAppointed should be false + +- when: calling resolveEncryptionOwner + and: + - given: the resolved owner is listed + and: + - when: the given address is appointed + then: + - it: owner should be the resolved owner + - it: appointedWallet should be the caller + - when: the given address is not appointed + then: + - it: owner should be the caller + - it: appointedWallet should be resolved appointed wallet + + - given: the resolved owner is not listed + then: + - it: should return a zero owner + - it: should return a zero appointedWallet + +- when: calling getEncryptionRecipients + and: + - given: the encryption registry has no accounts + then: + - it: returns an empty list, even with signers + - it: returns an empty list, without signers + + - given: the encryption registry has accounts + then: + - given: no overlap between registry and signerList + comment: Some are on the encryption registry only and some are on the signerList only + then: + - it: returns an empty list + - given: some addresses are registered everywhere + then: + - it: returns a list containing the overlapping addresses + - it: the result has the correct resolved addresses + comment: appointed wallets are present, not the owner + - it: result does not contain unregistered addresses + - it: result does not contain unlisted addresses + - it: result does not contain non appointed addresses + +- when: calling supportsInterface + then: + - it: supports ISignerList + - it: supports Addresslist + - it: supports the parents interfaces + + + + + From f375c1594ec34287fc6233f816e7ebcae279f2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 24 Oct 2024 18:15:13 +0400 Subject: [PATCH 13/45] New tests definition WIP --- test/Multisig.t.sol | 2 +- test/MultisigTree.t.yaml | 110 ++++++++++++++ test/SignerListTree.t.yaml | 291 ++++++++++++++++++------------------- 3 files changed, 256 insertions(+), 147 deletions(-) create mode 100644 test/MultisigTree.t.yaml diff --git a/test/Multisig.t.sol b/test/Multisig.t.sol index 149849b..6261a07 100644 --- a/test/Multisig.t.sol +++ b/test/Multisig.t.sol @@ -20,7 +20,7 @@ import {createProxyAndCall} from "../src/helpers/proxy.sol"; uint64 constant MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; -contract MultisigTest is AragonTest { +contract MultisigTestOld is AragonTest { DaoBuilder builder; DAO dao; diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml new file mode 100644 index 0000000..bba868b --- /dev/null +++ b/test/MultisigTree.t.yaml @@ -0,0 +1,110 @@ +MultisigTest: + - given: a newly deployed contract + then: + - given: calling initialize() + then: + - it: should initialize the first time + - it: should refuse to initialize again + - it: should set the DAO address + + # updateSettings() below should have the same branches: + - it: should set the minApprovals + - it: should set onlyListed + - it: should set signerList + - it: should set destinationProposalDuration + - it: should set proposalExpirationPeriod + - it: should emit MultisigSettingsUpdated + - when: minApprovals is greater than signerList length + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + - when: minApprovals is zero + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + + - when: calling supportsInterface() + then: + - it: does not support the empty interface + - it: supports IERC165Upgradeable + - it: supports IPlugin + - it: supports IProposal + + - when: calling updateSettings() + then: + - given: caller has no permission + then: + - it: should revert + - it: otherwise it should just work + + # initialize() above should have the same branches: + - it: should set the minApprovals + - it: should set onlyListed + - it: should set signerList + - it: should set destinationProposalDuration + - it: should set proposalExpirationPeriod + - it: should emit MultisigSettingsUpdated + - when: minApprovals is greater than signerList length + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + - when: minApprovals is zero + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + + - when: calling createProposal() + then: + - it: increments the proposal counter + - it: creates and return unique proposal IDs + - it: emits the ProposalCreatedEvent + - given: settings changed on the same block + then: + - it: reverts + - it: does not revert otherwise + - given: onlyListed is false + then: + - it: allows anyone to create + - given: onlyListed is true + and: + - given: creation caller is not listed or appointed + then: + - it: reverts + - given: creation caller is appointed by a former signer + then: + - it: reverts + - given: creation caller is listed + then: + - it: creates the proposal + - given: creation caller is appointed by a current signer + then: + - it: creates the proposal + - given: approveProposal is true + then: + - it: creates and calls approval in one go + - given: approveProposal is false + then: + - it: only creates the proposal + + # Proposal lifecycle stages + + - given: The proposal is not created + then: + - when: calling canApprove + then: + - it: should always return false (listed sender) + - it: should always return false (appointed sender) + - it: should always return false (unlisted unappointed sender) + + # - when: calling canApprove() + # and: + # - given: the proposal is not created + # then: + # - it: reverts + # - given: the proposal is expired + # then: + # - it: reverts diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index 768ab70..b42f35f 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -1,154 +1,153 @@ SignerListTest: -- when: deploying the contract - then: - - it: should initialize normally -- given: a deployed contract - then: - - it: should fail to initialize again -- given: a new instance - and: - - given: calling initialize - and: - - it: should set the DAO address - - it: should set the addresses as signers - - it: settings should match the given ones - - it: should emit the SignersAdded event - - it: should emit the SignerListSettingsUpdated event - - given: passing more addresses than supported - then: - - it: should revert - -- when: calling addSigners - and: - - when: addSigners without the permission + - when: deploying the contract then: - - it: should revert - - given: passing more addresses than allowed - then: - - it: should revert - - given: duplicate addresses + - it: should initialize normally + - given: a deployed contract then: - - it: should revert - - it: should append the new addresses to the list - - it: should emit the SignersAddedEvent - -- when: calling removeSigners - and: - - when: removeSigners without the permission - then: - - it: should revert - - given: removing too many addresses - comment: The new list will be smaller than minSignerListLength - then: - - it: should revert - - it: should more the given addresses - - it: should emit the SignersRemovedEvent - -- when: calling isListed - and: - - given: the member is listed - then: - - it: returns true - - given: the member is not listed - then: - - it: returns false - -- when: calling isListedAtBlock - and: - - given: the member was listed + - it: should refuse to initialize again + - given: a new instance and: - - given: the member is not listed now - then: - - it: returns true - - given: the member is listed now - then: - - it: returns true - - given: the member was not listed + - given: calling initialize + and: + - it: should set the DAO address + - it: should set the addresses as signers + - it: settings should match the given ones + - it: should emit the SignersAdded event + - it: should emit the SignerListSettingsUpdated event + - given: passing more addresses than supported + then: + - it: should revert + + - when: calling addSigners and: - - given: the member is delisted now - then: - - it: returns false - - given: the member is enlisted now - then: - - it: returns false - -- when: calling updateSettings - and: - - when: updateSettings without the permission - then: - - it: should revert - - when: encryptionRegistry is not compatible - then: - - it: should revert - - when: setting a minSignerListLength lower than the current list size - then: - - it: should revert - - it: set the new encryption registry - - it: set the new minSignerListLength - - it: should emit a SignerListSettingsUpdated event - -- when: calling resolveEncryptionAccountStatus - and: - - given: the caller is a listed signer - then: - - it: ownerIsListed should be true - - it: isAppointed should be false - - given: the caller is appointed by a signer - then: - - it: ownerIsListed should be true - - it: isAppointed should be true - - given: the caller is not listed or appointed - then: - - it: ownerIsListed should be false - - it: isAppointed should be false - -- when: calling resolveEncryptionOwner - and: - - given: the resolved owner is listed + - when: addSigners without the permission + then: + - it: should revert + - given: passing more addresses than allowed + then: + - it: should revert + - given: duplicate addresses + then: + - it: should revert + - it: should append the new addresses to the list + - it: should emit the SignersAddedEvent + + - when: calling removeSigners and: - - when: the given address is appointed - then: - - it: owner should be the resolved owner - - it: appointedWallet should be the caller - - when: the given address is not appointed - then: - - it: owner should be the caller - - it: appointedWallet should be resolved appointed wallet - - - given: the resolved owner is not listed - then: - - it: should return a zero owner - - it: should return a zero appointedWallet - -- when: calling getEncryptionRecipients - and: - - given: the encryption registry has no accounts - then: - - it: returns an empty list, even with signers - - it: returns an empty list, without signers - - - given: the encryption registry has accounts + - when: removeSigners without the permission + then: + - it: should revert + - given: removing too many addresses + comment: The new list will be smaller than minSignerListLength + then: + - it: should revert + - it: should more the given addresses + - it: should emit the SignersRemovedEvent + + - when: calling isListed + and: + - given: the member is listed + then: + - it: returns true + - given: the member is not listed + then: + - it: returns false + + - when: calling isListedAtBlock + and: + - given: the member was listed + and: + - given: the member is not listed now + then: + - it: returns true + - given: the member is listed now + then: + - it: returns true + - given: the member was not listed + and: + - given: the member is delisted now + then: + - it: returns false + - given: the member is enlisted now + then: + - it: returns false + + - when: calling updateSettings + and: + - when: updateSettings without the permission + then: + - it: should revert + - when: encryptionRegistry is not compatible + then: + - it: should revert + - when: setting a minSignerListLength lower than the current list size + then: + - it: should revert + - it: set the new encryption registry + - it: set the new minSignerListLength + - it: should emit a SignerListSettingsUpdated event + + - when: calling resolveEncryptionAccountStatus + and: + - given: the caller is a listed signer + then: + - it: ownerIsListed should be true + - it: isAppointed should be false + - given: the caller is appointed by a signer + then: + - it: ownerIsListed should be true + - it: isAppointed should be true + - given: the caller is not listed or appointed + then: + - it: ownerIsListed should be false + - it: isAppointed should be false + + - when: calling resolveEncryptionOwner + and: + - given: the resolved owner is listed + and: + - when: the given address is appointed + then: + - it: owner should be the resolved owner + - it: appointedWallet should be the caller + - when: the given address is not appointed + then: + - it: owner should be the caller + - it: appointedWallet should be resolved appointed wallet + + - given: the resolved owner is not listed + then: + - it: should return a zero owner + - it: should return a zero appointedWallet + + - when: calling getEncryptionRecipients + and: + - given: the encryption registry has no accounts + then: + - it: returns an empty list, even with signers + - it: returns an empty list, without signers + + - given: the encryption registry has accounts + then: + - given: no overlap between registry and signerList + comment: Some are on the encryption registry only and some are on the signerList only + then: + - it: returns an empty list + - given: some addresses are registered everywhere + then: + - it: returns a list containing the overlapping addresses + - it: the result has the correct resolved addresses + comment: appointed wallets are present, not the owner + - it: result does not contain unregistered addresses + - it: result does not contain unlisted addresses + - it: result does not contain non appointed addresses + + - when: calling supportsInterface then: - - given: no overlap between registry and signerList - comment: Some are on the encryption registry only and some are on the signerList only - then: - - it: returns an empty list - - given: some addresses are registered everywhere - then: - - it: returns a list containing the overlapping addresses - - it: the result has the correct resolved addresses - comment: appointed wallets are present, not the owner - - it: result does not contain unregistered addresses - - it: result does not contain unlisted addresses - - it: result does not contain non appointed addresses - -- when: calling supportsInterface - then: - - it: supports ISignerList - - it: supports Addresslist - - it: supports the parents interfaces - - - - + - it: does not support the empty interface + - it: supports IERC165Upgradeable + - it: supports IPlugin + - it: supports IProposal + - it: supports IMultisig + - when: calling From aa5d023e1e5f81ecd6f6a2f874b1674c5b8690d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 24 Oct 2024 18:15:39 +0400 Subject: [PATCH 14/45] Test tree --- TEST_TREE.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/TEST_TREE.md b/TEST_TREE.md index 8c86679..c5b1d1e 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -2,12 +2,85 @@ Below is the graphical definition of the contract tests implemented on [the test folder](./test) +``` +MultisigTest +├── Given a newly deployed contract +│ └── Given calling initialize() +│ ├── It should initialize the first time +│ ├── It should refuse to initialize again +│ ├── It should set the DAO address +│ ├── It should set the minApprovals +│ ├── It should set onlyListed +│ ├── It should set signerList +│ ├── It should set destinationProposalDuration +│ ├── It should set proposalExpirationPeriod +│ ├── It should emit MultisigSettingsUpdated +│ ├── When minApprovals is greater than signerList length +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ └── When minApprovals is zero +│ ├── It should revert +│ ├── It should revert (with onlyListed false) +│ └── It should not revert otherwise +├── When calling supportsInterface() +│ ├── It does not support the empty interface +│ ├── It supports IERC165Upgradeable +│ ├── It supports IPlugin +│ └── It supports IProposal +├── When calling updateSettings() +│ ├── Given caller has no permission +│ │ ├── It should revert +│ │ └── It otherwise it should just work +│ ├── It should set the minApprovals +│ ├── It should set onlyListed +│ ├── It should set signerList +│ ├── It should set destinationProposalDuration +│ ├── It should set proposalExpirationPeriod +│ ├── It should emit MultisigSettingsUpdated +│ ├── When minApprovals is greater than signerList length +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ └── When minApprovals is zero +│ ├── It should revert +│ ├── It should revert (with onlyListed false) +│ └── It should not revert otherwise +├── When calling createProposal() +│ ├── It increments the proposal counter +│ ├── It creates and return unique proposal IDs +│ ├── It emits the ProposalCreatedEvent +│ ├── Given settings changed on the same block +│ │ ├── It reverts +│ │ └── It does not revert otherwise +│ ├── Given onlyListed is false +│ │ └── It allows anyone to create +│ ├── Given onlyListed is true +│ │ ├── Given creation caller is not listed or appointed +│ │ │ └── It reverts +│ │ ├── Given creation caller is appointed by a former signer +│ │ │ └── It reverts +│ │ ├── Given creation caller is listed +│ │ │ └── It creates the proposal +│ │ └── Given creation caller is appointed by a current signer +│ │ └── It creates the proposal +│ ├── Given approveProposal is true +│ │ └── It creates and calls approval in one go +│ └── Given approveProposal is false +│ └── It only creates the proposal +└── Given The proposal is not created + └── When calling canApprove + ├── It should always return false (listed sender) + ├── It should always return false (appointed sender) + └── It should always return false (unlisted unappointed sender) +``` + ``` SignerListTest ├── When deploying the contract │ └── It should initialize normally ├── Given a deployed contract -│ └── It should fail to initialize again +│ └── It should refuse to initialize again ├── Given a new instance │ └── Given calling initialize │ ├── It should set the DAO address @@ -93,9 +166,12 @@ SignerListTest │ ├── It result does not contain unregistered addresses │ ├── It result does not contain unlisted addresses │ └── It result does not contain non appointed addresses -└── When calling supportsInterface - ├── It supports ISignerList - ├── It supports Addresslist - └── It supports the parents interfaces +├── When calling supportsInterface +│ ├── It does not support the empty interface +│ ├── It supports IERC165Upgradeable +│ ├── It supports IPlugin +│ ├── It supports IProposal +│ └── It supports IMultisig +└── When calling ``` From 338f31e3074c3a9a1c9b27da6c803f1139b6b132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 25 Oct 2024 13:37:09 +0400 Subject: [PATCH 15/45] Definig the multisig test branches --- test/MultisigTree.t.sol | 426 ++++++++++++++++++++++++++++++++++ test/MultisigTree.t.yaml | 279 +++++++++++++++++++--- test/SignerListTree.t.sol | 10 +- test/script/make-test-tree.ts | 8 +- 4 files changed, 687 insertions(+), 36 deletions(-) create mode 100644 test/MultisigTree.t.sol diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol new file mode 100644 index 0000000..6c49915 --- /dev/null +++ b/test/MultisigTree.t.sol @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.17; + +import {Test} from "forge-std/Test.sol"; + +contract MultisigTest is Test { + modifier givenANewlyDeployedContract() { + _; + } + + modifier givenCallingInitialize() { + _; + } + + function test_GivenCallingInitialize() external givenANewlyDeployedContract givenCallingInitialize { + // It should initialize the first time + // It should refuse to initialize again + // It should set the DAO address + // It should set the minApprovals + // It should set onlyListed + // It should set signerList + // It should set destinationProposalDuration + // It should set proposalExpirationPeriod + // It should emit MultisigSettingsUpdated + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthInitialize() + external + givenANewlyDeployedContract + givenCallingInitialize + { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsZeroInitialize() + external + givenANewlyDeployedContract + givenCallingInitialize + { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_WhenCallingUpgradeTo() external { + // It should revert when called without the permission + // It should work when called with the permission + vm.skip(true); + } + + function test_WhenCallingUpgradeToAndCall() external { + // It should revert when called without the permission + // It should work when called with the permission + vm.skip(true); + } + + modifier whenCallingUpdateSettings() { + _; + } + + function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { + // It should set the minApprovals + // It should set onlyListed + // It should set signerList + // It should set destinationProposalDuration + // It should set proposalExpirationPeriod + // It should emit MultisigSettingsUpdated + vm.skip(true); + } + + function test_RevertGiven_CallerHasNoPermission() external whenCallingUpdateSettings { + // It should revert + // It otherwise it should just work + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthUpdateSettings() + external + whenCallingUpdateSettings + { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsZeroUpdateSettings() external whenCallingUpdateSettings { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_WhenCallingSupportsInterface() external { + // It does not support the empty interface + // It supports IERC165Upgradeable + // It supports IPlugin + // It supports IProposal + vm.skip(true); + } + + modifier whenCallingCreateProposal() { + _; + } + + function test_WhenCallingCreateProposal() external whenCallingCreateProposal { + // It increments the proposal counter + // It creates and return unique proposal IDs + // It emits the ProposalCreatedEvent + // It creates a proposal with the given values + vm.skip(true); + } + + function test_GivenSettingsChangedOnTheSameBlock() external whenCallingCreateProposal { + // It reverts + // It does not revert otherwise + vm.skip(true); + } + + function test_GivenOnlyListedIsFalse() external whenCallingCreateProposal { + // It allows anyone to create + vm.skip(true); + } + + modifier givenOnlyListedIsTrue() { + _; + } + + function test_GivenCreationCallerIsNotListedOrAppointed() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It reverts + vm.skip(true); + } + + function test_GivenCreationCallerIsAppointedByAFormerSigner() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It reverts + vm.skip(true); + } + + function test_GivenCreationCallerIsListedAndSelfAppointed() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It creates the proposal + vm.skip(true); + } + + function test_GivenCreationCallerIsListedAppointingSomeoneElseNow() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It creates the proposal + vm.skip(true); + } + + function test_GivenCreationCallerIsAppointedByACurrentSigner() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It creates the proposal + vm.skip(true); + } + + function test_GivenApproveProposalIsTrue() external whenCallingCreateProposal { + // It creates and calls approval in one go + vm.skip(true); + } + + function test_GivenApproveProposalIsFalse() external whenCallingCreateProposal { + // It only creates the proposal + vm.skip(true); + } + + modifier givenTheProposalIsNotCreated() { + _; + } + + function test_WhenCallingGetProposalUncreated() external givenTheProposalIsNotCreated { + // It should return empty values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveUncreated() external givenTheProposalIsNotCreated { + // It canApprove should return false (when currently listed and self appointed) + // It approve should revert (when currently listed and self appointed) + // It canApprove should return false (when currently listed, appointing someone else now) + // It approve should revert (when currently listed, appointing someone else now) + // It canApprove should return false (when appointed by a listed signer) + // It approve should revert (when appointed by a listed signer) + // It canApprove should return false (when currently unlisted and unappointed) + // It approve should revert (when currently unlisted and unappointed) + vm.skip(true); + } + + function test_WhenCallingHasApprovedUncreated() external givenTheProposalIsNotCreated { + // It hasApproved should always return false + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteUncreated() external givenTheProposalIsNotCreated { + // It canExecute should return false (when currently listed and self appointed) + // It execute should revert (when currently listed and self appointed) + // It canExecute should return false (when currently listed, appointing someone else now) + // It execute should revert (when currently listed, appointing someone else now) + // It canExecute should return false (when appointed by a listed signer) + // It execute should revert (when appointed by a listed signer) + // It canExecute should return false (when currently unlisted and unappointed) + // It execute should revert (when currently unlisted and unappointed) + vm.skip(true); + } + + modifier givenTheProposalIsOpen() { + _; + } + + function test_WhenCallingGetProposalOpen() external givenTheProposalIsOpen { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveOpen() external givenTheProposalIsOpen { + // It canApprove should return true (when listed on creation, self appointed now) + // It approve should work (when listed on creation, self appointed now) + // It approve should emit an event (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return true (when currently appointed by a signer listed on creation) + // It approve should work (when currently appointed by a signer listed on creation) + // It approve should emit an event (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingApproveWithTryExecutionAndAlmostPassedOpen() external givenTheProposalIsOpen { + // It approve should also execute the proposal + // It approve should emit an Executed event + // It approve recreates the proposal on the destination plugin + // It The parameters of the recreated proposal match those of the approved one + // It A ProposalCreated event is emitted on the destination plugin + vm.skip(true); + } + + function test_WhenCallingHasApprovedOpen() external givenTheProposalIsOpen { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteOpen() external givenTheProposalIsOpen { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when listed on creation, appointing someone else now) + // It execute should revert (when listed on creation, appointing someone else now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + modifier givenTheProposalWasApprovedByTheAddress() { + _; + } + + function test_WhenCallingGetProposalApproved() external givenTheProposalWasApprovedByTheAddress { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveApproved() external givenTheProposalWasApprovedByTheAddress { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + vm.skip(true); + } + + function test_WhenCallingHasApprovedApproved() external givenTheProposalWasApprovedByTheAddress { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteApproved() external givenTheProposalWasApprovedByTheAddress { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + vm.skip(true); + } + + modifier givenTheProposalPassed() { + _; + } + + function test_WhenCallingGetProposalPassed() external givenTheProposalPassed { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApprovePassed() external givenTheProposalPassed { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedPassed() external givenTheProposalPassed { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecutePassed() external givenTheProposalPassed { + // It canExecute should return true (when listed on creation, self appointed now) + // It execute should work (when listed on creation, self appointed now) + // It execute should emit an event (when listed on creation, self appointed now) + // It canExecute should return true (when listed on creation, appointing someone else now) + // It execute should work (when listed on creation, appointing someone else now) + // It execute should emit an event (when listed on creation, appointing someone else now) + // It canExecute should return true (when currently appointed by a signer listed on creation) + // It execute should work (when currently appointed by a signer listed on creation) + // It execute should emit an event (when currently appointed by a signer listed on creation) + // It canExecute should return true (when unlisted on creation, unappointed now) + // It execute should work (when unlisted on creation, unappointed now) + // It execute should emit an event (when unlisted on creation, unappointed now) + // It execute recreates the proposal on the destination plugin + // It The parameters of the recreated proposal match those of the executed one + // It A ProposalCreated event is emitted on the destination plugin + vm.skip(true); + } + + modifier givenTheProposalIsAlreadyExecuted() { + _; + } + + function test_WhenCallingGetProposalExecuted() external givenTheProposalIsAlreadyExecuted { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveExecuted() external givenTheProposalIsAlreadyExecuted { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedExecuted() external givenTheProposalIsAlreadyExecuted { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteExecuted() external givenTheProposalIsAlreadyExecuted { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when listed on creation, appointing someone else now) + // It execute should revert (when listed on creation, appointing someone else now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + modifier givenTheProposalExpired() { + _; + } + + function test_WhenCallingGetProposalExpired() external givenTheProposalExpired { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveExpired() external givenTheProposalExpired { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedExpired() external givenTheProposalExpired { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteExpired() external givenTheProposalExpired { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when listed on creation, appointing someone else now) + // It execute should revert (when listed on creation, appointing someone else now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } +} diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml index bba868b..47d22a9 100644 --- a/test/MultisigTree.t.yaml +++ b/test/MultisigTree.t.yaml @@ -1,67 +1,81 @@ MultisigTest: + # Plugin lifecycle - given: a newly deployed contract then: - - given: calling initialize() + - given: calling initialize then: - it: should initialize the first time - it: should refuse to initialize again - it: should set the DAO address - # updateSettings() below should have the same branches: + # updateSettings below should have the same branches: - it: should set the minApprovals - it: should set onlyListed - it: should set signerList - it: should set destinationProposalDuration - it: should set proposalExpirationPeriod - it: should emit MultisigSettingsUpdated - - when: minApprovals is greater than signerList length + - when: minApprovals is greater than signerList length [initialize] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - when: minApprovals is zero + - when: minApprovals is zero [initialize] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - when: calling supportsInterface() + - when: calling upgradeTo then: - - it: does not support the empty interface - - it: supports IERC165Upgradeable - - it: supports IPlugin - - it: supports IProposal + - it: should revert when called without the permission + - it: should work when called with the permission - - when: calling updateSettings() + - when: calling upgradeToAndCall + then: + - it: should revert when called without the permission + - it: should work when called with the permission + + - when: calling updateSettings then: - given: caller has no permission then: - it: should revert - it: otherwise it should just work - # initialize() above should have the same branches: + # initialize above should have the same branches: - it: should set the minApprovals - it: should set onlyListed - it: should set signerList - it: should set destinationProposalDuration - it: should set proposalExpirationPeriod - it: should emit MultisigSettingsUpdated - - when: minApprovals is greater than signerList length + - when: minApprovals is greater than signerList length [updateSettings] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - when: minApprovals is zero + - when: minApprovals is zero [updateSettings] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - when: calling createProposal() + # General methods + + - when: calling supportsInterface + then: + - it: does not support the empty interface + - it: supports IERC165Upgradeable + - it: supports IPlugin + - it: supports IProposal + + - when: calling createProposal then: - it: increments the proposal counter - it: creates and return unique proposal IDs - it: emits the ProposalCreatedEvent + - it: creates a proposal with the given values - given: settings changed on the same block then: - it: reverts @@ -77,7 +91,10 @@ MultisigTest: - given: creation caller is appointed by a former signer then: - it: reverts - - given: creation caller is listed + - given: creation caller is listed and self appointed + then: + - it: creates the proposal + - given: creation caller is listed, appointing someone else now then: - it: creates the proposal - given: creation caller is appointed by a current signer @@ -90,21 +107,223 @@ MultisigTest: then: - it: only creates the proposal - # Proposal lifecycle stages + # Proposal lifecycle - given: The proposal is not created then: - - when: calling canApprove - then: - - it: should always return false (listed sender) - - it: should always return false (appointed sender) - - it: should always return false (unlisted unappointed sender) - - # - when: calling canApprove() - # and: - # - given: the proposal is not created - # then: - # - it: reverts - # - given: the proposal is expired - # then: - # - it: reverts + # Get proposal + - when: calling getProposal [uncreated] + then: + - it: should return empty values + # Approval + - when: calling canApprove and approve [uncreated] + then: + - it: canApprove should return false (when currently listed and self appointed) + - it: approve should revert (when currently listed and self appointed) + - it: canApprove should return false (when currently listed, appointing someone else now) + - it: approve should revert (when currently listed, appointing someone else now) + - it: canApprove should return false (when appointed by a listed signer) + - it: approve should revert (when appointed by a listed signer) + - it: canApprove should return false (when currently unlisted and unappointed) + - it: approve should revert (when currently unlisted and unappointed) + # Has approved + - when: calling hasApproved [uncreated] + then: + - it: hasApproved should always return false + # Execution + - when: calling canExecute and execute [uncreated] + then: + - it: canExecute should return false (when currently listed and self appointed) + - it: execute should revert (when currently listed and self appointed) + - it: canExecute should return false (when currently listed, appointing someone else now) + - it: execute should revert (when currently listed, appointing someone else now) + - it: canExecute should return false (when appointed by a listed signer) + - it: execute should revert (when appointed by a listed signer) + - it: canExecute should return false (when currently unlisted and unappointed) + - it: execute should revert (when currently unlisted and unappointed) + + - given: The proposal is open + then: + # Get proposal + - when: calling getProposal [open] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [open] + then: + - it: canApprove should return true (when listed on creation, self appointed now) + - it: approve should work (when listed on creation, self appointed now) + - it: approve should emit an event (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return true (when currently appointed by a signer listed on creation) + - it: approve should work (when currently appointed by a signer listed on creation) + - it: approve should emit an event (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Auto execution + - when: calling approve with tryExecution and almost passed [open] + then: + - it: approve should also execute the proposal + - it: approve should emit an Executed event + # Proposal forwarding on execution + - it: approve recreates the proposal on the destination plugin + - it: The parameters of the recreated proposal match those of the approved one + - it: A ProposalCreated event is emitted on the destination plugin + + # Has approved + - when: calling hasApproved [open] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [open] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + - it: canExecute should return false (when listed on creation, appointing someone else now) + - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + - it: canExecute should return false (when unlisted on creation, unappointed now) + - it: execute should revert (when unlisted on creation, unappointed now) + + - given: The proposal was approved by the address + then: + # Get proposal + - when: calling getProposal [approved] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [approved] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + # - it: canApprove should return false (when listed on creation, appointing someone else now) + # - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + # - it: canApprove should return false (when unlisted on creation, unappointed now) + # - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [approved] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [approved] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + # - it: canExecute should return false (when listed on creation, appointing someone else now) + # - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + # - it: canExecute should return false (when unlisted on creation, unappointed now) + # - it: execute should revert (when unlisted on creation, unappointed now) + + - given: The proposal passed + then: + # Get proposal + - when: calling getProposal [passed] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [passed] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [passed] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [passed] + then: + - it: canExecute should return true (when listed on creation, self appointed now) + - it: execute should work (when listed on creation, self appointed now) + - it: execute should emit an event (when listed on creation, self appointed now) + - it: canExecute should return true (when listed on creation, appointing someone else now) + - it: execute should work (when listed on creation, appointing someone else now) + - it: execute should emit an event (when listed on creation, appointing someone else now) + - it: canExecute should return true (when currently appointed by a signer listed on creation) + - it: execute should work (when currently appointed by a signer listed on creation) + - it: execute should emit an event (when currently appointed by a signer listed on creation) + - it: canExecute should return true (when unlisted on creation, unappointed now) + - it: execute should work (when unlisted on creation, unappointed now) + - it: execute should emit an event (when unlisted on creation, unappointed now) + # Proposal forwarding on execution + - it: execute recreates the proposal on the destination plugin + - it: The parameters of the recreated proposal match those of the executed one + - it: A ProposalCreated event is emitted on the destination plugin + + - given: The proposal is already executed + then: + # Get proposal + - when: calling getProposal [executed] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [executed] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [executed] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [executed] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + - it: canExecute should return false (when listed on creation, appointing someone else now) + - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + - it: canExecute should return false (when unlisted on creation, unappointed now) + - it: execute should revert (when unlisted on creation, unappointed now) + + - given: The proposal expired + then: + # Get proposal + - when: calling getProposal [expired] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [expired] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [expired] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [expired] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + - it: canExecute should return false (when listed on creation, appointing someone else now) + - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + - it: canExecute should return false (when unlisted on creation, unappointed now) + - it: execute should revert (when unlisted on creation, unappointed now) + diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index 39659eb..aef7651 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -10,7 +10,7 @@ contract SignerListTest is Test { } function test_GivenADeployedContract() external { - // It should fail to initialize again + // It should refuse to initialize again vm.skip(true); } @@ -249,9 +249,11 @@ contract SignerListTest is Test { } function test_WhenCallingSupportsInterface() external { - // It supports ISignerList - // It supports Addresslist - // It supports the parents interfaces + // It does not support the empty interface + // It supports IERC165Upgradeable + // It supports IPlugin + // It supports IProposal + // It supports IMultisig vm.skip(true); } } diff --git a/test/script/make-test-tree.ts b/test/script/make-test-tree.ts index 119ff14..84713b4 100644 --- a/test/script/make-test-tree.ts +++ b/test/script/make-test-tree.ts @@ -57,9 +57,9 @@ function parseRuleChildren(lines: Array): Array { let content = ""; if (rule.given) { - content = "Given " + rule.given; + content = "Given " + cleanText(rule.given); } else if (rule.when) { - content = "When " + rule.when; + content = "When " + cleanText(rule.when); } else if (rule.it) { content = "It " + rule.it; } @@ -134,6 +134,10 @@ function renderTreeItem( return result; } +function cleanText(input: string): string { + return input.replace(/[^a-zA-Z0-9 ]/g, "").trim(); +} + async function readStdinText() { let result = ""; const decoder = new TextDecoder(); From adb0993c6f421b60c84630aa8a5173ae637415f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 25 Oct 2024 14:57:13 +0400 Subject: [PATCH 16/45] Emergency multisig test definition --- TEST_TREE.md | 419 +++++++++++++++++++++++++++-- test/EmergencyMultisigTree.t.sol | 425 ++++++++++++++++++++++++++++++ test/EmergencyMultisigTree.t.yaml | 318 ++++++++++++++++++++++ test/MultisigTree.t.sol | 35 ++- test/MultisigTree.t.yaml | 35 ++- 5 files changed, 1179 insertions(+), 53 deletions(-) create mode 100644 test/EmergencyMultisigTree.t.sol create mode 100644 test/EmergencyMultisigTree.t.yaml diff --git a/TEST_TREE.md b/TEST_TREE.md index c5b1d1e..1fa8bf2 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -2,10 +2,218 @@ Below is the graphical definition of the contract tests implemented on [the test folder](./test) +``` +EmergencyMultisigTest +├── Given a newly deployed contract +│ └── Given calling initialize +│ ├── It should initialize the first time +│ ├── It should refuse to initialize again +│ ├── It should set the DAO address +│ ├── It should set the minApprovals +│ ├── It should set onlyListed +│ ├── It should set signerList +│ ├── It should set proposalExpirationPeriod +│ ├── It should emit MultisigSettingsUpdated +│ ├── When minApprovals is greater than signerList length on initialize +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ ├── When minApprovals is zero on initialize +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ └── When signerList is invalid on initialize +│ └── It should revert +├── When calling upgradeTo +│ ├── It should revert when called without the permission +│ └── It should work when called with the permission +├── When calling upgradeToAndCall +│ ├── It should revert when called without the permission +│ └── It should work when called with the permission +├── When calling supportsInterface +│ ├── It does not support the empty interface +│ ├── It supports IERC165Upgradeable +│ ├── It supports IPlugin +│ ├── It supports IProposal +│ └── It supports IEmergencyMultisig +├── When calling updateSettings +│ ├── Given caller has no permission +│ │ ├── It should revert +│ │ └── It otherwise it should just work +│ ├── It should set the minApprovals +│ ├── It should set onlyListed +│ ├── It should set signerList +│ ├── It should set proposalExpirationPeriod +│ ├── It should emit MultisigSettingsUpdated +│ ├── When minApprovals is greater than signerList length on updateSettings +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ ├── When minApprovals is zero on updateSettings +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ └── When signerList is invalid on updateSettings +│ └── It should revert +├── When calling createProposal +│ ├── It increments the proposal counter +│ ├── It creates and return unique proposal IDs +│ ├── It emits the ProposalCreated event +│ ├── It creates a proposal with the given values +│ ├── Given settings changed on the same block +│ │ ├── It reverts +│ │ └── It does not revert otherwise +│ ├── Given onlyListed is false +│ │ └── It allows anyone to create +│ ├── Given onlyListed is true +│ │ ├── Given creation caller is not listed or appointed +│ │ │ └── It reverts +│ │ ├── Given creation caller is appointed by a former signer +│ │ │ └── It reverts +│ │ ├── Given creation caller is listed and self appointed +│ │ │ └── It creates the proposal +│ │ ├── Given creation caller is listed appointing someone else now +│ │ │ └── It creates the proposal +│ │ └── Given creation caller is appointed by a current signer +│ │ └── It creates the proposal +│ ├── Given approveProposal is true +│ │ └── It creates and calls approval in one go +│ └── Given approveProposal is false +│ └── It only creates the proposal +├── When calling hashActions +│ ├── It returns the right result +│ └── It reacts to any of the values changing +├── Given The proposal is not created +│ ├── When calling getProposal uncreated +│ │ └── It should return empty values +│ ├── When calling canApprove and approve uncreated +│ │ ├── It canApprove should return false (when currently listed and self appointed) +│ │ ├── It approve should revert (when currently listed and self appointed) +│ │ ├── It canApprove should return false (when currently listed, appointing someone else now) +│ │ ├── It approve should revert (when currently listed, appointing someone else now) +│ │ ├── It canApprove should return false (when appointed by a listed signer) +│ │ ├── It approve should revert (when appointed by a listed signer) +│ │ ├── It canApprove should return false (when currently unlisted and unappointed) +│ │ └── It approve should revert (when currently unlisted and unappointed) +│ ├── When calling hasApproved uncreated +│ │ └── It hasApproved should always return false +│ └── When calling canExecute and execute uncreated +│ └── It canExecute should always return false +├── Given The proposal is open +│ ├── When calling getProposal open +│ │ └── It should return the right values +│ ├── When calling canApprove and approve open +│ │ ├── It canApprove should return true (when listed on creation, self appointed now) +│ │ ├── It approve should work (when listed on creation, self appointed now) +│ │ ├── It approve should emit an event (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) +│ │ ├── It approve should revert (when listed on creation, appointing someone else now) +│ │ ├── It canApprove should return true (when currently appointed by a signer listed on creation) +│ │ ├── It approve should work (when currently appointed by a signer listed on creation) +│ │ ├── It approve should emit an event (when currently appointed by a signer listed on creation) +│ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) +│ │ └── It approve should revert (when unlisted on creation, unappointed now) +│ ├── When calling hasApproved open +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute open +│ ├── It canExecute should return false (when listed on creation, self appointed now) +│ ├── It execute should revert (when listed on creation, self appointed now) +│ ├── It canExecute should return false (when listed on creation, appointing someone else now) +│ ├── It execute should revert (when listed on creation, appointing someone else now) +│ ├── It canExecute should return false (when currently appointed by a signer listed on creation) +│ ├── It execute should revert (when currently appointed by a signer listed on creation) +│ ├── It canExecute should return false (when unlisted on creation, unappointed now) +│ └── It execute should revert (when unlisted on creation, unappointed now) +├── Given The proposal was approved by the address +│ ├── When calling getProposal approved +│ │ └── It should return the right values +│ ├── When calling canApprove and approve approved +│ │ ├── It canApprove should return false (when listed on creation, self appointed now) +│ │ ├── It approve should revert (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) +│ │ └── It approve should revert (when currently appointed by a signer listed on creation) +│ ├── When calling hasApproved approved +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute approved +│ ├── It canExecute should return false (when listed on creation, self appointed now) +│ ├── It execute should revert (when listed on creation, self appointed now) +│ ├── It canExecute should return false (when currently appointed by a signer listed on creation) +│ └── It execute should revert (when currently appointed by a signer listed on creation) +├── Given The proposal passed +│ ├── When calling getProposal passed +│ │ └── It should return the right values +│ ├── When calling canApprove and approve passed +│ │ ├── It canApprove should return false (when listed on creation, self appointed now) +│ │ ├── It approve should revert (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) +│ │ ├── It approve should revert (when listed on creation, appointing someone else now) +│ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) +│ │ ├── It approve should revert (when currently appointed by a signer listed on creation) +│ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) +│ │ └── It approve should revert (when unlisted on creation, unappointed now) +│ ├── When calling hasApproved passed +│ │ └── It hasApproved should return false until approved +│ ├── When calling canExecute and execute with modified data passed +│ │ └── It execute should revert, always +│ └── When calling canExecute and execute passed +│ ├── It canExecute should return true, always +│ ├── It execute should work, always +│ ├── It execute should emit an event, always +│ ├── It execute recreates the proposal on the destination plugin +│ ├── It The parameters of the recreated proposal match the hash of the executed one +│ └── It A ProposalCreated event is emitted on the destination plugin +├── Given The proposal is already executed +│ ├── When calling getProposal executed +│ │ └── It should return the right values +│ ├── When calling canApprove and approve executed +│ │ ├── It canApprove should return false (when listed on creation, self appointed now) +│ │ ├── It approve should revert (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) +│ │ ├── It approve should revert (when listed on creation, appointing someone else now) +│ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) +│ │ ├── It approve should revert (when currently appointed by a signer listed on creation) +│ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) +│ │ └── It approve should revert (when unlisted on creation, unappointed now) +│ ├── When calling hasApproved executed +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute executed +│ ├── It canExecute should return false (when listed on creation, self appointed now) +│ ├── It execute should revert (when listed on creation, self appointed now) +│ ├── It canExecute should return false (when listed on creation, appointing someone else now) +│ ├── It execute should revert (when listed on creation, appointing someone else now) +│ ├── It canExecute should return false (when currently appointed by a signer listed on creation) +│ ├── It execute should revert (when currently appointed by a signer listed on creation) +│ ├── It canExecute should return false (when unlisted on creation, unappointed now) +│ └── It execute should revert (when unlisted on creation, unappointed now) +└── Given The proposal expired + ├── When calling getProposal expired + │ └── It should return the right values + ├── When calling canApprove and approve expired + │ ├── It canApprove should return false (when listed on creation, self appointed now) + │ ├── It approve should revert (when listed on creation, self appointed now) + │ ├── It canApprove should return false (when listed on creation, appointing someone else now) + │ ├── It approve should revert (when listed on creation, appointing someone else now) + │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) + │ ├── It approve should revert (when currently appointed by a signer listed on creation) + │ ├── It canApprove should return false (when unlisted on creation, unappointed now) + │ └── It approve should revert (when unlisted on creation, unappointed now) + ├── When calling hasApproved expired + │ └── It hasApproved should return false until approved + └── When calling canExecute and execute expired + ├── It canExecute should return false (when listed on creation, self appointed now) + ├── It execute should revert (when listed on creation, self appointed now) + ├── It canExecute should return false (when listed on creation, appointing someone else now) + ├── It execute should revert (when listed on creation, appointing someone else now) + ├── It canExecute should return false (when currently appointed by a signer listed on creation) + ├── It execute should revert (when currently appointed by a signer listed on creation) + ├── It canExecute should return false (when unlisted on creation, unappointed now) + └── It execute should revert (when unlisted on creation, unappointed now) +``` + ``` MultisigTest ├── Given a newly deployed contract -│ └── Given calling initialize() +│ └── Given calling initialize │ ├── It should initialize the first time │ ├── It should refuse to initialize again │ ├── It should set the DAO address @@ -15,20 +223,29 @@ MultisigTest │ ├── It should set destinationProposalDuration │ ├── It should set proposalExpirationPeriod │ ├── It should emit MultisigSettingsUpdated -│ ├── When minApprovals is greater than signerList length +│ ├── When minApprovals is greater than signerList length on initialize │ │ ├── It should revert │ │ ├── It should revert (with onlyListed false) │ │ └── It should not revert otherwise -│ └── When minApprovals is zero -│ ├── It should revert -│ ├── It should revert (with onlyListed false) -│ └── It should not revert otherwise -├── When calling supportsInterface() +│ ├── When minApprovals is zero on initialize +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ └── When signerList is invalid on initialize +│ └── It should revert +├── When calling upgradeTo +│ ├── It should revert when called without the permission +│ └── It should work when called with the permission +├── When calling upgradeToAndCall +│ ├── It should revert when called without the permission +│ └── It should work when called with the permission +├── When calling supportsInterface │ ├── It does not support the empty interface │ ├── It supports IERC165Upgradeable │ ├── It supports IPlugin -│ └── It supports IProposal -├── When calling updateSettings() +│ ├── It supports IProposal +│ └── It supports IMultisig +├── When calling updateSettings │ ├── Given caller has no permission │ │ ├── It should revert │ │ └── It otherwise it should just work @@ -38,18 +255,21 @@ MultisigTest │ ├── It should set destinationProposalDuration │ ├── It should set proposalExpirationPeriod │ ├── It should emit MultisigSettingsUpdated -│ ├── When minApprovals is greater than signerList length +│ ├── When minApprovals is greater than signerList length on updateSettings │ │ ├── It should revert │ │ ├── It should revert (with onlyListed false) │ │ └── It should not revert otherwise -│ └── When minApprovals is zero -│ ├── It should revert -│ ├── It should revert (with onlyListed false) -│ └── It should not revert otherwise -├── When calling createProposal() +│ ├── When minApprovals is zero on updateSettings +│ │ ├── It should revert +│ │ ├── It should revert (with onlyListed false) +│ │ └── It should not revert otherwise +│ └── When signerList is invalid on updateSettings +│ └── It should revert +├── When calling createProposal │ ├── It increments the proposal counter │ ├── It creates and return unique proposal IDs -│ ├── It emits the ProposalCreatedEvent +│ ├── It emits the ProposalCreated event +│ ├── It creates a proposal with the given values │ ├── Given settings changed on the same block │ │ ├── It reverts │ │ └── It does not revert otherwise @@ -60,7 +280,9 @@ MultisigTest │ │ │ └── It reverts │ │ ├── Given creation caller is appointed by a former signer │ │ │ └── It reverts -│ │ ├── Given creation caller is listed +│ │ ├── Given creation caller is listed and self appointed +│ │ │ └── It creates the proposal +│ │ ├── Given creation caller is listed appointing someone else now │ │ │ └── It creates the proposal │ │ └── Given creation caller is appointed by a current signer │ │ └── It creates the proposal @@ -68,11 +290,151 @@ MultisigTest │ │ └── It creates and calls approval in one go │ └── Given approveProposal is false │ └── It only creates the proposal -└── Given The proposal is not created - └── When calling canApprove - ├── It should always return false (listed sender) - ├── It should always return false (appointed sender) - └── It should always return false (unlisted unappointed sender) +├── Given The proposal is not created +│ ├── When calling getProposal uncreated +│ │ └── It should return empty values +│ ├── When calling canApprove and approve uncreated +│ │ ├── It canApprove should return false (when currently listed and self appointed) +│ │ ├── It approve should revert (when currently listed and self appointed) +│ │ ├── It canApprove should return false (when currently listed, appointing someone else now) +│ │ ├── It approve should revert (when currently listed, appointing someone else now) +│ │ ├── It canApprove should return false (when appointed by a listed signer) +│ │ ├── It approve should revert (when appointed by a listed signer) +│ │ ├── It canApprove should return false (when currently unlisted and unappointed) +│ │ └── It approve should revert (when currently unlisted and unappointed) +│ ├── When calling hasApproved uncreated +│ │ └── It hasApproved should always return false +│ └── When calling canExecute and execute uncreated +│ ├── It canExecute should return false (when currently listed and self appointed) +│ ├── It execute should revert (when currently listed and self appointed) +│ ├── It canExecute should return false (when currently listed, appointing someone else now) +│ ├── It execute should revert (when currently listed, appointing someone else now) +│ ├── It canExecute should return false (when appointed by a listed signer) +│ ├── It execute should revert (when appointed by a listed signer) +│ ├── It canExecute should return false (when currently unlisted and unappointed) +│ └── It execute should revert (when currently unlisted and unappointed) +├── Given The proposal is open +│ ├── When calling getProposal open +│ │ └── It should return the right values +│ ├── When calling canApprove and approve open +│ │ ├── It canApprove should return true (when listed on creation, self appointed now) +│ │ ├── It approve should work (when listed on creation, self appointed now) +│ │ ├── It approve should emit an event (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) +│ │ ├── It approve should revert (when listed on creation, appointing someone else now) +│ │ ├── It canApprove should return true (when currently appointed by a signer listed on creation) +│ │ ├── It approve should work (when currently appointed by a signer listed on creation) +│ │ ├── It approve should emit an event (when currently appointed by a signer listed on creation) +│ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) +│ │ └── It approve should revert (when unlisted on creation, unappointed now) +│ ├── When calling approve with tryExecution and almost passed open +│ │ ├── It approve should also execute the proposal +│ │ ├── It approve should emit an Executed event +│ │ ├── It approve recreates the proposal on the destination plugin +│ │ ├── It The parameters of the recreated proposal match those of the approved one +│ │ └── It A ProposalCreated event is emitted on the destination plugin +│ ├── When calling hasApproved open +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute open +│ ├── It canExecute should return false (when listed on creation, self appointed now) +│ ├── It execute should revert (when listed on creation, self appointed now) +│ ├── It canExecute should return false (when listed on creation, appointing someone else now) +│ ├── It execute should revert (when listed on creation, appointing someone else now) +│ ├── It canExecute should return false (when currently appointed by a signer listed on creation) +│ ├── It execute should revert (when currently appointed by a signer listed on creation) +│ ├── It canExecute should return false (when unlisted on creation, unappointed now) +│ └── It execute should revert (when unlisted on creation, unappointed now) +├── Given The proposal was approved by the address +│ ├── When calling getProposal approved +│ │ └── It should return the right values +│ ├── When calling canApprove and approve approved +│ │ ├── It canApprove should return false (when listed on creation, self appointed now) +│ │ ├── It approve should revert (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) +│ │ └── It approve should revert (when currently appointed by a signer listed on creation) +│ ├── When calling hasApproved approved +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute approved +│ ├── It canExecute should return false (when listed on creation, self appointed now) +│ ├── It execute should revert (when listed on creation, self appointed now) +│ ├── It canExecute should return false (when currently appointed by a signer listed on creation) +│ └── It execute should revert (when currently appointed by a signer listed on creation) +├── Given The proposal passed +│ ├── When calling getProposal passed +│ │ └── It should return the right values +│ ├── When calling canApprove and approve passed +│ │ ├── It canApprove should return false (when listed on creation, self appointed now) +│ │ ├── It approve should revert (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) +│ │ ├── It approve should revert (when listed on creation, appointing someone else now) +│ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) +│ │ ├── It approve should revert (when currently appointed by a signer listed on creation) +│ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) +│ │ └── It approve should revert (when unlisted on creation, unappointed now) +│ ├── When calling hasApproved passed +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute passed +│ ├── It canExecute should return true (when listed on creation, self appointed now) +│ ├── It execute should work (when listed on creation, self appointed now) +│ ├── It execute should emit an event (when listed on creation, self appointed now) +│ ├── It canExecute should return true (when listed on creation, appointing someone else now) +│ ├── It execute should work (when listed on creation, appointing someone else now) +│ ├── It execute should emit an event (when listed on creation, appointing someone else now) +│ ├── It canExecute should return true (when currently appointed by a signer listed on creation) +│ ├── It execute should work (when currently appointed by a signer listed on creation) +│ ├── It execute should emit an event (when currently appointed by a signer listed on creation) +│ ├── It canExecute should return true (when unlisted on creation, unappointed now) +│ ├── It execute should work (when unlisted on creation, unappointed now) +│ ├── It execute should emit an event (when unlisted on creation, unappointed now) +│ ├── It execute recreates the proposal on the destination plugin +│ ├── It The parameters of the recreated proposal match those of the executed one +│ └── It A ProposalCreated event is emitted on the destination plugin +├── Given The proposal is already executed +│ ├── When calling getProposal executed +│ │ └── It should return the right values +│ ├── When calling canApprove and approve executed +│ │ ├── It canApprove should return false (when listed on creation, self appointed now) +│ │ ├── It approve should revert (when listed on creation, self appointed now) +│ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) +│ │ ├── It approve should revert (when listed on creation, appointing someone else now) +│ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) +│ │ ├── It approve should revert (when currently appointed by a signer listed on creation) +│ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) +│ │ └── It approve should revert (when unlisted on creation, unappointed now) +│ ├── When calling hasApproved executed +│ │ └── It hasApproved should return false until approved +│ └── When calling canExecute and execute executed +│ ├── It canExecute should return false (when listed on creation, self appointed now) +│ ├── It execute should revert (when listed on creation, self appointed now) +│ ├── It canExecute should return false (when listed on creation, appointing someone else now) +│ ├── It execute should revert (when listed on creation, appointing someone else now) +│ ├── It canExecute should return false (when currently appointed by a signer listed on creation) +│ ├── It execute should revert (when currently appointed by a signer listed on creation) +│ ├── It canExecute should return false (when unlisted on creation, unappointed now) +│ └── It execute should revert (when unlisted on creation, unappointed now) +└── Given The proposal expired + ├── When calling getProposal expired + │ └── It should return the right values + ├── When calling canApprove and approve expired + │ ├── It canApprove should return false (when listed on creation, self appointed now) + │ ├── It approve should revert (when listed on creation, self appointed now) + │ ├── It canApprove should return false (when listed on creation, appointing someone else now) + │ ├── It approve should revert (when listed on creation, appointing someone else now) + │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) + │ ├── It approve should revert (when currently appointed by a signer listed on creation) + │ ├── It canApprove should return false (when unlisted on creation, unappointed now) + │ └── It approve should revert (when unlisted on creation, unappointed now) + ├── When calling hasApproved expired + │ └── It hasApproved should return false until approved + └── When calling canExecute and execute expired + ├── It canExecute should return false (when listed on creation, self appointed now) + ├── It execute should revert (when listed on creation, self appointed now) + ├── It canExecute should return false (when listed on creation, appointing someone else now) + ├── It execute should revert (when listed on creation, appointing someone else now) + ├── It canExecute should return false (when currently appointed by a signer listed on creation) + ├── It execute should revert (when currently appointed by a signer listed on creation) + ├── It canExecute should return false (when unlisted on creation, unappointed now) + └── It execute should revert (when unlisted on creation, unappointed now) ``` ``` @@ -166,12 +528,11 @@ SignerListTest │ ├── It result does not contain unregistered addresses │ ├── It result does not contain unlisted addresses │ └── It result does not contain non appointed addresses -├── When calling supportsInterface -│ ├── It does not support the empty interface -│ ├── It supports IERC165Upgradeable -│ ├── It supports IPlugin -│ ├── It supports IProposal -│ └── It supports IMultisig -└── When calling +└── When calling supportsInterface + ├── It does not support the empty interface + ├── It supports IERC165Upgradeable + ├── It supports IPlugin + ├── It supports IProposal + └── It supports IMultisig ``` diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol new file mode 100644 index 0000000..7be8947 --- /dev/null +++ b/test/EmergencyMultisigTree.t.sol @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.17; + +import {Test} from "forge-std/Test.sol"; + +contract EmergencyMultisigTest is Test { + modifier givenANewlyDeployedContract() { + _; + } + + modifier givenCallingInitialize() { + _; + } + + function test_GivenCallingInitialize() external givenANewlyDeployedContract givenCallingInitialize { + // It should initialize the first time + // It should refuse to initialize again + // It should set the DAO address + // It should set the minApprovals + // It should set onlyListed + // It should set signerList + // It should set proposalExpirationPeriod + // It should emit MultisigSettingsUpdated + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnInitialize() + external + givenANewlyDeployedContract + givenCallingInitialize + { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsZeroOnInitialize() + external + givenANewlyDeployedContract + givenCallingInitialize + { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_RevertWhen_SignerListIsInvalidOnInitialize() + external + givenANewlyDeployedContract + givenCallingInitialize + { + // It should revert + vm.skip(true); + } + + function test_WhenCallingUpgradeTo() external { + // It should revert when called without the permission + // It should work when called with the permission + vm.skip(true); + } + + function test_WhenCallingUpgradeToAndCall() external { + // It should revert when called without the permission + // It should work when called with the permission + vm.skip(true); + } + + function test_WhenCallingSupportsInterface() external { + // It does not support the empty interface + // It supports IERC165Upgradeable + // It supports IPlugin + // It supports IProposal + // It supports IEmergencyMultisig + vm.skip(true); + } + + modifier whenCallingUpdateSettings() { + _; + } + + function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { + // It should set the minApprovals + // It should set onlyListed + // It should set signerList + // It should set proposalExpirationPeriod + // It should emit MultisigSettingsUpdated + vm.skip(true); + } + + function test_RevertGiven_CallerHasNoPermission() external whenCallingUpdateSettings { + // It should revert + // It otherwise it should just work + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnUpdateSettings() + external + whenCallingUpdateSettings + { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_RevertWhen_MinApprovalsIsZeroOnUpdateSettings() external whenCallingUpdateSettings { + // It should revert + // It should revert (with onlyListed false) + // It should not revert otherwise + vm.skip(true); + } + + function test_RevertWhen_SignerListIsInvalidOnUpdateSettings() external whenCallingUpdateSettings { + // It should revert + vm.skip(true); + } + + modifier whenCallingCreateProposal() { + _; + } + + function test_WhenCallingCreateProposal() external whenCallingCreateProposal { + // It increments the proposal counter + // It creates and return unique proposal IDs + // It emits the ProposalCreated event + // It creates a proposal with the given values + vm.skip(true); + } + + function test_GivenSettingsChangedOnTheSameBlock() external whenCallingCreateProposal { + // It reverts + // It does not revert otherwise + vm.skip(true); + } + + function test_GivenOnlyListedIsFalse() external whenCallingCreateProposal { + // It allows anyone to create + vm.skip(true); + } + + modifier givenOnlyListedIsTrue() { + _; + } + + function test_GivenCreationCallerIsNotListedOrAppointed() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It reverts + vm.skip(true); + } + + function test_GivenCreationCallerIsAppointedByAFormerSigner() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It reverts + vm.skip(true); + } + + function test_GivenCreationCallerIsListedAndSelfAppointed() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It creates the proposal + vm.skip(true); + } + + function test_GivenCreationCallerIsListedAppointingSomeoneElseNow() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It creates the proposal + vm.skip(true); + } + + function test_GivenCreationCallerIsAppointedByACurrentSigner() + external + whenCallingCreateProposal + givenOnlyListedIsTrue + { + // It creates the proposal + vm.skip(true); + } + + function test_GivenApproveProposalIsTrue() external whenCallingCreateProposal { + // It creates and calls approval in one go + vm.skip(true); + } + + function test_GivenApproveProposalIsFalse() external whenCallingCreateProposal { + // It only creates the proposal + vm.skip(true); + } + + function test_WhenCallingHashActions() external { + // It returns the right result + // It reacts to any of the values changing + vm.skip(true); + } + + modifier givenTheProposalIsNotCreated() { + _; + } + + function test_WhenCallingGetProposalUncreated() external givenTheProposalIsNotCreated { + // It should return empty values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveUncreated() external givenTheProposalIsNotCreated { + // It canApprove should return false (when currently listed and self appointed) + // It approve should revert (when currently listed and self appointed) + // It canApprove should return false (when currently listed, appointing someone else now) + // It approve should revert (when currently listed, appointing someone else now) + // It canApprove should return false (when appointed by a listed signer) + // It approve should revert (when appointed by a listed signer) + // It canApprove should return false (when currently unlisted and unappointed) + // It approve should revert (when currently unlisted and unappointed) + vm.skip(true); + } + + function test_WhenCallingHasApprovedUncreated() external givenTheProposalIsNotCreated { + // It hasApproved should always return false + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteUncreated() external givenTheProposalIsNotCreated { + // It canExecute should always return false + vm.skip(true); + } + + modifier givenTheProposalIsOpen() { + _; + } + + function test_WhenCallingGetProposalOpen() external givenTheProposalIsOpen { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveOpen() external givenTheProposalIsOpen { + // It canApprove should return true (when listed on creation, self appointed now) + // It approve should work (when listed on creation, self appointed now) + // It approve should emit an event (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return true (when currently appointed by a signer listed on creation) + // It approve should work (when currently appointed by a signer listed on creation) + // It approve should emit an event (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedOpen() external givenTheProposalIsOpen { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteOpen() external givenTheProposalIsOpen { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when listed on creation, appointing someone else now) + // It execute should revert (when listed on creation, appointing someone else now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + modifier givenTheProposalWasApprovedByTheAddress() { + _; + } + + function test_WhenCallingGetProposalApproved() external givenTheProposalWasApprovedByTheAddress { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveApproved() external givenTheProposalWasApprovedByTheAddress { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + vm.skip(true); + } + + function test_WhenCallingHasApprovedApproved() external givenTheProposalWasApprovedByTheAddress { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteApproved() external givenTheProposalWasApprovedByTheAddress { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + vm.skip(true); + } + + modifier givenTheProposalPassed() { + _; + } + + function test_WhenCallingGetProposalPassed() external givenTheProposalPassed { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApprovePassed() external givenTheProposalPassed { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedPassed() external givenTheProposalPassed { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteWithModifiedDataPassed() external givenTheProposalPassed { + // It execute should revert, always + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecutePassed() external givenTheProposalPassed { + // It canExecute should return true, always + // It execute should work, always + // It execute should emit an event, always + // It execute recreates the proposal on the destination plugin + // It The parameters of the recreated proposal match the hash of the executed one + // It A ProposalCreated event is emitted on the destination plugin + vm.skip(true); + } + + modifier givenTheProposalIsAlreadyExecuted() { + _; + } + + function test_WhenCallingGetProposalExecuted() external givenTheProposalIsAlreadyExecuted { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveExecuted() external givenTheProposalIsAlreadyExecuted { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedExecuted() external givenTheProposalIsAlreadyExecuted { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteExecuted() external givenTheProposalIsAlreadyExecuted { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when listed on creation, appointing someone else now) + // It execute should revert (when listed on creation, appointing someone else now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + modifier givenTheProposalExpired() { + _; + } + + function test_WhenCallingGetProposalExpired() external givenTheProposalExpired { + // It should return the right values + vm.skip(true); + } + + function test_WhenCallingCanApproveAndApproveExpired() external givenTheProposalExpired { + // It canApprove should return false (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) + // It approve should revert (when listed on creation, appointing someone else now) + // It canApprove should return false (when currently appointed by a signer listed on creation) + // It approve should revert (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } + + function test_WhenCallingHasApprovedExpired() external givenTheProposalExpired { + // It hasApproved should return false until approved + vm.skip(true); + } + + function test_WhenCallingCanExecuteAndExecuteExpired() external givenTheProposalExpired { + // It canExecute should return false (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, self appointed now) + // It canExecute should return false (when listed on creation, appointing someone else now) + // It execute should revert (when listed on creation, appointing someone else now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + // It execute should revert (when currently appointed by a signer listed on creation) + // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when unlisted on creation, unappointed now) + vm.skip(true); + } +} diff --git a/test/EmergencyMultisigTree.t.yaml b/test/EmergencyMultisigTree.t.yaml new file mode 100644 index 0000000..e9ec052 --- /dev/null +++ b/test/EmergencyMultisigTree.t.yaml @@ -0,0 +1,318 @@ +EmergencyMultisigTest: + # Plugin lifecycle + - given: a newly deployed contract + then: + - given: calling initialize + then: + - it: should initialize the first time + - it: should refuse to initialize again + - it: should set the DAO address + + # updateSettings below should have the same branches: + - it: should set the minApprovals + - it: should set onlyListed + - it: should set signerList + - it: should set proposalExpirationPeriod + - it: should emit MultisigSettingsUpdated + - when: minApprovals is greater than signerList length [on initialize] + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + - when: minApprovals is zero [on initialize] + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + - when: signerList is invalid [on initialize] + then: + - it: should revert + + - when: calling upgradeTo + then: + - it: should revert when called without the permission + - it: should work when called with the permission + + - when: calling upgradeToAndCall + then: + - it: should revert when called without the permission + - it: should work when called with the permission + + # General methods + + - when: calling supportsInterface + then: + - it: does not support the empty interface + - it: supports IERC165Upgradeable + - it: supports IPlugin + - it: supports IProposal + - it: supports IEmergencyMultisig + + - when: calling updateSettings + then: + - given: caller has no permission + then: + - it: should revert + - it: otherwise it should just work + + # initialize above should have the same branches: + - it: should set the minApprovals + - it: should set onlyListed + - it: should set signerList + - it: should set proposalExpirationPeriod + - it: should emit MultisigSettingsUpdated + - when: minApprovals is greater than signerList length [on updateSettings] + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + - when: minApprovals is zero [on updateSettings] + then: + - it: should revert + - it: should revert (with onlyListed false) + - it: should not revert otherwise + - when: signerList is invalid [on updateSettings] + then: + - it: should revert + + - when: calling createProposal + then: + - it: increments the proposal counter + - it: creates and return unique proposal IDs + - it: emits the ProposalCreated event + - it: creates a proposal with the given values + - given: settings changed on the same block + then: + - it: reverts + - it: does not revert otherwise + - given: onlyListed is false + then: + - it: allows anyone to create + - given: onlyListed is true + and: + - given: creation caller is not listed or appointed + then: + - it: reverts + - given: creation caller is appointed by a former signer + then: + - it: reverts + - given: creation caller is listed and self appointed + then: + - it: creates the proposal + - given: creation caller is listed, appointing someone else now + then: + - it: creates the proposal + - given: creation caller is appointed by a current signer + then: + - it: creates the proposal + - given: approveProposal is true + then: + - it: creates and calls approval in one go + - given: approveProposal is false + then: + - it: only creates the proposal + + - when: calling hashActions + then: + - it: returns the right result + - it: reacts to any of the values changing + + # Proposal lifecycle + + - given: The proposal is not created + then: + # Get proposal + - when: calling getProposal [uncreated] + then: + - it: should return empty values + # Approval + - when: calling canApprove and approve [uncreated] + then: + - it: canApprove should return false (when currently listed and self appointed) + - it: approve should revert (when currently listed and self appointed) + - it: canApprove should return false (when currently listed, appointing someone else now) + - it: approve should revert (when currently listed, appointing someone else now) + - it: canApprove should return false (when appointed by a listed signer) + - it: approve should revert (when appointed by a listed signer) + - it: canApprove should return false (when currently unlisted and unappointed) + - it: approve should revert (when currently unlisted and unappointed) + # Has approved + - when: calling hasApproved [uncreated] + then: + - it: hasApproved should always return false + # Execution + - when: calling canExecute and execute [uncreated] + then: + - it: canExecute should always return false + + - given: The proposal is open + then: + # Get proposal + - when: calling getProposal [open] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [open] + then: + - it: canApprove should return true (when listed on creation, self appointed now) + - it: approve should work (when listed on creation, self appointed now) + - it: approve should emit an event (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return true (when currently appointed by a signer listed on creation) + - it: approve should work (when currently appointed by a signer listed on creation) + - it: approve should emit an event (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + + # Has approved + - when: calling hasApproved [open] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [open] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + - it: canExecute should return false (when listed on creation, appointing someone else now) + - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + - it: canExecute should return false (when unlisted on creation, unappointed now) + - it: execute should revert (when unlisted on creation, unappointed now) + + - given: The proposal was approved by the address + then: + # Get proposal + - when: calling getProposal [approved] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [approved] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + # - it: canApprove should return false (when listed on creation, appointing someone else now) + # - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + # - it: canApprove should return false (when unlisted on creation, unappointed now) + # - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [approved] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [approved] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + # - it: canExecute should return false (when listed on creation, appointing someone else now) + # - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + # - it: canExecute should return false (when unlisted on creation, unappointed now) + # - it: execute should revert (when unlisted on creation, unappointed now) + + - given: The proposal passed + then: + # Get proposal + - when: calling getProposal [passed] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [passed] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [passed] + then: + - it: hasApproved should return false until approved + # Execution integrity + - when: calling canExecute and execute with modified data [passed] + then: + - it: execute should revert, always + # Execution + - when: calling canExecute and execute [passed] + then: + - it: canExecute should return true, always + - it: execute should work, always + - it: execute should emit an event, always + # Proposal forwarding on execution + - it: execute recreates the proposal on the destination plugin + - it: The parameters of the recreated proposal match the hash of the executed one + - it: A ProposalCreated event is emitted on the destination plugin + + - given: The proposal is already executed + then: + # Get proposal + - when: calling getProposal [executed] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [executed] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [executed] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [executed] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + - it: canExecute should return false (when listed on creation, appointing someone else now) + - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + - it: canExecute should return false (when unlisted on creation, unappointed now) + - it: execute should revert (when unlisted on creation, unappointed now) + + - given: The proposal expired + then: + # Get proposal + - when: calling getProposal [expired] + then: + - it: should return the right values + # Approval + - when: calling canApprove and approve [expired] + then: + - it: canApprove should return false (when listed on creation, self appointed now) + - it: approve should revert (when listed on creation, self appointed now) + - it: canApprove should return false (when listed on creation, appointing someone else now) + - it: approve should revert (when listed on creation, appointing someone else now) + - it: canApprove should return false (when currently appointed by a signer listed on creation) + - it: approve should revert (when currently appointed by a signer listed on creation) + - it: canApprove should return false (when unlisted on creation, unappointed now) + - it: approve should revert (when unlisted on creation, unappointed now) + # Has approved + - when: calling hasApproved [expired] + then: + - it: hasApproved should return false until approved + # Execution + - when: calling canExecute and execute [expired] + then: + - it: canExecute should return false (when listed on creation, self appointed now) + - it: execute should revert (when listed on creation, self appointed now) + - it: canExecute should return false (when listed on creation, appointing someone else now) + - it: execute should revert (when listed on creation, appointing someone else now) + - it: canExecute should return false (when currently appointed by a signer listed on creation) + - it: execute should revert (when currently appointed by a signer listed on creation) + - it: canExecute should return false (when unlisted on creation, unappointed now) + - it: execute should revert (when unlisted on creation, unappointed now) + diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 6c49915..10a5f3f 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -25,7 +25,7 @@ contract MultisigTest is Test { vm.skip(true); } - function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthInitialize() + function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnInitialize() external givenANewlyDeployedContract givenCallingInitialize @@ -36,7 +36,7 @@ contract MultisigTest is Test { vm.skip(true); } - function test_RevertWhen_MinApprovalsIsZeroInitialize() + function test_RevertWhen_MinApprovalsIsZeroOnInitialize() external givenANewlyDeployedContract givenCallingInitialize @@ -47,6 +47,15 @@ contract MultisigTest is Test { vm.skip(true); } + function test_RevertWhen_SignerListIsInvalidOnInitialize() + external + givenANewlyDeployedContract + givenCallingInitialize + { + // It should revert + vm.skip(true); + } + function test_WhenCallingUpgradeTo() external { // It should revert when called without the permission // It should work when called with the permission @@ -59,6 +68,15 @@ contract MultisigTest is Test { vm.skip(true); } + function test_WhenCallingSupportsInterface() external { + // It does not support the empty interface + // It supports IERC165Upgradeable + // It supports IPlugin + // It supports IProposal + // It supports IMultisig + vm.skip(true); + } + modifier whenCallingUpdateSettings() { _; } @@ -79,7 +97,7 @@ contract MultisigTest is Test { vm.skip(true); } - function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthUpdateSettings() + function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnUpdateSettings() external whenCallingUpdateSettings { @@ -89,18 +107,15 @@ contract MultisigTest is Test { vm.skip(true); } - function test_RevertWhen_MinApprovalsIsZeroUpdateSettings() external whenCallingUpdateSettings { + function test_RevertWhen_MinApprovalsIsZeroOnUpdateSettings() external whenCallingUpdateSettings { // It should revert // It should revert (with onlyListed false) // It should not revert otherwise vm.skip(true); } - function test_WhenCallingSupportsInterface() external { - // It does not support the empty interface - // It supports IERC165Upgradeable - // It supports IPlugin - // It supports IProposal + function test_RevertWhen_SignerListIsInvalidOnUpdateSettings() external whenCallingUpdateSettings { + // It should revert vm.skip(true); } @@ -111,7 +126,7 @@ contract MultisigTest is Test { function test_WhenCallingCreateProposal() external whenCallingCreateProposal { // It increments the proposal counter // It creates and return unique proposal IDs - // It emits the ProposalCreatedEvent + // It emits the ProposalCreated event // It creates a proposal with the given values vm.skip(true); } diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml index 47d22a9..bd9c5e7 100644 --- a/test/MultisigTree.t.yaml +++ b/test/MultisigTree.t.yaml @@ -15,16 +15,19 @@ MultisigTest: - it: should set destinationProposalDuration - it: should set proposalExpirationPeriod - it: should emit MultisigSettingsUpdated - - when: minApprovals is greater than signerList length [initialize] + - when: minApprovals is greater than signerList length [on initialize] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - when: minApprovals is zero [initialize] + - when: minApprovals is zero [on initialize] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise + - when: signerList is invalid [on initialize] + then: + - it: should revert - when: calling upgradeTo then: @@ -36,6 +39,16 @@ MultisigTest: - it: should revert when called without the permission - it: should work when called with the permission + # General methods + + - when: calling supportsInterface + then: + - it: does not support the empty interface + - it: supports IERC165Upgradeable + - it: supports IPlugin + - it: supports IProposal + - it: supports IMultisig + - when: calling updateSettings then: - given: caller has no permission @@ -50,31 +63,25 @@ MultisigTest: - it: should set destinationProposalDuration - it: should set proposalExpirationPeriod - it: should emit MultisigSettingsUpdated - - when: minApprovals is greater than signerList length [updateSettings] + - when: minApprovals is greater than signerList length [on updateSettings] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - when: minApprovals is zero [updateSettings] + - when: minApprovals is zero [on updateSettings] then: - it: should revert - it: should revert (with onlyListed false) - it: should not revert otherwise - - # General methods - - - when: calling supportsInterface - then: - - it: does not support the empty interface - - it: supports IERC165Upgradeable - - it: supports IPlugin - - it: supports IProposal + - when: signerList is invalid [on updateSettings] + then: + - it: should revert - when: calling createProposal then: - it: increments the proposal counter - it: creates and return unique proposal IDs - - it: emits the ProposalCreatedEvent + - it: emits the ProposalCreated event - it: creates a proposal with the given values - given: settings changed on the same block then: From 5f0646798b4389d74b306249eb8c0e0498bf76af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 25 Oct 2024 15:29:22 +0400 Subject: [PATCH 17/45] Test definitions ready --- TEST_TREE.md | 114 +++++++++++++++--------------- test/EmergencyMultisigTree.t.sol | 15 +++- test/EmergencyMultisigTree.t.yaml | 13 +++- test/MultisigTree.t.sol | 21 +++--- test/MultisigTree.t.yaml | 19 ++--- test/SignerListTree.t.sol | 85 +++++++++++----------- test/SignerListTree.t.yaml | 58 ++++++++------- 7 files changed, 173 insertions(+), 152 deletions(-) diff --git a/TEST_TREE.md b/TEST_TREE.md index 1fa8bf2..a43a529 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -82,7 +82,8 @@ EmergencyMultisigTest │ └── It only creates the proposal ├── When calling hashActions │ ├── It returns the right result -│ └── It reacts to any of the values changing +│ ├── It reacts to any of the values changing +│ └── It same input produces the same output ├── Given The proposal is not created │ ├── When calling getProposal uncreated │ │ └── It should return empty values @@ -154,14 +155,19 @@ EmergencyMultisigTest │ ├── When calling hasApproved passed │ │ └── It hasApproved should return false until approved │ ├── When calling canExecute and execute with modified data passed -│ │ └── It execute should revert, always -│ └── When calling canExecute and execute passed -│ ├── It canExecute should return true, always -│ ├── It execute should work, always -│ ├── It execute should emit an event, always -│ ├── It execute recreates the proposal on the destination plugin -│ ├── It The parameters of the recreated proposal match the hash of the executed one -│ └── It A ProposalCreated event is emitted on the destination plugin +│ │ ├── It execute should revert with modified metadata +│ │ ├── It execute should revert with modified actions +│ │ └── It execute should work with matching data +│ ├── When calling canExecute and execute passed +│ │ ├── It canExecute should return true, always +│ │ ├── It execute should work, when called by anyone with the actions +│ │ ├── It execute should emit an event, when called by anyone with the actions +│ │ ├── It execute recreates the proposal on the destination plugin +│ │ ├── It The parameters of the recreated proposal match the hash of the executed one +│ │ ├── It A ProposalCreated event is emitted on the destination plugin +│ │ └── It Execution is immediate on the destination plugin +│ └── Given TaikoL1 is incompatible +│ └── It executes successfully, regardless ├── Given The proposal is already executed │ ├── When calling getProposal executed │ │ └── It should return the right values @@ -373,22 +379,16 @@ MultisigTest │ │ └── It approve should revert (when unlisted on creation, unappointed now) │ ├── When calling hasApproved passed │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute passed -│ ├── It canExecute should return true (when listed on creation, self appointed now) -│ ├── It execute should work (when listed on creation, self appointed now) -│ ├── It execute should emit an event (when listed on creation, self appointed now) -│ ├── It canExecute should return true (when listed on creation, appointing someone else now) -│ ├── It execute should work (when listed on creation, appointing someone else now) -│ ├── It execute should emit an event (when listed on creation, appointing someone else now) -│ ├── It canExecute should return true (when currently appointed by a signer listed on creation) -│ ├── It execute should work (when currently appointed by a signer listed on creation) -│ ├── It execute should emit an event (when currently appointed by a signer listed on creation) -│ ├── It canExecute should return true (when unlisted on creation, unappointed now) -│ ├── It execute should work (when unlisted on creation, unappointed now) -│ ├── It execute should emit an event (when unlisted on creation, unappointed now) -│ ├── It execute recreates the proposal on the destination plugin -│ ├── It The parameters of the recreated proposal match those of the executed one -│ └── It A ProposalCreated event is emitted on the destination plugin +│ ├── When calling canExecute and execute passed +│ │ ├── It canExecute should return true, always +│ │ ├── It execute should work, when called by anyone +│ │ ├── It execute should emit an event, when called by anyone +│ │ ├── It execute recreates the proposal on the destination plugin +│ │ ├── It The parameters of the recreated proposal match those of the executed one +│ │ ├── It The proposal duration on the destination plugin matches the multisig settings +│ │ └── It A ProposalCreated event is emitted on the destination plugin +│ └── Given TaikoL1 is incompatible +│ └── It executes successfully, regardless ├── Given The proposal is already executed │ ├── When calling getProposal executed │ │ └── It should return the right values @@ -452,8 +452,24 @@ SignerListTest │ ├── It should emit the SignerListSettingsUpdated event │ └── Given passing more addresses than supported │ └── It should revert +├── When calling updateSettings +│ ├── When updateSettings without the permission +│ │ └── It should revert +│ ├── When encryptionRegistry is not compatible +│ │ └── It should revert +│ ├── When setting a minSignerListLength lower than the current list size +│ │ └── It should revert +│ ├── It set the new encryption registry +│ ├── It set the new minSignerListLength +│ └── It should emit a SignerListSettingsUpdated event +├── When calling supportsInterface +│ ├── It does not support the empty interface +│ ├── It supports IERC165Upgradeable +│ ├── It supports IPlugin +│ ├── It supports IProposal +│ └── It supports IMultisig ├── When calling addSigners -│ ├── When addSigners without the permission +│ ├── When adding without the permission │ │ └── It should revert │ ├── Given passing more addresses than allowed │ │ └── It should revert @@ -462,8 +478,10 @@ SignerListTest │ ├── It should append the new addresses to the list │ └── It should emit the SignersAddedEvent ├── When calling removeSigners -│ ├── When removeSigners without the permission +│ ├── When removing without the permission │ │ └── It should revert +│ ├── When removing an unlisted address +│ │ └── It should continue gracefully │ ├── Given removing too many addresses // The new list will be smaller than minSignerListLength │ │ └── It should revert │ ├── It should more the given addresses @@ -484,16 +502,6 @@ SignerListTest │ │ └── It returns false │ └── Given the member is enlisted now │ └── It returns false -├── When calling updateSettings -│ ├── When updateSettings without the permission -│ │ └── It should revert -│ ├── When encryptionRegistry is not compatible -│ │ └── It should revert -│ ├── When setting a minSignerListLength lower than the current list size -│ │ └── It should revert -│ ├── It set the new encryption registry -│ ├── It set the new minSignerListLength -│ └── It should emit a SignerListSettingsUpdated event ├── When calling resolveEncryptionAccountStatus │ ├── Given the caller is a listed signer │ │ ├── It ownerIsListed should be true @@ -515,24 +523,18 @@ SignerListTest │ └── Given the resolved owner is not listed │ ├── It should return a zero owner │ └── It should return a zero appointedWallet -├── When calling getEncryptionRecipients -│ ├── Given the encryption registry has no accounts -│ │ ├── It returns an empty list, even with signers -│ │ └── It returns an empty list, without signers -│ └── Given the encryption registry has accounts -│ ├── Given no overlap between registry and signerList // Some are on the encryption registry only and some are on the signerList only -│ │ └── It returns an empty list -│ └── Given some addresses are registered everywhere -│ ├── It returns a list containing the overlapping addresses -│ ├── It the result has the correct resolved addresses // appointed wallets are present, not the owner -│ ├── It result does not contain unregistered addresses -│ ├── It result does not contain unlisted addresses -│ └── It result does not contain non appointed addresses -└── When calling supportsInterface - ├── It does not support the empty interface - ├── It supports IERC165Upgradeable - ├── It supports IPlugin - ├── It supports IProposal - └── It supports IMultisig +└── When calling getEncryptionRecipients + ├── Given the encryption registry has no accounts + │ ├── It returns an empty list, even with signers + │ └── It returns an empty list, without signers + └── Given the encryption registry has accounts + ├── Given no overlap between registry and signerList // Some are on the encryption registry only and some are on the signerList only + │ └── It returns an empty list + └── Given some addresses are registered everywhere + ├── It returns a list containing the overlapping addresses + ├── It the result has the correct resolved addresses // appointed wallets are present, not the owner + ├── It result does not contain unregistered addresses + ├── It result does not contain unlisted addresses + └── It result does not contain non appointed addresses ``` diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 7be8947..927d232 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -202,6 +202,7 @@ contract EmergencyMultisigTest is Test { function test_WhenCallingHashActions() external { // It returns the right result // It reacts to any of the values changing + // It same input produces the same output vm.skip(true); } @@ -333,17 +334,25 @@ contract EmergencyMultisigTest is Test { } function test_WhenCallingCanExecuteAndExecuteWithModifiedDataPassed() external givenTheProposalPassed { - // It execute should revert, always + // It execute should revert with modified metadata + // It execute should revert with modified actions + // It execute should work with matching data vm.skip(true); } function test_WhenCallingCanExecuteAndExecutePassed() external givenTheProposalPassed { // It canExecute should return true, always - // It execute should work, always - // It execute should emit an event, always + // It execute should work, when called by anyone with the actions + // It execute should emit an event, when called by anyone with the actions // It execute recreates the proposal on the destination plugin // It The parameters of the recreated proposal match the hash of the executed one // It A ProposalCreated event is emitted on the destination plugin + // It Execution is immediate on the destination plugin + vm.skip(true); + } + + function test_GivenTaikoL1IsIncompatible() external givenTheProposalPassed { + // It executes successfully, regardless vm.skip(true); } diff --git a/test/EmergencyMultisigTree.t.yaml b/test/EmergencyMultisigTree.t.yaml index e9ec052..e12707d 100644 --- a/test/EmergencyMultisigTree.t.yaml +++ b/test/EmergencyMultisigTree.t.yaml @@ -116,6 +116,7 @@ EmergencyMultisigTest: then: - it: returns the right result - it: reacts to any of the values changing + - it: same input produces the same output # Proposal lifecycle @@ -238,17 +239,23 @@ EmergencyMultisigTest: # Execution integrity - when: calling canExecute and execute with modified data [passed] then: - - it: execute should revert, always + - it: execute should revert with modified metadata + - it: execute should revert with modified actions + - it: execute should work with matching data # Execution - when: calling canExecute and execute [passed] then: - it: canExecute should return true, always - - it: execute should work, always - - it: execute should emit an event, always + - it: execute should work, when called by anyone with the actions + - it: execute should emit an event, when called by anyone with the actions # Proposal forwarding on execution - it: execute recreates the proposal on the destination plugin - it: The parameters of the recreated proposal match the hash of the executed one - it: A ProposalCreated event is emitted on the destination plugin + - it: Execution is immediate on the destination plugin + - given: TaikoL1 is incompatible + then: + - it: executes successfully, regardless - given: The proposal is already executed then: diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 10a5f3f..ed9b8af 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -345,24 +345,21 @@ contract MultisigTest is Test { } function test_WhenCallingCanExecuteAndExecutePassed() external givenTheProposalPassed { - // It canExecute should return true (when listed on creation, self appointed now) - // It execute should work (when listed on creation, self appointed now) - // It execute should emit an event (when listed on creation, self appointed now) - // It canExecute should return true (when listed on creation, appointing someone else now) - // It execute should work (when listed on creation, appointing someone else now) - // It execute should emit an event (when listed on creation, appointing someone else now) - // It canExecute should return true (when currently appointed by a signer listed on creation) - // It execute should work (when currently appointed by a signer listed on creation) - // It execute should emit an event (when currently appointed by a signer listed on creation) - // It canExecute should return true (when unlisted on creation, unappointed now) - // It execute should work (when unlisted on creation, unappointed now) - // It execute should emit an event (when unlisted on creation, unappointed now) + // It canExecute should return true, always + // It execute should work, when called by anyone + // It execute should emit an event, when called by anyone // It execute recreates the proposal on the destination plugin // It The parameters of the recreated proposal match those of the executed one + // It The proposal duration on the destination plugin matches the multisig settings // It A ProposalCreated event is emitted on the destination plugin vm.skip(true); } + function test_GivenTaikoL1IsIncompatible() external givenTheProposalPassed { + // It executes successfully, regardless + vm.skip(true); + } + modifier givenTheProposalIsAlreadyExecuted() { _; } diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml index bd9c5e7..cd26d5f 100644 --- a/test/MultisigTree.t.yaml +++ b/test/MultisigTree.t.yaml @@ -251,22 +251,17 @@ MultisigTest: # Execution - when: calling canExecute and execute [passed] then: - - it: canExecute should return true (when listed on creation, self appointed now) - - it: execute should work (when listed on creation, self appointed now) - - it: execute should emit an event (when listed on creation, self appointed now) - - it: canExecute should return true (when listed on creation, appointing someone else now) - - it: execute should work (when listed on creation, appointing someone else now) - - it: execute should emit an event (when listed on creation, appointing someone else now) - - it: canExecute should return true (when currently appointed by a signer listed on creation) - - it: execute should work (when currently appointed by a signer listed on creation) - - it: execute should emit an event (when currently appointed by a signer listed on creation) - - it: canExecute should return true (when unlisted on creation, unappointed now) - - it: execute should work (when unlisted on creation, unappointed now) - - it: execute should emit an event (when unlisted on creation, unappointed now) + - it: canExecute should return true, always + - it: execute should work, when called by anyone + - it: execute should emit an event, when called by anyone # Proposal forwarding on execution - it: execute recreates the proposal on the destination plugin - it: The parameters of the recreated proposal match those of the executed one + - it: The proposal duration on the destination plugin matches the multisig settings - it: A ProposalCreated event is emitted on the destination plugin + - given: TaikoL1 is incompatible + then: + - it: executes successfully, regardless - given: The proposal is already executed then: diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index aef7651..9129eeb 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -36,6 +36,44 @@ contract SignerListTest is Test { vm.skip(true); } + modifier whenCallingUpdateSettings() { + _; + } + + function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { + // It set the new encryption registry + // It set the new minSignerListLength + // It should emit a SignerListSettingsUpdated event + vm.skip(true); + } + + function test_RevertWhen_UpdateSettingsWithoutThePermission() external whenCallingUpdateSettings { + // It should revert + vm.skip(true); + } + + function test_RevertWhen_EncryptionRegistryIsNotCompatible() external whenCallingUpdateSettings { + // It should revert + vm.skip(true); + } + + function test_RevertWhen_SettingAMinSignerListLengthLowerThanTheCurrentListSize() + external + whenCallingUpdateSettings + { + // It should revert + vm.skip(true); + } + + function test_WhenCallingSupportsInterface() external { + // It does not support the empty interface + // It supports IERC165Upgradeable + // It supports IPlugin + // It supports IProposal + // It supports IMultisig + vm.skip(true); + } + modifier whenCallingAddSigners() { _; } @@ -46,7 +84,7 @@ contract SignerListTest is Test { vm.skip(true); } - function test_RevertWhen_AddSignersWithoutThePermission() external whenCallingAddSigners { + function test_RevertWhen_AddingWithoutThePermission() external whenCallingAddSigners { // It should revert vm.skip(true); } @@ -71,11 +109,16 @@ contract SignerListTest is Test { vm.skip(true); } - function test_RevertWhen_RemoveSignersWithoutThePermission() external whenCallingRemoveSigners { + function test_RevertWhen_RemovingWithoutThePermission() external whenCallingRemoveSigners { // It should revert vm.skip(true); } + function test_WhenRemovingAnUnlistedAddress() external whenCallingRemoveSigners { + // It should continue gracefully + vm.skip(true); + } + function test_RevertGiven_RemovingTooManyAddresses() external whenCallingRemoveSigners { // It should revert vm.skip(true); @@ -127,35 +170,6 @@ contract SignerListTest is Test { vm.skip(true); } - modifier whenCallingUpdateSettings() { - _; - } - - function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { - // It set the new encryption registry - // It set the new minSignerListLength - // It should emit a SignerListSettingsUpdated event - vm.skip(true); - } - - function test_RevertWhen_UpdateSettingsWithoutThePermission() external whenCallingUpdateSettings { - // It should revert - vm.skip(true); - } - - function test_RevertWhen_EncryptionRegistryIsNotCompatible() external whenCallingUpdateSettings { - // It should revert - vm.skip(true); - } - - function test_RevertWhen_SettingAMinSignerListLengthLowerThanTheCurrentListSize() - external - whenCallingUpdateSettings - { - // It should revert - vm.skip(true); - } - modifier whenCallingResolveEncryptionAccountStatus() { _; } @@ -247,13 +261,4 @@ contract SignerListTest is Test { // It result does not contain non appointed addresses vm.skip(true); } - - function test_WhenCallingSupportsInterface() external { - // It does not support the empty interface - // It supports IERC165Upgradeable - // It supports IPlugin - // It supports IProposal - // It supports IMultisig - vm.skip(true); - } } diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index b42f35f..eca5f54 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -1,4 +1,5 @@ SignerListTest: + # contract lifecycle - when: deploying the contract then: - it: should initialize normally @@ -18,9 +19,33 @@ SignerListTest: then: - it: should revert + - when: calling updateSettings + and: + - when: updateSettings without the permission + then: + - it: should revert + - when: encryptionRegistry is not compatible + then: + - it: should revert + - when: setting a minSignerListLength lower than the current list size + then: + - it: should revert + - it: set the new encryption registry + - it: set the new minSignerListLength + - it: should emit a SignerListSettingsUpdated event + + - when: calling supportsInterface + then: + - it: does not support the empty interface + - it: supports IERC165Upgradeable + - it: supports IPlugin + - it: supports IProposal + - it: supports IMultisig + + # List lifecycle - when: calling addSigners and: - - when: addSigners without the permission + - when: adding without the permission then: - it: should revert - given: passing more addresses than allowed @@ -34,9 +59,12 @@ SignerListTest: - when: calling removeSigners and: - - when: removeSigners without the permission + - when: removing without the permission then: - it: should revert + - when: removing an unlisted address + then: + - it: should continue gracefully - given: removing too many addresses comment: The new list will be smaller than minSignerListLength then: @@ -44,6 +72,7 @@ SignerListTest: - it: should more the given addresses - it: should emit the SignersRemovedEvent + # Getters - when: calling isListed and: - given: the member is listed @@ -72,21 +101,6 @@ SignerListTest: then: - it: returns false - - when: calling updateSettings - and: - - when: updateSettings without the permission - then: - - it: should revert - - when: encryptionRegistry is not compatible - then: - - it: should revert - - when: setting a minSignerListLength lower than the current list size - then: - - it: should revert - - it: set the new encryption registry - - it: set the new minSignerListLength - - it: should emit a SignerListSettingsUpdated event - - when: calling resolveEncryptionAccountStatus and: - given: the caller is a listed signer @@ -142,12 +156,4 @@ SignerListTest: - it: result does not contain unlisted addresses - it: result does not contain non appointed addresses - - when: calling supportsInterface - then: - - it: does not support the empty interface - - it: supports IERC165Upgradeable - - it: supports IPlugin - - it: supports IProposal - - it: supports IMultisig - - - when: calling + # - when: calling From ffc85eb4bd1fa9fc0dd84331451a31b078b8708d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 25 Oct 2024 16:52:59 +0400 Subject: [PATCH 18/45] Test definition ready --- TEST_TREE.md | 32 ++++++++----- test/SignerList.t.sol | 98 -------------------------------------- test/SignerListTree.t.sol | 51 +++++++++++++++----- test/SignerListTree.t.yaml | 39 +++++++++------ 4 files changed, 83 insertions(+), 137 deletions(-) diff --git a/TEST_TREE.md b/TEST_TREE.md index a43a529..7c4ef58 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -446,18 +446,25 @@ SignerListTest ├── Given a new instance │ └── Given calling initialize │ ├── It should set the DAO address -│ ├── It should set the addresses as signers -│ ├── It settings should match the given ones -│ ├── It should emit the SignersAdded event -│ ├── It should emit the SignerListSettingsUpdated event -│ └── Given passing more addresses than supported -│ └── It should revert +│ ├── Given passing more addresses than supported on initialize +│ │ └── It should revert +│ ├── Given duplicate addresses on initialize +│ │ └── It should revert +│ ├── It should append the new addresses to the list +│ ├── It should emit the SignersAddedEvent +│ ├── When encryptionRegistry is not compatible on initialize +│ │ └── It should revert +│ ├── When minSignerListLength is lower than the list size on initialize +│ │ └── It should revert +│ ├── It sets the new encryption registry +│ ├── It sets the new minSignerListLength +│ └── It should emit a SignerListSettingsUpdated event ├── When calling updateSettings │ ├── When updateSettings without the permission │ │ └── It should revert -│ ├── When encryptionRegistry is not compatible +│ ├── When encryptionRegistry is not compatible on updateSettings │ │ └── It should revert -│ ├── When setting a minSignerListLength lower than the current list size +│ ├── When minSignerListLength is lower than the list size on updateSettings │ │ └── It should revert │ ├── It set the new encryption registry │ ├── It set the new minSignerListLength @@ -465,15 +472,14 @@ SignerListTest ├── When calling supportsInterface │ ├── It does not support the empty interface │ ├── It supports IERC165Upgradeable -│ ├── It supports IPlugin -│ ├── It supports IProposal -│ └── It supports IMultisig +│ ├── It supports Addresslist +│ └── It supports ISignerList ├── When calling addSigners │ ├── When adding without the permission │ │ └── It should revert -│ ├── Given passing more addresses than allowed +│ ├── Given passing more addresses than supported on updateSettings │ │ └── It should revert -│ ├── Given duplicate addresses +│ ├── Given duplicate addresses on updateSettings │ │ └── It should revert │ ├── It should append the new addresses to the list │ └── It should emit the SignersAddedEvent diff --git a/test/SignerList.t.sol b/test/SignerList.t.sol index 450dfb6..9de1e4e 100644 --- a/test/SignerList.t.sol +++ b/test/SignerList.t.sol @@ -1103,101 +1103,3 @@ contract SignerListTestTemp is AragonTest { vm.startPrank(alice); } } - -/* -1. Constructor Tests - - Default Values: Ensure the constructor initializes settings correctly. - Invalid Encryption Registry: Verify that passing an invalid encryption registry address reverts. - -2. Settings Update Tests - - Update Settings: Test updating the settings with valid values. - No Change: Ensure no change is made if the new settings are the same as the current ones. - Invalid Encryption Registry: Verify that updating with an invalid encryption registry address reverts. - Min Signer List Length Out of Bounds: Ensure updating with a min signer list length greater than the current length reverts. - -3. Encryption Account Status Tests - - Listed Owner: Test resolveEncryptionAccountStatus when the sender is listed directly. - Appointed Owner: Test resolveEncryptionAccountStatus when the sender is appointed by another address. - Unlisted Sender: Test resolveEncryptionAccountStatus when the sender is not listed. - -4. Encryption Owner Tests - - Listed Owner: Test resolveEncryptionOwner when the sender is listed directly. - Appointed Owner: Test resolveEncryptionOwner when the sender is appointed by another address. - Unlisted Sender: Test resolveEncryptionOwner when the sender is not listed. - -5. Encryption Account Tests - - Listed Owner: Test resolveEncryptionAccount when the sender is listed directly. - Appointed Owner: Test resolveEncryptionAccount when the sender is appointed by another address. - Unlisted Sender: Test resolveEncryptionAccount when the sender is not listed. - -6. Get Encryption Recipients Tests - - All Listed Accounts: Test getEncryptionRecipients when all accounts are listed. - Mixed Listed and Unlisted Accounts: Test getEncryptionRecipients when some accounts are listed and others are unlisted. - No Listed Accounts: Test getEncryptionRecipients when no accounts are listed. - -7. Interface Support Tests - - ISignerList Interface: Ensure the contract supports the ISignerList interface. - Addresslist Interface: Ensure the contract supports the Addresslist interface. - Other Interfaces: Ensure the contract does not support other interfaces it should not. - -8. Edge Cases and Error Handling - - Zero Address: Test with zero addresses for both sender and encryption registry. - Max Int16 Value: Test with the maximum value of uint16 for min signer list length. - Min Int16 Value: Test with the minimum value of int16 for min signer list length. - -9. Gas Optimization - - Performance: Measure and optimize gas usage, especially in loops and conditional statements. - Memory Management: Ensure memory management is efficient to avoid out-of-gas errors. - -10. Reentrancy Tests - - Reentrant Calls: Test for reentrancy vulnerabilities by calling functions that modify state from within a callback or event handler. - -11. Access Control - - Role-Based Access: Ensure only authorized roles can update settings. - Unauthorized Access: Verify that unauthorized addresses cannot update settings. - -12. State Consistency - - Consistent State: Ensure the state remains consistent after each function call, especially in multi-step operations. - -13. Documentation and Comments - - Code Documentation: Ensure all functions have clear documentation explaining their purpose and parameters. - Comments: Ensure critical logic has comments for clarity. - -14. Unit Tests - - Isolated Tests: Write unit tests that isolate each function to ensure they work independently. - Edge Case Coverage: Cover edge cases in unit tests to ensure robustness. - -15. Integration Tests - - Contract Interactions: Test interactions between SignerList and other contracts it depends on, such as EncryptionRegistry. - Complex Scenarios: Simulate complex scenarios involving multiple contract calls and state changes. - -16. Security Audits - - Code Review: Have the code reviewed by security experts. - Formal Verification: Use formal verification tools to check for bugs and vulnerabilities. - - -EDGE CASES: -- Multisig: lower minApprovals than addressListLength() -- SignerList: removing members without lowering the minSize - -- Multisig's check settings values - - On initialize - - When updating - -*/ diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index 9129eeb..5a8b575 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -24,14 +24,42 @@ contract SignerListTest is Test { function test_GivenCallingInitialize() external givenANewInstance givenCallingInitialize { // It should set the DAO address - // It should set the addresses as signers - // It settings should match the given ones - // It should emit the SignersAdded event - // It should emit the SignerListSettingsUpdated event + // It should append the new addresses to the list + // It should emit the SignersAddedEvent + // It sets the new encryption registry + // It sets the new minSignerListLength + // It should emit a SignerListSettingsUpdated event vm.skip(true); } - function test_RevertGiven_PassingMoreAddressesThanSupported() external givenANewInstance givenCallingInitialize { + function test_RevertGiven_PassingMoreAddressesThanSupportedOnInitialize() + external + givenANewInstance + givenCallingInitialize + { + // It should revert + vm.skip(true); + } + + function test_RevertGiven_DuplicateAddressesOnInitialize() external givenANewInstance givenCallingInitialize { + // It should revert + vm.skip(true); + } + + function test_RevertWhen_EncryptionRegistryIsNotCompatibleOnInitialize() + external + givenANewInstance + givenCallingInitialize + { + // It should revert + vm.skip(true); + } + + function test_RevertWhen_MinSignerListLengthIsLowerThanTheListSizeOnInitialize() + external + givenANewInstance + givenCallingInitialize + { // It should revert vm.skip(true); } @@ -52,12 +80,12 @@ contract SignerListTest is Test { vm.skip(true); } - function test_RevertWhen_EncryptionRegistryIsNotCompatible() external whenCallingUpdateSettings { + function test_RevertWhen_EncryptionRegistryIsNotCompatibleOnUpdateSettings() external whenCallingUpdateSettings { // It should revert vm.skip(true); } - function test_RevertWhen_SettingAMinSignerListLengthLowerThanTheCurrentListSize() + function test_RevertWhen_MinSignerListLengthIsLowerThanTheListSizeOnUpdateSettings() external whenCallingUpdateSettings { @@ -68,9 +96,8 @@ contract SignerListTest is Test { function test_WhenCallingSupportsInterface() external { // It does not support the empty interface // It supports IERC165Upgradeable - // It supports IPlugin - // It supports IProposal - // It supports IMultisig + // It supports Addresslist + // It supports ISignerList vm.skip(true); } @@ -89,12 +116,12 @@ contract SignerListTest is Test { vm.skip(true); } - function test_RevertGiven_PassingMoreAddressesThanAllowed() external whenCallingAddSigners { + function test_RevertGiven_PassingMoreAddressesThanSupportedOnUpdateSettings() external whenCallingAddSigners { // It should revert vm.skip(true); } - function test_RevertGiven_DuplicateAddresses() external whenCallingAddSigners { + function test_RevertGiven_DuplicateAddressesOnUpdateSettings() external whenCallingAddSigners { // It should revert vm.skip(true); } diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index eca5f54..298c4ef 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -11,23 +11,36 @@ SignerListTest: - given: calling initialize and: - it: should set the DAO address - - it: should set the addresses as signers - - it: settings should match the given ones - - it: should emit the SignersAdded event - - it: should emit the SignerListSettingsUpdated event - - given: passing more addresses than supported + # Same checks as addSigners below + - given: passing more addresses than supported [on initialize] then: - it: should revert + - given: duplicate addresses [on initialize] + then: + - it: should revert + - it: should append the new addresses to the list + - it: should emit the SignersAddedEvent + # Same checks as updateSettings below + - when: encryptionRegistry is not compatible [on initialize] + then: + - it: should revert + - when: minSignerListLength is lower than the list size [on initialize] + then: + - it: should revert + - it: sets the new encryption registry + - it: sets the new minSignerListLength + - it: should emit a SignerListSettingsUpdated event - when: calling updateSettings and: - when: updateSettings without the permission then: - it: should revert - - when: encryptionRegistry is not compatible + # Same checks as initialize does above + - when: encryptionRegistry is not compatible [on updateSettings] then: - it: should revert - - when: setting a minSignerListLength lower than the current list size + - when: minSignerListLength is lower than the list size [on updateSettings] then: - it: should revert - it: set the new encryption registry @@ -38,9 +51,8 @@ SignerListTest: then: - it: does not support the empty interface - it: supports IERC165Upgradeable - - it: supports IPlugin - - it: supports IProposal - - it: supports IMultisig + - it: supports Addresslist + - it: supports ISignerList # List lifecycle - when: calling addSigners @@ -48,10 +60,10 @@ SignerListTest: - when: adding without the permission then: - it: should revert - - given: passing more addresses than allowed + - given: passing more addresses than supported [on updateSettings] then: - it: should revert - - given: duplicate addresses + - given: duplicate addresses [on updateSettings] then: - it: should revert - it: should append the new addresses to the list @@ -101,6 +113,7 @@ SignerListTest: then: - it: returns false + # Encryption getters - when: calling resolveEncryptionAccountStatus and: - given: the caller is a listed signer @@ -155,5 +168,3 @@ SignerListTest: - it: result does not contain unregistered addresses - it: result does not contain unlisted addresses - it: result does not contain non appointed addresses - - # - when: calling From 3155f3faa6a176a60d63be6afe0162a82e934ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 25 Oct 2024 16:58:40 +0400 Subject: [PATCH 19/45] Clearer names --- TEST_TREE.md | 100 +++++++++++++++--------------- test/EmergencyMultisigTree.t.sol | 50 +++++++-------- test/EmergencyMultisigTree.t.yaml | 50 +++++++-------- test/MultisigTree.t.sol | 50 +++++++-------- test/MultisigTree.t.yaml | 50 +++++++-------- 5 files changed, 150 insertions(+), 150 deletions(-) diff --git a/TEST_TREE.md b/TEST_TREE.md index 7c4ef58..0bd36cc 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -85,9 +85,9 @@ EmergencyMultisigTest │ ├── It reacts to any of the values changing │ └── It same input produces the same output ├── Given The proposal is not created -│ ├── When calling getProposal uncreated +│ ├── When calling getProposal being uncreated │ │ └── It should return empty values -│ ├── When calling canApprove and approve uncreated +│ ├── When calling canApprove and approve being uncreated │ │ ├── It canApprove should return false (when currently listed and self appointed) │ │ ├── It approve should revert (when currently listed and self appointed) │ │ ├── It canApprove should return false (when currently listed, appointing someone else now) @@ -96,14 +96,14 @@ EmergencyMultisigTest │ │ ├── It approve should revert (when appointed by a listed signer) │ │ ├── It canApprove should return false (when currently unlisted and unappointed) │ │ └── It approve should revert (when currently unlisted and unappointed) -│ ├── When calling hasApproved uncreated +│ ├── When calling hasApproved being uncreated │ │ └── It hasApproved should always return false -│ └── When calling canExecute and execute uncreated +│ └── When calling canExecute and execute being uncreated │ └── It canExecute should always return false ├── Given The proposal is open -│ ├── When calling getProposal open +│ ├── When calling getProposal being open │ │ └── It should return the right values -│ ├── When calling canApprove and approve open +│ ├── When calling canApprove and approve being open │ │ ├── It canApprove should return true (when listed on creation, self appointed now) │ │ ├── It approve should work (when listed on creation, self appointed now) │ │ ├── It approve should emit an event (when listed on creation, self appointed now) @@ -114,9 +114,9 @@ EmergencyMultisigTest │ │ ├── It approve should emit an event (when currently appointed by a signer listed on creation) │ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ │ └── It approve should revert (when unlisted on creation, unappointed now) -│ ├── When calling hasApproved open +│ ├── When calling hasApproved being open │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute open +│ └── When calling canExecute and execute being open │ ├── It canExecute should return false (when listed on creation, self appointed now) │ ├── It execute should revert (when listed on creation, self appointed now) │ ├── It canExecute should return false (when listed on creation, appointing someone else now) @@ -126,24 +126,24 @@ EmergencyMultisigTest │ ├── It canExecute should return false (when unlisted on creation, unappointed now) │ └── It execute should revert (when unlisted on creation, unappointed now) ├── Given The proposal was approved by the address -│ ├── When calling getProposal approved +│ ├── When calling getProposal being approved │ │ └── It should return the right values -│ ├── When calling canApprove and approve approved +│ ├── When calling canApprove and approve being approved │ │ ├── It canApprove should return false (when listed on creation, self appointed now) │ │ ├── It approve should revert (when listed on creation, self appointed now) │ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) │ │ └── It approve should revert (when currently appointed by a signer listed on creation) -│ ├── When calling hasApproved approved +│ ├── When calling hasApproved being approved │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute approved +│ └── When calling canExecute and execute being approved │ ├── It canExecute should return false (when listed on creation, self appointed now) │ ├── It execute should revert (when listed on creation, self appointed now) │ ├── It canExecute should return false (when currently appointed by a signer listed on creation) │ └── It execute should revert (when currently appointed by a signer listed on creation) ├── Given The proposal passed -│ ├── When calling getProposal passed +│ ├── When calling getProposal being passed │ │ └── It should return the right values -│ ├── When calling canApprove and approve passed +│ ├── When calling canApprove and approve being passed │ │ ├── It canApprove should return false (when listed on creation, self appointed now) │ │ ├── It approve should revert (when listed on creation, self appointed now) │ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) @@ -152,13 +152,13 @@ EmergencyMultisigTest │ │ ├── It approve should revert (when currently appointed by a signer listed on creation) │ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ │ └── It approve should revert (when unlisted on creation, unappointed now) -│ ├── When calling hasApproved passed +│ ├── When calling hasApproved being passed │ │ └── It hasApproved should return false until approved -│ ├── When calling canExecute and execute with modified data passed +│ ├── When calling canExecute and execute with modified data being passed │ │ ├── It execute should revert with modified metadata │ │ ├── It execute should revert with modified actions │ │ └── It execute should work with matching data -│ ├── When calling canExecute and execute passed +│ ├── When calling canExecute and execute being passed │ │ ├── It canExecute should return true, always │ │ ├── It execute should work, when called by anyone with the actions │ │ ├── It execute should emit an event, when called by anyone with the actions @@ -169,9 +169,9 @@ EmergencyMultisigTest │ └── Given TaikoL1 is incompatible │ └── It executes successfully, regardless ├── Given The proposal is already executed -│ ├── When calling getProposal executed +│ ├── When calling getProposal being executed │ │ └── It should return the right values -│ ├── When calling canApprove and approve executed +│ ├── When calling canApprove and approve being executed │ │ ├── It canApprove should return false (when listed on creation, self appointed now) │ │ ├── It approve should revert (when listed on creation, self appointed now) │ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) @@ -180,9 +180,9 @@ EmergencyMultisigTest │ │ ├── It approve should revert (when currently appointed by a signer listed on creation) │ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ │ └── It approve should revert (when unlisted on creation, unappointed now) -│ ├── When calling hasApproved executed +│ ├── When calling hasApproved being executed │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute executed +│ └── When calling canExecute and execute being executed │ ├── It canExecute should return false (when listed on creation, self appointed now) │ ├── It execute should revert (when listed on creation, self appointed now) │ ├── It canExecute should return false (when listed on creation, appointing someone else now) @@ -192,9 +192,9 @@ EmergencyMultisigTest │ ├── It canExecute should return false (when unlisted on creation, unappointed now) │ └── It execute should revert (when unlisted on creation, unappointed now) └── Given The proposal expired - ├── When calling getProposal expired + ├── When calling getProposal being expired │ └── It should return the right values - ├── When calling canApprove and approve expired + ├── When calling canApprove and approve being expired │ ├── It canApprove should return false (when listed on creation, self appointed now) │ ├── It approve should revert (when listed on creation, self appointed now) │ ├── It canApprove should return false (when listed on creation, appointing someone else now) @@ -203,9 +203,9 @@ EmergencyMultisigTest │ ├── It approve should revert (when currently appointed by a signer listed on creation) │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ └── It approve should revert (when unlisted on creation, unappointed now) - ├── When calling hasApproved expired + ├── When calling hasApproved being expired │ └── It hasApproved should return false until approved - └── When calling canExecute and execute expired + └── When calling canExecute and execute being expired ├── It canExecute should return false (when listed on creation, self appointed now) ├── It execute should revert (when listed on creation, self appointed now) ├── It canExecute should return false (when listed on creation, appointing someone else now) @@ -297,9 +297,9 @@ MultisigTest │ └── Given approveProposal is false │ └── It only creates the proposal ├── Given The proposal is not created -│ ├── When calling getProposal uncreated +│ ├── When calling getProposal being uncreated │ │ └── It should return empty values -│ ├── When calling canApprove and approve uncreated +│ ├── When calling canApprove and approve being uncreated │ │ ├── It canApprove should return false (when currently listed and self appointed) │ │ ├── It approve should revert (when currently listed and self appointed) │ │ ├── It canApprove should return false (when currently listed, appointing someone else now) @@ -308,9 +308,9 @@ MultisigTest │ │ ├── It approve should revert (when appointed by a listed signer) │ │ ├── It canApprove should return false (when currently unlisted and unappointed) │ │ └── It approve should revert (when currently unlisted and unappointed) -│ ├── When calling hasApproved uncreated +│ ├── When calling hasApproved being uncreated │ │ └── It hasApproved should always return false -│ └── When calling canExecute and execute uncreated +│ └── When calling canExecute and execute being uncreated │ ├── It canExecute should return false (when currently listed and self appointed) │ ├── It execute should revert (when currently listed and self appointed) │ ├── It canExecute should return false (when currently listed, appointing someone else now) @@ -320,9 +320,9 @@ MultisigTest │ ├── It canExecute should return false (when currently unlisted and unappointed) │ └── It execute should revert (when currently unlisted and unappointed) ├── Given The proposal is open -│ ├── When calling getProposal open +│ ├── When calling getProposal being open │ │ └── It should return the right values -│ ├── When calling canApprove and approve open +│ ├── When calling canApprove and approve being open │ │ ├── It canApprove should return true (when listed on creation, self appointed now) │ │ ├── It approve should work (when listed on creation, self appointed now) │ │ ├── It approve should emit an event (when listed on creation, self appointed now) @@ -333,15 +333,15 @@ MultisigTest │ │ ├── It approve should emit an event (when currently appointed by a signer listed on creation) │ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ │ └── It approve should revert (when unlisted on creation, unappointed now) -│ ├── When calling approve with tryExecution and almost passed open +│ ├── When calling approve with tryExecution and almost passed being open │ │ ├── It approve should also execute the proposal │ │ ├── It approve should emit an Executed event │ │ ├── It approve recreates the proposal on the destination plugin │ │ ├── It The parameters of the recreated proposal match those of the approved one │ │ └── It A ProposalCreated event is emitted on the destination plugin -│ ├── When calling hasApproved open +│ ├── When calling hasApproved being open │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute open +│ └── When calling canExecute and execute being open │ ├── It canExecute should return false (when listed on creation, self appointed now) │ ├── It execute should revert (when listed on creation, self appointed now) │ ├── It canExecute should return false (when listed on creation, appointing someone else now) @@ -351,24 +351,24 @@ MultisigTest │ ├── It canExecute should return false (when unlisted on creation, unappointed now) │ └── It execute should revert (when unlisted on creation, unappointed now) ├── Given The proposal was approved by the address -│ ├── When calling getProposal approved +│ ├── When calling getProposal being approved │ │ └── It should return the right values -│ ├── When calling canApprove and approve approved +│ ├── When calling canApprove and approve being approved │ │ ├── It canApprove should return false (when listed on creation, self appointed now) │ │ ├── It approve should revert (when listed on creation, self appointed now) │ │ ├── It canApprove should return false (when currently appointed by a signer listed on creation) │ │ └── It approve should revert (when currently appointed by a signer listed on creation) -│ ├── When calling hasApproved approved +│ ├── When calling hasApproved being approved │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute approved +│ └── When calling canExecute and execute being approved │ ├── It canExecute should return false (when listed on creation, self appointed now) │ ├── It execute should revert (when listed on creation, self appointed now) │ ├── It canExecute should return false (when currently appointed by a signer listed on creation) │ └── It execute should revert (when currently appointed by a signer listed on creation) ├── Given The proposal passed -│ ├── When calling getProposal passed +│ ├── When calling getProposal being passed │ │ └── It should return the right values -│ ├── When calling canApprove and approve passed +│ ├── When calling canApprove and approve being passed │ │ ├── It canApprove should return false (when listed on creation, self appointed now) │ │ ├── It approve should revert (when listed on creation, self appointed now) │ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) @@ -377,9 +377,9 @@ MultisigTest │ │ ├── It approve should revert (when currently appointed by a signer listed on creation) │ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ │ └── It approve should revert (when unlisted on creation, unappointed now) -│ ├── When calling hasApproved passed +│ ├── When calling hasApproved being passed │ │ └── It hasApproved should return false until approved -│ ├── When calling canExecute and execute passed +│ ├── When calling canExecute and execute being passed │ │ ├── It canExecute should return true, always │ │ ├── It execute should work, when called by anyone │ │ ├── It execute should emit an event, when called by anyone @@ -390,9 +390,9 @@ MultisigTest │ └── Given TaikoL1 is incompatible │ └── It executes successfully, regardless ├── Given The proposal is already executed -│ ├── When calling getProposal executed +│ ├── When calling getProposal being executed │ │ └── It should return the right values -│ ├── When calling canApprove and approve executed +│ ├── When calling canApprove and approve being executed │ │ ├── It canApprove should return false (when listed on creation, self appointed now) │ │ ├── It approve should revert (when listed on creation, self appointed now) │ │ ├── It canApprove should return false (when listed on creation, appointing someone else now) @@ -401,9 +401,9 @@ MultisigTest │ │ ├── It approve should revert (when currently appointed by a signer listed on creation) │ │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ │ └── It approve should revert (when unlisted on creation, unappointed now) -│ ├── When calling hasApproved executed +│ ├── When calling hasApproved being executed │ │ └── It hasApproved should return false until approved -│ └── When calling canExecute and execute executed +│ └── When calling canExecute and execute being executed │ ├── It canExecute should return false (when listed on creation, self appointed now) │ ├── It execute should revert (when listed on creation, self appointed now) │ ├── It canExecute should return false (when listed on creation, appointing someone else now) @@ -413,9 +413,9 @@ MultisigTest │ ├── It canExecute should return false (when unlisted on creation, unappointed now) │ └── It execute should revert (when unlisted on creation, unappointed now) └── Given The proposal expired - ├── When calling getProposal expired + ├── When calling getProposal being expired │ └── It should return the right values - ├── When calling canApprove and approve expired + ├── When calling canApprove and approve being expired │ ├── It canApprove should return false (when listed on creation, self appointed now) │ ├── It approve should revert (when listed on creation, self appointed now) │ ├── It canApprove should return false (when listed on creation, appointing someone else now) @@ -424,9 +424,9 @@ MultisigTest │ ├── It approve should revert (when currently appointed by a signer listed on creation) │ ├── It canApprove should return false (when unlisted on creation, unappointed now) │ └── It approve should revert (when unlisted on creation, unappointed now) - ├── When calling hasApproved expired + ├── When calling hasApproved being expired │ └── It hasApproved should return false until approved - └── When calling canExecute and execute expired + └── When calling canExecute and execute being expired ├── It canExecute should return false (when listed on creation, self appointed now) ├── It execute should revert (when listed on creation, self appointed now) ├── It canExecute should return false (when listed on creation, appointing someone else now) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 927d232..2947522 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -210,12 +210,12 @@ contract EmergencyMultisigTest is Test { _; } - function test_WhenCallingGetProposalUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingGetProposalBeingUncreated() external givenTheProposalIsNotCreated { // It should return empty values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { // It canApprove should return false (when currently listed and self appointed) // It approve should revert (when currently listed and self appointed) // It canApprove should return false (when currently listed, appointing someone else now) @@ -227,12 +227,12 @@ contract EmergencyMultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingHasApprovedBeingUncreated() external givenTheProposalIsNotCreated { // It hasApproved should always return false vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanExecuteAndExecuteBeingUncreated() external givenTheProposalIsNotCreated { // It canExecute should always return false vm.skip(true); } @@ -241,12 +241,12 @@ contract EmergencyMultisigTest is Test { _; } - function test_WhenCallingGetProposalOpen() external givenTheProposalIsOpen { + function test_WhenCallingGetProposalBeingOpen() external givenTheProposalIsOpen { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { // It canApprove should return true (when listed on creation, self appointed now) // It approve should work (when listed on creation, self appointed now) // It approve should emit an event (when listed on creation, self appointed now) @@ -260,12 +260,12 @@ contract EmergencyMultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedOpen() external givenTheProposalIsOpen { + function test_WhenCallingHasApprovedBeingOpen() external givenTheProposalIsOpen { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanExecuteAndExecuteBeingOpen() external givenTheProposalIsOpen { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -281,12 +281,12 @@ contract EmergencyMultisigTest is Test { _; } - function test_WhenCallingGetProposalApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingGetProposalBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when currently appointed by a signer listed on creation) @@ -294,12 +294,12 @@ contract EmergencyMultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingHasApprovedBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanExecuteAndExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when currently appointed by a signer listed on creation) @@ -311,12 +311,12 @@ contract EmergencyMultisigTest is Test { _; } - function test_WhenCallingGetProposalPassed() external givenTheProposalPassed { + function test_WhenCallingGetProposalBeingPassed() external givenTheProposalPassed { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApprovePassed() external givenTheProposalPassed { + function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) @@ -328,19 +328,19 @@ contract EmergencyMultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedPassed() external givenTheProposalPassed { + function test_WhenCallingHasApprovedBeingPassed() external givenTheProposalPassed { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteWithModifiedDataPassed() external givenTheProposalPassed { + function test_WhenCallingCanExecuteAndExecuteWithModifiedDataBeingPassed() external givenTheProposalPassed { // It execute should revert with modified metadata // It execute should revert with modified actions // It execute should work with matching data vm.skip(true); } - function test_WhenCallingCanExecuteAndExecutePassed() external givenTheProposalPassed { + function test_WhenCallingCanExecuteAndExecuteBeingPassed() external givenTheProposalPassed { // It canExecute should return true, always // It execute should work, when called by anyone with the actions // It execute should emit an event, when called by anyone with the actions @@ -360,12 +360,12 @@ contract EmergencyMultisigTest is Test { _; } - function test_WhenCallingGetProposalExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingGetProposalBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) @@ -377,12 +377,12 @@ contract EmergencyMultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingHasApprovedBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanExecuteAndExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -398,12 +398,12 @@ contract EmergencyMultisigTest is Test { _; } - function test_WhenCallingGetProposalExpired() external givenTheProposalExpired { + function test_WhenCallingGetProposalBeingExpired() external givenTheProposalExpired { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveExpired() external givenTheProposalExpired { + function test_WhenCallingCanApproveAndApproveBeingExpired() external givenTheProposalExpired { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) @@ -415,12 +415,12 @@ contract EmergencyMultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedExpired() external givenTheProposalExpired { + function test_WhenCallingHasApprovedBeingExpired() external givenTheProposalExpired { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteExpired() external givenTheProposalExpired { + function test_WhenCallingCanExecuteAndExecuteBeingExpired() external givenTheProposalExpired { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) diff --git a/test/EmergencyMultisigTree.t.yaml b/test/EmergencyMultisigTree.t.yaml index e12707d..7807618 100644 --- a/test/EmergencyMultisigTree.t.yaml +++ b/test/EmergencyMultisigTree.t.yaml @@ -123,11 +123,11 @@ EmergencyMultisigTest: - given: The proposal is not created then: # Get proposal - - when: calling getProposal [uncreated] + - when: calling getProposal [being uncreated] then: - it: should return empty values # Approval - - when: calling canApprove and approve [uncreated] + - when: calling canApprove and approve [being uncreated] then: - it: canApprove should return false (when currently listed and self appointed) - it: approve should revert (when currently listed and self appointed) @@ -138,22 +138,22 @@ EmergencyMultisigTest: - it: canApprove should return false (when currently unlisted and unappointed) - it: approve should revert (when currently unlisted and unappointed) # Has approved - - when: calling hasApproved [uncreated] + - when: calling hasApproved [being uncreated] then: - it: hasApproved should always return false # Execution - - when: calling canExecute and execute [uncreated] + - when: calling canExecute and execute [being uncreated] then: - it: canExecute should always return false - given: The proposal is open then: # Get proposal - - when: calling getProposal [open] + - when: calling getProposal [being open] then: - it: should return the right values # Approval - - when: calling canApprove and approve [open] + - when: calling canApprove and approve [being open] then: - it: canApprove should return true (when listed on creation, self appointed now) - it: approve should work (when listed on creation, self appointed now) @@ -167,11 +167,11 @@ EmergencyMultisigTest: - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [open] + - when: calling hasApproved [being open] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [open] + - when: calling canExecute and execute [being open] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -185,11 +185,11 @@ EmergencyMultisigTest: - given: The proposal was approved by the address then: # Get proposal - - when: calling getProposal [approved] + - when: calling getProposal [being approved] then: - it: should return the right values # Approval - - when: calling canApprove and approve [approved] + - when: calling canApprove and approve [being approved] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -200,11 +200,11 @@ EmergencyMultisigTest: # - it: canApprove should return false (when unlisted on creation, unappointed now) # - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [approved] + - when: calling hasApproved [being approved] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [approved] + - when: calling canExecute and execute [being approved] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -218,11 +218,11 @@ EmergencyMultisigTest: - given: The proposal passed then: # Get proposal - - when: calling getProposal [passed] + - when: calling getProposal [being passed] then: - it: should return the right values # Approval - - when: calling canApprove and approve [passed] + - when: calling canApprove and approve [being passed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -233,17 +233,17 @@ EmergencyMultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [passed] + - when: calling hasApproved [being passed] then: - it: hasApproved should return false until approved # Execution integrity - - when: calling canExecute and execute with modified data [passed] + - when: calling canExecute and execute with modified data [being passed] then: - it: execute should revert with modified metadata - it: execute should revert with modified actions - it: execute should work with matching data # Execution - - when: calling canExecute and execute [passed] + - when: calling canExecute and execute [being passed] then: - it: canExecute should return true, always - it: execute should work, when called by anyone with the actions @@ -260,11 +260,11 @@ EmergencyMultisigTest: - given: The proposal is already executed then: # Get proposal - - when: calling getProposal [executed] + - when: calling getProposal [being executed] then: - it: should return the right values # Approval - - when: calling canApprove and approve [executed] + - when: calling canApprove and approve [being executed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -275,11 +275,11 @@ EmergencyMultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [executed] + - when: calling hasApproved [being executed] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [executed] + - when: calling canExecute and execute [being executed] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -293,11 +293,11 @@ EmergencyMultisigTest: - given: The proposal expired then: # Get proposal - - when: calling getProposal [expired] + - when: calling getProposal [being expired] then: - it: should return the right values # Approval - - when: calling canApprove and approve [expired] + - when: calling canApprove and approve [being expired] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -308,11 +308,11 @@ EmergencyMultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [expired] + - when: calling hasApproved [being expired] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [expired] + - when: calling canExecute and execute [being expired] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index ed9b8af..232f4f6 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -205,12 +205,12 @@ contract MultisigTest is Test { _; } - function test_WhenCallingGetProposalUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingGetProposalBeingUncreated() external givenTheProposalIsNotCreated { // It should return empty values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { // It canApprove should return false (when currently listed and self appointed) // It approve should revert (when currently listed and self appointed) // It canApprove should return false (when currently listed, appointing someone else now) @@ -222,12 +222,12 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingHasApprovedBeingUncreated() external givenTheProposalIsNotCreated { // It hasApproved should always return false vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanExecuteAndExecuteBeingUncreated() external givenTheProposalIsNotCreated { // It canExecute should return false (when currently listed and self appointed) // It execute should revert (when currently listed and self appointed) // It canExecute should return false (when currently listed, appointing someone else now) @@ -243,12 +243,12 @@ contract MultisigTest is Test { _; } - function test_WhenCallingGetProposalOpen() external givenTheProposalIsOpen { + function test_WhenCallingGetProposalBeingOpen() external givenTheProposalIsOpen { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { // It canApprove should return true (when listed on creation, self appointed now) // It approve should work (when listed on creation, self appointed now) // It approve should emit an event (when listed on creation, self appointed now) @@ -262,7 +262,7 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingApproveWithTryExecutionAndAlmostPassedOpen() external givenTheProposalIsOpen { + function test_WhenCallingApproveWithTryExecutionAndAlmostPassedBeingOpen() external givenTheProposalIsOpen { // It approve should also execute the proposal // It approve should emit an Executed event // It approve recreates the proposal on the destination plugin @@ -271,12 +271,12 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedOpen() external givenTheProposalIsOpen { + function test_WhenCallingHasApprovedBeingOpen() external givenTheProposalIsOpen { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanExecuteAndExecuteBeingOpen() external givenTheProposalIsOpen { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -292,12 +292,12 @@ contract MultisigTest is Test { _; } - function test_WhenCallingGetProposalApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingGetProposalBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when currently appointed by a signer listed on creation) @@ -305,12 +305,12 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingHasApprovedBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanExecuteAndExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when currently appointed by a signer listed on creation) @@ -322,12 +322,12 @@ contract MultisigTest is Test { _; } - function test_WhenCallingGetProposalPassed() external givenTheProposalPassed { + function test_WhenCallingGetProposalBeingPassed() external givenTheProposalPassed { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApprovePassed() external givenTheProposalPassed { + function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) @@ -339,12 +339,12 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedPassed() external givenTheProposalPassed { + function test_WhenCallingHasApprovedBeingPassed() external givenTheProposalPassed { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecutePassed() external givenTheProposalPassed { + function test_WhenCallingCanExecuteAndExecuteBeingPassed() external givenTheProposalPassed { // It canExecute should return true, always // It execute should work, when called by anyone // It execute should emit an event, when called by anyone @@ -364,12 +364,12 @@ contract MultisigTest is Test { _; } - function test_WhenCallingGetProposalExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingGetProposalBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) @@ -381,12 +381,12 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingHasApprovedBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanExecuteAndExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -402,12 +402,12 @@ contract MultisigTest is Test { _; } - function test_WhenCallingGetProposalExpired() external givenTheProposalExpired { + function test_WhenCallingGetProposalBeingExpired() external givenTheProposalExpired { // It should return the right values vm.skip(true); } - function test_WhenCallingCanApproveAndApproveExpired() external givenTheProposalExpired { + function test_WhenCallingCanApproveAndApproveBeingExpired() external givenTheProposalExpired { // It canApprove should return false (when listed on creation, self appointed now) // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) @@ -419,12 +419,12 @@ contract MultisigTest is Test { vm.skip(true); } - function test_WhenCallingHasApprovedExpired() external givenTheProposalExpired { + function test_WhenCallingHasApprovedBeingExpired() external givenTheProposalExpired { // It hasApproved should return false until approved vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteExpired() external givenTheProposalExpired { + function test_WhenCallingCanExecuteAndExecuteBeingExpired() external givenTheProposalExpired { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml index cd26d5f..85346ca 100644 --- a/test/MultisigTree.t.yaml +++ b/test/MultisigTree.t.yaml @@ -119,11 +119,11 @@ MultisigTest: - given: The proposal is not created then: # Get proposal - - when: calling getProposal [uncreated] + - when: calling getProposal [being uncreated] then: - it: should return empty values # Approval - - when: calling canApprove and approve [uncreated] + - when: calling canApprove and approve [being uncreated] then: - it: canApprove should return false (when currently listed and self appointed) - it: approve should revert (when currently listed and self appointed) @@ -134,11 +134,11 @@ MultisigTest: - it: canApprove should return false (when currently unlisted and unappointed) - it: approve should revert (when currently unlisted and unappointed) # Has approved - - when: calling hasApproved [uncreated] + - when: calling hasApproved [being uncreated] then: - it: hasApproved should always return false # Execution - - when: calling canExecute and execute [uncreated] + - when: calling canExecute and execute [being uncreated] then: - it: canExecute should return false (when currently listed and self appointed) - it: execute should revert (when currently listed and self appointed) @@ -152,11 +152,11 @@ MultisigTest: - given: The proposal is open then: # Get proposal - - when: calling getProposal [open] + - when: calling getProposal [being open] then: - it: should return the right values # Approval - - when: calling canApprove and approve [open] + - when: calling canApprove and approve [being open] then: - it: canApprove should return true (when listed on creation, self appointed now) - it: approve should work (when listed on creation, self appointed now) @@ -169,7 +169,7 @@ MultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Auto execution - - when: calling approve with tryExecution and almost passed [open] + - when: calling approve with tryExecution and almost passed [being open] then: - it: approve should also execute the proposal - it: approve should emit an Executed event @@ -179,11 +179,11 @@ MultisigTest: - it: A ProposalCreated event is emitted on the destination plugin # Has approved - - when: calling hasApproved [open] + - when: calling hasApproved [being open] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [open] + - when: calling canExecute and execute [being open] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -197,11 +197,11 @@ MultisigTest: - given: The proposal was approved by the address then: # Get proposal - - when: calling getProposal [approved] + - when: calling getProposal [being approved] then: - it: should return the right values # Approval - - when: calling canApprove and approve [approved] + - when: calling canApprove and approve [being approved] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -212,11 +212,11 @@ MultisigTest: # - it: canApprove should return false (when unlisted on creation, unappointed now) # - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [approved] + - when: calling hasApproved [being approved] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [approved] + - when: calling canExecute and execute [being approved] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -230,11 +230,11 @@ MultisigTest: - given: The proposal passed then: # Get proposal - - when: calling getProposal [passed] + - when: calling getProposal [being passed] then: - it: should return the right values # Approval - - when: calling canApprove and approve [passed] + - when: calling canApprove and approve [being passed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -245,11 +245,11 @@ MultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [passed] + - when: calling hasApproved [being passed] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [passed] + - when: calling canExecute and execute [being passed] then: - it: canExecute should return true, always - it: execute should work, when called by anyone @@ -266,11 +266,11 @@ MultisigTest: - given: The proposal is already executed then: # Get proposal - - when: calling getProposal [executed] + - when: calling getProposal [being executed] then: - it: should return the right values # Approval - - when: calling canApprove and approve [executed] + - when: calling canApprove and approve [being executed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -281,11 +281,11 @@ MultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [executed] + - when: calling hasApproved [being executed] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [executed] + - when: calling canExecute and execute [being executed] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -299,11 +299,11 @@ MultisigTest: - given: The proposal expired then: # Get proposal - - when: calling getProposal [expired] + - when: calling getProposal [being expired] then: - it: should return the right values # Approval - - when: calling canApprove and approve [expired] + - when: calling canApprove and approve [being expired] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -314,11 +314,11 @@ MultisigTest: - it: canApprove should return false (when unlisted on creation, unappointed now) - it: approve should revert (when unlisted on creation, unappointed now) # Has approved - - when: calling hasApproved [expired] + - when: calling hasApproved [being expired] then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [expired] + - when: calling canExecute and execute [being expired] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) From bb04d32361fd2da22477caf188b3ee1a9491dec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 5 Nov 2024 19:36:49 +0700 Subject: [PATCH 20/45] Minor edits --- .gitignore | 4 ++++ Makefile | 7 ++++--- script/multisig-members.json | 13 +------------ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index e74f8df..8e7fe72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Signer lists +script/multisig-*.json +!script/multisig-members.json + # Compiler files cache/ out/ diff --git a/Makefile b/Makefile index 9cd4f8f..9096670 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,18 @@ .DEFAULT_TARGET: help SOLIDITY_VERSION=0.8.17 +TEST_TREE_MARKDOWN=TEST_TREE.md SOURCE_FILES=$(wildcard test/*.t.yaml test/integration/*.t.yaml) TREE_FILES = $(SOURCE_FILES:.t.yaml=.tree) TARGET_TEST_FILES = $(SOURCE_FILES:.tree=.t.sol) MAKE_TEST_TREE=deno run ./test/script/make-test-tree.ts -TEST_TREE_MARKDOWN=TEST_TREE.md +MAKEFILE=Makefile .PHONY: help help: @echo "Available targets:" - @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ - | sed -n 's/^\(.*\): \(.*\)##\(.*\)/- make \1 \3/p' + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE) \ + | sed -n 's/^\(.*\): \(.*\)##\(.*\)/- make \1 \3/p' all: sync markdown ## Builds all tree files and updates the test tree markdown diff --git a/script/multisig-members.json b/script/multisig-members.json index 22ec7da..88c3329 100644 --- a/script/multisig-members.json +++ b/script/multisig-members.json @@ -1,14 +1,3 @@ { - "members": [ - "0x0123456789012345678901234567890123456789", - "0x1234567890123456789012345678901234567890", - "0x2345678901234567890123456789012345678901", - "0x3456789012345678901234567890123456789012", - "0x4567890123456789012345678901234567890123", - "0x5678901234567890123456789012345678901234", - "0x6789012345678901234567890123456789012345", - "0x7890123456789012345678901234567890123456", - "0x8901234567890123456789012345678901234567", - "0x9012345678901234567890123456789012345678" - ] + "members": [] } From 6a804fd794c10cd57362be12d557ca7ef3aee849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 6 Nov 2024 15:11:51 +0700 Subject: [PATCH 21/45] Signer List tests WIP --- README.md | 1 - TEST_TREE.md | 3 +- src/EncryptionRegistry.sol | 10 +- src/SignerList.sol | 11 +- test/SignerListTree.t.sol | 472 ++++++++++++++++++++++++++++++++++-- test/SignerListTree.t.yaml | 14 +- test/helpers/DaoBuilder.sol | 15 +- 7 files changed, 483 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 2e85300..14c8cdb 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,6 @@ Then use `make` to automatically sync the described branches into solidity test ```sh $ make Available targets: -Available targets: - make all Builds all tree files and updates the test tree markdown - make sync Scaffold or sync tree files into solidity tests - make check Checks if solidity files are out of sync diff --git a/TEST_TREE.md b/TEST_TREE.md index 0bd36cc..9be0ba0 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -451,6 +451,7 @@ SignerListTest │ ├── Given duplicate addresses on initialize │ │ └── It should revert │ ├── It should append the new addresses to the list +│ ├── It should return true on isListed │ ├── It should emit the SignersAddedEvent │ ├── When encryptionRegistry is not compatible on initialize │ │ └── It should revert @@ -482,6 +483,7 @@ SignerListTest │ ├── Given duplicate addresses on updateSettings │ │ └── It should revert │ ├── It should append the new addresses to the list +│ ├── It should return true on isListed │ └── It should emit the SignersAddedEvent ├── When calling removeSigners │ ├── When removing without the permission @@ -543,4 +545,3 @@ SignerListTest ├── It result does not contain unlisted addresses └── It result does not contain non appointed addresses ``` - diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index 4454ba8..abdf7fb 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -4,13 +4,14 @@ pragma solidity ^0.8.17; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IEncryptionRegistry} from "./interfaces/IEncryptionRegistry.sol"; /// @title EncryptionRegistry - Release 1, Build 1 /// @author Aragon Association - 2024 /// @notice A smart contract where accounts can register their libsodium public key for encryption purposes, as well as appointing an EOA -contract EncryptionRegistry is IEncryptionRegistry { +contract EncryptionRegistry is IEncryptionRegistry, ERC165 { struct AccountEntry { address appointedWallet; bytes32 publicKey; @@ -110,6 +111,13 @@ contract EncryptionRegistry is IEncryptionRegistry { return _member; } + /// @notice Checks if this or the parent contract supports an interface by its ID. + /// @param _interfaceId The ID of the interface. + /// @return Returns `true` if the interface is supported. + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return _interfaceId == type(IEncryptionRegistry).interfaceId || super.supportsInterface(_interfaceId); + } + // Internal helpers function _setPublicKey(address _account, bytes32 _publicKey) internal { diff --git a/src/SignerList.sol b/src/SignerList.sol index 228ec02..d909d3b 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -129,8 +129,11 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza function resolveEncryptionOwner(address _address) public view returns (address owner) { (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_address); - if (!ownerIsListed) return address(0); - else if (isAppointed) return settings.encryptionRegistry.appointedBy(_address); + if (!ownerIsListed) { + return address(0); + } else if (isAppointed) { + return settings.encryptionRegistry.appointedBy(_address); + } return _address; } @@ -212,9 +215,7 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza && _newSettings.minSignerListLength == settings.minSignerListLength ) { return; - } else if ( - !IERC165(address(_newSettings.encryptionRegistry)).supportsInterface(type(IEncryptionRegistry).interfaceId) - ) { + } else if (!_newSettings.encryptionRegistry.supportsInterface(type(IEncryptionRegistry).interfaceId)) { revert InvalidEncryptionRegitry(address(_newSettings.encryptionRegistry)); } diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index 5a8b575..ca53fdc 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -1,17 +1,78 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; -import {Test} from "forge-std/Test.sol"; +import {AragonTest} from "./base/AragonTest.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; +import { + SignerList, + ISignerList, + UPDATE_SIGNER_LIST_PERMISSION_ID, + UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID +} from "../src/SignerList.sol"; +import {DaoBuilder} from "./helpers/DaoBuilder.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {Multisig} from "../src/Multisig.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; +import {createProxyAndCall} from "../src/helpers/proxy.sol"; + +contract SignerListTest is AragonTest { + SignerList signerList; + EncryptionRegistry encryptionRegistry; + DaoBuilder builder; + DAO dao; + Multisig multisig; + address[] signers; + + address immutable SIGNER_LIST_BASE = address(new SignerList()); + + // Events/errors to be tested here (duplicate) + error SignerListLengthOutOfBounds(uint16 limit, uint256 actual); + error InvalidEncryptionRegitry(address givenAddress); + error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); + + event SignerListSettingsUpdated(EncryptionRegistry encryptionRegistry, uint16 minSignerListLength); + event SignersAdded(address[] signers); + event SignersRemoved(address[] signers); + + function setUp() public { + vm.startPrank(alice); + + builder = new DaoBuilder(); + (dao,, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) + .withMultisigMember(carol).withMultisigMember(david).build(); + + signers = new address[](4); + signers[0] = alice; + signers[1] = bob; + signers[2] = carol; + signers[3] = david; + } -contract SignerListTest is Test { function test_WhenDeployingTheContract() external { // It should initialize normally - vm.skip(true); + encryptionRegistry = EncryptionRegistry(address(0)); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); } function test_GivenADeployedContract() external { // It should refuse to initialize again - vm.skip(true); + encryptionRegistry = EncryptionRegistry(address(0)); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + + vm.expectRevert(bytes("Initializable: contract is already initialized")); + signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); } modifier givenANewInstance() { @@ -23,13 +84,119 @@ contract SignerListTest is Test { } function test_GivenCallingInitialize() external givenANewInstance givenCallingInitialize { + encryptionRegistry = EncryptionRegistry(address(0)); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + // It should set the DAO address + vm.assertEq(address(signerList.dao()), address(dao), "Incorrect DAO addres"); + + (EncryptionRegistry reg, uint16 minSignerListLength) = signerList.settings(); + // It should append the new addresses to the list - // It should emit the SignersAddedEvent + // It should return true on isListed + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + // It sets the new encryption registry + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + // It sets the new minSignerListLength + vm.assertEq(minSignerListLength, 0); + + // It should emit the SignersAdded event + + vm.expectEmit(); + emit SignersAdded({signers: signers}); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + // It should emit a SignerListSettingsUpdated event - vm.skip(true); + + vm.expectEmit(); + emit SignerListSettingsUpdated({encryptionRegistry: encryptionRegistry, minSignerListLength: 0}); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + + // It should set the right values in general + + // 2 + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + + (reg, minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(minSignerListLength, 0); + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + + // 3 + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 2))) + ) + ); + + (reg, minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(minSignerListLength, 2); + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + + // 4 + signers = new address[](2); + signers[0] = address(100); + signers[0] = address(200); + + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 1))) + ) + ); + + (reg, minSignerListLength) = signerList.settings(); + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(minSignerListLength, 1); + vm.assertEq(signerList.addresslistLength(), 2, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(bob), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(carol), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(david), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); } function test_RevertGiven_PassingMoreAddressesThanSupportedOnInitialize() @@ -38,12 +205,43 @@ contract SignerListTest is Test { givenCallingInitialize { // It should revert - vm.skip(true); + + // 1 + signers = new address[](type(uint16).max + 1); + + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + + // 2 + signers = new address[](type(uint16).max + 10); + + vm.expectRevert( + abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 10) + ); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); } function test_RevertGiven_DuplicateAddressesOnInitialize() external givenANewInstance givenCallingInitialize { // It should revert - vm.skip(true); + + // 1 + signers[2] = signers[1]; + vm.expectRevert(); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 2))) + ) + ); } function test_RevertWhen_EncryptionRegistryIsNotCompatibleOnInitialize() @@ -52,53 +250,197 @@ contract SignerListTest is Test { givenCallingInitialize { // It should revert - vm.skip(true); - } - function test_RevertWhen_MinSignerListLengthIsLowerThanTheListSizeOnInitialize() + // 1 + vm.expectRevert(InvalidEncryptionRegitry.selector); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(alice)), 2)) + ) + ) + ); + + // 2 + vm.expectRevert(InvalidEncryptionRegitry.selector); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(bob)), 3)) + ) + ) + ); + + // OK + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, + (dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2)) + ) + ) + ); + } + + function test_RevertWhen_MinSignerListLengthIsBiggerThanTheListSizeOnInitialize() external givenANewInstance givenCallingInitialize { // It should revert - vm.skip(true); + + // 1 + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 5)); + + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 5)) + ) + ) + ); + + // 2 + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 10)); + + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 10)) + ) + ) + ); + + // 3 + signers = new address[](5); + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 5, 15)); + + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 15)) + ) + ) + ); } modifier whenCallingUpdateSettings() { + // Initialize + encryptionRegistry = EncryptionRegistry(address(0)); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) + ) + ); + _; } function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { - // It set the new encryption registry - // It set the new minSignerListLength + encryptionRegistry = new EncryptionRegistry(Addresslist(address(0))); + + // 1 + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); + + (EncryptionRegistry reg, uint16 minSignerListLength) = signerList.settings(); + + // It sets the new encryption registry + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect encryptionRegistry"); + + // It sets the new minSignerListLength + vm.assertEq(minSignerListLength, 0); + // It should emit a SignerListSettingsUpdated event - vm.skip(true); + vm.expectEmit(); + emit SignerListSettingsUpdated({encryptionRegistry: encryptionRegistry, minSignerListLength: 0}); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); + + // 2 + encryptionRegistry = new EncryptionRegistry(Addresslist(address(0))); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 3)); + + (reg, minSignerListLength) = signerList.settings(); + + // It sets the new encryption registry + vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect encryptionRegistry"); + + // It sets the new minSignerListLength + vm.assertEq(minSignerListLength, 3); + + // It should emit a SignerListSettingsUpdated event + vm.expectEmit(); + emit SignerListSettingsUpdated({encryptionRegistry: encryptionRegistry, minSignerListLength: 4}); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 4)); } function test_RevertWhen_UpdateSettingsWithoutThePermission() external whenCallingUpdateSettings { // It should revert - vm.skip(true); + + encryptionRegistry = EncryptionRegistry(address(0)); + + vm.startPrank(bob); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(signerList), + alice, + UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID + ) + ); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); } function test_RevertWhen_EncryptionRegistryIsNotCompatibleOnUpdateSettings() external whenCallingUpdateSettings { // It should revert - vm.skip(true); + + vm.expectRevert(InvalidEncryptionRegitry.selector); + signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(alice)), 0)); } - function test_RevertWhen_MinSignerListLengthIsLowerThanTheListSizeOnUpdateSettings() + function test_RevertWhen_MinSignerListLengthIsBiggerThanTheListSizeOnUpdateSettings() external whenCallingUpdateSettings { // It should revert - vm.skip(true); + + // 1 + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 15)); + signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(0)), 15)); + + // 2 + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 20)); + signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(0)), 20)); + + // 3 + signers = new address[](5); + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 5, 50)); + signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(0)), 50)); } - function test_WhenCallingSupportsInterface() external { + function test_WhenCallingSupportsInterface() external view { // It does not support the empty interface + bool supported = multisig.supportsInterface(bytes4(0xffffffff)); + assertEq(supported, true, "Should not support the empty interface"); + // It supports IERC165Upgradeable + supported = multisig.supportsInterface(type(IERC165Upgradeable).interfaceId); + assertEq(supported, true, "Should support IERC165Upgradeable"); + // It supports Addresslist + supported = multisig.supportsInterface(type(Addresslist).interfaceId); + assertEq(supported, true, "Should support Addresslist"); + // It supports ISignerList - vm.skip(true); + supported = multisig.supportsInterface(type(ISignerList).interfaceId); + assertEq(supported, true, "Should support ISignerList"); } modifier whenCallingAddSigners() { @@ -107,23 +449,101 @@ contract SignerListTest is Test { function test_WhenCallingAddSigners() external whenCallingAddSigners { // It should append the new addresses to the list - // It should emit the SignersAddedEvent - vm.skip(true); + // It should return true on isListed + + // 0 + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + + // 1 + address[] memory newSigners = new address[](1); + newSigners[0] = address(100); + signerList.addSigners(newSigners); + + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + + // 2 + newSigners[0] = address(200); + signerList.addSigners(newSigners); + + vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); + + // It should emit the SignersAdded event + newSigners[0] = address(300); + vm.expectEmit(); + emit SignersAdded({signers: newSigners}); + signerList.addSigners(newSigners); } function test_RevertWhen_AddingWithoutThePermission() external whenCallingAddSigners { // It should revert - vm.skip(true); + + address[] memory newSigners = new address[](1); + newSigners[0] = address(100); + + vm.startPrank(bob); + + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, address(dao), address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID + ) + ); + signerList.addSigners(newSigners); } function test_RevertGiven_PassingMoreAddressesThanSupportedOnUpdateSettings() external whenCallingAddSigners { // It should revert - vm.skip(true); + + // 1 + address[] memory newSigners = new address[](type(uint8).max + 1); + vm.expectRevert( + abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 1) + ); + signerList.addSigners(newSigners); + + // 2 + newSigners = new address[](type(uint8).max + 10); + vm.expectRevert( + abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 10) + ); + signerList.addSigners(newSigners); } function test_RevertGiven_DuplicateAddressesOnUpdateSettings() external whenCallingAddSigners { // It should revert - vm.skip(true); + + // 1 + address[] memory newSigners = new address[](1); + newSigners[0] = alice; // Alice is a signer already + vm.expectRevert(); + signerList.addSigners(newSigners); + + // 2 + newSigners[0] = bob; // Bob is a signer already + vm.expectRevert(); + signerList.addSigners(newSigners); + + // OK + newSigners[0] = address(1234); + vm.expectRevert(); + signerList.addSigners(newSigners); } modifier whenCallingRemoveSigners() { diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index 298c4ef..14acfa1 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -19,12 +19,13 @@ SignerListTest: then: - it: should revert - it: should append the new addresses to the list - - it: should emit the SignersAddedEvent + - it: should return true on isListed + - it: should emit the SignersAdded event # Same checks as updateSettings below - when: encryptionRegistry is not compatible [on initialize] then: - it: should revert - - when: minSignerListLength is lower than the list size [on initialize] + - when: minSignerListLength is bigger than the list size [on initialize] then: - it: should revert - it: sets the new encryption registry @@ -40,11 +41,11 @@ SignerListTest: - when: encryptionRegistry is not compatible [on updateSettings] then: - it: should revert - - when: minSignerListLength is lower than the list size [on updateSettings] + - when: minSignerListLength is bigger than the list size [on updateSettings] then: - it: should revert - - it: set the new encryption registry - - it: set the new minSignerListLength + - it: sets the new encryption registry + - it: sets the new minSignerListLength - it: should emit a SignerListSettingsUpdated event - when: calling supportsInterface @@ -67,7 +68,8 @@ SignerListTest: then: - it: should revert - it: should append the new addresses to the list - - it: should emit the SignersAddedEvent + - it: should return true on isListed + - it: should emit the SignersAdded event - when: calling removeSigners and: diff --git a/test/helpers/DaoBuilder.sol b/test/helpers/DaoBuilder.sol index f1896e1..af55966 100644 --- a/test/helpers/DaoBuilder.sol +++ b/test/helpers/DaoBuilder.sol @@ -6,7 +6,7 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {Multisig} from "../../src/Multisig.sol"; import {EmergencyMultisig} from "../../src/EmergencyMultisig.sol"; import {OptimisticTokenVotingPlugin} from "../../src/OptimisticTokenVotingPlugin.sol"; -import {SignerList} from "../../src/SignerList.sol"; +import {SignerList, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID} from "../../src/SignerList.sol"; import {EncryptionRegistry} from "../../src/EncryptionRegistry.sol"; import {createProxyAndCall} from "../../src/helpers/proxy.sol"; import {RATIO_BASE} from "@aragon/osx/plugins/utils/Ratio.sol"; @@ -20,6 +20,7 @@ contract DaoBuilder is Test { address immutable MULTISIG_BASE = address(new Multisig()); address immutable EMERGENCY_MULTISIG_BASE = address(new EmergencyMultisig()); address immutable OPTIMISTIC_BASE = address(new OptimisticTokenVotingPlugin()); + address immutable SIGNER_LIST_BASE = address(new SignerList()); enum TaikoL1Status { Standard, @@ -234,10 +235,18 @@ contract DaoBuilder is Test { signers[0] = owner; } - signerList = new SignerList(); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + signerList = SignerList( + createProxyAndCall( + address(SIGNER_LIST_BASE), + abi.encodeCall( + SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)) + ) + ) + ); encryptionRegistry = new EncryptionRegistry(signerList); + dao.grant(address(signerList), address(this), UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); signerList.updateSettings(SignerList.Settings(encryptionRegistry, uint16(signers.length))); + dao.revoke(address(signerList), address(this), UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); } // Standard multisig From 6d5683164a917f29d3a4316b714a464c2ce08226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 6 Nov 2024 18:48:54 +0700 Subject: [PATCH 22/45] SignerList tree tests --- test/SignerListTree.t.sol | 518 +++++++++++++++++++++----------------- 1 file changed, 293 insertions(+), 225 deletions(-) diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index ca53fdc..48042c0 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -31,6 +31,7 @@ contract SignerListTest is AragonTest { error SignerListLengthOutOfBounds(uint16 limit, uint256 actual); error InvalidEncryptionRegitry(address givenAddress); error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); + error InvalidAddresslistUpdate(address member); event SignerListSettingsUpdated(EncryptionRegistry encryptionRegistry, uint16 minSignerListLength); event SignersAdded(address[] signers); @@ -52,27 +53,19 @@ contract SignerListTest is AragonTest { function test_WhenDeployingTheContract() external { // It should initialize normally - encryptionRegistry = EncryptionRegistry(address(0)); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); } function test_GivenADeployedContract() external { // It should refuse to initialize again - encryptionRegistry = EncryptionRegistry(address(0)); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); vm.expectRevert(bytes("Initializable: contract is already initialized")); - signerList.initialize(dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + signerList.initialize(dao, signers); } modifier givenANewInstance() { @@ -84,12 +77,8 @@ contract SignerListTest is AragonTest { } function test_GivenCallingInitialize() external givenANewInstance givenCallingInitialize { - encryptionRegistry = EncryptionRegistry(address(0)); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); // It should set the DAO address @@ -107,46 +96,28 @@ contract SignerListTest is AragonTest { vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); - // It sets the new encryption registry - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + // It the encryption registry should be empty + vm.assertEq(address(reg), address(0), "Incorrect address"); - // It sets the new minSignerListLength + // It minSignerListLength should be zero vm.assertEq(minSignerListLength, 0); // It should emit the SignersAdded event - vm.expectEmit(); emit SignersAdded({signers: signers}); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) - ); - - // It should emit a SignerListSettingsUpdated event - - vm.expectEmit(); - emit SignerListSettingsUpdated({encryptionRegistry: encryptionRegistry, minSignerListLength: 0}); - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); // It should set the right values in general // 2 signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); (reg, minSignerListLength) = signerList.settings(); - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); + vm.assertEq(address(reg), address(0), "Incorrect address"); vm.assertEq(minSignerListLength, 0); vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); @@ -157,39 +128,20 @@ contract SignerListTest is AragonTest { vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); // 3 - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 2))) - ) - ); - - (reg, minSignerListLength) = signerList.settings(); - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); - vm.assertEq(minSignerListLength, 2); - vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); - vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); - vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); - vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); - vm.assertEq(signerList.isListed(david), true, "Should be a signer"); - vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); - vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); - - // 4 signers = new address[](2); signers[0] = address(100); - signers[0] = address(200); + signers[1] = address(200); + // It should emit the SignersAdded event + vm.expectEmit(); + emit SignersAdded({signers: signers}); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 1))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); (reg, minSignerListLength) = signerList.settings(); - vm.assertEq(address(reg), address(encryptionRegistry), "Incorrect address"); - vm.assertEq(minSignerListLength, 1); + vm.assertEq(address(reg), address(0), "Incorrect address"); + vm.assertEq(minSignerListLength, 0); vm.assertEq(signerList.addresslistLength(), 2, "Incorrect length"); vm.assertEq(signerList.isListed(alice), false, "Should not be a signer"); vm.assertEq(signerList.isListed(bob), false, "Should not be a signer"); @@ -207,144 +159,55 @@ contract SignerListTest is AragonTest { // It should revert // 1 - signers = new address[](type(uint16).max + 1); - - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) - ); - - // 2 - signers = new address[](type(uint16).max + 10); - + signers = new address[](uint256(type(uint16).max) + 1); vm.expectRevert( - abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 10) - ); - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) - ); - } - - function test_RevertGiven_DuplicateAddressesOnInitialize() external givenANewInstance givenCallingInitialize { - // It should revert - - // 1 - signers[2] = signers[1]; - vm.expectRevert(); - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 2))) + abi.encodeWithSelector( + SignerListLengthOutOfBounds.selector, type(uint16).max, uint256(type(uint16).max) + 1 ) ); - } - - function test_RevertWhen_EncryptionRegistryIsNotCompatibleOnInitialize() - external - givenANewInstance - givenCallingInitialize - { - // It should revert - - // 1 - vm.expectRevert(InvalidEncryptionRegitry.selector); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(alice)), 2)) - ) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); // 2 - vm.expectRevert(InvalidEncryptionRegitry.selector); - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(bob)), 3)) - ) + signers = new address[](uint256(type(uint16).max) + 10); + vm.expectRevert( + abi.encodeWithSelector( + SignerListLengthOutOfBounds.selector, type(uint16).max, uint256(type(uint16).max) + 10 ) ); - - // OK signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, - (dao, signers, SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2)) - ) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); } - function test_RevertWhen_MinSignerListLengthIsBiggerThanTheListSizeOnInitialize() - external - givenANewInstance - givenCallingInitialize - { + function test_RevertGiven_DuplicateAddressesOnInitialize() external givenANewInstance givenCallingInitialize { // It should revert // 1 - vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 5)); - - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 5)) - ) - ) - ); - - // 2 - vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 10)); - - signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 10)) - ) - ) - ); - - // 3 - signers = new address[](5); - vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 5, 15)); + signers[2] = signers[1]; + vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, signers[2])); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 15)) - ) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); } modifier whenCallingUpdateSettings() { // Initialize - encryptionRegistry = EncryptionRegistry(address(0)); signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall(SignerList.initialize, (dao, signers, SignerList.Settings(encryptionRegistry, 0))) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); + // Grant update permission to Alice + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + _; } function test_WhenCallingUpdateSettings() external whenCallingUpdateSettings { - encryptionRegistry = new EncryptionRegistry(Addresslist(address(0))); + encryptionRegistry = new EncryptionRegistry(signerList); // 1 signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); @@ -363,7 +226,7 @@ contract SignerListTest is AragonTest { signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); // 2 - encryptionRegistry = new EncryptionRegistry(Addresslist(address(0))); + encryptionRegistry = new EncryptionRegistry(signerList); signerList.updateSettings(SignerList.Settings(encryptionRegistry, 3)); (reg, minSignerListLength) = signerList.settings(); @@ -383,67 +246,68 @@ contract SignerListTest is AragonTest { function test_RevertWhen_UpdateSettingsWithoutThePermission() external whenCallingUpdateSettings { // It should revert - encryptionRegistry = EncryptionRegistry(address(0)); - vm.startPrank(bob); vm.expectRevert( abi.encodeWithSelector( DaoUnauthorized.selector, address(dao), address(signerList), - alice, + bob, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID ) ); signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); } - function test_RevertWhen_EncryptionRegistryIsNotCompatibleOnUpdateSettings() external whenCallingUpdateSettings { + function test_RevertWhen_EncryptionRegistryIsNotCompatible() external whenCallingUpdateSettings { // It should revert - vm.expectRevert(InvalidEncryptionRegitry.selector); - signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(alice)), 0)); + vm.expectRevert(abi.encodeWithSelector(InvalidEncryptionRegitry.selector, address(dao))); + signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(dao)), 0)); + + vm.expectRevert(); + signerList.updateSettings(SignerList.Settings(EncryptionRegistry(bob), 0)); } - function test_RevertWhen_MinSignerListLengthIsBiggerThanTheListSizeOnUpdateSettings() - external - whenCallingUpdateSettings - { + function test_RevertWhen_MinSignerListLengthIsBiggerThanTheListSize() external whenCallingUpdateSettings { // It should revert // 1 vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 15)); - signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(0)), 15)); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 15)); // 2 vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 20)); - signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(0)), 20)); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 20)); // 3 - signers = new address[](5); + signers = new address[](1); + signerList.addSigners(signers); vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 5, 50)); - signerList.updateSettings(SignerList.Settings(EncryptionRegistry(address(0)), 50)); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 50)); } function test_WhenCallingSupportsInterface() external view { // It does not support the empty interface - bool supported = multisig.supportsInterface(bytes4(0xffffffff)); - assertEq(supported, true, "Should not support the empty interface"); + bool supported = signerList.supportsInterface(bytes4(0xffffffff)); + assertEq(supported, false, "Should not support the empty interface"); // It supports IERC165Upgradeable - supported = multisig.supportsInterface(type(IERC165Upgradeable).interfaceId); + supported = signerList.supportsInterface(type(IERC165Upgradeable).interfaceId); assertEq(supported, true, "Should support IERC165Upgradeable"); // It supports Addresslist - supported = multisig.supportsInterface(type(Addresslist).interfaceId); + supported = signerList.supportsInterface(type(Addresslist).interfaceId); assertEq(supported, true, "Should support Addresslist"); // It supports ISignerList - supported = multisig.supportsInterface(type(ISignerList).interfaceId); + supported = signerList.supportsInterface(type(ISignerList).interfaceId); assertEq(supported, true, "Should support ISignerList"); } modifier whenCallingAddSigners() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + _; } @@ -465,7 +329,7 @@ contract SignerListTest is AragonTest { newSigners[0] = address(100); signerList.addSigners(newSigners); - vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.addresslistLength(), 5, "Incorrect length"); vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); @@ -477,7 +341,7 @@ contract SignerListTest is AragonTest { newSigners[0] = address(200); signerList.addSigners(newSigners); - vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); + vm.assertEq(signerList.addresslistLength(), 6, "Incorrect length"); vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); @@ -493,133 +357,337 @@ contract SignerListTest is AragonTest { } function test_RevertWhen_AddingWithoutThePermission() external whenCallingAddSigners { + dao.revoke(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + // It should revert address[] memory newSigners = new address[](1); newSigners[0] = address(100); - vm.startPrank(bob); - + // 1 vm.expectRevert( abi.encodeWithSelector( DaoUnauthorized.selector, address(dao), address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID ) ); signerList.addSigners(newSigners); + + // 2 + vm.startPrank(bob); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, address(dao), address(signerList), bob, UPDATE_SIGNER_LIST_PERMISSION_ID + ) + ); + signerList.addSigners(newSigners); } - function test_RevertGiven_PassingMoreAddressesThanSupportedOnUpdateSettings() external whenCallingAddSigners { + function test_RevertGiven_PassingMoreAddressesThanSupportedOnAddSigners() external whenCallingAddSigners { // It should revert + uint256 addedSize = uint256(type(uint16).max); + // 1 - address[] memory newSigners = new address[](type(uint8).max + 1); - vm.expectRevert( - abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 1) - ); + address[] memory newSigners = new address[](addedSize); + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, addedSize + 4)); signerList.addSigners(newSigners); // 2 - newSigners = new address[](type(uint8).max + 10); - vm.expectRevert( - abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, type(uint16).max + 10) - ); + addedSize = uint256(type(uint16).max) + 10; + newSigners = new address[](addedSize); + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, type(uint16).max, addedSize + 4)); signerList.addSigners(newSigners); } - function test_RevertGiven_DuplicateAddressesOnUpdateSettings() external whenCallingAddSigners { + function test_RevertGiven_DuplicateAddressesOnAddSigners() external whenCallingAddSigners { // It should revert // 1 address[] memory newSigners = new address[](1); newSigners[0] = alice; // Alice is a signer already - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, newSigners[0])); signerList.addSigners(newSigners); // 2 newSigners[0] = bob; // Bob is a signer already - vm.expectRevert(); + vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, newSigners[0])); signerList.addSigners(newSigners); // OK newSigners[0] = address(1234); - vm.expectRevert(); signerList.addSigners(newSigners); } modifier whenCallingRemoveSigners() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + _; } function test_WhenCallingRemoveSigners() external whenCallingRemoveSigners { - // It should more the given addresses - // It should emit the SignersRemovedEvent - vm.skip(true); + // It should remove the given addresses + // It should return false on isListed + + address[] memory newSigners = new address[](3); + newSigners[0] = address(100); + newSigners[1] = address(200); + newSigners[2] = address(300); + signerList.addSigners(newSigners); + + // 0 + vm.assertEq(signerList.addresslistLength(), 7, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); + + // 1 + address[] memory rmSigners = new address[](1); + rmSigners[0] = david; + signerList.removeSigners(rmSigners); + + vm.assertEq(signerList.addresslistLength(), 6, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); + + // 2 + rmSigners[0] = carol; + signerList.removeSigners(rmSigners); + + vm.assertEq(signerList.addresslistLength(), 5, "Incorrect length"); + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(david), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(100)), true, "Should be a signer"); + vm.assertEq(signerList.isListed(address(200)), true, "Should be a signer"); + + // It should emit the SignersRemoved event + rmSigners[0] = bob; + vm.expectEmit(); + emit SignersRemoved({signers: rmSigners}); + signerList.removeSigners(rmSigners); } function test_RevertWhen_RemovingWithoutThePermission() external whenCallingRemoveSigners { + address[] memory newSigners = new address[](3); + newSigners[0] = address(100); + newSigners[1] = address(200); + newSigners[2] = address(300); + signerList.addSigners(newSigners); + + dao.revoke(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + address[] memory rmSigners = new address[](2); + rmSigners[0] = david; + // It should revert - vm.skip(true); + + // 1 + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, address(dao), address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID + ) + ); + signerList.removeSigners(newSigners); + + // 2 + vm.startPrank(bob); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, address(dao), address(signerList), bob, UPDATE_SIGNER_LIST_PERMISSION_ID + ) + ); + signerList.removeSigners(newSigners); } function test_WhenRemovingAnUnlistedAddress() external whenCallingRemoveSigners { - // It should continue gracefully - vm.skip(true); + address[] memory newSigners = new address[](1); + newSigners[0] = address(100); + signerList.addSigners(newSigners); + + // It should revert + + // 1 + address[] memory rmSigners = new address[](1); + rmSigners[0] = address(200); + vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, rmSigners[0])); + signerList.removeSigners(rmSigners); + + // 2 + rmSigners[0] = address(500); + vm.expectRevert(abi.encodeWithSelector(InvalidAddresslistUpdate.selector, rmSigners[0])); + signerList.removeSigners(rmSigners); } function test_RevertGiven_RemovingTooManyAddresses() external whenCallingRemoveSigners { + address[] memory newSigners = new address[](1); + newSigners[0] = address(100); + signerList.addSigners(newSigners); + // It should revert - vm.skip(true); + // NOTE: The new list will be smaller than minSignerListLength + + // 1 + address[] memory rmSigners = new address[](2); + rmSigners[0] = david; + rmSigners[1] = carol; + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 3)); + signerList.removeSigners(rmSigners); + + // 2 + rmSigners = new address[](3); + rmSigners[0] = david; + rmSigners[1] = carol; + rmSigners[2] = bob; + vm.expectRevert(abi.encodeWithSelector(SignerListLengthOutOfBounds.selector, 4, 2)); + signerList.removeSigners(rmSigners); + + // OK + rmSigners = new address[](1); + rmSigners[0] = david; + signerList.removeSigners(rmSigners); } modifier whenCallingIsListed() { + signerList = SignerList( + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) + ); + _; } function test_GivenTheMemberIsListed() external whenCallingIsListed { // It returns true - vm.skip(true); + + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); } function test_GivenTheMemberIsNotListed() external whenCallingIsListed { // It returns false - vm.skip(true); + + vm.assertEq(signerList.isListed(address(100)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(200)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(400)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(800)), false, "Should not be a signer"); + vm.assertEq(signerList.isListed(address(1234)), false, "Should not be a signer"); + } + + function testFuzz_GivenTheMemberIsNotListed(address random) external whenCallingIsListed { + if (random == alice || random == bob || random == carol || random == david) return; + + // It returns false + + vm.assertEq(signerList.isListed(random), false, "Should not be a signer"); } modifier whenCallingIsListedAtBlock() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + _; } modifier givenTheMemberWasListed() { + vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); + vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); + vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); + vm.assertEq(signerList.isListed(david), true, "Should be a signer"); + _; } function test_GivenTheMemberIsNotListedNow() external whenCallingIsListedAtBlock givenTheMemberWasListed { + vm.roll(block.number + 1); + + // Replace the list + address[] memory newSigners = new address[](4); + newSigners[0] = address(0); + newSigners[1] = address(1); + newSigners[2] = address(2); + newSigners[3] = address(3); + signerList.addSigners(newSigners); + address[] memory rmSigners = new address[](4); + rmSigners[0] = alice; + rmSigners[1] = bob; + rmSigners[2] = carol; + rmSigners[3] = david; + signerList.removeSigners(rmSigners); + // It returns true - vm.skip(true); + vm.assertEq(signerList.isListedAtBlock(alice, block.number - 1), true, "Should be a signer"); + vm.assertEq(signerList.isListedAtBlock(bob, block.number - 1), true, "Should be a signer"); + vm.assertEq(signerList.isListedAtBlock(carol, block.number - 1), true, "Should be a signer"); + vm.assertEq(signerList.isListedAtBlock(david, block.number - 1), true, "Should be a signer"); } function test_GivenTheMemberIsListedNow() external whenCallingIsListedAtBlock givenTheMemberWasListed { + vm.roll(block.number + 1); + // It returns true - vm.skip(true); + vm.assertEq(signerList.isListedAtBlock(alice, block.number - 1), true, "Should be a signer"); + vm.assertEq(signerList.isListedAtBlock(bob, block.number - 1), true, "Should be a signer"); + vm.assertEq(signerList.isListedAtBlock(carol, block.number - 1), true, "Should be a signer"); + vm.assertEq(signerList.isListedAtBlock(david, block.number - 1), true, "Should be a signer"); } modifier givenTheMemberWasNotListed() { + // Replace the list + address[] memory newSigners = new address[](4); + newSigners[0] = address(0); + newSigners[1] = address(1); + newSigners[2] = address(2); + newSigners[3] = address(3); + signerList.addSigners(newSigners); + address[] memory rmSigners = new address[](4); + rmSigners[0] = alice; + rmSigners[1] = bob; + rmSigners[2] = carol; + rmSigners[3] = david; + signerList.removeSigners(rmSigners); + + // +1 + vm.roll(block.number + 1); + _; } function test_GivenTheMemberIsDelistedNow() external whenCallingIsListedAtBlock givenTheMemberWasNotListed { // It returns false - vm.skip(true); + vm.assertEq(signerList.isListedAtBlock(alice, block.number - 1), false, "Should not be a signer"); + vm.assertEq(signerList.isListedAtBlock(bob, block.number - 1), false, "Should not be a signer"); + vm.assertEq(signerList.isListedAtBlock(carol, block.number - 1), false, "Should not be a signer"); + vm.assertEq(signerList.isListedAtBlock(david, block.number - 1), false, "Should not be a signer"); } function test_GivenTheMemberIsEnlistedNow() external whenCallingIsListedAtBlock givenTheMemberWasNotListed { + // Add again + address[] memory newSigners = new address[](4); + newSigners[0] = alice; + newSigners[1] = bob; + newSigners[2] = carol; + newSigners[3] = david; + signerList.addSigners(newSigners); + // It returns false - vm.skip(true); + vm.assertEq(signerList.isListedAtBlock(alice, block.number - 1), false, "Should not be a signer"); + vm.assertEq(signerList.isListedAtBlock(bob, block.number - 1), false, "Should not be a signer"); + vm.assertEq(signerList.isListedAtBlock(carol, block.number - 1), false, "Should not be a signer"); + vm.assertEq(signerList.isListedAtBlock(david, block.number - 1), false, "Should not be a signer"); } modifier whenCallingResolveEncryptionAccountStatus() { _; } + /* function test_GivenTheCallerIsAListedSigner() external whenCallingResolveEncryptionAccountStatus { // It ownerIsListed should be true @@ -707,5 +775,5 @@ contract SignerListTest is AragonTest { // It result does not contain unlisted addresses // It result does not contain non appointed addresses vm.skip(true); - } + }*/ } From 0a261c6fa69168c980d10bfa65c6f4493960fdae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 6 Nov 2024 20:44:58 +0700 Subject: [PATCH 23/45] SignerList tests --- TEST_TREE.md | 45 +++--- src/SignerList.sol | 13 +- src/factory/TaikoDaoFactory.sol | 6 +- test/SignerListTree.t.sol | 245 +++++++++++++++++++++++++++++--- test/SignerListTree.t.yaml | 53 ++++--- test/helpers/DaoBuilder.sol | 7 +- 6 files changed, 288 insertions(+), 81 deletions(-) diff --git a/TEST_TREE.md b/TEST_TREE.md index 9be0ba0..7eabb0c 100644 --- a/TEST_TREE.md +++ b/TEST_TREE.md @@ -452,23 +452,18 @@ SignerListTest │ │ └── It should revert │ ├── It should append the new addresses to the list │ ├── It should return true on isListed -│ ├── It should emit the SignersAddedEvent -│ ├── When encryptionRegistry is not compatible on initialize -│ │ └── It should revert -│ ├── When minSignerListLength is lower than the list size on initialize -│ │ └── It should revert -│ ├── It sets the new encryption registry -│ ├── It sets the new minSignerListLength -│ └── It should emit a SignerListSettingsUpdated event +│ ├── It should emit the SignersAdded event +│ ├── It the encryption registry should be empty +│ └── It minSignerListLength should be zero ├── When calling updateSettings │ ├── When updateSettings without the permission │ │ └── It should revert -│ ├── When encryptionRegistry is not compatible on updateSettings +│ ├── When encryptionRegistry is not compatible │ │ └── It should revert -│ ├── When minSignerListLength is lower than the list size on updateSettings +│ ├── When minSignerListLength is bigger than the list size │ │ └── It should revert -│ ├── It set the new encryption registry -│ ├── It set the new minSignerListLength +│ ├── It sets the new encryption registry +│ ├── It sets the new minSignerListLength │ └── It should emit a SignerListSettingsUpdated event ├── When calling supportsInterface │ ├── It does not support the empty interface @@ -478,22 +473,23 @@ SignerListTest ├── When calling addSigners │ ├── When adding without the permission │ │ └── It should revert -│ ├── Given passing more addresses than supported on updateSettings +│ ├── Given passing more addresses than supported on addSigners │ │ └── It should revert -│ ├── Given duplicate addresses on updateSettings +│ ├── Given duplicate addresses on addSigners │ │ └── It should revert │ ├── It should append the new addresses to the list │ ├── It should return true on isListed -│ └── It should emit the SignersAddedEvent +│ └── It should emit the SignersAdded event ├── When calling removeSigners │ ├── When removing without the permission │ │ └── It should revert │ ├── When removing an unlisted address -│ │ └── It should continue gracefully +│ │ └── It should revert │ ├── Given removing too many addresses // The new list will be smaller than minSignerListLength │ │ └── It should revert -│ ├── It should more the given addresses -│ └── It should emit the SignersRemovedEvent +│ ├── It should remove the given addresses +│ ├── It should return false on isListed +│ └── It should emit the SignersRemoved event ├── When calling isListed │ ├── Given the member is listed │ │ └── It returns true @@ -521,14 +517,22 @@ SignerListTest │ ├── It ownerIsListed should be false │ └── It isAppointed should be false ├── When calling resolveEncryptionOwner -│ ├── Given the resolved owner is listed +│ ├── Given the resolved owner is listed on resolveEncryptionOwner +│ │ ├── When the given address is the owner +│ │ │ └── It should return the given address +│ │ └── When the given address is appointed by the owner +│ │ └── It should return the resolved owner +│ └── Given the resolved owner is not listed on resolveEncryptionOwner +│ └── It should return a zero value +├── When calling resolveEncryptionAccount +│ ├── Given the resolved owner is listed on resolveEncryptionAccount │ │ ├── When the given address is appointed │ │ │ ├── It owner should be the resolved owner │ │ │ └── It appointedWallet should be the caller │ │ └── When the given address is not appointed │ │ ├── It owner should be the caller │ │ └── It appointedWallet should be resolved appointed wallet -│ └── Given the resolved owner is not listed +│ └── Given the resolved owner is not listed on resolveEncryptionAccount │ ├── It should return a zero owner │ └── It should return a zero appointedWallet └── When calling getEncryptionRecipients @@ -545,3 +549,4 @@ SignerListTest ├── It result does not contain unlisted addresses └── It result does not contain non appointed addresses ``` + diff --git a/src/SignerList.sol b/src/SignerList.sol index d909d3b..4876e8f 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -47,12 +47,12 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza _disableInitializers(); } - /// @notice Initializes Release 1, Build 1. + /// @notice Initializes Release 1, Build 1 without any settings yet. /// @dev This method is required to support [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822). + /// @dev updateSettings() must be called after the EncryptionRegistry has been deployed. /// @param _dao The IDAO interface of the associated DAO. /// @param _signers The addresses of the initial signers to be added. - /// @param _settings The settings to define on the new instance. - function initialize(IDAO _dao, address[] calldata _signers, Settings calldata _settings) external initializer { + function initialize(IDAO _dao, address[] calldata _signers) external initializer { __DaoAuthorizableUpgradeable_init(_dao); // Validating _signers[] @@ -62,13 +62,6 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza _addAddresses(_signers); emit SignersAdded({signers: _signers}); - - // Settings (validated within _updateSettings) - _updateSettings(_settings); - emit SignerListSettingsUpdated({ - encryptionRegistry: _settings.encryptionRegistry, - minSignerListLength: _settings.minSignerListLength - }); } /// @inheritdoc ISignerList diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol index 8e247fe..0d168b4 100644 --- a/src/factory/TaikoDaoFactory.sol +++ b/src/factory/TaikoDaoFactory.sol @@ -115,7 +115,7 @@ contract TaikoDaoFactory { deployment.dao = dao; // DEPLOY THE SIGNER LIST AND REGISTRY - deployment.signerList = deploySignerListNoSettings(dao); + deployment.signerList = deploySignerListWithoutSettings(dao); deployment.encryptionRegistry = deployEncryptionRegistry(); // Link them together deployment.signerList.updateSettings( @@ -313,10 +313,10 @@ contract TaikoDaoFactory { return (OptimisticTokenVotingPlugin(plugin), pluginRepo, preparedSetupData); } - function deploySignerListNoSettings(DAO dao) internal returns (SignerList helper) { + function deploySignerListWithoutSettings(DAO dao) internal returns (SignerList helper) { helper = new SignerList(); - helper.initialize(dao, settings.multisigMembers, SignerList.Settings(EncryptionRegistry(address(0)), 0)); + helper.initialize(dao, settings.multisigMembers); } function deployEncryptionRegistry() internal returns (EncryptionRegistry) { diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index 48042c0..b511fe6 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -507,7 +507,7 @@ contract SignerListTest is AragonTest { signerList.removeSigners(newSigners); } - function test_WhenRemovingAnUnlistedAddress() external whenCallingRemoveSigners { + function test_RevertWhen_RemovingAnUnlistedAddress() external whenCallingRemoveSigners { address[] memory newSigners = new address[](1); newSigners[0] = address(100); signerList.addSigners(newSigners); @@ -640,7 +640,7 @@ contract SignerListTest is AragonTest { } modifier givenTheMemberWasNotListed() { - // Replace the list + // Replace the list address[] memory newSigners = new address[](4); newSigners[0] = address(0); newSigners[1] = address(1); @@ -685,60 +685,267 @@ contract SignerListTest is AragonTest { } modifier whenCallingResolveEncryptionAccountStatus() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 2)); + + // Remove Carol and David + address[] memory rmSigners = new address[](2); + rmSigners[0] = carol; + rmSigners[1] = david; + signerList.removeSigners(rmSigners); + + // Alice (owner) appoints david + encryptionRegistry.appointWallet(david); + + // Bob is the owner + _; } - /* function test_GivenTheCallerIsAListedSigner() external whenCallingResolveEncryptionAccountStatus { + // 1 + (bool ownerIsListed, bool appointed) = signerList.resolveEncryptionAccountStatus(alice); + // It ownerIsListed should be true + assertEq(ownerIsListed, true, "The owner should be listed"); + // It isAppointed should be false - vm.skip(true); + assertEq(appointed, false, "Should not be appointed"); + + // 2 + (ownerIsListed, appointed) = signerList.resolveEncryptionAccountStatus(bob); + + // It ownerIsListed should be true + assertEq(ownerIsListed, true, "The owner should be listed"); + + // It isAppointed should be false + assertEq(appointed, false, "Should not be appointed"); } function test_GivenTheCallerIsAppointedByASigner() external whenCallingResolveEncryptionAccountStatus { + (bool ownerIsListed, bool appointed) = signerList.resolveEncryptionAccountStatus(david); + // It ownerIsListed should be true + assertEq(ownerIsListed, true, "The owner should be listed"); + // It isAppointed should be true - vm.skip(true); + assertEq(appointed, true, "Should be appointed"); } function test_GivenTheCallerIsNotListedOrAppointed() external whenCallingResolveEncryptionAccountStatus { + // 1 + (bool ownerIsListed, bool appointed) = signerList.resolveEncryptionAccountStatus(carol); + // It ownerIsListed should be false + assertEq(ownerIsListed, false, "The owner should be listed"); + // It isAppointed should be false - vm.skip(true); + assertEq(appointed, false, "Should be appointed"); + + // 2 + (ownerIsListed, appointed) = signerList.resolveEncryptionAccountStatus(address(1234)); + + // It ownerIsListed should be false + assertEq(ownerIsListed, false, "The owner should not be listed"); + + // It isAppointed should be false + assertEq(appointed, false, "Should not be appointed"); } modifier whenCallingResolveEncryptionOwner() { + // Alice (owner) appoints address(0x1234) + encryptionRegistry.appointWallet(address(0x1234)); + + // Bob (owner) appoints address(0x2345) + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x2345)); + + vm.startPrank(alice); + + // Carol is owner + // David is owner + _; } - modifier givenTheResolvedOwnerIsListed() { + modifier givenTheResolvedOwnerIsListedOnResolveEncryptionOwner() { _; } - function test_WhenTheGivenAddressIsAppointed() + function test_WhenTheGivenAddressIsTheOwner() external whenCallingResolveEncryptionOwner - givenTheResolvedOwnerIsListed + givenTheResolvedOwnerIsListedOnResolveEncryptionOwner { + address resolvedOwner; + + // It should return the given address + resolvedOwner = signerList.resolveEncryptionOwner(alice); + assertEq(resolvedOwner, alice, "Should be alice"); + + resolvedOwner = signerList.resolveEncryptionOwner(bob); + assertEq(resolvedOwner, bob, "Should be bob"); + + resolvedOwner = signerList.resolveEncryptionOwner(carol); + assertEq(resolvedOwner, carol, "Should be carol"); + + resolvedOwner = signerList.resolveEncryptionOwner(david); + assertEq(resolvedOwner, david, "Should be david"); + } + + function test_WhenTheGivenAddressIsAppointedByTheOwner() + external + whenCallingResolveEncryptionOwner + givenTheResolvedOwnerIsListedOnResolveEncryptionOwner + { + address resolvedOwner; + + // It should return the resolved owner + resolvedOwner = signerList.resolveEncryptionOwner(address(0x1234)); + assertEq(resolvedOwner, alice, "Should be alice"); + + resolvedOwner = signerList.resolveEncryptionOwner(address(0x2345)); + assertEq(resolvedOwner, bob, "Should be bob"); + } + + function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionOwner() + external + whenCallingResolveEncryptionOwner + { + address resolvedOwner; + + // It should return a zero value + resolvedOwner = signerList.resolveEncryptionOwner(address(0x3456)); + assertEq(resolvedOwner, address(0), "Should be zero"); + + resolvedOwner = signerList.resolveEncryptionOwner(address(0x4567)); + assertEq(resolvedOwner, address(0), "Should be zero"); + } + + modifier whenCallingResolveEncryptionAccount() { + // Alice (owner) appoints address(0x1234) + encryptionRegistry.appointWallet(address(0x1234)); + + // Bob (owner) appoints address(0x2345) + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x2345)); + + vm.startPrank(alice); + + // Carol is owner + // David is owner + + _; + } + + modifier givenTheResolvedOwnerIsListedOnResolveEncryptionAccount() { + _; + } + + function test_WhenTheGivenAddressIsAppointed() + external + whenCallingResolveEncryptionAccount + givenTheResolvedOwnerIsListedOnResolveEncryptionAccount + { + address resolvedOwner; + address appointedWallet; + // It owner should be the resolved owner - // It appointedWallet should be the caller - vm.skip(true); + // It appointedWallet should be the given address + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x1234)); + assertEq(resolvedOwner, alice, "Should be alice"); + assertEq(appointedWallet, address(0x1234), "Should be 0x1234"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x2345)); + assertEq(resolvedOwner, bob, "Should be bob"); + assertEq(appointedWallet, address(0x2345), "Should be 0x2345"); } function test_WhenTheGivenAddressIsNotAppointed() external - whenCallingResolveEncryptionOwner - givenTheResolvedOwnerIsListed + whenCallingResolveEncryptionAccount + givenTheResolvedOwnerIsListedOnResolveEncryptionAccount { - // It owner should be the caller - // It appointedWallet should be resolved appointed wallet - vm.skip(true); + address resolvedOwner; + address appointedWallet; + + // 1 - owner appoints + + // It owner should be the given address + // It appointedWallet should be the resolved appointed wallet + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(alice); + assertEq(resolvedOwner, alice, "Should be alice"); + assertEq(appointedWallet, address(0x1234), "Should be 0x1234"); + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x1234)); + assertEq(resolvedOwner, alice, "Should be alice"); + assertEq(appointedWallet, address(0x1234), "Should be 0x1234"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(bob); + assertEq(resolvedOwner, bob, "Should be bob"); + assertEq(appointedWallet, address(0x2345), "Should be 0x2345"); + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x2345)); + assertEq(resolvedOwner, bob, "Should be bob"); + assertEq(appointedWallet, address(0x2345), "Should be 0x2345"); + + // 2 - No appointed wallet + + // It owner should be the given address + // It appointedWallet should be the resolved appointed wallet + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(carol); + assertEq(resolvedOwner, carol, "Should be carol"); + assertEq(appointedWallet, carol, "Should be carol"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(david); + assertEq(resolvedOwner, david, "Should be david"); + assertEq(appointedWallet, david, "Should be david"); } - function test_GivenTheResolvedOwnerIsNotListed() external whenCallingResolveEncryptionOwner { + function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionAccount() + external + whenCallingResolveEncryptionAccount + { + address resolvedOwner; + address appointedWallet; + // It should return a zero owner // It should return a zero appointedWallet - vm.skip(true); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0)); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(appointedWallet, address(0), "Should be 0"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x5555)); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(appointedWallet, address(0), "Should be 0"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0xaaaa)); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(appointedWallet, address(0), "Should be 0"); + + // Formerly a signer + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Remove Bob (appointed 0x2345) and David + address[] memory rmSigners = new address[](2); + rmSigners[0] = bob; + rmSigners[1] = david; + signerList.removeSigners(rmSigners); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(bob); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(appointedWallet, address(0), "Should be 0"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x2345)); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(appointedWallet, address(0), "Should be 0"); + + (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(david); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(appointedWallet, address(0), "Should be 0"); } modifier whenCallingGetEncryptionRecipients() { @@ -775,5 +982,5 @@ contract SignerListTest is AragonTest { // It result does not contain unlisted addresses // It result does not contain non appointed addresses vm.skip(true); - }*/ + } } diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index 14acfa1..50dd7b4 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -21,27 +21,18 @@ SignerListTest: - it: should append the new addresses to the list - it: should return true on isListed - it: should emit the SignersAdded event - # Same checks as updateSettings below - - when: encryptionRegistry is not compatible [on initialize] - then: - - it: should revert - - when: minSignerListLength is bigger than the list size [on initialize] - then: - - it: should revert - - it: sets the new encryption registry - - it: sets the new minSignerListLength - - it: should emit a SignerListSettingsUpdated event + - it: the encryption registry should be empty + - it: minSignerListLength should be zero - when: calling updateSettings and: - when: updateSettings without the permission then: - it: should revert - # Same checks as initialize does above - - when: encryptionRegistry is not compatible [on updateSettings] + - when: encryptionRegistry is not compatible then: - it: should revert - - when: minSignerListLength is bigger than the list size [on updateSettings] + - when: minSignerListLength is bigger than the list size then: - it: should revert - it: sets the new encryption registry @@ -61,10 +52,10 @@ SignerListTest: - when: adding without the permission then: - it: should revert - - given: passing more addresses than supported [on updateSettings] + - given: passing more addresses than supported [on addSigners] then: - it: should revert - - given: duplicate addresses [on updateSettings] + - given: duplicate addresses [on addSigners] then: - it: should revert - it: should append the new addresses to the list @@ -78,13 +69,14 @@ SignerListTest: - it: should revert - when: removing an unlisted address then: - - it: should continue gracefully + - it: should revert - given: removing too many addresses comment: The new list will be smaller than minSignerListLength then: - it: should revert - - it: should more the given addresses - - it: should emit the SignersRemovedEvent + - it: should remove the given addresses + - it: should return false on isListed + - it: should emit the SignersRemoved event # Getters - when: calling isListed @@ -133,18 +125,33 @@ SignerListTest: - when: calling resolveEncryptionOwner and: - - given: the resolved owner is listed + - given: the resolved owner is listed [on resolveEncryptionOwner] + and: + - when: the given address is the owner + then: + - it: should return the given address + - when: the given address is appointed by the owner + then: + - it: should return the resolved owner + + - given: the resolved owner is not listed [on resolveEncryptionOwner] + then: + - it: should return a zero value + + - when: calling resolveEncryptionAccount + and: + - given: the resolved owner is listed [on resolveEncryptionAccount] and: - when: the given address is appointed then: - it: owner should be the resolved owner - - it: appointedWallet should be the caller + - it: appointedWallet should be the given address - when: the given address is not appointed then: - - it: owner should be the caller - - it: appointedWallet should be resolved appointed wallet + - it: owner should be the given address + - it: appointedWallet should be the resolved appointed wallet - - given: the resolved owner is not listed + - given: the resolved owner is not listed [on resolveEncryptionAccount] then: - it: should return a zero owner - it: should return a zero appointedWallet diff --git a/test/helpers/DaoBuilder.sol b/test/helpers/DaoBuilder.sol index af55966..c51c5b8 100644 --- a/test/helpers/DaoBuilder.sol +++ b/test/helpers/DaoBuilder.sol @@ -236,12 +236,7 @@ contract DaoBuilder is Test { } signerList = SignerList( - createProxyAndCall( - address(SIGNER_LIST_BASE), - abi.encodeCall( - SignerList.initialize, (dao, signers, SignerList.Settings(EncryptionRegistry(address(0)), 0)) - ) - ) + createProxyAndCall(address(SIGNER_LIST_BASE), abi.encodeCall(SignerList.initialize, (dao, signers))) ); encryptionRegistry = new EncryptionRegistry(signerList); dao.grant(address(signerList), address(this), UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); From 0188026086b47e2fe92d3ef4bc17f6ac760a5887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 7 Nov 2024 14:44:52 +0700 Subject: [PATCH 24/45] Signer List tests ready --- src/EncryptionRegistry.sol | 10 +- test/SignerList.t.sol | 1105 ------------------------------------ test/SignerListTree.t.sol | 124 +++- 3 files changed, 126 insertions(+), 1113 deletions(-) delete mode 100644 test/SignerList.t.sol diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index abdf7fb..32df6a9 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -87,15 +87,15 @@ contract EncryptionRegistry is IEncryptionRegistry, ERC165 { } /// @inheritdoc IEncryptionRegistry - function setPublicKey(address _account, bytes32 _publicKey) public { - if (!addresslist.isListed(_account)) { + function setPublicKey(address _accountOwner, bytes32 _publicKey) public { + if (!addresslist.isListed(_accountOwner)) { revert MustBeListed(); - } else if (accounts[_account].appointedWallet != msg.sender) { + } else if (accounts[_accountOwner].appointedWallet != msg.sender) { revert MustBeAppointed(); } - _setPublicKey(_account, _publicKey); - emit PublicKeySet(_account, _publicKey); + _setPublicKey(_accountOwner, _publicKey); + emit PublicKeySet(_accountOwner, _publicKey); } /// @inheritdoc IEncryptionRegistry diff --git a/test/SignerList.t.sol b/test/SignerList.t.sol deleted file mode 100644 index 9de1e4e..0000000 --- a/test/SignerList.t.sol +++ /dev/null @@ -1,1105 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import {AragonTest} from "./base/AragonTest.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; -import {SignerList} from "../src/SignerList.sol"; -import {DaoBuilder} from "./helpers/DaoBuilder.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {Multisig} from "../src/Multisig.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; - -contract SignerListTestTemp is AragonTest { - SignerList signerList; - EncryptionRegistry encryptionRegistry; - DaoBuilder builder; - DAO dao; - Multisig multisig; - address[] signers; - - // Events/errors to be tested here (duplicate) - error SignerListLengthOutOfBounds(uint16 limit, uint256 actual); - error InvalidEncryptionRegitry(address givenAddress); - - function setUp() public { - builder = new DaoBuilder(); - (dao, , multisig, , , signerList, encryptionRegistry, ) = builder - .withMultisigMember(alice) - .withMultisigMember(bob) - .withMultisigMember(carol) - .withMultisigMember(david) - .build(); - - signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - } - - // Initialize - function test_InitializeRevertsIfInitialized() public { - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(0)), 0) - ); - - vm.expectRevert( - bytes("Initializable: contract is already initialized") - ); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(0)), 0) - ); - } - - function test_InitializeSetsTheRightValues() public { - // 1 - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(0)), 0) - ); - - (EncryptionRegistry reg, uint16 minSignerListLength) = signerList - .settings(); - vm.assertEq(address(reg), address(0), "Incorrect address"); - vm.assertEq(minSignerListLength, 0); - vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); - vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); - vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); - vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); - vm.assertEq(signerList.isListed(david), true, "Should be a signer"); - vm.assertEq( - signerList.isListed(address(100)), - false, - "Should not be a signer" - ); - vm.assertEq( - signerList.isListed(address(200)), - false, - "Should not be a signer" - ); - - // 2 - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(encryptionRegistry), 0) - ); - - (reg, minSignerListLength) = signerList.settings(); - vm.assertEq( - address(reg), - address(encryptionRegistry), - "Incorrect address" - ); - vm.assertEq(minSignerListLength, 0); - vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); - vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); - vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); - vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); - vm.assertEq(signerList.isListed(david), true, "Should be a signer"); - vm.assertEq( - signerList.isListed(address(100)), - false, - "Should not be a signer" - ); - vm.assertEq( - signerList.isListed(address(200)), - false, - "Should not be a signer" - ); - - // 3 - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2) - ); - - (reg, minSignerListLength) = signerList.settings(); - vm.assertEq( - address(reg), - address(encryptionRegistry), - "Incorrect address" - ); - vm.assertEq(minSignerListLength, 2); - vm.assertEq(signerList.addresslistLength(), 4, "Incorrect length"); - vm.assertEq(signerList.isListed(alice), true, "Should be a signer"); - vm.assertEq(signerList.isListed(bob), true, "Should be a signer"); - vm.assertEq(signerList.isListed(carol), true, "Should be a signer"); - vm.assertEq(signerList.isListed(david), true, "Should be a signer"); - vm.assertEq( - signerList.isListed(address(100)), - false, - "Should not be a signer" - ); - vm.assertEq( - signerList.isListed(address(200)), - false, - "Should not be a signer" - ); - - // 4 - signers = new address[](2); - signers[0] = address(100); - signers[0] = address(200); - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(encryptionRegistry), 1) - ); - - (reg, minSignerListLength) = signerList.settings(); - vm.assertEq( - address(reg), - address(encryptionRegistry), - "Incorrect address" - ); - vm.assertEq(minSignerListLength, 1); - vm.assertEq(signerList.addresslistLength(), 2, "Incorrect length"); - vm.assertEq( - signerList.isListed(alice), - false, - "Should not be a signer" - ); - vm.assertEq(signerList.isListed(bob), false, "Should not be a signer"); - vm.assertEq( - signerList.isListed(carol), - false, - "Should not be a signer" - ); - vm.assertEq( - signerList.isListed(david), - false, - "Should not be a signer" - ); - vm.assertEq( - signerList.isListed(address(100)), - true, - "Should be a signer" - ); - vm.assertEq( - signerList.isListed(address(200)), - true, - "Should be a signer" - ); - } - - function test_InitializingWithAnInvalidRegistryShouldRevert() public { - // 1 - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(alice)), 2) - ); - - vm.expectRevert(InvalidEncryptionRegitry.selector); - - // 2 - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(bob)), 3) - ); - - vm.expectRevert(InvalidEncryptionRegitry.selector); - - // OK - signerList = new SignerList(); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(encryptionRegistry), 2) - ); - } - - function test_InitializingWithTooManySignersReverts() public { - // 1 - signers = new address[](type(uint16).max + 1); - - signerList = new SignerList(); - vm.expectRevert( - abi.encodeWithSelector( - SignerListLengthOutOfBounds.selector, - type(uint16).max, - type(uint16).max + 1 - ) - ); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(0)), 0) - ); - - // 2 - signers = new address[](type(uint16).max + 10); - - signerList = new SignerList(); - vm.expectRevert( - abi.encodeWithSelector( - SignerListLengthOutOfBounds.selector, - type(uint16).max, - type(uint16).max + 10 - ) - ); - signerList.initialize( - dao, - signers, - SignerList.Settings(EncryptionRegistry(address(0)), 0) - ); - } - - // function test_SupportsIMembership() public view { - // bool supported = multisig.supportsInterface(type(IMembership).interfaceId); - // assertEq(supported, true, "Should support IMembership"); - // } - - function test_SupportsAddresslist() public view { - bool supported = multisig.supportsInterface( - type(Addresslist).interfaceId - ); - assertEq(supported, true, "Should support Addresslist"); - } - - function test_DoesntSupportTheEmptyInterface() public view { - bool supported = multisig.supportsInterface(0); - assertEq(supported, false, "Should not support the empty interface"); - } - - function test_SupportsIERC165Upgradeable() public view { - bool supported = multisig.supportsInterface( - type(IERC165Upgradeable).interfaceId - ); - assertEq(supported, true, "Should support IERC165Upgradeable"); - } - - function test_IsMemberShouldReturnWhenApropriate() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = alice; - - multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), - abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) - ); - - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), false, "Should not be a member"); - assertEq(multisig.isMember(carol), false, "Should not be a member"); - assertEq(multisig.isMember(david), false, "Should not be a member"); - - // More members - settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - signers = new address[](3); - signers[0] = bob; - signers[1] = carol; - signers[2] = david; - - multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), - abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) - ); - - assertEq(multisig.isMember(alice), false, "Should not be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - } - - function test_IsMemberIsListedShouldReturnTheSameValue() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = alice; - - multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), - abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) - ); - - assertEq( - multisig.isListed(alice), - multisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - multisig.isListed(bob), - multisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - multisig.isListed(carol), - multisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - multisig.isListed(david), - multisig.isMember(david), - "isMember isListed should be equal" - ); - - // More members - settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - signers = new address[](3); - signers[0] = bob; - signers[1] = carol; - signers[2] = david; - - multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), - abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) - ); - - assertEq( - multisig.isListed(alice), - multisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - multisig.isListed(bob), - multisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - multisig.isListed(carol), - multisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - multisig.isListed(david), - multisig.isMember(david), - "isMember isListed should be equal" - ); - } - - function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); // 0x0... would be a member but the chance is negligible - - multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), - abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) - ); - - assertEq(multisig.isListed(randomWallet), false, "Should be false"); - assertEq( - multisig.isListed( - vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy)))) - ), - false, - "Should be false" - ); - } - - function test_AddsNewMembersAndEmits() public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - - // No - assertEq( - multisig.isMember(randomWallet), - false, - "Should not be a member" - ); - - address[] memory addrs = new address[](1); - addrs[0] = randomWallet; - - vm.expectEmit(); - emit MembersAdded({members: addrs}); - multisig.addAddresses(addrs); - - // Yes - assertEq(multisig.isMember(randomWallet), true, "Should be a member"); - - // Next - addrs = new address[](3); - addrs[0] = vm.addr(1234); - addrs[1] = vm.addr(2345); - addrs[2] = vm.addr(3456); - - // No - assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); - assertEq(multisig.isMember(addrs[1]), false, "Should not be a member"); - assertEq(multisig.isMember(addrs[2]), false, "Should not be a member"); - - vm.expectEmit(); - emit MembersAdded({members: addrs}); - multisig.addAddresses(addrs); - - // Yes - assertEq(multisig.isMember(addrs[0]), true, "Should be a member"); - assertEq(multisig.isMember(addrs[1]), true, "Should be a member"); - assertEq(multisig.isMember(addrs[2]), true, "Should be a member"); - } - - function test_RemovesMembersAndEmits() public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig.updateMultisigSettings(settings); - - // Before - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - address[] memory addrs = new address[](2); - addrs[0] = alice; - addrs[1] = bob; - - vm.expectEmit(); - emit MembersRemoved({members: addrs}); - multisig.removeAddresses(addrs); - - // After - assertEq(multisig.isMember(alice), false, "Should not be a member"); - assertEq(multisig.isMember(bob), false, "Should not be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // Next - addrs = new address[](3); - addrs[0] = vm.addr(1234); - addrs[1] = vm.addr(2345); - addrs[2] = vm.addr(3456); - multisig.addAddresses(addrs); - - // Remove - addrs = new address[](2); - addrs[0] = carol; - addrs[1] = david; - - vm.expectEmit(); - emit MembersRemoved({members: addrs}); - multisig.removeAddresses(addrs); - - // Yes - assertEq(multisig.isMember(carol), false, "Should not be a member"); - assertEq(multisig.isMember(david), false, "Should not be a member"); - } - - function test_RevertsIfAddingTooManyMembers() public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - - address[] memory addrs = new address[](type(uint16).max); - addrs[0] = address(12345678); - - assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); - vm.expectRevert( - abi.encodeWithSelector( - Multisig.AddresslistLengthOutOfBounds.selector, - type(uint16).max, - uint256(type(uint16).max) + 4 - ) - ); - multisig.addAddresses(addrs); - - assertEq(multisig.isMember(addrs[0]), false, "Should not be a member"); - } - - function test_ShouldRevertIfEmptySignersList() public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig.updateMultisigSettings(settings); - - // Before - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // ok - address[] memory addrs = new address[](1); - addrs[0] = alice; - multisig.removeAddresses(addrs); - - addrs[0] = bob; - multisig.removeAddresses(addrs); - - addrs[0] = carol; - multisig.removeAddresses(addrs); - - assertEq(multisig.isMember(alice), false, "Should not be a member"); - assertEq(multisig.isMember(bob), false, "Should not be a member"); - assertEq(multisig.isMember(carol), false, "Should not be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // ko - addrs[0] = david; - vm.expectRevert( - abi.encodeWithSelector( - Multisig.MinApprovalsOutOfBounds.selector, - 1, - 0 - ) - ); - multisig.removeAddresses(addrs); - - // Next - addrs = new address[](1); - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - // Retry removing David - addrs = new address[](1); - addrs[0] = david; - - multisig.removeAddresses(addrs); - - // Yes - assertEq(multisig.isMember(david), false, "Should not be a member"); - } - - function test_ShouldRevertIfLessThanMinApproval() public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - - // Before - assertEq(multisig.isMember(alice), true, "Should be a member"); - assertEq(multisig.isMember(bob), true, "Should be a member"); - assertEq(multisig.isMember(carol), true, "Should be a member"); - assertEq(multisig.isMember(david), true, "Should be a member"); - - // ok - address[] memory addrs = new address[](1); - addrs[0] = alice; - multisig.removeAddresses(addrs); - - // ko - addrs[0] = bob; - vm.expectRevert( - abi.encodeWithSelector( - Multisig.MinApprovalsOutOfBounds.selector, - 3, - 2 - ) - ); - multisig.removeAddresses(addrs); - - // ko - addrs[0] = carol; - vm.expectRevert( - abi.encodeWithSelector( - Multisig.MinApprovalsOutOfBounds.selector, - 3, - 2 - ) - ); - multisig.removeAddresses(addrs); - - // ko - addrs[0] = david; - vm.expectRevert( - abi.encodeWithSelector( - Multisig.MinApprovalsOutOfBounds.selector, - 3, - 2 - ) - ); - multisig.removeAddresses(addrs); - - // Add and retry removing - - addrs = new address[](1); - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - addrs = new address[](1); - addrs[0] = bob; - multisig.removeAddresses(addrs); - - // 2 - addrs = new address[](1); - addrs[0] = vm.addr(2345); - multisig.addAddresses(addrs); - - addrs = new address[](1); - addrs[0] = carol; - multisig.removeAddresses(addrs); - - // 3 - addrs = new address[](1); - addrs[0] = vm.addr(3456); - multisig.addAddresses(addrs); - - addrs = new address[](1); - addrs[0] = david; - multisig.removeAddresses(addrs); - } - - function test_IsMemberShouldReturnWhenApropriate() public { - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - dao.grant( - address(stdMultisig), - alice, - stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - address[] memory signers = new address[](1); - signers[0] = bob; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), false, "Should not be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - // 2 - stdMultisig.addAddresses(signers); // Add Bob back - signers[0] = alice; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), false, "Should not be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - // 3 - stdMultisig.addAddresses(signers); // Add Alice back - signers[0] = carol; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), false, "Should not be a member"); - assertEq(eMultisig.isMember(david), true, "Should be a member"); - - // 4 - stdMultisig.addAddresses(signers); // Add Carol back - signers[0] = david; - stdMultisig.removeAddresses(signers); - - assertEq(eMultisig.isMember(alice), true, "Should be a member"); - assertEq(eMultisig.isMember(bob), true, "Should be a member"); - assertEq(eMultisig.isMember(carol), true, "Should be a member"); - assertEq(eMultisig.isMember(david), false, "Should not be a member"); - } - - function test_IsMemberIsListedShouldReturnTheSameValue() public { - assertEq( - stdMultisig.isListed(alice), - eMultisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(bob), - eMultisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(carol), - eMultisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(david), - eMultisig.isMember(david), - "isMember isListed should be equal" - ); - - dao.grant( - address(stdMultisig), - alice, - stdMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - address[] memory signers = new address[](1); - signers[0] = alice; - stdMultisig.removeAddresses(signers); - - assertEq( - stdMultisig.isListed(alice), - eMultisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(bob), - eMultisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(carol), - eMultisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(david), - eMultisig.isMember(david), - "isMember isListed should be equal" - ); - - // 2 - stdMultisig.addAddresses(signers); // Add Alice back - signers[0] = bob; - stdMultisig.removeAddresses(signers); - - assertEq( - stdMultisig.isListed(alice), - eMultisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(bob), - eMultisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(carol), - eMultisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(david), - eMultisig.isMember(david), - "isMember isListed should be equal" - ); - - // 3 - stdMultisig.addAddresses(signers); // Add Bob back - signers[0] = carol; - stdMultisig.removeAddresses(signers); - - assertEq( - stdMultisig.isListed(alice), - eMultisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(bob), - eMultisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(carol), - eMultisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(david), - eMultisig.isMember(david), - "isMember isListed should be equal" - ); - - // 4 - stdMultisig.addAddresses(signers); // Add Carol back - signers[0] = david; - stdMultisig.removeAddresses(signers); - - assertEq( - stdMultisig.isListed(alice), - eMultisig.isMember(alice), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(bob), - eMultisig.isMember(bob), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(carol), - eMultisig.isMember(carol), - "isMember isListed should be equal" - ); - assertEq( - stdMultisig.isListed(david), - eMultisig.isMember(david), - "isMember isListed should be equal" - ); - } - - function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public { - // Deploy a new stdMultisig instance - Multisig.MultisigSettings memory mSettings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = address(0x0); // 0x0... would be a member but the chance is negligible - - stdMultisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), - abi.encodeCall(Multisig.initialize, (dao, signers, mSettings)) - ) - ); - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig - .MultisigSettings({ - onlyListed: true, - minApprovals: 1, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), - abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - assertEq( - eMultisig.isMember( - vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy)))) - ), - false, - "Should be false" - ); - } - - function test_ShouldRevertIfDuplicatingAddresses() public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - - // ok - address[] memory addrs = new address[](1); - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - // ko - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.addAddresses(addrs); - - // 1 - addrs[0] = alice; - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.addAddresses(addrs); - - // 2 - addrs[0] = bob; - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.addAddresses(addrs); - - // 3 - addrs[0] = carol; - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.addAddresses(addrs); - - // 4 - addrs[0] = david; - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.addAddresses(addrs); - - // ok - addrs[0] = vm.addr(1234); - multisig.removeAddresses(addrs); - - // ko - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.removeAddresses(addrs); - - addrs[0] = vm.addr(2345); - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.removeAddresses(addrs); - - addrs[0] = vm.addr(3456); - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.removeAddresses(addrs); - - addrs[0] = vm.addr(4567); - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.removeAddresses(addrs); - - addrs[0] = randomWallet; - vm.expectRevert( - abi.encodeWithSelector(InvalidAddresslistUpdate.selector, addrs[0]) - ); - multisig.removeAddresses(addrs); - } - - function test_onlyWalletWithPermissionsCanAddRemove() public { - // ko - address[] memory addrs = new address[](1); - addrs[0] = vm.addr(1234); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.addAddresses(addrs); - - // ko - addrs[0] = alice; - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.removeAddresses(addrs); - - // Permission - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - - // ok - addrs[0] = vm.addr(1234); - multisig.addAddresses(addrs); - - addrs[0] = alice; - multisig.removeAddresses(addrs); - } - - function testFuzz_PermissionedAddRemoveMembers( - address randomAccount - ) public { - dao.grant( - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ); - - assertEq(multisig.isMember(randomWallet), false, "Should be false"); - - // in - address[] memory addrs = new address[](1); - addrs[0] = randomWallet; - multisig.addAddresses(addrs); - assertEq(multisig.isMember(randomWallet), true, "Should be true"); - - // out - multisig.removeAddresses(addrs); - assertEq(multisig.isMember(randomWallet), false, "Should be false"); - - // someone else - if (randomAccount != alice) { - vm.startPrank(randomAccount); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - randomAccount, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.addAddresses(addrs); - assertEq(multisig.isMember(randomWallet), false, "Should be false"); - - addrs[0] = carol; - assertEq(multisig.isMember(carol), true, "Should be true"); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - randomAccount, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.removeAddresses(addrs); - - assertEq(multisig.isMember(carol), true, "Should be true"); - } - - vm.startPrank(alice); - } -} diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index b511fe6..8e24179 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -953,9 +953,28 @@ contract SignerListTest is AragonTest { } function test_GivenTheEncryptionRegistryHasNoAccounts() external whenCallingGetEncryptionRecipients { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + // No accounts registered a public key + // It returns an empty list, even with signers + address[] memory recipients = signerList.getEncryptionRecipients(); + assertEq(recipients.length, 0, "Should be empty"); + + // Empty the list + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 0)); + + address[] memory rmSigners = new address[](4); + rmSigners[0] = alice; + rmSigners[1] = bob; + rmSigners[2] = carol; + rmSigners[3] = david; + signerList.removeSigners(rmSigners); + // It returns an empty list, without signers - vm.skip(true); + recipients = signerList.getEncryptionRecipients(); + assertEq(recipients.length, 0, "Should be empty"); } modifier givenTheEncryptionRegistryHasAccounts() { @@ -967,8 +986,50 @@ contract SignerListTest is AragonTest { whenCallingGetEncryptionRecipients givenTheEncryptionRegistryHasAccounts { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + // Old accounts register a public key or appoint + vm.startPrank(alice); + encryptionRegistry.setOwnPublicKey(bytes32(uint256(0x5555))); + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x1234)); + vm.startPrank(address(0x1234)); + encryptionRegistry.setPublicKey(bob, bytes32(uint256(0x1234))); + vm.startPrank(carol); + encryptionRegistry.setOwnPublicKey(bytes32(uint256(0x5555))); + vm.startPrank(david); + encryptionRegistry.appointWallet(address(0x2345)); + vm.startPrank(address(0x2345)); + encryptionRegistry.setPublicKey(david, bytes32(uint256(0x2345))); + // It returns an empty list - vm.skip(true); + address[] memory recipients = signerList.getEncryptionRecipients(); + assertEq(recipients.length, 4, "Should have 4 members"); + assertEq(recipients[0], alice, "Should be alice"); + assertEq(recipients[1], address(0x1234), "Should be 1234"); + assertEq(recipients[2], carol, "Should be carol"); + assertEq(recipients[3], address(0x2345), "Should be 2345"); + + vm.startPrank(alice); + + // Replace the list of signers + address[] memory newSigners = new address[](4); + newSigners[0] = address(0); + newSigners[1] = address(1); + newSigners[2] = address(2); + newSigners[3] = address(3); + signerList.addSigners(newSigners); + + address[] memory rmSigners = new address[](4); + rmSigners[0] = alice; + rmSigners[1] = bob; + rmSigners[2] = carol; + rmSigners[3] = david; + signerList.removeSigners(rmSigners); + + // It returns an empty list + recipients = signerList.getEncryptionRecipients(); + assertEq(recipients.length, 0, "Should be empty"); } function test_GivenSomeAddressesAreRegisteredEverywhere() @@ -981,6 +1042,63 @@ contract SignerListTest is AragonTest { // It result does not contain unregistered addresses // It result does not contain unlisted addresses // It result does not contain non appointed addresses - vm.skip(true); + + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + address[] memory newSigners = new address[](4); + newSigners[0] = address(0x10); + newSigners[1] = address(0x11); + newSigners[2] = address(0x12); + newSigners[3] = address(0x13); + signerList.addSigners(newSigners); + + // Owner + vm.startPrank(alice); + encryptionRegistry.setOwnPublicKey(bytes32(uint256(0x5555))); + // Appointing 1234 + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x1234)); + // Appointed + vm.startPrank(address(0x1234)); + encryptionRegistry.setPublicKey(bob, bytes32(uint256(0x1234))); + // Owner with no pubKey + // vm.startPrank(carol); + // encryptionRegistry.setOwnPublicKey(bytes32(uint256(0))); + // Appointing 2345 + vm.startPrank(david); + encryptionRegistry.appointWallet(address(0x2345)); + // Appointed with no pubKey + // vm.startPrank(address(0x2345)); + // encryptionRegistry.setPublicKey(david, bytes32(uint256(0))); + + address[] memory recipients = signerList.getEncryptionRecipients(); + assertEq(recipients.length, 3, "Should have 3 members"); + assertEq(recipients[0], alice, "Should be alice"); + assertEq(recipients[1], address(0x1234), "Should be 1234"); + // Carol didn't interact yet + assertEq(recipients[2], address(0x2345), "Should be 2345"); + + // Register the missing public keys + vm.startPrank(carol); + encryptionRegistry.setOwnPublicKey(bytes32(uint256(0x7777))); + // Appointed by david + vm.startPrank(address(0x2345)); + encryptionRegistry.setPublicKey(david, bytes32(uint256(0x2345))); + + // Updated list + recipients = signerList.getEncryptionRecipients(); + assertEq(recipients.length, 4, "Should have 4 members"); + assertEq(recipients[0], alice, "Should be alice"); + assertEq(recipients[1], address(0x1234), "Should be 1234"); + assertEq(recipients[2], address(0x2345), "Should be 2345"); + assertEq(recipients[3], carol, "Should be carol"); + } + + // Additional tests beyond SignerListTree.t.yaml + + function testFuzz_IsMemberIsFalseByDefault(uint256 _randomEntropy) public view { + assertEq( + signerList.isListed(vm.addr(uint256(keccak256(abi.encodePacked(_randomEntropy))))), false, "Should be false" + ); } } From 6b42038fe3b4fa47c6993e377d6812d5d67e6683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 7 Nov 2024 15:55:55 +0700 Subject: [PATCH 25/45] DAO Factory and tests updated --- src/factory/TaikoDaoFactory.sol | 40 ++++++++----- test/integration/TaikoDaoFactory.t.sol | 81 ++++++++++++++------------ 2 files changed, 71 insertions(+), 50 deletions(-) diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol index 0d168b4..da168c9 100644 --- a/src/factory/TaikoDaoFactory.sol +++ b/src/factory/TaikoDaoFactory.sol @@ -5,7 +5,7 @@ import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {DAOFactory} from "@aragon/osx/framework/dao/DAOFactory.sol"; import {Multisig} from "../Multisig.sol"; import {EmergencyMultisig} from "../EmergencyMultisig.sol"; -import {SignerList} from "../SignerList.sol"; +import {SignerList, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID} from "../SignerList.sol"; import {EncryptionRegistry} from "../EncryptionRegistry.sol"; import {OptimisticTokenVotingPlugin} from "../OptimisticTokenVotingPlugin.sol"; import {OptimisticTokenVotingPluginSetup} from "../setup/OptimisticTokenVotingPluginSetup.sol"; @@ -115,12 +115,7 @@ contract TaikoDaoFactory { deployment.dao = dao; // DEPLOY THE SIGNER LIST AND REGISTRY - deployment.signerList = deploySignerListWithoutSettings(dao); - deployment.encryptionRegistry = deployEncryptionRegistry(); - // Link them together - deployment.signerList.updateSettings( - SignerList.Settings(deployment.encryptionRegistry, uint16(settings.multisigMembers.length)) - ); + (deployment.signerList, deployment.encryptionRegistry) = prepareSignerListAndEncryptionRegistry(dao); // DEPLOY THE PLUGINS (deployment.multisigPlugin, deployment.multisigPluginRepo, preparedMultisigSetupData) = @@ -196,6 +191,25 @@ contract TaikoDaoFactory { dao.applySingleTargetPermissions(address(dao), items); } + function prepareSignerListAndEncryptionRegistry(DAO dao) + internal + returns (SignerList signerList, EncryptionRegistry encryptionRegistry) + { + signerList = deploySignerListWithoutSettings(dao); + encryptionRegistry = new EncryptionRegistry(signerList); + + // Link them together + { + // Grant temporary permission to update the settings + dao.grant(address(signerList), address(this), UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + + signerList.updateSettings(SignerList.Settings(encryptionRegistry, uint16(settings.multisigMembers.length))); + + // Revoke the remporary permission + dao.revoke(address(signerList), address(this), UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + } + } + function prepareMultisig(DAO dao, SignerList signerList) internal returns (Multisig, PluginRepo, IPluginSetup.PreparedSetupData memory) @@ -314,13 +328,11 @@ contract TaikoDaoFactory { } function deploySignerListWithoutSettings(DAO dao) internal returns (SignerList helper) { - helper = new SignerList(); - - helper.initialize(dao, settings.multisigMembers); - } - - function deployEncryptionRegistry() internal returns (EncryptionRegistry) { - return new EncryptionRegistry(deployment.signerList); + helper = SignerList( + createERC1967Proxy( + address(new SignerList()), abi.encodeCall(SignerList.initialize, (dao, settings.multisigMembers)) + ) + ); } function applyPluginInstallation( diff --git a/test/integration/TaikoDaoFactory.t.sol b/test/integration/TaikoDaoFactory.t.sol index 3304233..2c1024d 100644 --- a/test/integration/TaikoDaoFactory.t.sol +++ b/test/integration/TaikoDaoFactory.t.sol @@ -22,6 +22,7 @@ import {createProxyAndCall} from "../../src/helpers/proxy.sol"; import {MultisigPluginSetup} from "../../src/setup/MultisigPluginSetup.sol"; import {EmergencyMultisigPluginSetup} from "../../src/setup/EmergencyMultisigPluginSetup.sol"; import {OptimisticTokenVotingPluginSetup} from "../../src/setup/OptimisticTokenVotingPluginSetup.sol"; +import {SignerList} from "../../src/SignerList.sol"; contract TaikoDaoFactoryTest is AragonTest { function test_ShouldStoreTheSettings_1() public { @@ -393,6 +394,16 @@ contract TaikoDaoFactoryTest is AragonTest { "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" ); + // Signer list + + assertEq(deployment.signerList.addresslistLength(), 13, "Invalid addresslistLength"); + for (uint256 i = 0; i < 13; i++) { + assertEq(deployment.signerList.isListed(multisigMembers[i]), true, "Should be a member"); + } + for (uint256 i = 14; i < 50; i++) { + assertEq(deployment.signerList.isListed(address(uint160(i))), false, "Should not be a member"); + } + // Multisig plugin assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); @@ -402,20 +413,20 @@ contract TaikoDaoFactoryTest is AragonTest { "Invalid lastMultisigSettingsChange" ); assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); - assertEq(deployment.multisigPlugin.addresslistLength(), 13, "Invalid addresslistLength"); - for (uint256 i = 0; i < 13; i++) { - assertEq(deployment.multisigPlugin.isMember(multisigMembers[i]), true, "Should be a member"); - } - for (uint256 i = 14; i < 50; i++) { - assertEq(deployment.multisigPlugin.isMember(address(uint160(i))), false, "Should not be a member"); - } + { - (bool onlyListed, uint16 minApprovals, uint64 destinationProposalDuration, uint64 expirationPeriod) = - deployment.multisigPlugin.multisigSettings(); + ( + bool onlyListed, + uint16 minApprovals, + uint64 destinationProposalDuration, + SignerList signerList, + uint64 expirationPeriod + ) = deployment.multisigPlugin.multisigSettings(); assertEq(onlyListed, true, "Invalid onlyListed"); assertEq(minApprovals, 7, "Invalid minApprovals"); assertEq(destinationProposalDuration, 10 days, "Invalid destinationProposalDuration"); + assertEq(address(signerList), address(deployment.signerList), "Incorrect signerList"); assertEq(expirationPeriod, 15 days, "Invalid expirationPeriod"); } @@ -428,19 +439,13 @@ contract TaikoDaoFactoryTest is AragonTest { "Invalid lastMultisigSettingsChange" ); assertEq(deployment.emergencyMultisigPlugin.proposalCount(), 0, "Invalid proposal count"); - for (uint256 i = 0; i < 13; i++) { - assertEq(deployment.emergencyMultisigPlugin.isMember(multisigMembers[i]), true, "Should be a member"); - } - for (uint256 i = 14; i < 50; i++) { - assertEq(deployment.emergencyMultisigPlugin.isMember(address(uint160(i))), false, "Should not be a member"); - } { - (bool onlyListed, uint16 minApprovals, Addresslist addresslistSource, uint64 expirationPeriod) = + (bool onlyListed, uint16 minApprovals, Addresslist signerList, uint64 expirationPeriod) = deployment.emergencyMultisigPlugin.multisigSettings(); assertEq(onlyListed, true, "Invalid onlyListed"); assertEq(minApprovals, 11, "Invalid minApprovals"); - assertEq(address(addresslistSource), address(deployment.multisigPlugin), "Invalid addresslistSource"); + assertEq(address(signerList), address(deployment.signerList), "Invalid signerList"); assertEq(expirationPeriod, 15 days, "Invalid expirationPeriod"); } @@ -519,7 +524,7 @@ contract TaikoDaoFactoryTest is AragonTest { // ENCRYPTION REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); assertEq( - deployment.encryptionRegistry.getRegisteredAddresses().length, 0, "Invalid getRegisteredAddresses().length" + deployment.encryptionRegistry.getRegisteredAccounts().length, 0, "Invalid getRegisteredAccounts().length" ); } @@ -632,6 +637,16 @@ contract TaikoDaoFactoryTest is AragonTest { "The DAO should have REGISTER_STANDARD_CALLBACK_PERMISSION_ID on itself" ); + // Signer List + + assertEq(deployment.signerList.addresslistLength(), 16, "Invalid addresslistLength"); + for (uint256 i = 0; i < 16; i++) { + assertEq(deployment.signerList.isListed(multisigMembers[i]), true, "Should be a member"); + } + for (uint256 i = 17; i < 50; i++) { + assertEq(deployment.signerList.isListed(address(uint160(i))), false, "Should not be a member"); + } + // Multisig plugin assertNotEq(address(deployment.multisigPlugin), address(0), "Empty multisig field"); @@ -641,20 +656,20 @@ contract TaikoDaoFactoryTest is AragonTest { "Invalid lastMultisigSettingsChange" ); assertEq(deployment.multisigPlugin.proposalCount(), 0, "Invalid proposal count"); - assertEq(deployment.multisigPlugin.addresslistLength(), 16, "Invalid addresslistLength"); - for (uint256 i = 0; i < 16; i++) { - assertEq(deployment.multisigPlugin.isMember(multisigMembers[i]), true, "Should be a member"); - } - for (uint256 i = 17; i < 50; i++) { - assertEq(deployment.multisigPlugin.isMember(address(uint160(i))), false, "Should not be a member"); - } + { - (bool onlyListed, uint16 minApprovals, uint64 destinationProposalDuration, uint64 expirationPeriod) = - deployment.multisigPlugin.multisigSettings(); + ( + bool onlyListed, + uint16 minApprovals, + uint64 destinationProposalDuration, + SignerList signerList, + uint64 expirationPeriod + ) = deployment.multisigPlugin.multisigSettings(); assertEq(onlyListed, true, "Invalid onlyListed"); assertEq(minApprovals, 9, "Invalid minApprovals"); assertEq(destinationProposalDuration, 21 days, "Invalid destinationProposalDuration"); + assertEq(address(signerList), address(deployment.signerList), "Incorrect signerList"); assertEq(expirationPeriod, 22 days, "Invalid expirationPeriod"); } @@ -667,19 +682,13 @@ contract TaikoDaoFactoryTest is AragonTest { "Invalid lastMultisigSettingsChange" ); assertEq(deployment.emergencyMultisigPlugin.proposalCount(), 0, "Invalid proposal count"); - for (uint256 i = 0; i < 16; i++) { - assertEq(deployment.emergencyMultisigPlugin.isMember(multisigMembers[i]), true, "Should be a member"); - } - for (uint256 i = 17; i < 50; i++) { - assertEq(deployment.emergencyMultisigPlugin.isMember(address(uint160(i))), false, "Should not be a member"); - } { - (bool onlyListed, uint16 minApprovals, Addresslist addresslistSource, uint64 expirationPeriod) = + (bool onlyListed, uint16 minApprovals, Addresslist signerList, uint64 expirationPeriod) = deployment.emergencyMultisigPlugin.multisigSettings(); assertEq(onlyListed, true, "Invalid onlyListed"); assertEq(minApprovals, 15, "Invalid minApprovals"); - assertEq(address(addresslistSource), address(deployment.multisigPlugin), "Invalid addresslistSource"); + assertEq(address(signerList), address(deployment.signerList), "Invalid signerList"); assertEq(expirationPeriod, 22 days, "Invalid expirationPeriod"); } @@ -758,7 +767,7 @@ contract TaikoDaoFactoryTest is AragonTest { // ENCRYPTION REGISTRY assertNotEq(address(deployment.encryptionRegistry), address(0), "Empty encryptionRegistry field"); assertEq( - deployment.encryptionRegistry.getRegisteredAddresses().length, 0, "Invalid getRegisteredAddresses().length" + deployment.encryptionRegistry.getRegisteredAccounts().length, 0, "Invalid getRegisteredAccounts().length" ); } From faf711c220ea99dedf231e6fcad5e05e2c89e663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 7 Nov 2024 16:46:30 +0700 Subject: [PATCH 26/45] Adapted emergency plugin setup and factory --- src/factory/TaikoDaoFactory.sol | 2 +- test/EmergencyMultisigPluginSetup.t.sol | 103 ++++++++---------------- 2 files changed, 34 insertions(+), 71 deletions(-) diff --git a/src/factory/TaikoDaoFactory.sol b/src/factory/TaikoDaoFactory.sol index da168c9..b5733fa 100644 --- a/src/factory/TaikoDaoFactory.sol +++ b/src/factory/TaikoDaoFactory.sol @@ -114,7 +114,7 @@ contract TaikoDaoFactory { DAO dao = prepareDao(); deployment.dao = dao; - // DEPLOY THE SIGNER LIST AND REGISTRY + // DEPLOY THE SIGNER LIST AND THE ENCRYPTION REGISTRY (deployment.signerList, deployment.encryptionRegistry) = prepareSignerListAndEncryptionRegistry(dao); // DEPLOY THE PLUGINS diff --git a/test/EmergencyMultisigPluginSetup.t.sol b/test/EmergencyMultisigPluginSetup.t.sol index ec5715a..9ff45fe 100644 --- a/test/EmergencyMultisigPluginSetup.t.sol +++ b/test/EmergencyMultisigPluginSetup.t.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; -import {Test} from "forge-std/Test.sol"; +import {AragonTest} from "./base/AragonTest.sol"; +import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; -import {Multisig} from "../src/Multisig.sol"; +import { + SignerList, + UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID, + UPDATE_SIGNER_LIST_PERMISSION_ID +} from "../src/SignerList.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"; @@ -17,52 +22,31 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import {ERC20Mock} from "./mocks/ERC20Mock.sol"; import {ITaikoL1} from "../src/adapted-dependencies/ITaikoL1.sol"; -contract EmergencyMultisigPluginSetupTest is Test { +contract EmergencyMultisigPluginSetupTest is AragonTest { EmergencyMultisigPluginSetup public pluginSetup; GovernanceERC20 governanceERC20Base; GovernanceWrappedERC20 governanceWrappedERC20Base; address immutable daoBase = address(new DAO()); - address immutable stdMultisigBase = address(new Multisig()); + address immutable signerListBase = address(new SignerList()); 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(); + address[] signers; + SignerList signerList; function setUp() public { - pluginSetup = new EmergencyMultisigPluginSetup(); + DaoBuilder builder = new DaoBuilder(); + (dao,,,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember(carol) + .withMultisigMember(david).build(); - // 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, - proposalExpirationPeriod: 15 days - }); - stdMultisig = Multisig( - createProxyAndCall( - stdMultisigBase, abi.encodeCall(Multisig.initialize, (IDAO(dao), stdMembers, stdSettings)) - ) - ); + pluginSetup = new EmergencyMultisigPluginSetup(); // Default params eMultisigSettings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 3, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 15 days }); } @@ -72,37 +56,28 @@ contract EmergencyMultisigPluginSetupTest is Test { bytes memory output = pluginSetup.encodeInstallationParameters(eMultisigSettings); bytes memory expected = - hex"000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000005991a2df15a8f6a256d3ec51e99254cd3fb576a9000000000000000000000000000000000000000000000000000000000013c680"; + hex"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000a0279152cf631d6c493901f9b576d88e2847bfa1000000000000000000000000000000000000000000000000000000000013c680"; 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, - proposalExpirationPeriod: 17 days - }); - stdMultisig = Multisig( - createProxyAndCall( - stdMultisigBase, abi.encodeCall(Multisig.initialize, (IDAO(dao), stdMembers, stdSettings)) - ) - ); + signers = new address[](2); + signers[0] = alice; + signers[1] = bob; + signerList = + SignerList(createProxyAndCall(signerListBase, abi.encodeCall(SignerList.initialize, (IDAO(dao), signers)))); eMultisigSettings = EmergencyMultisig.MultisigSettings({ onlyListed: true, minApprovals: 1, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 17 days }); bytes memory output = pluginSetup.encodeInstallationParameters(eMultisigSettings); bytes memory expected = - hex"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000c7183455a4c133ae270771860664b6b7ec320bb10000000000000000000000000000000000000000000000000000000000166980"; + hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000003a6a84cd762d9707a21605b548aaab891562aab0000000000000000000000000000000000000000000000000000000000166980"; assertEq(output, expected, "Incorrect encoded bytes"); } @@ -116,34 +91,22 @@ contract EmergencyMultisigPluginSetupTest is Test { 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" - ); + assertEq(address(outSettings.signerList), address(signerList), "Incorrect signer list"); } 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, - proposalExpirationPeriod: 5 days - }); - stdMultisig = Multisig( - createProxyAndCall( - stdMultisigBase, abi.encodeCall(Multisig.initialize, (IDAO(dao), stdMembers, stdSettings)) - ) - ); + signers = new address[](2); + signers[0] = alice; + signers[1] = bob; + signerList = + SignerList(createProxyAndCall(signerListBase, abi.encodeCall(SignerList.initialize, (IDAO(dao), signers)))); + eMultisigSettings = EmergencyMultisig.MultisigSettings({ onlyListed: false, minApprovals: 1, - addresslistSource: stdMultisig, + signerList: signerList, proposalExpirationPeriod: 5 days }); @@ -155,7 +118,7 @@ contract EmergencyMultisigPluginSetupTest is Test { assertEq(outSettings.onlyListed, false, "Should be false"); assertEq(outSettings.minApprovals, 1, "Should be 1"); - assertEq(address(outSettings.addresslistSource), address(stdMultisig), "Incorrect address list source"); + assertEq(address(outSettings.signerList), address(signerList), "Incorrect signer list"); } function test_PrepareInstallationReturnsTheProperPermissions() public { From 225fc81d6b332664303b8c0456573b3f1dc46b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 7 Nov 2024 17:03:22 +0700 Subject: [PATCH 27/45] Adapted tests WIP --- test/EmergencyMultisigPluginSetup.t.sol | 10 +-- test/MultisigPluginSetup.t.sol | 94 +++++++++----------- test/OptimisticTokenVotingPlugin.t.sol | 113 ++++++++++++------------ 3 files changed, 98 insertions(+), 119 deletions(-) diff --git a/test/EmergencyMultisigPluginSetup.t.sol b/test/EmergencyMultisigPluginSetup.t.sol index 9ff45fe..94b8122 100644 --- a/test/EmergencyMultisigPluginSetup.t.sol +++ b/test/EmergencyMultisigPluginSetup.t.sol @@ -4,28 +4,20 @@ pragma solidity ^0.8.17; import {AragonTest} from "./base/AragonTest.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; +import {EmergencyMultisigPluginSetup} from "../src/setup/EmergencyMultisigPluginSetup.sol"; import { SignerList, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID, UPDATE_SIGNER_LIST_PERMISSION_ID } from "../src/SignerList.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 {ITaikoL1} from "../src/adapted-dependencies/ITaikoL1.sol"; contract EmergencyMultisigPluginSetupTest is AragonTest { EmergencyMultisigPluginSetup public pluginSetup; - GovernanceERC20 governanceERC20Base; - GovernanceWrappedERC20 governanceWrappedERC20Base; address immutable daoBase = address(new DAO()); address immutable signerListBase = address(new SignerList()); DAO dao; diff --git a/test/MultisigPluginSetup.t.sol b/test/MultisigPluginSetup.t.sol index b047141..8bd2a13 100644 --- a/test/MultisigPluginSetup.t.sol +++ b/test/MultisigPluginSetup.t.sol @@ -1,40 +1,37 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; -import {Test} from "forge-std/Test.sol"; +import {AragonTest} from "./base/AragonTest.sol"; +import {DaoBuilder} from "./helpers/DaoBuilder.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 { + SignerList, + UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID, + UPDATE_SIGNER_LIST_PERMISSION_ID +} from "../src/SignerList.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 {ITaikoL1} from "../src/adapted-dependencies/ITaikoL1.sol"; -contract MultisigPluginSetupTest is Test { +contract MultisigPluginSetupTest is AragonTest { MultisigPluginSetup public pluginSetup; - GovernanceERC20 governanceERC20Base; - GovernanceWrappedERC20 governanceWrappedERC20Base; address immutable daoBase = address(new DAO()); + address immutable signerListBase = address(new SignerList()); 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(); + address[] signers; + SignerList signerList; function setUp() public { + DaoBuilder builder = new DaoBuilder(); + (dao,,,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember(carol) + .withMultisigMember(david).build(); + pluginSetup = new MultisigPluginSetup(); // Default params @@ -42,57 +39,48 @@ contract MultisigPluginSetupTest is Test { onlyListed: true, minApprovals: 3, destinationProposalDuration: 10 days, + signerList: signerList, proposalExpirationPeriod: 15 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 output = pluginSetup.encodeInstallationParameters(multisigSettings); bytes memory expected = - hex"00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000d2f00000000000000000000000000000000000000000000000000000000000013c680000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000a11ce0000000000000000000000000000000000000000000000000000000000000b0b00000000000000000000000000000000000000000000000000000000000c4601000000000000000000000000000000000000000000000000000000000000d473"; + hex"0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000d2f00000000000000000000000000a0279152cf631d6c493901f9b576d88e2847bfa1000000000000000000000000000000000000000000000000000000000013c680"; assertEq(output, expected, "Incorrect encoded bytes"); } function test_ShouldEncodeInstallationParameters_2() public { // 2 + signers = new address[](2); + signers[0] = alice; + signers[1] = bob; + signerList = + SignerList(createProxyAndCall(signerListBase, abi.encodeCall(SignerList.initialize, (IDAO(dao), signers)))); + multisigSettings = Multisig.MultisigSettings({ onlyListed: true, minApprovals: 1, destinationProposalDuration: 5 days, + signerList: signerList, proposalExpirationPeriod: 33 days }); - members = new address[](2); - members[0] = alice; - members[1] = bob; - - bytes memory output = pluginSetup.encodeInstallationParameters(members, multisigSettings); + bytes memory output = pluginSetup.encodeInstallationParameters(multisigSettings); bytes memory expected = - hex"00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000006978000000000000000000000000000000000000000000000000000000000002b8180000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000a11ce0000000000000000000000000000000000000000000000000000000000000b0b"; + hex"00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000006978000000000000000000000000003a6a84cd762d9707a21605b548aaab891562aab00000000000000000000000000000000000000000000000000000000002b8180"; assertEq(output, expected, "Incorrect encoded bytes"); } function test_ShouldDecodeInstallationParameters_1() public view { // 1 - bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + bytes memory installationParams = pluginSetup.encodeInstallationParameters(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"); + (Multisig.MultisigSettings memory outSettings) = pluginSetup.decodeInstallationParameters(installationParams); assertEq(outSettings.onlyListed, true, "Should be true"); assertEq(outSettings.minApprovals, 3, "Should be 3"); @@ -101,26 +89,24 @@ contract MultisigPluginSetupTest is Test { function test_ShouldDecodeInstallationParameters_2() public { // 2 + signers = new address[](2); + signers[0] = alice; + signers[1] = bob; + signerList = + SignerList(createProxyAndCall(signerListBase, abi.encodeCall(SignerList.initialize, (IDAO(dao), signers)))); + multisigSettings = Multisig.MultisigSettings({ onlyListed: false, minApprovals: 1, destinationProposalDuration: 5 days, + signerList: signerList, proposalExpirationPeriod: 55 days }); - members = new address[](2); - members[0] = alice; - members[1] = bob; - - bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + bytes memory installationParams = pluginSetup.encodeInstallationParameters(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"); + (Multisig.MultisigSettings memory outSettings) = pluginSetup.decodeInstallationParameters(installationParams); assertEq(outSettings.onlyListed, false, "Should be false"); assertEq(outSettings.minApprovals, 1, "Should be 1"); @@ -128,7 +114,7 @@ contract MultisigPluginSetupTest is Test { } function test_PrepareInstallationReturnsTheProperPermissions() public { - bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + bytes memory installationParams = pluginSetup.encodeInstallationParameters(multisigSettings); (address _plugin, IPluginSetup.PreparedSetupData memory _preparedSetupData) = pluginSetup.prepareInstallation(address(dao), installationParams); @@ -167,7 +153,7 @@ contract MultisigPluginSetupTest is Test { function test_PrepareUninstallationReturnsTheProperPermissions_1() public { // Prepare a dummy install - bytes memory installationParams = pluginSetup.encodeInstallationParameters(members, multisigSettings); + bytes memory installationParams = pluginSetup.encodeInstallationParameters(multisigSettings); (address _dummyPlugin, IPluginSetup.PreparedSetupData memory _preparedSetupData) = pluginSetup.prepareInstallation(address(dao), installationParams); diff --git a/test/OptimisticTokenVotingPlugin.t.sol b/test/OptimisticTokenVotingPlugin.t.sol index 259b60b..a01beb0 100644 --- a/test/OptimisticTokenVotingPlugin.t.sol +++ b/test/OptimisticTokenVotingPlugin.t.sol @@ -53,7 +53,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { builder = new DaoBuilder(); // alice has root permission on the DAO, is a multisig member, holds tokens and can create proposals // on the optimistic token voting plugin - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.build(); + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.build(); } // Initialize @@ -405,7 +405,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withTokenHolder(alice, 5 ether).withTokenHolder(bob, 200 ether) + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 5 ether).withTokenHolder(bob, 200 ether) .withTokenHolder(carol, 2.5 ether).build(); assertEq(optimisticPlugin.totalVotingPower(block.timestamp - 1), 207.5 ether, "Incorrect total voting power"); @@ -413,7 +413,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withTokenHolder(alice, 50 ether).withTokenHolder(bob, 30 ether) + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 50 ether).withTokenHolder(bob, 30 ether) .withTokenHolder(carol, 0.1234 ether).withTokenHolder(david, 100 ether).build(); assertEq(optimisticPlugin.totalVotingPower(block.timestamp - 1), 180.1234 ether, "Incorrect total voting power"); @@ -428,7 +428,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 1 bridged tokens builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); assertEq(optimisticPlugin.bridgedVotingPower(block.timestamp - 1), 10 ether, "Incorrect bridged voting power"); @@ -437,7 +437,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 bridged tokens builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 1 ether) + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 1 ether) .withTokenHolder(taikoBridge, 1).build(); assertEq(optimisticPlugin.bridgedVotingPower(block.timestamp - 1), 1, "Incorrect bridged voting power"); @@ -460,7 +460,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 1 bridged tokens builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); assertEq( @@ -476,7 +476,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 bridged tokens builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 1 ether) + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 1 ether) .withTokenHolder(taikoBridge, 1234).build(); assertEq( @@ -496,19 +496,19 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withMinVetoRatio(1_000).build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withMinVetoRatio(1_000).build(); assertEq(optimisticPlugin.minVetoRatio(), 1_000, "Incorrect minVetoRatio"); // 3 builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withMinVetoRatio(500_000).build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withMinVetoRatio(500_000).build(); assertEq(optimisticPlugin.minVetoRatio(), 500_000, "Incorrect minVetoRatio"); // 4 builder = new DaoBuilder(); - (, optimisticPlugin,,, votingToken,) = builder.withMinVetoRatio(300_000).build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withMinVetoRatio(300_000).build(); assertEq(optimisticPlugin.minVetoRatio(), 300_000, "Incorrect minVetoRatio"); } @@ -552,16 +552,16 @@ contract OptimisticTokenVotingPluginTest is AragonTest { assertEq(optimisticPlugin.isL2Available(), true, "isL2Available should be true"); // skipL2 setting - (, optimisticPlugin,,, votingToken,) = builder.withSkipL2().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withSkipL2().build(); assertEq(optimisticPlugin.isL2Available(), false, "isL2Available should be false"); builder.withoutSkipL2(); // paused - (, optimisticPlugin,,, votingToken,) = builder.withPausedTaikoL1().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withPausedTaikoL1().build(); assertEq(optimisticPlugin.isL2Available(), false, "isL2Available should be false"); // out of sync - (, optimisticPlugin,,, votingToken,) = builder.withOutOfSyncTaikoL1().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withOutOfSyncTaikoL1().build(); assertEq(optimisticPlugin.isL2Available(), false, "isL2Available should be false"); // out of sync: diff below lowerl2InactivityPeriod @@ -570,12 +570,12 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // out of sync: still within the period vm.warp(50 days); - (, optimisticPlugin,,, votingToken,) = builder.withL2InactivityPeriod(50 days).build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withL2InactivityPeriod(50 days).build(); assertEq(optimisticPlugin.isL2Available(), true, "isL2Available should be true"); // out of sync: over vm.warp(50 days + 1); - (, optimisticPlugin,,, votingToken,) = builder.withL2InactivityPeriod(50 days).build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withL2InactivityPeriod(50 days).build(); assertEq(optimisticPlugin.isL2Available(), false, "isL2Available should be false"); } @@ -616,7 +616,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_CreateProposalRevertsIfThereIsNoVotingPowerOnlyL1Tokens() public { // 1 // Paused L2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withPausedTaikoL1().withTokenHolder(alice, 0).withProposerOnOptimistic(alice).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -631,7 +631,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 // Out of sync L2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOutOfSyncTaikoL1().withTokenHolder(alice, 0).withProposerOnOptimistic(alice).build(); vm.expectRevert(abi.encodeWithSelector(OptimisticTokenVotingPlugin.NoVotingPower.selector)); @@ -646,7 +646,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 3 // Taiko Bridge now has voting power (should be ignored) // Paused L2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withPausedTaikoL1().withTokenHolder( + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withPausedTaikoL1().withTokenHolder( taikoBridge, 10000 ether ).withProposerOnOptimistic(alice).build(); @@ -661,7 +661,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 4 // Out of sync L2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withOutOfSyncTaikoL1().withTokenHolder( + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOutOfSyncTaikoL1().withTokenHolder( taikoBridge, 10000 ether ).withProposerOnOptimistic(alice).build(); @@ -677,7 +677,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_CreateProposalRevertsIfThereIsNoVotingPowerWithL1L2Tokens() public { // 1 - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOkTaikoL1().withTokenHolder(alice, 0).withProposerOnOptimistic(alice).build(); // Try to create @@ -692,7 +692,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { optimisticPlugin.createProposal("", actions, 0, 4 days); // 2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOkTaikoL1().withTokenHolder(taikoBridge, 0).withProposerOnOptimistic(alice).build(); // Try to create @@ -707,7 +707,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { optimisticPlugin.createProposal("", actions, 0, 4 days); // 2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOkTaikoL1().withTokenHolder(taikoBridge, 0).withProposerOnOptimistic(alice).build(); // Try to create @@ -724,7 +724,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_CreateProposalRevertsIfDurationIsLowerThanMin() public { vm.startPrank(alice); - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withMinDuration(0).withProposerOnOptimistic(alice).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -733,7 +733,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { optimisticPlugin.createProposal("", actions, 0, 0); // 2 ko - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withMinDuration(10 minutes).withProposerOnOptimistic(alice).build(); vm.expectRevert( @@ -745,7 +745,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { optimisticPlugin.createProposal("", actions, 0, 10 minutes); // 4 ko - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withMinDuration(10 hours).withProposerOnOptimistic(alice).build(); vm.expectRevert( @@ -768,7 +768,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { } function test_CreateProposalStartsDespiteRevertingTaikoL1() public { - (dao, optimisticPlugin,,,,) = builder.withIncompatibleTaikoL1().build(); + (dao, optimisticPlugin,,,,,,) = builder.withIncompatibleTaikoL1().build(); vm.warp(2 days); @@ -783,7 +783,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { vm.warp(50 days - 1); // L2 Paused - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withPausedTaikoL1().build(); + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withPausedTaikoL1().build(); IDAO.Action[] memory actions = new IDAO.Action[](0); uint256 proposalId = optimisticPlugin.createProposal("", actions, 0, 10 days); @@ -806,7 +806,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 out of sync vm.warp(50 days - 1); - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withOutOfSyncTaikoL1().build(); + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOutOfSyncTaikoL1().build(); actions = new IDAO.Action[](0); proposalId = optimisticPlugin.createProposal("", actions, 0, 10 days); @@ -829,7 +829,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_CreateProposalEndsAfterMinDurationWithL1L2Tokens() public { // 1 vm.warp(50 days - 1); - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.build(); + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.build(); IDAO.Action[] memory actions = new IDAO.Action[](0); uint256 proposalId = optimisticPlugin.createProposal("", actions, 0, 10 days); @@ -852,7 +852,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 // With tokens on the Taiko Bridge vm.warp(50 days - 1); - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); proposalId = optimisticPlugin.createProposal("", actions, 0, 10 days); @@ -1045,7 +1045,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 - With L2 tokens vm.warp(3 days - 1); - (dao, optimisticPlugin,,, votingToken, taikoL1) = + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); vetoPeriod = 30 days; @@ -1094,7 +1094,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 3 with L2 paused vm.warp(3 days - 1); - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withPausedTaikoL1().build(); + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withPausedTaikoL1().build(); vetoPeriod = 15 days; actions[0].to = carol; @@ -1141,7 +1141,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 4 with L2 out of sync vm.warp(3 days - 1); - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withOutOfSyncTaikoL1().build(); + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOutOfSyncTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://my-uri", actions, failSafeBitmap, vetoPeriod); @@ -1261,7 +1261,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { } function test_CanVetoReturnsFalseForTheBridge() public { - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withOkTaikoL1().withTokenHolder(alice, 10 ether) + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOkTaikoL1().withTokenHolder(alice, 10 ether) .withTokenHolder(bob, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1351,7 +1351,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { } function test_VetoRevertsForTheBridge() public { - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withOkTaikoL1().withTokenHolder(alice, 10 ether) + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withOkTaikoL1().withTokenHolder(alice, 10 ether) .withTokenHolder(bob, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1400,7 +1400,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { optimisticPlugin.veto(proposalId); // 2 - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withTokenHolder(alice, 5 ether).withTokenHolder( + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withTokenHolder(alice, 5 ether).withTokenHolder( bob, 10 ether ).withTokenHolder(carol, 15 ether).build(); @@ -1423,7 +1423,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // Has vetoed function test_HasVetoedReturnsTheRightValues() public { - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withTokenHolder(alice, 5 ether).withTokenHolder( + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withTokenHolder(alice, 5 ether).withTokenHolder( bob, 10 ether ).withTokenHolder(carol, 15 ether).build(); @@ -1491,7 +1491,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 vetoes required when L1 only // 3 vetoes required when L1+L2 - (, optimisticPlugin,,, votingToken,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 10 ether) + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 10 ether) .withTokenHolder(taikoBridge, 10 ether).withMinVetoRatio(700_000).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1510,7 +1510,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 paused, less token supply // Alice and Bob are now 100% - (, optimisticPlugin,,, votingToken,) = builder.withPausedTaikoL1().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withPausedTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); @@ -1526,7 +1526,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 out of sync, less token supply // Alice and Bob are now 100% - (, optimisticPlugin,,, votingToken,) = builder.withOutOfSyncTaikoL1().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withOutOfSyncTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); @@ -1556,7 +1556,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_CanExecuteReturnsFalseWhenEndedButL2GracePeriodUnmet() public { // An ended proposal with L2 enabled has an additional grace period - (, optimisticPlugin,,,,) = + (, optimisticPlugin,,,,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1617,7 +1617,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 vetoes required when L1 only // 3 vetoes required when L1+L2 - (, optimisticPlugin,,, votingToken,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 10 ether) + (, optimisticPlugin,,, votingToken,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 10 ether) .withTokenHolder(taikoBridge, 10 ether).withMinVetoRatio(700_000).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1640,7 +1640,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 paused, less token supply // Alice and Bob are now 100% - (, optimisticPlugin,,, votingToken,) = builder.withPausedTaikoL1().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withPausedTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); @@ -1660,7 +1660,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 out of sync, less token supply // Alice and Bob are now 100% - (, optimisticPlugin,,, votingToken,) = builder.withOutOfSyncTaikoL1().build(); + (, optimisticPlugin,,, votingToken,,,) = builder.withOutOfSyncTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); @@ -1681,7 +1681,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_CanExecuteReturnsTrueWhenSkipL2AndFullyEnded() public { // An ended proposal with L2 skipped - (, optimisticPlugin,,,,) = + (, optimisticPlugin,,,,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).withSkipL2().build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1723,7 +1723,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { } function test_CanExecuteReturnsTrueOnDurationZero_WithoutL2GracePeriodOrExitWindow() public { - (, optimisticPlugin,,,,) = builder.withMinDuration(0).build(); + (, optimisticPlugin,,,,,,) = builder.withMinDuration(0).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); uint256 proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 0 days); @@ -1746,7 +1746,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { assertEq(optimisticPlugin.canExecute(proposalId), false, "The proposal shouldn't be executable yet"); - vm.warp(block.timestamp + 4 days - 1) ; + vm.warp(block.timestamp + 4 days - 1); assertEq(optimisticPlugin.canExecute(proposalId), false, "The proposal shouldn't be executable yet"); // Ended @@ -1754,7 +1754,8 @@ contract OptimisticTokenVotingPluginTest is AragonTest { assertEq(optimisticPlugin.canExecute(proposalId), false, "The proposal shouldn't be executable yet"); // No L2 votes available, so no L2 aggregation period - (,, OptimisticTokenVotingPlugin.ProposalParameters memory parameters,,,,) = optimisticPlugin.getProposal(proposalId); + (,, OptimisticTokenVotingPlugin.ProposalParameters memory parameters,,,,) = + optimisticPlugin.getProposal(proposalId); assertEq(parameters.unavailableL2, true, "unavailableL2 should be true"); vm.warp(block.timestamp + optimisticPlugin.EXIT_WINDOW() - 1); @@ -1767,7 +1768,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // Veto threshold reached function test_IsMinVetoRatioReachedReturnsTheAppropriateValuesOnlyL1Tokens() public { - (dao, optimisticPlugin,,, votingToken, taikoL1) = builder.withMinVetoRatio(250_000).withTokenHolder( + (dao, optimisticPlugin,,, votingToken,,, taikoL1) = builder.withMinVetoRatio(250_000).withTokenHolder( alice, 24 ether ).withTokenHolder(bob, 1 ether).withTokenHolder(randomWallet, 75 ether).build(); @@ -1798,7 +1799,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_IsMinVetoRatioReachedReturnsTheAppropriateValuesWithL1L2Tokens() public { // 200/300 vs 200/400 scenario builder = new DaoBuilder(); - (, optimisticPlugin,,,,) = builder.withOkTaikoL1().withMinVetoRatio(510_000).withTokenHolder(alice, 100 ether) + (, optimisticPlugin,,,,,,) = builder.withOkTaikoL1().withMinVetoRatio(510_000).withTokenHolder(alice, 100 ether) .withTokenHolder(bob, 100 ether).withTokenHolder(carol, 100 ether).withTokenHolder(taikoBridge, 100 ether).build( ); @@ -1822,7 +1823,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 paused vm.startPrank(alice); - (, optimisticPlugin,,,,) = builder.withPausedTaikoL1().build(); + (, optimisticPlugin,,,,,,) = builder.withPausedTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); (,, parameters,,,,) = optimisticPlugin.getProposal(proposalId); @@ -1843,7 +1844,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 out of sync vm.startPrank(alice); - (, optimisticPlugin,,,,) = builder.withOutOfSyncTaikoL1().build(); + (, optimisticPlugin,,,,,,) = builder.withOutOfSyncTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); (,, parameters,,,,) = optimisticPlugin.getProposal(proposalId); @@ -1961,7 +1962,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // 2 vetoes required when L1 only // 3 vetoes required when L1+L2 - (, optimisticPlugin,,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 10 ether) + (, optimisticPlugin,,,,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(bob, 10 ether) .withTokenHolder(taikoBridge, 10 ether).withMinVetoRatio(700_000).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1989,7 +1990,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 paused, less token supply // Alice and Bob are now 100% - (, optimisticPlugin,,,,) = builder.withPausedTaikoL1().build(); + (, optimisticPlugin,,,,,,) = builder.withPausedTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); @@ -2016,7 +2017,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { // L2 out of sync, less token supply // Alice and Bob are now 100% - (, optimisticPlugin,,,,) = builder.withOutOfSyncTaikoL1().build(); + (, optimisticPlugin,,,,,,) = builder.withOutOfSyncTaikoL1().build(); proposalId = optimisticPlugin.createProposal("ipfs://", actions, 0, 4 days); @@ -2115,7 +2116,7 @@ contract OptimisticTokenVotingPluginTest is AragonTest { function test_ExecuteRevertsWhenEndedBeforeGracePeriodOrExitWindow_L1L2Tokens() public { // An ended proposal with L2 enabled has an additional grace period - (, optimisticPlugin,,,,) = + (, optimisticPlugin,,,,,,) = builder.withTokenHolder(alice, 10 ether).withTokenHolder(taikoBridge, 10 ether).build(); IDAO.Action[] memory actions = new IDAO.Action[](0); From 8bca1d12f5a9c87cf0b42330b6e52725db9e4b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 7 Nov 2024 18:02:22 +0700 Subject: [PATCH 28/45] Encryption registry changes --- test/EncryptionRegistry.t.sol | 66 +++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index 40698e2..74646bb 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -147,6 +147,9 @@ contract EncryptionRegistryTest is AragonTest { function testFuzz_ShouldRegisterMemberPublicKeys(address appointedWallet) public { if (Address.isContract(appointedWallet)) return; + else if (Address.isContract(address(uint160(appointedWallet) + 1))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 2))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 3))) return; address addrValue; bytes32 bytesValue; @@ -167,56 +170,59 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); - registry.appointWallet(appointedWallet); - vm.startPrank(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 1)); + vm.startPrank(address(uint160(appointedWallet) + 1)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); - registry.appointWallet(appointedWallet); - vm.startPrank(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 2)); + vm.startPrank(address(uint160(appointedWallet) + 2)); registry.setPublicKey(carol, 0x0000000090ab0000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 2)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); - registry.appointWallet(appointedWallet); - vm.startPrank(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 3)); + vm.startPrank(address(uint160(appointedWallet) + 3)); registry.setPublicKey(david, 0x000000000000cdef000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 2)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(david); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 3)); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } function test_ShouldClearPublicKeyAfterAppointing(address appointedWallet) public { if (appointedWallet == address(0)) return; else if (Address.isContract(appointedWallet)) return; + else if (Address.isContract(address(uint160(appointedWallet) + 1))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 2))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 3))) return; address addrValue; bytes32 bytesValue; @@ -243,13 +249,13 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 1)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol @@ -258,16 +264,16 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(carol); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 2)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 2)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David @@ -276,19 +282,19 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(david); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 3)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 2)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(david); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 3)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } @@ -374,10 +380,10 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 1)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(appointedWallet); + vm.startPrank(address(uint160(appointedWallet) + 1)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(bob, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -391,10 +397,10 @@ contract EncryptionRegistryTest is AragonTest { // Carol vm.startPrank(carol); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 2)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(appointedWallet); + vm.startPrank(address(uint160(appointedWallet) + 2)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(carol, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -411,10 +417,10 @@ contract EncryptionRegistryTest is AragonTest { // David vm.startPrank(david); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 3)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(appointedWallet); + vm.startPrank(address(uint160(appointedWallet) + 3)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(david, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -468,14 +474,14 @@ contract EncryptionRegistryTest is AragonTest { assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - registry.appointWallet(appointedWallet); + registry.appointWallet(address(uint160(appointedWallet) + 1)); // Appointed - vm.startPrank(appointedWallet); + vm.startPrank(address(uint160(appointedWallet) + 1)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, appointedWallet); + assertEq(addrValue, address(uint160(appointedWallet) + 1)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); } From 52c8e200eace5e3071f64c5ef6a74353c927e764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 8 Nov 2024 14:05:39 +0700 Subject: [PATCH 29/45] Multisigs WIP --- src/Multisig.sol | 2 +- test/EmergencyMultisigTree.t.sol | 124 ++++++++++++++++++++++++++++++- test/MultisigTree.t.sol | 121 +++++++++++++++++++++++++++++- 3 files changed, 239 insertions(+), 8 deletions(-) diff --git a/src/Multisig.sol b/src/Multisig.sol index 4e135f0..883afb9 100644 --- a/src/Multisig.sol +++ b/src/Multisig.sol @@ -124,8 +124,8 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { event MultisigSettingsUpdated( bool onlyListed, uint16 indexed minApprovals, - SignerList signerList, uint64 destinationProposalDuration, + SignerList signerList, uint64 proposalExpirationPeriod ); diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 2947522..61a5139 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1,9 +1,43 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; -import {Test} from "forge-std/Test.sol"; +import {AragonTest} from "./base/AragonTest.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; +import {SignerList} from "../src/SignerList.sol"; +import {DaoBuilder} from "./helpers/DaoBuilder.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {createProxyAndCall} from "../src/helpers/proxy.sol"; + +uint64 constant EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; + +contract EmergencyMultisigTest is AragonTest { + SignerList signerList; + DaoBuilder builder; + DAO dao; + EmergencyMultisig eMultisig; + + address immutable SIGNER_LIST_BASE = address(new SignerList()); + + // Events/errors to be tested here (duplicate) + error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); + error InvalidAddresslistUpdate(address member); + + event MultisigSettingsUpdated( + bool onlyListed, uint16 indexed minApprovals, SignerList signerList, uint64 proposalExpirationPeriod + ); + + function setUp() public { + vm.startPrank(alice); + vm.warp(1 days); + vm.roll(100); + + builder = new DaoBuilder(); + (dao,,, eMultisig,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( + carol + ).withMultisigMember(david).build(); + } -contract EmergencyMultisigTest is Test { modifier givenANewlyDeployedContract() { _; } @@ -13,15 +47,99 @@ contract EmergencyMultisigTest is Test { } function test_GivenCallingInitialize() external givenANewlyDeployedContract givenCallingInitialize { + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 3, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + // It should initialize the first time + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + // It should refuse to initialize again + vm.expectRevert("Initializable: contract is already initialized"); + eMultisig.initialize(dao, settings); + // It should set the DAO address + + assertEq((address(eMultisig.dao())), address(dao), "Incorrect dao"); + // It should set the minApprovals + + (, uint16 minApprovals,,) = eMultisig.multisigSettings(); + assertEq(minApprovals, uint16(3), "Incorrect minApprovals"); + settings.minApprovals = 1; + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + (, minApprovals,,) = eMultisig.multisigSettings(); + assertEq(minApprovals, uint16(1), "Incorrect minApprovals"); + // It should set onlyListed + + (bool onlyListed,,,) = eMultisig.multisigSettings(); + assertEq(onlyListed, true, "Incorrect onlyListed"); + settings.onlyListed = false; + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + (onlyListed,,,) = eMultisig.multisigSettings(); + assertEq(onlyListed, false, "Incorrect onlyListed"); + // It should set signerList + + (,, Addresslist givenSignerList,) = eMultisig.multisigSettings(); + assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); + (,,,,, signerList,,) = builder.build(); + settings.signerList = signerList; + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + (,, signerList,) = eMultisig.multisigSettings(); + assertEq(address(signerList), address(settings.signerList), "Incorrect addresslistSource"); + // It should set proposalExpirationPeriod + + (,,, uint64 expirationPeriod) = eMultisig.multisigSettings(); + assertEq(expirationPeriod, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expirationPeriod"); + settings.proposalExpirationPeriod = 3 days; + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + (,,, expirationPeriod) = eMultisig.multisigSettings(); + assertEq(expirationPeriod, 3 days, "Incorrect expirationPeriod"); + // It should emit MultisigSettingsUpdated - vm.skip(true); + + (,,,,, SignerList newSignerList,,) = builder.build(); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 2, + signerList: newSignerList, + proposalExpirationPeriod: 15 days + }); + vm.expectEmit(); + emit MultisigSettingsUpdated(false, uint16(2), newSignerList, 15 days); + + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); } function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnInitialize() diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 232f4f6..c920598 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -1,9 +1,45 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.17; -import {Test} from "forge-std/Test.sol"; +import {AragonTest} from "./base/AragonTest.sol"; +import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; +import {Multisig} from "../src/Multisig.sol"; +import {SignerList} from "../src/SignerList.sol"; +import {DaoBuilder} from "./helpers/DaoBuilder.sol"; +import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {createProxyAndCall} from "../src/helpers/proxy.sol"; + +uint64 constant MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; + +contract MultisigTest is AragonTest { + SignerList signerList; + DaoBuilder builder; + DAO dao; + Multisig multisig; + + address immutable SIGNER_LIST_BASE = address(new SignerList()); + + // Events/errors to be tested here (duplicate) + error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); + error InvalidAddresslistUpdate(address member); + + event MultisigSettingsUpdated( + bool onlyListed, + uint16 indexed minApprovals, + uint64 destinationProposalDuration, + SignerList signerList, + uint64 proposalExpirationPeriod + ); + + function setUp() public { + vm.startPrank(alice); + + builder = new DaoBuilder(); + (dao,, multisig,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( + carol + ).withMultisigMember(david).build(); + } -contract MultisigTest is Test { modifier givenANewlyDeployedContract() { _; } @@ -13,16 +49,93 @@ contract MultisigTest is Test { } function test_GivenCallingInitialize() external givenANewlyDeployedContract givenCallingInitialize { + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 3, + destinationProposalDuration: 4 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + // It should initialize the first time + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + // It should refuse to initialize again + vm.expectRevert("Initializable: contract is already initialized"); + multisig.initialize(dao, settings); + // It should set the DAO address + + assertEq((address(multisig.dao())), address(dao), "Incorrect dao"); + // It should set the minApprovals + + (, uint16 minApprovals,,,) = multisig.multisigSettings(); + assertEq(minApprovals, uint16(3), "Incorrect minApprovals"); + settings.minApprovals = 1; + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + (, minApprovals,,,) = multisig.multisigSettings(); + assertEq(minApprovals, uint16(1), "Incorrect minApprovals"); + // It should set onlyListed - // It should set signerList + + (bool onlyListed,,,,) = multisig.multisigSettings(); + assertEq(onlyListed, true, "Incorrect onlyListed"); + settings.onlyListed = false; + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + (onlyListed,,,,) = multisig.multisigSettings(); + assertEq(onlyListed, false, "Incorrect onlyListed"); + // It should set destinationProposalDuration + + (,, uint64 destinationProposalDuration,,) = multisig.multisigSettings(); + assertEq(destinationProposalDuration, 4 days, "Incorrect destinationProposalDuration"); + settings.destinationProposalDuration = 3 days; + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + (,, destinationProposalDuration,,) = multisig.multisigSettings(); + assertEq(destinationProposalDuration, 3 days, "Incorrect destinationProposalDuration"); + + // It should set signerList + + (,,, Addresslist givenSignerList,) = multisig.multisigSettings(); + assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); + (,,,,, signerList,,) = builder.build(); + settings.signerList = signerList; + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + (,,, signerList,) = multisig.multisigSettings(); + assertEq(address(signerList), address(settings.signerList), "Incorrect addresslistSource"); + // It should set proposalExpirationPeriod + + (,,,, uint64 expirationPeriod) = multisig.multisigSettings(); + assertEq(expirationPeriod, MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expirationPeriod"); + settings.proposalExpirationPeriod = 3 days; + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + (,,,, expirationPeriod) = multisig.multisigSettings(); + assertEq(expirationPeriod, 3 days, "Incorrect expirationPeriod"); + // It should emit MultisigSettingsUpdated - vm.skip(true); + + (,,,,, SignerList newSignerList,,) = builder.build(); + + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 2, + destinationProposalDuration: 4 days, + signerList: newSignerList, + proposalExpirationPeriod: 15 days + }); + vm.expectEmit(); + emit MultisigSettingsUpdated(false, uint16(2), 4 days, newSignerList, 15 days); + + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnInitialize() From e4abfec9fd6df09e26f156042d36a2fceaf6e55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 8 Nov 2024 17:36:07 +0700 Subject: [PATCH 30/45] Multisig tests WIP --- test/EmergencyMultisigTree.t.sol | 676 ++++++++++++++++++++++++++++++- test/MultisigTree.t.sol | 674 +++++++++++++++++++++++++++++- 2 files changed, 1317 insertions(+), 33 deletions(-) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 61a5139..5612558 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -4,10 +4,16 @@ pragma solidity 0.8.17; import {AragonTest} from "./base/AragonTest.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; -import {SignerList} from "../src/SignerList.sol"; +import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; +import {SignerList, UPDATE_SIGNER_LIST_PERMISSION_ID} from "../src/SignerList.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {createProxyAndCall} from "../src/helpers/proxy.sol"; +import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; +import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; +import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; +import {IEmergencyMultisig} from "../src/interfaces/IEmergencyMultisig.sol"; uint64 constant EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; @@ -16,17 +22,35 @@ contract EmergencyMultisigTest is AragonTest { DaoBuilder builder; DAO dao; EmergencyMultisig eMultisig; + OptimisticTokenVotingPlugin optimisticPlugin; address immutable SIGNER_LIST_BASE = address(new SignerList()); // Events/errors to be tested here (duplicate) error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); error InvalidAddresslistUpdate(address member); + error InvalidActions(uint256 proposalId); event MultisigSettingsUpdated( bool onlyListed, uint16 indexed minApprovals, SignerList signerList, uint64 proposalExpirationPeriod ); + event EmergencyProposalCreated(uint256 indexed proposalId, address indexed creator, bytes encryptedPayloadURI); + + // OptimisticTokenVotingPlugin's event + event ProposalCreated( + uint256 indexed proposalId, + address indexed creator, + uint64 startDate, + uint64 endDate, + bytes metadata, + IDAO.Action[] actions, + uint256 allowFailureMap + ); + event Approved(uint256 indexed proposalId, address indexed approver); + event Executed(uint256 indexed proposalId); + event Upgraded(address indexed implementation); + function setUp() public { vm.startPrank(alice); vm.warp(1 days); @@ -35,7 +59,7 @@ contract EmergencyMultisigTest is AragonTest { builder = new DaoBuilder(); (dao,,, eMultisig,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( carol - ).withMultisigMember(david).build(); + ).withMultisigMember(david).withMinApprovals(3).build(); } modifier givenANewlyDeployedContract() { @@ -148,9 +172,60 @@ contract EmergencyMultisigTest is AragonTest { givenCallingInitialize { // It should revert + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 5, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + // It should revert (with onlyListed false) + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 5, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + // It should not revert otherwise - vm.skip(true); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 4, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); } function test_RevertWhen_MinApprovalsIsZeroOnInitialize() @@ -159,9 +234,60 @@ contract EmergencyMultisigTest is AragonTest { givenCallingInitialize { // It should revert + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 0, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 1, 0)); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + // It should revert (with onlyListed false) + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 0, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 1, 0)); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + // It should not revert otherwise - vm.skip(true); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 4, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); } function test_RevertWhen_SignerListIsInvalidOnInitialize() @@ -170,28 +296,123 @@ contract EmergencyMultisigTest is AragonTest { givenCallingInitialize { // It should revert - vm.skip(true); + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: SignerList(address(dao)), + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidSignerList.selector, address(dao))); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + + // ko 2 + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: SignerList(address(builder)), + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + + // ok + (,,,,, SignerList newSignerList,,) = builder.build(); + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: newSignerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); } function test_WhenCallingUpgradeTo() external { // It should revert when called without the permission + address initialImplementation = eMultisig.implementation(); + address _newImplementation = address(new EmergencyMultisig()); + + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(eMultisig), + alice, + eMultisig.UPGRADE_PLUGIN_PERMISSION_ID() + ) + ); + eMultisig.upgradeTo(_newImplementation); + assertEq(eMultisig.implementation(), initialImplementation); + // It should work when called with the permission - vm.skip(true); + dao.grant(address(eMultisig), alice, eMultisig.UPGRADE_PLUGIN_PERMISSION_ID()); + eMultisig.upgradeTo(_newImplementation); } function test_WhenCallingUpgradeToAndCall() external { // It should revert when called without the permission + address initialImplementation = eMultisig.implementation(); + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + address _newImplementation = address(new EmergencyMultisig()); + + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 3, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(eMultisig), + alice, + eMultisig.UPGRADE_PLUGIN_PERMISSION_ID() + ) + ); + eMultisig.upgradeToAndCall( + _newImplementation, abi.encodeCall(EmergencyMultisig.updateMultisigSettings, (settings)) + ); + assertEq(eMultisig.implementation(), initialImplementation); + // It should work when called with the permission - vm.skip(true); + dao.grant(address(eMultisig), alice, eMultisig.UPGRADE_PLUGIN_PERMISSION_ID()); + eMultisig.upgradeToAndCall( + _newImplementation, abi.encodeCall(EmergencyMultisig.updateMultisigSettings, (settings)) + ); } - function test_WhenCallingSupportsInterface() external { + function test_WhenCallingSupportsInterface() external view { // It does not support the empty interface + bool supported = eMultisig.supportsInterface(0); + assertEq(supported, false, "Should not support the empty interface"); + // It supports IERC165Upgradeable + supported = eMultisig.supportsInterface(type(IERC165Upgradeable).interfaceId); + assertEq(supported, true, "Should support IERC165Upgradeable"); + // It supports IPlugin + supported = eMultisig.supportsInterface(type(IPlugin).interfaceId); + assertEq(supported, true, "Should support IPlugin"); + // It supports IProposal + supported = eMultisig.supportsInterface(type(IProposal).interfaceId); + assertEq(supported, true, "Should support IProposal"); + // It supports IEmergencyMultisig - vm.skip(true); + supported = eMultisig.supportsInterface(type(IEmergencyMultisig).interfaceId); + assertEq(supported, true, "Should support IEmergencyMultisig"); } modifier whenCallingUpdateSettings() { @@ -204,13 +425,138 @@ contract EmergencyMultisigTest is AragonTest { // It should set signerList // It should set proposalExpirationPeriod // It should emit MultisigSettingsUpdated - vm.skip(true); + + bool givenOnlyListed; + uint16 givenMinApprovals; + SignerList givenSignerList; + uint64 givenProposalExpirationPeriod; + dao.grant(address(eMultisig), address(alice), eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + // 1 + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(true, 1, signerList, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + eMultisig.updateMultisigSettings(settings); + + (givenOnlyListed, givenMinApprovals, givenSignerList, givenProposalExpirationPeriod) = + eMultisig.multisigSettings(); + assertEq(givenOnlyListed, true, "onlyListed should be true"); + assertEq(givenMinApprovals, 1, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(signerList), "Incorrect givenSignerList"); + assertEq( + givenProposalExpirationPeriod, + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect givenProposalExpirationPeriod" + ); + + // 2 + (,,,,, SignerList newSignerList,,) = builder.build(); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 2, + signerList: newSignerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1 + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(true, 2, newSignerList, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); + eMultisig.updateMultisigSettings(settings); + + (givenOnlyListed, givenMinApprovals, givenSignerList, givenProposalExpirationPeriod) = + eMultisig.multisigSettings(); + assertEq(givenOnlyListed, true, "onlyListed should be true"); + assertEq(givenMinApprovals, 2, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect givenSignerList"); + assertEq( + givenProposalExpirationPeriod, + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1, + "Incorrect givenProposalExpirationPeriod" + ); + + // 3 + (,,,,, newSignerList,,) = builder.build(); + + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 3, + signerList: newSignerList, + proposalExpirationPeriod: 4 days + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(false, 3, newSignerList, 4 days); + eMultisig.updateMultisigSettings(settings); + + (givenOnlyListed, givenMinApprovals, givenSignerList, givenProposalExpirationPeriod) = + eMultisig.multisigSettings(); + assertEq(givenOnlyListed, false, "onlyListed should be false"); + assertEq(givenMinApprovals, 3, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect givenSignerList"); + assertEq(givenProposalExpirationPeriod, 4 days, "Incorrect givenProposalExpirationPeriod"); + + // 4 + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + signerList: signerList, + proposalExpirationPeriod: 8 days + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(false, 4, signerList, 8 days); + eMultisig.updateMultisigSettings(settings); + + (givenOnlyListed, givenMinApprovals, givenSignerList, givenProposalExpirationPeriod) = + eMultisig.multisigSettings(); + assertEq(givenOnlyListed, false, "onlyListed should be true"); + assertEq(givenMinApprovals, 4, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(signerList), "Incorrect givenSignerList"); + assertEq(givenProposalExpirationPeriod, 8 days, "Incorrect givenProposalExpirationPeriod"); } function test_RevertGiven_CallerHasNoPermission() external whenCallingUpdateSettings { // It should revert + (,,,,, SignerList newSignerList,,) = builder.build(); + + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 2, + signerList: newSignerList, + proposalExpirationPeriod: 3 days + }); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(eMultisig), + alice, + eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + eMultisig.updateMultisigSettings(settings); + + // Nothing changed + (bool onlyListed, uint16 minApprovals, Addresslist currentSource, uint64 expiration) = + eMultisig.multisigSettings(); + assertEq(onlyListed, true); + assertEq(minApprovals, 3); + assertEq(address(currentSource), address(signerList)); + assertEq(expiration, 10 days); + // It otherwise it should just work - vm.skip(true); + // Retry with the permission + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + vm.expectEmit(); + emit MultisigSettingsUpdated(false, 2, newSignerList, 3 days); + eMultisig.updateMultisigSettings(settings); } function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnUpdateSettings() @@ -218,21 +564,176 @@ contract EmergencyMultisigTest is AragonTest { whenCallingUpdateSettings { // It should revert + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 5, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); + eMultisig.updateMultisigSettings(settings); + // It should revert (with onlyListed false) + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 5, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); + eMultisig.updateMultisigSettings(settings); + // It should not revert otherwise - vm.skip(true); + + // More signers + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + address[] memory signers = new address[](1); + signers[0] = randomWallet; + signerList.addSigners(signers); + + eMultisig.updateMultisigSettings(settings); } function test_RevertWhen_MinApprovalsIsZeroOnUpdateSettings() external whenCallingUpdateSettings { // It should revert + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 0, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 1, 0)); + eMultisig.updateMultisigSettings(settings); + // It should revert (with onlyListed false) + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 0, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 1, 0)); + eMultisig.updateMultisigSettings(settings); + // It should not revert otherwise - vm.skip(true); + + settings.minApprovals = 1; + eMultisig.updateMultisigSettings(settings); + + settings.onlyListed = true; + eMultisig.updateMultisigSettings(settings); } function test_RevertWhen_SignerListIsInvalidOnUpdateSettings() external whenCallingUpdateSettings { // It should revert - vm.skip(true); + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + // ko + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: SignerList(address(dao)), + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidSignerList.selector, address(dao))); + eMultisig.updateMultisigSettings(settings); + + // ko 2 + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: SignerList(address(builder)), + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(); + eMultisig.updateMultisigSettings(settings); + + // ok + (,,,,, SignerList newSignerList,,) = builder.build(); + settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: newSignerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig.updateMultisigSettings(settings); + } + + function testFuzz_PermissionedUpdateSettings(address randomAccount) public { + dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + (bool onlyListed, uint16 minApprovals, SignerList givenSignerList, uint64 expiration) = + eMultisig.multisigSettings(); + assertEq(minApprovals, 3, "Should be 3"); + assertEq(onlyListed, true, "Should be true"); + assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); + assertEq(expiration, 10 days, "Should be 10"); + + // in + (,,,,, SignerList newSignerList,,) = builder.build(); + EmergencyMultisig.MultisigSettings memory newSettings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 2, + signerList: newSignerList, + proposalExpirationPeriod: 4 days + }); + eMultisig.updateMultisigSettings(newSettings); + + (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); + assertEq(minApprovals, 2, "Should be 2"); + assertEq(onlyListed, false, "Should be false"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect signerList"); + assertEq(expiration, 4 days, "Should be 4"); + + // out + newSettings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + signerList: signerList, + proposalExpirationPeriod: 1 days + }); + eMultisig.updateMultisigSettings(newSettings); + (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); + assertEq(minApprovals, 1, "Should be 1"); + assertEq(onlyListed, true, "Should be true"); + assertEq(address(givenSignerList), address(signerList), "Incorrect signerList"); + assertEq(expiration, 1 days, "Should be 1"); + + vm.roll(block.number + 1); + + // someone else + if (randomAccount != alice && randomAccount != address(0)) { + vm.startPrank(randomAccount); + + (,,,,, newSignerList,,) = builder.build(); + newSettings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + signerList: newSignerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(eMultisig), + randomAccount, + eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + eMultisig.updateMultisigSettings(newSettings); + + (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); + assertEq(minApprovals, 1, "Should still be 1"); + assertEq(onlyListed, true, "Should still be true"); + assertEq(address(givenSignerList), address(signerList), "Should still be signerList"); + assertEq(expiration, 1 days, "Should still be 1"); + } } modifier whenCallingCreateProposal() { @@ -240,17 +741,158 @@ contract EmergencyMultisigTest is AragonTest { } function test_WhenCallingCreateProposal() external whenCallingCreateProposal { + uint256 pid; + bool executed; + uint16 approvals; + EmergencyMultisig.ProposalParameters memory parameters; + bytes memory encryptedPayloadURI; + bytes32 publicMetadataUriHash; + bytes32 destinationActionsHash; + OptimisticTokenVotingPlugin destinationPlugin; + // It increments the proposal counter // It creates and return unique proposal IDs - // It emits the ProposalCreated event + // It emits the EmergencyProposalCreated event // It creates a proposal with the given values - vm.skip(true); + + assertEq(eMultisig.proposalCount(), 0, "Should have no proposals"); + + // 1 + vm.expectEmit(); + emit EmergencyProposalCreated({proposalId: 0, creator: alice, encryptedPayloadURI: "ipfs://"}); + pid = eMultisig.createProposal( + "ipfs://", + bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), + bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), + optimisticPlugin, + false + ); + assertEq(pid, 0, "Should be 0"); + assertEq(eMultisig.proposalCount(), 1, "Should have 1 proposal"); + + ( + executed, + approvals, + parameters, + encryptedPayloadURI, + publicMetadataUriHash, + destinationActionsHash, + destinationPlugin + ) = eMultisig.getProposal(pid); + assertEq(executed, false, "Should be false"); + assertEq(approvals, 0, "Should be 0"); + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, + block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect expirationDate" + ); + assertEq(encryptedPayloadURI, "ipfs://", "Incorrect encryptedPayloadURI"); + assertEq( + publicMetadataUriHash, + bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), + "Incorrect publicMetadataUriHash" + ); + assertEq( + destinationActionsHash, + bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), + "Incorrect destinationActionsHash" + ); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); + + // 2 + vm.startPrank(bob); + + vm.expectEmit(); + emit EmergencyProposalCreated({proposalId: 1, creator: bob, encryptedPayloadURI: "ipfs://more"}); + pid = eMultisig.createProposal( + "ipfs://more", + bytes32(0x2345000000000000000000000000000000000000000000000000000000000000), + bytes32(0x0000234500000000000000000000000000000000000000000000000000000000), + optimisticPlugin, + true + ); + + assertEq(pid, 1, "Should be 1"); + assertEq(eMultisig.proposalCount(), 2, "Should have 2 proposals"); + + ( + executed, + approvals, + parameters, + encryptedPayloadURI, + publicMetadataUriHash, + destinationActionsHash, + destinationPlugin + ) = eMultisig.getProposal(pid); + assertEq(executed, false, "Should be false"); + assertEq(approvals, 1, "Should be 1"); + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, + block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect expirationDate" + ); + assertEq(encryptedPayloadURI, "ipfs://more", "Incorrect encryptedPayloadURI"); + assertEq( + publicMetadataUriHash, + bytes32(0x2345000000000000000000000000000000000000000000000000000000000000), + "Incorrect publicMetadataUriHash" + ); + assertEq( + destinationActionsHash, + bytes32(0x0000234500000000000000000000000000000000000000000000000000000000), + "Incorrect destinationActionsHash" + ); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); + + // 3 + vm.startPrank(carol); + OptimisticTokenVotingPlugin newOptimistic; + (, newOptimistic,, eMultisig,,,,) = builder.withMinApprovals(2).build(); + + vm.expectEmit(); + emit EmergencyProposalCreated({proposalId: 0, creator: carol, encryptedPayloadURI: "ipfs://more"}); + pid = eMultisig.createProposal( + "ipfs://more", + bytes32(0x2345000000000000000000000000000000000000000000000000000000000000), + bytes32(0x0000234500000000000000000000000000000000000000000000000000000000), + newOptimistic, + true + ); + + (,, parameters,,,, destinationPlugin) = eMultisig.getProposal(pid); + assertEq(parameters.minApprovals, 2, "Incorrect minApprovals"); + assertEq(address(destinationPlugin), address(newOptimistic), "Incorrect destinationPlugin"); } function test_GivenSettingsChangedOnTheSameBlock() external whenCallingCreateProposal { + { + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: true, + minApprovals: 3, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + } + // It reverts + // Same block + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, alice)); + eMultisig.createProposal("", bytes32(0), bytes32(0), optimisticPlugin, false); + // It does not revert otherwise - vm.skip(true); + // Next block + vm.roll(block.number + 1); + eMultisig.createProposal("", bytes32(0), bytes32(0), optimisticPlugin, false); } function test_GivenOnlyListedIsFalse() external whenCallingCreateProposal { diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index c920598..e222000 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -4,10 +4,16 @@ pragma solidity 0.8.17; import {AragonTest} from "./base/AragonTest.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {Multisig} from "../src/Multisig.sol"; -import {SignerList} from "../src/SignerList.sol"; +import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; +import {SignerList, UPDATE_SIGNER_LIST_PERMISSION_ID} from "../src/SignerList.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; +import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; import {createProxyAndCall} from "../src/helpers/proxy.sol"; +import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; +import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; +import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; +import {IMultisig} from "../src/interfaces/IMultisig.sol"; uint64 constant MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; @@ -16,12 +22,14 @@ contract MultisigTest is AragonTest { DaoBuilder builder; DAO dao; Multisig multisig; + OptimisticTokenVotingPlugin optimisticPlugin; address immutable SIGNER_LIST_BASE = address(new SignerList()); // Events/errors to be tested here (duplicate) error DaoUnauthorized(address dao, address where, address who, bytes32 permissionId); error InvalidAddresslistUpdate(address member); + error InvalidActions(uint256 proposalId); event MultisigSettingsUpdated( bool onlyListed, @@ -30,6 +38,21 @@ contract MultisigTest is AragonTest { SignerList signerList, uint64 proposalExpirationPeriod ); + // Multisig proposal + event ProposalCreated(uint256 indexed proposalId, address indexed creator, bytes encryptedPayloadURI); + // OptimisticTokenVotingPlugin's event + event ProposalCreated( + uint256 indexed proposalId, + address indexed creator, + uint64 startDate, + uint64 endDate, + bytes metadata, + IDAO.Action[] actions, + uint256 allowFailureMap + ); + event Approved(uint256 indexed proposalId, address indexed approver); + event Executed(uint256 indexed proposalId); + event Upgraded(address indexed implementation); function setUp() public { vm.startPrank(alice); @@ -37,7 +60,7 @@ contract MultisigTest is AragonTest { builder = new DaoBuilder(); (dao,, multisig,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( carol - ).withMultisigMember(david).build(); + ).withMultisigMember(david).withMinApprovals(3).build(); } modifier givenANewlyDeployedContract() { @@ -144,9 +167,52 @@ contract MultisigTest is AragonTest { givenCallingInitialize { // It should revert + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 5, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + // It should revert (with onlyListed false) + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 5, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + // It should not revert otherwise - vm.skip(true); + + settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 4, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_RevertWhen_MinApprovalsIsZeroOnInitialize() @@ -155,9 +221,52 @@ contract MultisigTest is AragonTest { givenCallingInitialize { // It should revert + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 0, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + // It should revert (with onlyListed false) + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 0, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + // It should not revert otherwise - vm.skip(true); + + settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 4, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + destinationProposalDuration: 10 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_RevertWhen_SignerListIsInvalidOnInitialize() @@ -166,28 +275,114 @@ contract MultisigTest is AragonTest { givenCallingInitialize { // It should revert - vm.skip(true); + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + destinationProposalDuration: 10 days, + signerList: SignerList(address(dao)), + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(Multisig.InvalidSignerList.selector, address(dao))); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + + // ko 2 + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + destinationProposalDuration: 10 days, + signerList: SignerList(address(builder)), + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + + // ok + (,,,,, SignerList newSignerList,,) = builder.build(); + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + destinationProposalDuration: 10 days, + signerList: newSignerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); } function test_WhenCallingUpgradeTo() external { // It should revert when called without the permission + address initialImplementation = multisig.implementation(); + address _newImplementation = address(new Multisig()); + + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + alice, + multisig.UPGRADE_PLUGIN_PERMISSION_ID() + ) + ); + multisig.upgradeTo(_newImplementation); + assertEq(multisig.implementation(), initialImplementation); + // It should work when called with the permission - vm.skip(true); + dao.grant(address(multisig), alice, multisig.UPGRADE_PLUGIN_PERMISSION_ID()); + multisig.upgradeTo(_newImplementation); } function test_WhenCallingUpgradeToAndCall() external { // It should revert when called without the permission + address initialImplementation = multisig.implementation(); + dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + address _newImplementation = address(new Multisig()); + + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 3, + destinationProposalDuration: 3 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + alice, + multisig.UPGRADE_PLUGIN_PERMISSION_ID() + ) + ); + multisig.upgradeToAndCall(_newImplementation, abi.encodeCall(Multisig.updateMultisigSettings, (settings))); + assertEq(multisig.implementation(), initialImplementation); + // It should work when called with the permission - vm.skip(true); + dao.grant(address(multisig), alice, multisig.UPGRADE_PLUGIN_PERMISSION_ID()); + multisig.upgradeToAndCall(_newImplementation, abi.encodeCall(Multisig.updateMultisigSettings, (settings))); } - function test_WhenCallingSupportsInterface() external { + function test_WhenCallingSupportsInterface() external view { // It does not support the empty interface + bool supported = multisig.supportsInterface(0); + assertEq(supported, false, "Should not support the empty interface"); + // It supports IERC165Upgradeable + supported = multisig.supportsInterface(type(IERC165Upgradeable).interfaceId); + assertEq(supported, true, "Should support IERC165Upgradeable"); + // It supports IPlugin + supported = multisig.supportsInterface(type(IPlugin).interfaceId); + assertEq(supported, true, "Should support IPlugin"); + // It supports IProposal + supported = multisig.supportsInterface(type(IProposal).interfaceId); + assertEq(supported, true, "Should support IProposal"); + // It supports IMultisig - vm.skip(true); + supported = multisig.supportsInterface(type(IMultisig).interfaceId); + assertEq(supported, true, "Should support IMultisig"); } modifier whenCallingUpdateSettings() { @@ -201,13 +396,170 @@ contract MultisigTest is AragonTest { // It should set destinationProposalDuration // It should set proposalExpirationPeriod // It should emit MultisigSettingsUpdated - vm.skip(true); + + bool givenOnlyListed; + uint16 givenMinApprovals; + uint64 givenDestinationProposalDuration; + SignerList givenSignerList; + uint64 givenProposalExpirationPeriod; + dao.grant(address(multisig), address(alice), multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + // 1 + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 1 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(true, 1, 1 days, signerList, MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + multisig.updateMultisigSettings(settings); + + ( + givenOnlyListed, + givenMinApprovals, + givenDestinationProposalDuration, + givenSignerList, + givenProposalExpirationPeriod + ) = multisig.multisigSettings(); + assertEq(givenOnlyListed, true, "onlyListed should be true"); + assertEq(givenMinApprovals, 1, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(signerList), "Incorrect givenSignerList"); + assertEq( + givenProposalExpirationPeriod, + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect givenProposalExpirationPeriod" + ); + + // 2 + (,,,,, SignerList newSignerList,,) = builder.build(); + + settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 2, + destinationProposalDuration: 2 days, + signerList: newSignerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1 + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(true, 2, 2 days, newSignerList, MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); + multisig.updateMultisigSettings(settings); + + ( + givenOnlyListed, + givenMinApprovals, + givenDestinationProposalDuration, + givenSignerList, + givenProposalExpirationPeriod + ) = multisig.multisigSettings(); + assertEq(givenOnlyListed, true, "onlyListed should be true"); + assertEq(givenMinApprovals, 2, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect givenSignerList"); + assertEq( + givenProposalExpirationPeriod, + MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1, + "Incorrect givenProposalExpirationPeriod" + ); + + // 3 + (,,,,, newSignerList,,) = builder.build(); + + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 3, + destinationProposalDuration: 3 days, + signerList: newSignerList, + proposalExpirationPeriod: 4 days + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(false, 3, 3 days, newSignerList, 4 days); + multisig.updateMultisigSettings(settings); + + ( + givenOnlyListed, + givenMinApprovals, + givenDestinationProposalDuration, + givenSignerList, + givenProposalExpirationPeriod + ) = multisig.multisigSettings(); + assertEq(givenOnlyListed, false, "onlyListed should be false"); + assertEq(givenMinApprovals, 3, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect givenSignerList"); + assertEq(givenProposalExpirationPeriod, 4 days, "Incorrect givenProposalExpirationPeriod"); + + // 4 + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + destinationProposalDuration: 4 days, + signerList: signerList, + proposalExpirationPeriod: 8 days + }); + + vm.expectEmit(); + emit MultisigSettingsUpdated(false, 4, 4 days, signerList, 8 days); + multisig.updateMultisigSettings(settings); + + ( + givenOnlyListed, + givenMinApprovals, + givenDestinationProposalDuration, + givenSignerList, + givenProposalExpirationPeriod + ) = multisig.multisigSettings(); + assertEq(givenOnlyListed, false, "onlyListed should be true"); + assertEq(givenMinApprovals, 4, "Incorrect givenMinApprovals"); + assertEq(address(givenSignerList), address(signerList), "Incorrect givenSignerList"); + assertEq(givenProposalExpirationPeriod, 8 days, "Incorrect givenProposalExpirationPeriod"); } function test_RevertGiven_CallerHasNoPermission() external whenCallingUpdateSettings { // It should revert + (,,,,, SignerList newSignerList,,) = builder.build(); + + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 2, + destinationProposalDuration: 17 days, + signerList: newSignerList, + proposalExpirationPeriod: 3 days + }); + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + alice, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + multisig.updateMultisigSettings(settings); + + // Nothing changed + ( + bool onlyListed, + uint16 minApprovals, + uint64 currentDestinationProposalDuration, + Addresslist currentSource, + uint64 expiration + ) = multisig.multisigSettings(); + assertEq(onlyListed, true); + assertEq(minApprovals, 3); + assertEq(currentDestinationProposalDuration, 10 days); + assertEq(address(currentSource), address(signerList)); + assertEq(expiration, 10 days); + // It otherwise it should just work - vm.skip(true); + // Retry with the permission + dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + vm.expectEmit(); + emit MultisigSettingsUpdated(false, 2, 17 days, newSignerList, 3 days); + multisig.updateMultisigSettings(settings); } function test_RevertWhen_MinApprovalsIsGreaterThanSignerListLengthOnUpdateSettings() @@ -215,21 +567,190 @@ contract MultisigTest is AragonTest { whenCallingUpdateSettings { // It should revert + dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 5, + destinationProposalDuration: 4 days, // More than 4 + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); + multisig.updateMultisigSettings(settings); + // It should revert (with onlyListed false) + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 5, + destinationProposalDuration: 4 days, // More than 4 + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); + multisig.updateMultisigSettings(settings); + // It should not revert otherwise - vm.skip(true); + + // More signers + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + address[] memory signers = new address[](1); + signers[0] = randomWallet; + signerList.addSigners(signers); + + multisig.updateMultisigSettings(settings); } function test_RevertWhen_MinApprovalsIsZeroOnUpdateSettings() external whenCallingUpdateSettings { // It should revert + dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 0, + destinationProposalDuration: 4 days, // More than 4 + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); + multisig.updateMultisigSettings(settings); + // It should revert (with onlyListed false) + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 0, + destinationProposalDuration: 4 days, // More than 4 + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); + multisig.updateMultisigSettings(settings); + // It should not revert otherwise - vm.skip(true); + + settings.minApprovals = 1; + multisig.updateMultisigSettings(settings); + + settings.onlyListed = true; + multisig.updateMultisigSettings(settings); } function test_RevertWhen_SignerListIsInvalidOnUpdateSettings() external whenCallingUpdateSettings { // It should revert - vm.skip(true); + dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + // ko + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + destinationProposalDuration: 10 days, + signerList: SignerList(address(dao)), + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(abi.encodeWithSelector(Multisig.InvalidSignerList.selector, address(dao))); + multisig.updateMultisigSettings(settings); + + // ko 2 + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + destinationProposalDuration: 10 days, + signerList: SignerList(address(builder)), + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + vm.expectRevert(); + multisig.updateMultisigSettings(settings); + + // ok + (,,,,, SignerList newSignerList,,) = builder.build(); + settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + destinationProposalDuration: 10 days, + signerList: newSignerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig.updateMultisigSettings(settings); + } + + function testFuzz_PermissionedUpdateSettings(address randomAccount) public { + dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); + + (bool onlyListed, uint16 minApprovals, uint64 destMinDuration, SignerList givenSignerList, uint64 expiration) = + multisig.multisigSettings(); + assertEq(minApprovals, 3, "Should be 3"); + assertEq(onlyListed, true, "Should be true"); + assertEq(destMinDuration, 10 days, "Incorrect destMinDuration"); + assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); + assertEq(expiration, 10 days, "Should be 10"); + + // in + (,,,,, SignerList newSignerList,,) = builder.build(); + Multisig.MultisigSettings memory newSettings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 2, + destinationProposalDuration: 5 days, + signerList: newSignerList, + proposalExpirationPeriod: 4 days + }); + multisig.updateMultisigSettings(newSettings); + + (onlyListed, minApprovals, destMinDuration, givenSignerList, expiration) = multisig.multisigSettings(); + assertEq(minApprovals, 2, "Should be 2"); + assertEq(onlyListed, false, "Should be false"); + assertEq(destMinDuration, 5 days, "Incorrect destMinDuration B"); + assertEq(address(givenSignerList), address(newSignerList), "Incorrect signerList"); + assertEq(expiration, 4 days, "Should be 4"); + + // out + newSettings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 6 days, + signerList: signerList, + proposalExpirationPeriod: 1 days + }); + multisig.updateMultisigSettings(newSettings); + (onlyListed, minApprovals, destMinDuration, givenSignerList, expiration) = multisig.multisigSettings(); + assertEq(minApprovals, 1, "Should be 1"); + assertEq(onlyListed, true, "Should be true"); + assertEq(destMinDuration, 6 days, "Incorrect destMinDuration B"); + assertEq(address(givenSignerList), address(signerList), "Incorrect signerList"); + assertEq(expiration, 1 days, "Should be 1"); + + vm.roll(block.number + 1); + + // someone else + if (randomAccount != alice && randomAccount != address(0)) { + vm.startPrank(randomAccount); + + (,,,,, newSignerList,,) = builder.build(); + newSettings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 4, + destinationProposalDuration: 4 days, + signerList: newSignerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + + vm.expectRevert( + abi.encodeWithSelector( + DaoUnauthorized.selector, + address(dao), + address(multisig), + randomAccount, + multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() + ) + ); + multisig.updateMultisigSettings(newSettings); + + (onlyListed, minApprovals, destMinDuration, givenSignerList, expiration) = multisig.multisigSettings(); + assertEq(minApprovals, 1, "Should still be 1"); + assertEq(onlyListed, true, "Should still be true"); + assertEq(destMinDuration, 6 days, "Should still be 6 days"); + assertEq(address(givenSignerList), address(signerList), "Should still be signerList"); + assertEq(expiration, 1 days, "Should still be 1"); + } } modifier whenCallingCreateProposal() { @@ -237,17 +758,138 @@ contract MultisigTest is AragonTest { } function test_WhenCallingCreateProposal() external whenCallingCreateProposal { + uint256 pid; + bool executed; + uint16 approvals; + Multisig.ProposalParameters memory parameters; + bytes memory metadataURI; + OptimisticTokenVotingPlugin destinationPlugin; + IDAO.Action[] memory inputActions = new IDAO.Action[](0); + IDAO.Action[] memory outputActions = new IDAO.Action[](0); + // It increments the proposal counter // It creates and return unique proposal IDs // It emits the ProposalCreated event // It creates a proposal with the given values - vm.skip(true); + + assertEq(multisig.proposalCount(), 0, "Should have no proposals"); + + // 1 + vm.expectEmit(); + emit ProposalCreated({ + proposalId: 0, + creator: alice, + metadata: "ipfs://", + startDate: uint64(block.timestamp), + endDate: uint64(block.timestamp) + 10 days, + actions: inputActions, + allowFailureMap: 0 + }); + multisig.createProposal("ipfs://", inputActions, optimisticPlugin, false); + assertEq(pid, 0, "Should be 0"); + assertEq(multisig.proposalCount(), 1, "Should have 1 proposal"); + + (executed, approvals, parameters, metadataURI, outputActions, destinationPlugin) = multisig.getProposal(pid); + assertEq(executed, false, "Should be false"); + assertEq(approvals, 0, "Should be 0"); + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expirationDate" + ); + assertEq(metadataURI, "ipfs://", "Incorrect metadataURI"); + assertEq(outputActions.length, 0, "Incorrect actions length"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); + + // 2 + vm.startPrank(bob); + vm.roll(block.number + 100); + vm.warp(block.timestamp + 100); + + inputActions = new IDAO.Action[](1); + inputActions[0].to = carol; + inputActions[0].value = 1 ether; + address[] memory addrs = new address[](1); + inputActions[0].data = abi.encodeCall(SignerList.addSigners, (addrs)); + + vm.expectEmit(); + emit ProposalCreated({ + proposalId: 1, + creator: bob, + metadata: "ipfs://more", + startDate: uint64(block.timestamp), + endDate: uint64(block.timestamp) + 10 days, + actions: inputActions, + allowFailureMap: 0 + }); + pid = multisig.createProposal("ipfs://more", inputActions, optimisticPlugin, true); + + assertEq(pid, 1, "Should be 1"); + assertEq(multisig.proposalCount(), 2, "Should have 2 proposals"); + + (executed, approvals, parameters, metadataURI, outputActions, destinationPlugin) = multisig.getProposal(pid); + assertEq(executed, false, "Should be false"); + assertEq(approvals, 1, "Should be 1"); + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expirationDate" + ); + assertEq(metadataURI, "ipfs://more", "Incorrect metadataURI"); + assertEq(outputActions.length, 1, "Incorrect actions length"); + assertEq(outputActions[0].to, carol, "Incorrect to"); + assertEq(outputActions[0].value, 1 ether, "Incorrect value"); + assertEq(outputActions[0].data, abi.encodeCall(SignerList.addSigners, (addrs)), "Incorrect data"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); + + // 3 + vm.startPrank(carol); + vm.roll(block.number + 100); + vm.warp(block.timestamp + 100); + + OptimisticTokenVotingPlugin newOptimistic; + (, newOptimistic, multisig,,,,,) = builder.withMinApprovals(2).build(); + + vm.expectEmit(); + emit ProposalCreated({ + proposalId: 0, + creator: carol, + metadata: "ipfs://1234", + startDate: uint64(block.timestamp), + endDate: uint64(block.timestamp) + 10 days, + actions: inputActions, + allowFailureMap: 0 + }); + pid = multisig.createProposal("ipfs://1234", inputActions, newOptimistic, true); + + (,, parameters,,, destinationPlugin) = multisig.getProposal(pid); + assertEq(parameters.minApprovals, 2, "Incorrect minApprovals"); + assertEq(address(destinationPlugin), address(newOptimistic), "Incorrect destinationPlugin"); } function test_GivenSettingsChangedOnTheSameBlock() external whenCallingCreateProposal { // It reverts + + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: false, + minApprovals: 3, + destinationProposalDuration: 4 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + multisig = + Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); + + // 1 + IDAO.Action[] memory actions = new IDAO.Action[](0); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, alice)); + multisig.createProposal("", actions, optimisticPlugin, false); + // It does not revert otherwise - vm.skip(true); + + // Next block + vm.roll(block.number + 1); + multisig.createProposal("", actions, optimisticPlugin, false); } function test_GivenOnlyListedIsFalse() external whenCallingCreateProposal { From d3e91e2d0cca0862dd3fb862a56ee03b977042bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 8 Nov 2024 18:35:38 +0700 Subject: [PATCH 31/45] Multisig testing refactor WIP --- test/EmergencyMultisigTree.t.sol | 232 +++++++++++++++++++++++++++--- test/EmergencyMultisigTree.t.yaml | 28 ++-- test/MultisigTree.t.sol | 196 ++++++++++++++++++++++--- test/MultisigTree.t.yaml | 26 ++-- 4 files changed, 411 insertions(+), 71 deletions(-) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 5612558..81b973a 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -5,7 +5,12 @@ import {AragonTest} from "./base/AragonTest.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; -import {SignerList, UPDATE_SIGNER_LIST_PERMISSION_ID} from "../src/SignerList.sol"; +import { + SignerList, + UPDATE_SIGNER_LIST_PERMISSION_ID, + UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID +} from "../src/SignerList.sol"; +import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; @@ -18,11 +23,12 @@ import {IEmergencyMultisig} from "../src/interfaces/IEmergencyMultisig.sol"; uint64 constant EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; contract EmergencyMultisigTest is AragonTest { - SignerList signerList; DaoBuilder builder; DAO dao; EmergencyMultisig eMultisig; OptimisticTokenVotingPlugin optimisticPlugin; + SignerList signerList; + EncryptionRegistry encryptionRegistry; address immutable SIGNER_LIST_BASE = address(new SignerList()); @@ -57,9 +63,8 @@ contract EmergencyMultisigTest is AragonTest { vm.roll(100); builder = new DaoBuilder(); - (dao,,, eMultisig,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( - carol - ).withMultisigMember(david).withMinApprovals(3).build(); + (dao,,, eMultisig,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) + .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); } modifier givenANewlyDeployedContract() { @@ -897,7 +902,18 @@ contract EmergencyMultisigTest is AragonTest { function test_GivenOnlyListedIsFalse() external whenCallingCreateProposal { // It allows anyone to create - vm.skip(true); + + // Deploy a new instance with custom settings + (dao, optimisticPlugin,, eMultisig,,,,) = builder.withoutOnlyListed().build(); + + vm.startPrank(randomWallet); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + vm.startPrank(address(0x1234)); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + vm.startPrank(address(0x22345)); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); } modifier givenOnlyListedIsTrue() { @@ -910,7 +926,31 @@ contract EmergencyMultisigTest is AragonTest { givenOnlyListedIsTrue { // It reverts - vm.skip(true); + + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 3)); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, randomWallet)); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + // 2 + vm.startPrank(taikoBridge); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, taikoBridge)); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + // It reverts if listed before but not now + + vm.startPrank(alice); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + address[] memory addrs = new address[](1); + addrs[0] = alice; + signerList.removeSigners(addrs); + + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, alice)); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); } function test_GivenCreationCallerIsAppointedByAFormerSigner() @@ -919,7 +959,26 @@ contract EmergencyMultisigTest is AragonTest { givenOnlyListedIsTrue { // It reverts - vm.skip(true); + + encryptionRegistry.appointWallet(randomWallet); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 3)); + + address[] memory addrs = new address[](1); + addrs[0] = alice; + signerList.removeSigners(addrs); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, randomWallet)); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + // Undo + vm.startPrank(alice); + signerList.addSigners(addrs); + + vm.startPrank(randomWallet); + eMultisig.createProposal("", 0, 0, optimisticPlugin, false); } function test_GivenCreationCallerIsListedAndSelfAppointed() @@ -928,7 +987,18 @@ contract EmergencyMultisigTest is AragonTest { givenOnlyListedIsTrue { // It creates the proposal - vm.skip(true); + + vm.startPrank(alice); + eMultisig.createProposal("a", 0, 0, optimisticPlugin, false); + + vm.startPrank(bob); + eMultisig.createProposal("b", 0, 0, optimisticPlugin, false); + + vm.startPrank(carol); + eMultisig.createProposal("c", 0, 0, optimisticPlugin, true); + + vm.startPrank(david); + eMultisig.createProposal("d", 0, 0, optimisticPlugin, false); } function test_GivenCreationCallerIsListedAppointingSomeoneElseNow() @@ -937,7 +1007,22 @@ contract EmergencyMultisigTest is AragonTest { givenOnlyListedIsTrue { // It creates the proposal - vm.skip(true); + + vm.startPrank(alice); + encryptionRegistry.appointWallet(address(0x1234)); + eMultisig.createProposal("a", 0, 0, optimisticPlugin, false); + + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x2345)); + eMultisig.createProposal("b", 0, 0, optimisticPlugin, false); + + vm.startPrank(carol); + encryptionRegistry.appointWallet(address(0x3456)); + eMultisig.createProposal("c", 0, 0, optimisticPlugin, false); + + vm.startPrank(david); + encryptionRegistry.appointWallet(address(0x4567)); + eMultisig.createProposal("d", 0, 0, optimisticPlugin, false); } function test_GivenCreationCallerIsAppointedByACurrentSigner() @@ -946,33 +1031,134 @@ contract EmergencyMultisigTest is AragonTest { givenOnlyListedIsTrue { // It creates the proposal - vm.skip(true); + + vm.startPrank(alice); + encryptionRegistry.appointWallet(address(0x1234)); + vm.startPrank(address(0x1234)); + eMultisig.createProposal("a", 0, 0, optimisticPlugin, false); + + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x2345)); + vm.startPrank(address(0x2345)); + eMultisig.createProposal("b", 0, 0, optimisticPlugin, false); + + vm.startPrank(carol); + encryptionRegistry.appointWallet(address(0x3456)); + vm.startPrank(address(0x3456)); + eMultisig.createProposal("c", 0, 0, optimisticPlugin, false); + + vm.startPrank(david); + encryptionRegistry.appointWallet(address(0x4567)); + vm.startPrank(address(0x4567)); + eMultisig.createProposal("d", 0, 0, optimisticPlugin, false); } function test_GivenApproveProposalIsTrue() external whenCallingCreateProposal { + uint256 pid; + uint256 approvals; + // It creates and calls approval in one go - vm.skip(true); + + vm.startPrank(alice); + pid = eMultisig.createProposal("a", 0, 0, optimisticPlugin, true); + (, approvals,,,,,) = eMultisig.getProposal(pid); + assertEq(approvals, 1, "Should be 1"); + + vm.startPrank(bob); + pid = eMultisig.createProposal("b", 0, 0, optimisticPlugin, true); + (, approvals,,,,,) = eMultisig.getProposal(pid); + assertEq(approvals, 1, "Should be 1"); } function test_GivenApproveProposalIsFalse() external whenCallingCreateProposal { + uint256 pid; + uint256 approvals; + // It only creates the proposal - vm.skip(true); + + vm.startPrank(alice); + pid = eMultisig.createProposal("a", 0, 0, optimisticPlugin, true); + (, approvals,,,,,) = eMultisig.getProposal(pid); + assertEq(approvals, 1, "Should be 1"); + + vm.startPrank(bob); + pid = eMultisig.createProposal("b", 0, 0, optimisticPlugin, true); + (, approvals,,,,,) = eMultisig.getProposal(pid); + assertEq(approvals, 1, "Should be 1"); + + vm.startPrank(carol); + pid = eMultisig.createProposal("c", 0, 0, optimisticPlugin, false); + (, approvals,,,,,) = eMultisig.getProposal(pid); + assertEq(approvals, 0, "Should be 0"); + + vm.startPrank(david); + pid = eMultisig.createProposal("d", 0, 0, optimisticPlugin, false); + (, approvals,,,,,) = eMultisig.getProposal(pid); + assertEq(approvals, 0, "Should be 0"); } function test_WhenCallingHashActions() external { + bytes32 hashedActions; + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It returns the right result // It reacts to any of the values changing // It same input produces the same output - vm.skip(true); + + hashedActions = eMultisig.hashActions(actions); + assertEq(hashedActions, hex"569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd"); + + actions = new IDAO.Action[](1); + actions[0] = IDAO.Action(address(0), 0, bytes(string(""))); + hashedActions = eMultisig.hashActions(actions); + assertEq(hashedActions, hex"7cde746dfbb8dfd7721b5995769f873e3ff50416302673a354990b553bb0e208"); + + actions = new IDAO.Action[](1); + actions[0] = IDAO.Action(bob, 1 ether, bytes(string(""))); + hashedActions = eMultisig.hashActions(actions); + assertEq(hashedActions, hex"e212a57e4595f81151b46333ea31e2d5043b53bd562141e1efa1b2778cb3c208"); + + actions = new IDAO.Action[](2); + actions[0] = IDAO.Action(bob, 1 ether, bytes(string(""))); + actions[1] = IDAO.Action(carol, 2 ether, bytes(string("data"))); + hashedActions = eMultisig.hashActions(actions); + assertEq(hashedActions, hex"4be399aee320511a56f584fae21b92c78f47bff143ec3965b7d911776d39bc7d"); } modifier givenTheProposalIsNotCreated() { _; } - function test_WhenCallingGetProposalBeingUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingGetProposalBeingUncreated() external view givenTheProposalIsNotCreated { // It should return empty values - vm.skip(true); + uint256 pid; + bool executed; + uint16 approvals; + EmergencyMultisig.ProposalParameters memory parameters; + bytes memory encryptedPayloadURI; + bytes32 publicMetadataUriHash; + bytes32 destinationActionsHash; + OptimisticTokenVotingPlugin destinationPlugin; + + ( + executed, + approvals, + parameters, + encryptedPayloadURI, + publicMetadataUriHash, + destinationActionsHash, + destinationPlugin + ) = eMultisig.getProposal(pid); + + assertEq(executed, false, "Should be false"); + assertEq(approvals, 0, "Should be 0"); + assertEq(parameters.minApprovals, 0, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, 0, "Incorrect snapshotBlock"); + assertEq(parameters.expirationDate, 0, "Incorrect expirationDate"); + assertEq(encryptedPayloadURI, "", "Incorrect encryptedPayloadURI"); + assertEq(publicMetadataUriHash, bytes32(0), "Incorrect publicMetadataUriHash"); + assertEq(destinationActionsHash, bytes32(0), "Incorrect destinationActionsHash"); + assertEq(address(destinationPlugin), address(0), "Incorrect destinationPlugin"); } function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { @@ -992,7 +1178,7 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanExecuteOrExecuteBeingUncreated() external givenTheProposalIsNotCreated { // It canExecute should always return false vm.skip(true); } @@ -1025,7 +1211,7 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanExecuteOrExecuteBeingOpen() external givenTheProposalIsOpen { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -1059,7 +1245,7 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanExecuteOrExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when currently appointed by a signer listed on creation) @@ -1093,14 +1279,14 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteWithModifiedDataBeingPassed() external givenTheProposalPassed { + function test_WhenCallingCanExecuteOrExecuteWithModifiedDataBeingPassed() external givenTheProposalPassed { // It execute should revert with modified metadata // It execute should revert with modified actions // It execute should work with matching data vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingPassed() external givenTheProposalPassed { + function test_WhenCallingCanExecuteOrExecuteBeingPassed() external givenTheProposalPassed { // It canExecute should return true, always // It execute should work, when called by anyone with the actions // It execute should emit an event, when called by anyone with the actions @@ -1142,7 +1328,7 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanExecuteOrExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -1180,7 +1366,7 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingExpired() external givenTheProposalExpired { + function test_WhenCallingCanExecuteOrExecuteBeingExpired() external givenTheProposalExpired { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) diff --git a/test/EmergencyMultisigTree.t.yaml b/test/EmergencyMultisigTree.t.yaml index 7807618..952dfed 100644 --- a/test/EmergencyMultisigTree.t.yaml +++ b/test/EmergencyMultisigTree.t.yaml @@ -93,6 +93,7 @@ EmergencyMultisigTest: - given: creation caller is not listed or appointed then: - it: reverts + - it: reverts if listed before but not now - given: creation caller is appointed by a former signer then: - it: reverts @@ -127,7 +128,7 @@ EmergencyMultisigTest: then: - it: should return empty values # Approval - - when: calling canApprove and approve [being uncreated] + - when: calling canApprove or approve [being uncreated] then: - it: canApprove should return false (when currently listed and self appointed) - it: approve should revert (when currently listed and self appointed) @@ -142,7 +143,7 @@ EmergencyMultisigTest: then: - it: hasApproved should always return false # Execution - - when: calling canExecute and execute [being uncreated] + - when: calling canExecute or execute [being uncreated] then: - it: canExecute should always return false @@ -153,7 +154,7 @@ EmergencyMultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being open] + - when: calling canApprove or approve [being open] then: - it: canApprove should return true (when listed on creation, self appointed now) - it: approve should work (when listed on creation, self appointed now) @@ -171,7 +172,7 @@ EmergencyMultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being open] + - when: calling canExecute or execute [being open] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -189,7 +190,7 @@ EmergencyMultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being approved] + - when: calling canApprove or approve [being approved] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -204,7 +205,7 @@ EmergencyMultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being approved] + - when: calling canExecute or execute [being approved] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -222,7 +223,7 @@ EmergencyMultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being passed] + - when: calling canApprove or approve [being passed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -237,13 +238,13 @@ EmergencyMultisigTest: then: - it: hasApproved should return false until approved # Execution integrity - - when: calling canExecute and execute with modified data [being passed] + - when: calling canExecute or execute with modified data [being passed] then: - it: execute should revert with modified metadata - it: execute should revert with modified actions - it: execute should work with matching data # Execution - - when: calling canExecute and execute [being passed] + - when: calling canExecute or execute [being passed] then: - it: canExecute should return true, always - it: execute should work, when called by anyone with the actions @@ -264,7 +265,7 @@ EmergencyMultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being executed] + - when: calling canApprove or approve [being executed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -279,7 +280,7 @@ EmergencyMultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being executed] + - when: calling canExecute or execute [being executed] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -297,7 +298,7 @@ EmergencyMultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being expired] + - when: calling canApprove or approve [being expired] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -312,7 +313,7 @@ EmergencyMultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being expired] + - when: calling canExecute or execute [being expired] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -322,4 +323,3 @@ EmergencyMultisigTest: - it: execute should revert (when currently appointed by a signer listed on creation) - it: canExecute should return false (when unlisted on creation, unappointed now) - it: execute should revert (when unlisted on creation, unappointed now) - diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index e222000..e9da558 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -5,7 +5,12 @@ import {AragonTest} from "./base/AragonTest.sol"; import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; import {Multisig} from "../src/Multisig.sol"; import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; -import {SignerList, UPDATE_SIGNER_LIST_PERMISSION_ID} from "../src/SignerList.sol"; +import { + SignerList, + UPDATE_SIGNER_LIST_PERMISSION_ID, + UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID +} from "../src/SignerList.sol"; +import {EncryptionRegistry} from "../src/EncryptionRegistry.sol"; import {DaoBuilder} from "./helpers/DaoBuilder.sol"; import {DAO} from "@aragon/osx/core/dao/DAO.sol"; import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; @@ -18,11 +23,12 @@ import {IMultisig} from "../src/interfaces/IMultisig.sol"; uint64 constant MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; contract MultisigTest is AragonTest { - SignerList signerList; DaoBuilder builder; DAO dao; Multisig multisig; OptimisticTokenVotingPlugin optimisticPlugin; + SignerList signerList; + EncryptionRegistry encryptionRegistry; address immutable SIGNER_LIST_BASE = address(new SignerList()); @@ -58,9 +64,8 @@ contract MultisigTest is AragonTest { vm.startPrank(alice); builder = new DaoBuilder(); - (dao,, multisig,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob).withMultisigMember( - carol - ).withMultisigMember(david).withMinApprovals(3).build(); + (dao,, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) + .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); } modifier givenANewlyDeployedContract() { @@ -894,7 +899,14 @@ contract MultisigTest is AragonTest { function test_GivenOnlyListedIsFalse() external whenCallingCreateProposal { // It allows anyone to create - vm.skip(true); + + builder = new DaoBuilder(); + (, optimisticPlugin, multisig,,,,,) = builder.withMultisigMember(alice).withoutOnlyListed().build(); + + vm.startPrank(randomWallet); + + IDAO.Action[] memory actions = new IDAO.Action[](0); + multisig.createProposal("", actions, optimisticPlugin, false); } modifier givenOnlyListedIsTrue() { @@ -906,8 +918,34 @@ contract MultisigTest is AragonTest { whenCallingCreateProposal givenOnlyListedIsTrue { + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It reverts - vm.skip(true); + + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 3)); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, randomWallet)); + multisig.createProposal("", actions, optimisticPlugin, false); + + // 2 + vm.startPrank(taikoBridge); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, taikoBridge)); + multisig.createProposal("", actions, optimisticPlugin, false); + + // It reverts if listed before but not now + + vm.startPrank(alice); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + address[] memory addrs = new address[](1); + addrs[0] = alice; + signerList.removeSigners(addrs); + + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, alice)); + multisig.createProposal("", actions, optimisticPlugin, false); } function test_GivenCreationCallerIsAppointedByAFormerSigner() @@ -915,8 +953,29 @@ contract MultisigTest is AragonTest { whenCallingCreateProposal givenOnlyListedIsTrue { + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It reverts - vm.skip(true); + + encryptionRegistry.appointWallet(randomWallet); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 3)); + + address[] memory addrs = new address[](1); + addrs[0] = alice; + signerList.removeSigners(addrs); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, randomWallet)); + multisig.createProposal("", actions, optimisticPlugin, false); + + // Undo + vm.startPrank(alice); + signerList.addSigners(addrs); + + vm.startPrank(randomWallet); + multisig.createProposal("", actions, optimisticPlugin, false); } function test_GivenCreationCallerIsListedAndSelfAppointed() @@ -924,8 +983,21 @@ contract MultisigTest is AragonTest { whenCallingCreateProposal givenOnlyListedIsTrue { + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It creates the proposal - vm.skip(true); + + vm.startPrank(alice); + multisig.createProposal("a", actions, optimisticPlugin, false); + + vm.startPrank(bob); + multisig.createProposal("b", actions, optimisticPlugin, false); + + vm.startPrank(carol); + multisig.createProposal("c", actions, optimisticPlugin, false); + + vm.startPrank(david); + multisig.createProposal("d", actions, optimisticPlugin, false); } function test_GivenCreationCallerIsListedAppointingSomeoneElseNow() @@ -933,8 +1005,25 @@ contract MultisigTest is AragonTest { whenCallingCreateProposal givenOnlyListedIsTrue { + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It creates the proposal - vm.skip(true); + + vm.startPrank(alice); + encryptionRegistry.appointWallet(address(0x1234)); + multisig.createProposal("a", actions, optimisticPlugin, false); + + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x2345)); + multisig.createProposal("b", actions, optimisticPlugin, false); + + vm.startPrank(carol); + encryptionRegistry.appointWallet(address(0x3456)); + multisig.createProposal("c", actions, optimisticPlugin, false); + + vm.startPrank(david); + encryptionRegistry.appointWallet(address(0x4567)); + multisig.createProposal("d", actions, optimisticPlugin, false); } function test_GivenCreationCallerIsAppointedByACurrentSigner() @@ -942,27 +1031,92 @@ contract MultisigTest is AragonTest { whenCallingCreateProposal givenOnlyListedIsTrue { + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It creates the proposal - vm.skip(true); + + vm.startPrank(alice); + encryptionRegistry.appointWallet(address(0x1234)); + vm.startPrank(address(0x1234)); + multisig.createProposal("a", actions, optimisticPlugin, false); + + vm.startPrank(bob); + encryptionRegistry.appointWallet(address(0x2345)); + vm.startPrank(address(0x2345)); + multisig.createProposal("b", actions, optimisticPlugin, false); + + vm.startPrank(carol); + encryptionRegistry.appointWallet(address(0x3456)); + vm.startPrank(address(0x3456)); + multisig.createProposal("c", actions, optimisticPlugin, false); + + vm.startPrank(david); + encryptionRegistry.appointWallet(address(0x4567)); + vm.startPrank(address(0x4567)); + multisig.createProposal("d", actions, optimisticPlugin, false); } function test_GivenApproveProposalIsTrue() external whenCallingCreateProposal { + uint256 pid; + uint256 approvals; + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It creates and calls approval in one go - vm.skip(true); + + vm.startPrank(alice); + pid = multisig.createProposal("a", actions, optimisticPlugin, true); + (, approvals,,,,) = multisig.getProposal(pid); + assertEq(approvals, 1, "Should be 1"); + + vm.startPrank(bob); + pid = multisig.createProposal("b", actions, optimisticPlugin, true); + (, approvals,,,,) = multisig.getProposal(pid); + assertEq(approvals, 1, "Should be 1"); } function test_GivenApproveProposalIsFalse() external whenCallingCreateProposal { + uint256 pid; + uint256 approvals; + IDAO.Action[] memory actions = new IDAO.Action[](0); + // It only creates the proposal - vm.skip(true); + + vm.startPrank(carol); + pid = multisig.createProposal("c", actions, optimisticPlugin, false); + (, approvals,,,,) = multisig.getProposal(pid); + assertEq(approvals, 0, "Should be 0"); + + vm.startPrank(david); + pid = multisig.createProposal("d", actions, optimisticPlugin, false); + (, approvals,,,,) = multisig.getProposal(pid); + assertEq(approvals, 0, "Should be 0"); } modifier givenTheProposalIsNotCreated() { _; } - function test_WhenCallingGetProposalBeingUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingGetProposalBeingUncreated() external view givenTheProposalIsNotCreated { // It should return empty values - vm.skip(true); + + uint256 pid; + bool executed; + uint16 approvals; + Multisig.ProposalParameters memory parameters; + bytes memory metadataURI; + IDAO.Action[] memory actions = new IDAO.Action[](0); + OptimisticTokenVotingPlugin destinationPlugin; + + (executed, approvals, parameters, metadataURI, actions, destinationPlugin) = multisig.getProposal(pid); + + assertEq(executed, false, "Should be false"); + assertEq(approvals, 0, "Should be 0"); + assertEq(parameters.minApprovals, 0, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, 0, "Incorrect snapshotBlock"); + assertEq(parameters.expirationDate, 0, "Incorrect expirationDate"); + assertEq(metadataURI, "", "Incorrect metadataURI"); + assertEq(actions.length, 0, "Incorrect actions.length"); + assertEq(address(destinationPlugin), address(0), "Incorrect destinationPlugin"); } function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { @@ -982,7 +1136,7 @@ contract MultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanExecuteOrExecuteBeingUncreated() external givenTheProposalIsNotCreated { // It canExecute should return false (when currently listed and self appointed) // It execute should revert (when currently listed and self appointed) // It canExecute should return false (when currently listed, appointing someone else now) @@ -1031,7 +1185,7 @@ contract MultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanExecuteOrExecuteBeingOpen() external givenTheProposalIsOpen { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -1065,7 +1219,7 @@ contract MultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanExecuteOrExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when currently appointed by a signer listed on creation) @@ -1099,7 +1253,7 @@ contract MultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingPassed() external givenTheProposalPassed { + function test_WhenCallingCanExecuteOrExecuteBeingPassed() external givenTheProposalPassed { // It canExecute should return true, always // It execute should work, when called by anyone // It execute should emit an event, when called by anyone @@ -1141,7 +1295,7 @@ contract MultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanExecuteOrExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) @@ -1179,7 +1333,7 @@ contract MultisigTest is AragonTest { vm.skip(true); } - function test_WhenCallingCanExecuteAndExecuteBeingExpired() external givenTheProposalExpired { + function test_WhenCallingCanExecuteOrExecuteBeingExpired() external givenTheProposalExpired { // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml index 85346ca..1b59a59 100644 --- a/test/MultisigTree.t.yaml +++ b/test/MultisigTree.t.yaml @@ -95,6 +95,7 @@ MultisigTest: - given: creation caller is not listed or appointed then: - it: reverts + - it: reverts if listed before but not now - given: creation caller is appointed by a former signer then: - it: reverts @@ -123,7 +124,7 @@ MultisigTest: then: - it: should return empty values # Approval - - when: calling canApprove and approve [being uncreated] + - when: calling canApprove or approve [being uncreated] then: - it: canApprove should return false (when currently listed and self appointed) - it: approve should revert (when currently listed and self appointed) @@ -138,7 +139,7 @@ MultisigTest: then: - it: hasApproved should always return false # Execution - - when: calling canExecute and execute [being uncreated] + - when: calling canExecute or execute [being uncreated] then: - it: canExecute should return false (when currently listed and self appointed) - it: execute should revert (when currently listed and self appointed) @@ -156,7 +157,7 @@ MultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being open] + - when: calling canApprove or approve [being open] then: - it: canApprove should return true (when listed on creation, self appointed now) - it: approve should work (when listed on creation, self appointed now) @@ -183,7 +184,7 @@ MultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being open] + - when: calling canExecute or execute [being open] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -201,7 +202,7 @@ MultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being approved] + - when: calling canApprove or approve [being approved] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -216,7 +217,7 @@ MultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being approved] + - when: calling canExecute or execute [being approved] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -234,7 +235,7 @@ MultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being passed] + - when: calling canApprove or approve [being passed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -249,7 +250,7 @@ MultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being passed] + - when: calling canExecute or execute [being passed] then: - it: canExecute should return true, always - it: execute should work, when called by anyone @@ -270,7 +271,7 @@ MultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being executed] + - when: calling canApprove or approve [being executed] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -285,7 +286,7 @@ MultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being executed] + - when: calling canExecute or execute [being executed] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -303,7 +304,7 @@ MultisigTest: then: - it: should return the right values # Approval - - when: calling canApprove and approve [being expired] + - when: calling canApprove or approve [being expired] then: - it: canApprove should return false (when listed on creation, self appointed now) - it: approve should revert (when listed on creation, self appointed now) @@ -318,7 +319,7 @@ MultisigTest: then: - it: hasApproved should return false until approved # Execution - - when: calling canExecute and execute [being expired] + - when: calling canExecute or execute [being expired] then: - it: canExecute should return false (when listed on creation, self appointed now) - it: execute should revert (when listed on creation, self appointed now) @@ -328,4 +329,3 @@ MultisigTest: - it: execute should revert (when currently appointed by a signer listed on creation) - it: canExecute should return false (when unlisted on creation, unappointed now) - it: execute should revert (when unlisted on creation, unappointed now) - From 6adf49e3d3f845b9331cc9a8495a1a7caba999d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 8 Nov 2024 20:41:41 +0700 Subject: [PATCH 32/45] Multisig's testing WIP --- test/EmergencyMultisigTree.t.sol | 544 +++++++++++++++++++++++++++++- test/EmergencyMultisigTree.t.yaml | 12 +- test/MultisigTree.t.sol | 466 ++++++++++++++++++++++++- test/MultisigTree.t.yaml | 24 +- 4 files changed, 1002 insertions(+), 44 deletions(-) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 81b973a..c16d920 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1097,7 +1097,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(approvals, 0, "Should be 0"); } - function test_WhenCallingHashActions() external { + function test_WhenCallingHashActions() external view { bytes32 hashedActions; IDAO.Action[] memory actions = new IDAO.Action[](0); @@ -1126,12 +1126,21 @@ contract EmergencyMultisigTest is AragonTest { } modifier givenTheProposalIsNotCreated() { + // Alice: listed and self appointed + + // Bob: listed, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer + + // 0x1234: unlisted and unappointed + _; } - function test_WhenCallingGetProposalBeingUncreated() external view givenTheProposalIsNotCreated { + function test_WhenCallingGetProposalBeingUncreated() external givenTheProposalIsNotCreated { // It should return empty values - uint256 pid; bool executed; uint16 approvals; EmergencyMultisig.ProposalParameters memory parameters; @@ -1148,7 +1157,7 @@ contract EmergencyMultisigTest is AragonTest { publicMetadataUriHash, destinationActionsHash, destinationPlugin - ) = eMultisig.getProposal(pid); + ) = eMultisig.getProposal(1234); assertEq(executed, false, "Should be false"); assertEq(approvals, 0, "Should be 0"); @@ -1162,34 +1171,245 @@ contract EmergencyMultisigTest is AragonTest { } function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { + uint256 randomProposalId = 1234; + bool canApprove; + // It canApprove should return false (when currently listed and self appointed) + vm.startPrank(alice); + canApprove = eMultisig.canApprove(randomProposalId, alice); + assertEq(canApprove, false, "Should be false"); + // It approve should revert (when currently listed and self appointed) + vm.expectRevert( + abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, alice) + ); + eMultisig.approve(randomProposalId); + // It canApprove should return false (when currently listed, appointing someone else now) + randomProposalId++; + vm.startPrank(bob); + canApprove = eMultisig.canApprove(randomProposalId, bob); + assertEq(canApprove, false, "Should be false"); + // It approve should revert (when currently listed, appointing someone else now) + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, bob)); + eMultisig.approve(randomProposalId); + // It canApprove should return false (when appointed by a listed signer) + randomProposalId++; + vm.startPrank(randomWallet); + canApprove = eMultisig.canApprove(randomProposalId, randomWallet); + assertEq(canApprove, false, "Should be false"); + // It approve should revert (when appointed by a listed signer) + vm.expectRevert( + abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, randomWallet) + ); + eMultisig.approve(randomProposalId); + // It canApprove should return false (when currently unlisted and unappointed) + randomProposalId++; + vm.startPrank(address(1234)); + canApprove = eMultisig.canApprove(randomProposalId, address(1234)); + assertEq(canApprove, false, "Should be false"); + // It approve should revert (when currently unlisted and unappointed) - vm.skip(true); + vm.expectRevert( + abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, address(1234)) + ); + eMultisig.approve(randomProposalId); } function test_WhenCallingHasApprovedBeingUncreated() external givenTheProposalIsNotCreated { + bool hasApproved; + uint256 randomProposalId = 1234; // It hasApproved should always return false - vm.skip(true); + + hasApproved = eMultisig.hasApproved(randomProposalId, alice); + assertEq(hasApproved, false, "Should be false"); + + randomProposalId++; + hasApproved = eMultisig.hasApproved(randomProposalId, bob); + assertEq(hasApproved, false, "Should be false"); + + randomProposalId++; + hasApproved = eMultisig.hasApproved(randomProposalId, randomWallet); + assertEq(hasApproved, false, "Should be false"); + + randomProposalId++; + hasApproved = eMultisig.hasApproved(randomProposalId, address(1234)); + assertEq(hasApproved, false, "Should be false"); } function test_WhenCallingCanExecuteOrExecuteBeingUncreated() external givenTheProposalIsNotCreated { + uint256 randomProposalId = 1234; // It canExecute should always return false - vm.skip(true); + + bool canExecute = eMultisig.canExecute(randomProposalId); + assertEq(canExecute, false, "Should be false"); + } + + function testFuzz_WhenCallingCanExecuteOrExecuteBeingUncreated(uint256 randomProposalId) + external + givenTheProposalIsNotCreated + { + // It canExecute should always return false + + bool canExecute = eMultisig.canExecute(randomProposalId); + assertEq(canExecute, false, "Should be false"); } modifier givenTheProposalIsOpen() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + + // 0x1234: unlisted and unappointed on creation + + // Create proposal + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 actionsHash = eMultisig.hashActions(actions); + eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + + vm.startPrank(alice); + signerList.removeSigners(addrs); + _; } + function testFuzz_CanApproveReturnsfFalseIfNotCreated(uint256 randomProposalId) public view { + // returns `false` if the proposal doesn't exist + + assertEq(eMultisig.canApprove(randomProposalId, alice), false, "Should be false"); + assertEq(eMultisig.canApprove(randomProposalId, bob), false, "Should be false"); + assertEq(eMultisig.canApprove(randomProposalId, carol), false, "Should be false"); + assertEq(eMultisig.canApprove(randomProposalId, david), false, "Should be false"); + } + + function testFuzz_ApproveRevertsIfNotCreated(uint256 randomProposalId) public { + // Reverts if the proposal doesn't exist + + vm.startPrank(alice); + vm.expectRevert( + abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, alice) + ); + eMultisig.approve(randomProposalId); + + // 2 + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, bob)); + eMultisig.approve(randomProposalId); + + // 3 + vm.startPrank(carol); + vm.expectRevert( + abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, carol) + ); + eMultisig.approve(randomProposalId); + + // 4 + vm.startPrank(david); + vm.expectRevert( + abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, david) + ); + eMultisig.approve(randomProposalId); + } + + function testFuzz_CanExecuteReturnsFalseIfNotCreated(uint256 randomProposalId) public view { + // returns `false` if the proposal doesn't exist + + assertEq(eMultisig.canExecute(randomProposalId), false, "Should be false"); + } + + function testFuzz_ExecuteRevertsIfNotCreated(uint256 randomProposalId) public { + // reverts if the proposal doesn't exist + + IDAO.Action[] memory actions = new IDAO.Action[](0); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, randomProposalId)); + eMultisig.execute(randomProposalId, "", actions); + } + function test_WhenCallingGetProposalBeingOpen() external givenTheProposalIsOpen { // It should return the right values - vm.skip(true); + + // Get proposal returns the right values + + vm.warp(10); + { + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(0); + + assertEq(executed, false); + assertEq(approvals, 0); + assertEq(parameters.minApprovals, 3); + assertEq(parameters.snapshotBlock, block.number - 1); + assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + assertEq(encryptedPayloadURI, "ipfs://"); + assertEq(publicMetadataUriHash, hex""); + assertEq(destinationActionsHash, hex""); + assertEq(address(destinationPlugin), address(optimisticPlugin)); + } + // new proposal + + OptimisticTokenVotingPlugin newOptimisticPlugin; + (dao, newOptimisticPlugin,, eMultisig,,,,) = builder.build(); + vm.deal(address(dao), 1 ether); + + { + bytes32 metadataUriHash = keccak256("ipfs://another-public-metadata"); + + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = alice; + actions[0].data = hex"00112233"; + bytes32 actionsHash = eMultisig.hashActions(actions); + eMultisig.createProposal("ipfs://12340000", metadataUriHash, actionsHash, newOptimisticPlugin, true); + + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(1); + + assertEq(executed, false); + assertEq(approvals, 1); + assertEq(parameters.minApprovals, 3); + assertEq(parameters.snapshotBlock, block.number - 1); + assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + assertEq(encryptedPayloadURI, "ipfs://12340000"); + assertEq(publicMetadataUriHash, metadataUriHash); + assertEq(destinationActionsHash, actionsHash); + assertEq(address(destinationPlugin), address(newOptimisticPlugin)); + } } function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { @@ -1206,6 +1426,56 @@ contract EmergencyMultisigTest is AragonTest { vm.skip(true); } + function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { + // returns `false` if the approver is not listed + + { + // Leaving the deployment for fuzz efficiency + EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ + onlyListed: false, + minApprovals: 1, + signerList: signerList, + proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + eMultisig = EmergencyMultisig( + createProxyAndCall( + address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) + ) + ); + + vm.roll(block.number + 1); + } + + uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + // ko + if (randomWallet != alice) { + assertEq(eMultisig.canApprove(pid, randomWallet), false, "Should be false"); + } + + // static ok + assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); + } + + function testFuzz_ApproveRevertsIfNotListed(address randomSigner) public { + // Reverts if the signer is not listed + + builder = new DaoBuilder(); + (,,, eMultisig,,,,) = builder.withMultisigMember(alice).withMinApprovals(1).build(); + uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); + + if (randomSigner == alice) { + return; + } + + vm.startPrank(randomSigner); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, randomSigner)); + eMultisig.approve(pid); + + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, randomSigner)); + eMultisig.approve(pid); + } + function test_WhenCallingHasApprovedBeingOpen() external givenTheProposalIsOpen { // It hasApproved should return false until approved vm.skip(true); @@ -1224,12 +1494,76 @@ contract EmergencyMultisigTest is AragonTest { } modifier givenTheProposalWasApprovedByTheAddress() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + + // 0x1234: unlisted and unappointed on creation + + // Create proposal + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 actionsHash = eMultisig.hashActions(actions); + eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + + vm.startPrank(alice); + signerList.removeSigners(addrs); + + eMultisig.approve(0); + _; } function test_WhenCallingGetProposalBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It should return the right values vm.skip(true); + + // vm.startPrank(bob); + // eMultisig.approve(pid); + // vm.startPrank(carol); + // eMultisig.approve(pid); + + // ( + // executed, + // approvals, + // parameters, + // encryptedPayloadURI, + // publicMetadataUriHash, + // destinationActionsHash, + // destinationPlugin + // ) = eMultisig.getProposal(pid); + // assertEq(executed, false, "Should not be executed"); + // assertEq(approvals, 3, "Should be 3"); + + // assertEq(parameters.minApprovals, 3); + // assertEq(parameters.snapshotBlock, block.number - 1); + // assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + // assertEq(encryptedPayloadURI, "ipfs://12340000"); + // assertEq(publicMetadataUriHash, metadataUriHash); + // assertEq(destinationActionsHash, actionsHash); + // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); + + // // Execute + // vm.startPrank(alice); + // dao.grant(address(newOptimisticPlugin), address(eMultisig), newOptimisticPlugin.PROPOSER_PERMISSION_ID()); + // eMultisig.execute(pid, "ipfs://another-public-metadata", actions); } function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { @@ -1254,12 +1588,95 @@ contract EmergencyMultisigTest is AragonTest { } modifier givenTheProposalPassed() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + + // 0x1234: unlisted and unappointed on creation + + // Create proposal + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 actionsHash = eMultisig.hashActions(actions); + uint256 pid = + eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + + vm.startPrank(alice); + signerList.removeSigners(addrs); + + eMultisig.approve(pid); + + vm.startPrank(bob); + eMultisig.approve(pid); + + vm.startPrank(randomWallet); + eMultisig.approve(pid); + + vm.startPrank(alice); + _; } function test_WhenCallingGetProposalBeingPassed() external givenTheProposalPassed { // It should return the right values vm.skip(true); + + // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + // signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // // Alice: listed on creation and self appointed + + // // Bob: listed on creation, appointing someone else now + // vm.startPrank(bob); + // encryptionRegistry.appointWallet(randomWallet); + + // // Random Wallet: appointed by a listed signer on creation + + // // 0x1234: unlisted and unappointed on creation + + // // Create proposal + // IDAO.Action[] memory actions = new IDAO.Action[](1); + // actions[0].value = 1 ether; + // actions[0].to = address(bob); + // actions[0].data = hex"00112233"; + // bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + // bytes32 actionsHash = eMultisig.hashActions(actions); + // eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // // Remove (later) + // vm.roll(block.number + 50); + // address[] memory addrs = new address[](2); + // addrs[0] = alice; + // addrs[1] = bob; + + // vm.startPrank(alice); + // signerList.removeSigners(addrs); + + // eMultisig.approve(0); + + // vm.startPrank(bob); + // eMultisig.approve(0); + + // vm.startPrank(randomWallet); + // eMultisig.approve(0); } function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { @@ -1303,12 +1720,78 @@ contract EmergencyMultisigTest is AragonTest { } modifier givenTheProposalIsAlreadyExecuted() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + + // 0x1234: unlisted and unappointed on creation + + // Create proposal + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 actionsHash = eMultisig.hashActions(actions); + uint256 pid = + eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + + vm.startPrank(alice); + signerList.removeSigners(addrs); + + eMultisig.approve(pid); + + vm.startPrank(bob); + eMultisig.approve(pid); + + vm.startPrank(randomWallet); + eMultisig.approve(pid); + + eMultisig.execute(pid, "ipfs://the-metadata", actions); + + vm.startPrank(alice); + _; } function test_WhenCallingGetProposalBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It should return the right values vm.skip(true); + + // ( + // executed, + // approvals, + // parameters, + // encryptedPayloadURI, + // publicMetadataUriHash, + // destinationActionsHash, + // destinationPlugin + // ) = eMultisig.getProposal(pid); + + // assertEq(executed, true, "Should be executed"); + // assertEq(approvals, 3, "Should be 3"); + + // assertEq(parameters.minApprovals, 3); + // assertEq(parameters.snapshotBlock, block.number - 1); + // assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + // assertEq(encryptedPayloadURI, "ipfs://12340000"); + // assertEq(publicMetadataUriHash, metadataUriHash); + // assertEq(destinationActionsHash, actionsHash); + // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); } function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { @@ -1341,6 +1824,51 @@ contract EmergencyMultisigTest is AragonTest { } modifier givenTheProposalExpired() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + + // 0x1234: unlisted and unappointed on creation + + // Create proposal + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 actionsHash = eMultisig.hashActions(actions); + uint256 pid = + eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + + vm.startPrank(alice); + signerList.removeSigners(addrs); + + eMultisig.approve(pid); + + vm.startPrank(bob); + eMultisig.approve(pid); + + vm.startPrank(randomWallet); + eMultisig.approve(pid); + + vm.roll(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + + vm.startPrank(alice); + _; } diff --git a/test/EmergencyMultisigTree.t.yaml b/test/EmergencyMultisigTree.t.yaml index 952dfed..0209798 100644 --- a/test/EmergencyMultisigTree.t.yaml +++ b/test/EmergencyMultisigTree.t.yaml @@ -130,14 +130,14 @@ EmergencyMultisigTest: # Approval - when: calling canApprove or approve [being uncreated] then: - - it: canApprove should return false (when currently listed and self appointed) - - it: approve should revert (when currently listed and self appointed) - - it: canApprove should return false (when currently listed, appointing someone else now) - - it: approve should revert (when currently listed, appointing someone else now) + - it: canApprove should return false (when listed and self appointed) + - it: approve should revert (when listed and self appointed) + - it: canApprove should return false (when listed, appointing someone else now) + - it: approve should revert (when listed, appointing someone else now) - it: canApprove should return false (when appointed by a listed signer) - it: approve should revert (when appointed by a listed signer) - - it: canApprove should return false (when currently unlisted and unappointed) - - it: approve should revert (when currently unlisted and unappointed) + - it: canApprove should return false (when unlisted and unappointed) + - it: approve should revert (when unlisted and unappointed) # Has approved - when: calling hasApproved [being uncreated] then: diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index e9da558..d4db036 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -1093,13 +1093,22 @@ contract MultisigTest is AragonTest { } modifier givenTheProposalIsNotCreated() { + // Alice: listed and self appointed + + // Bob: listed, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer + + // 0x1234: unlisted and unappointed + _; } - function test_WhenCallingGetProposalBeingUncreated() external view givenTheProposalIsNotCreated { + function test_WhenCallingGetProposalBeingUncreated() external givenTheProposalIsNotCreated { // It should return empty values - uint256 pid; bool executed; uint16 approvals; Multisig.ProposalParameters memory parameters; @@ -1107,7 +1116,7 @@ contract MultisigTest is AragonTest { IDAO.Action[] memory actions = new IDAO.Action[](0); OptimisticTokenVotingPlugin destinationPlugin; - (executed, approvals, parameters, metadataURI, actions, destinationPlugin) = multisig.getProposal(pid); + (executed, approvals, parameters, metadataURI, actions, destinationPlugin) = multisig.getProposal(1234); assertEq(executed, false, "Should be false"); assertEq(approvals, 0, "Should be 0"); @@ -1120,38 +1129,193 @@ contract MultisigTest is AragonTest { } function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { - // It canApprove should return false (when currently listed and self appointed) - // It approve should revert (when currently listed and self appointed) - // It canApprove should return false (when currently listed, appointing someone else now) - // It approve should revert (when currently listed, appointing someone else now) + uint256 randomProposalId = 1234; + bool canApprove; + + // It canApprove should return false (when listed and self appointed) + vm.startPrank(alice); + canApprove = multisig.canApprove(randomProposalId, alice); + assertEq(canApprove, false, "Should be false"); + + // It approve should revert (when listed and self appointed) + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, alice)); + multisig.approve(randomProposalId, true); + + // It canApprove should return false (when listed, appointing someone else now) + randomProposalId++; + vm.startPrank(bob); + canApprove = multisig.canApprove(randomProposalId, bob); + assertEq(canApprove, false, "Should be false"); + + // It approve should revert (when listed, appointing someone else now) + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, bob)); + multisig.approve(randomProposalId, true); + // It canApprove should return false (when appointed by a listed signer) + randomProposalId++; + vm.startPrank(randomWallet); + canApprove = multisig.canApprove(randomProposalId, randomWallet); + assertEq(canApprove, false, "Should be false"); + // It approve should revert (when appointed by a listed signer) - // It canApprove should return false (when currently unlisted and unappointed) - // It approve should revert (when currently unlisted and unappointed) - vm.skip(true); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, randomWallet)); + multisig.approve(randomProposalId, false); + + // It canApprove should return false (when unlisted and unappointed) + randomProposalId++; + vm.startPrank(address(1234)); + canApprove = multisig.canApprove(randomProposalId, address(1234)); + assertEq(canApprove, false, "Should be false"); + + // It approve should revert (when unlisted and unappointed) + vm.expectRevert( + abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, address(1234)) + ); + multisig.approve(randomProposalId, false); } function test_WhenCallingHasApprovedBeingUncreated() external givenTheProposalIsNotCreated { + bool hasApproved; + uint256 randomProposalId = 1234; // It hasApproved should always return false - vm.skip(true); + + hasApproved = multisig.hasApproved(randomProposalId, alice); + assertEq(hasApproved, false, "Should be false"); + + randomProposalId++; + hasApproved = multisig.hasApproved(randomProposalId, bob); + assertEq(hasApproved, false, "Should be false"); + + randomProposalId++; + hasApproved = multisig.hasApproved(randomProposalId, randomWallet); + assertEq(hasApproved, false, "Should be false"); + + randomProposalId++; + hasApproved = multisig.hasApproved(randomProposalId, address(1234)); + assertEq(hasApproved, false, "Should be false"); } function test_WhenCallingCanExecuteOrExecuteBeingUncreated() external givenTheProposalIsNotCreated { - // It canExecute should return false (when currently listed and self appointed) - // It execute should revert (when currently listed and self appointed) - // It canExecute should return false (when currently listed, appointing someone else now) - // It execute should revert (when currently listed, appointing someone else now) + bool canExecute; + uint256 randomProposalId = 1234; + + // It canExecute should return false (when listed and self appointed) + vm.startPrank(alice); + canExecute = multisig.canExecute(randomProposalId); + assertEq(canExecute, false, "Should be false"); + + // It execute should revert (when listed and self appointed) + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, randomProposalId)); + multisig.execute(randomProposalId); + + // It canExecute should return false (when listed, appointing someone else now) + randomProposalId++; + vm.startPrank(bob); + canExecute = multisig.canExecute(randomProposalId); + assertEq(canExecute, false, "Should be false"); + + // It execute should revert (when listed, appointing someone else now) + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, randomProposalId)); + multisig.execute(randomProposalId); + // It canExecute should return false (when appointed by a listed signer) + randomProposalId++; + vm.startPrank(randomWallet); + canExecute = multisig.canExecute(randomProposalId); + assertEq(canExecute, false, "Should be false"); + // It execute should revert (when appointed by a listed signer) - // It canExecute should return false (when currently unlisted and unappointed) - // It execute should revert (when currently unlisted and unappointed) - vm.skip(true); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, randomProposalId)); + multisig.execute(randomProposalId); + + // It canExecute should return false (when unlisted and unappointed) + randomProposalId++; + vm.startPrank(address(1234)); + canExecute = multisig.canExecute(randomProposalId); + assertEq(canExecute, false, "Should be false"); + + // It execute should revert (when unlisted and unappointed) + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, randomProposalId)); + multisig.execute(randomProposalId); } modifier givenTheProposalIsOpen() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + // 0x1234: unlisted and unappointed on creation + + // Create proposal 0 + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + vm.startPrank(alice); + signerList.removeSigners(addrs); + _; } + function testFuzz_CanApproveReturnsfFalseIfNotCreated(uint256 randomProposalId) public view { + // returns `false` if the proposal doesn't exist + + assertEq(multisig.canApprove(randomProposalId, alice), false, "Should be false"); + assertEq(multisig.canApprove(randomProposalId, bob), false, "Should be false"); + assertEq(multisig.canApprove(randomProposalId, carol), false, "Should be false"); + assertEq(multisig.canApprove(randomProposalId, david), false, "Should be false"); + } + + function testFuzz_ApproveRevertsIfNotCreated(uint256 randomProposalId) public { + // Reverts if the proposal doesn't exist + + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, alice)); + multisig.approve(randomProposalId, false); + + // 2 + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, bob)); + multisig.approve(randomProposalId, false); + + // 3 + vm.startPrank(carol); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, carol)); + multisig.approve(randomProposalId, true); + + // 4 + vm.startPrank(david); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, david)); + multisig.approve(randomProposalId, true); + } + + function testFuzz_CanExecuteReturnsFalseIfNotCreated(uint256 randomProposalId) public view { + // returns `false` if the proposal doesn't exist + + assertEq(multisig.canExecute(randomProposalId), false, "Should be false"); + } + + function testFuzz_ExecuteRevertsIfNotCreated(uint256 randomProposalId) public { + // reverts if the proposal doesn't exist + + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, randomProposalId)); + multisig.execute(randomProposalId); + } + function test_WhenCallingGetProposalBeingOpen() external givenTheProposalIsOpen { // It should return the right values vm.skip(true); @@ -1171,6 +1335,60 @@ contract MultisigTest is AragonTest { vm.skip(true); } + function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { + // returns `false` if the approver is not listed + + { + // Deploy a new multisig instance (more efficient than the builder for fuzz testing) + Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ + onlyListed: true, + minApprovals: 1, + destinationProposalDuration: 4 days, + signerList: signerList, + proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD + }); + address[] memory signers = new address[](1); + signers[0] = alice; + + multisig = Multisig( + createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings))) + ); + vm.roll(block.number + 1); + } + + IDAO.Action[] memory actions = new IDAO.Action[](0); + uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); + + // ko + if (randomWallet != alice) { + assertEq(multisig.canApprove(pid, randomWallet), false, "Should be false"); + } + + // static ok + assertEq(multisig.canApprove(pid, alice), true, "Should be true"); + } + + function testFuzz_ApproveRevertsIfNotListed(address randomSigner) public { + // Reverts if the signer is not listed + + builder = new DaoBuilder(); + (,, multisig,,,,,) = builder.withMultisigMember(alice).withMinApprovals(1).build(); + + IDAO.Action[] memory actions = new IDAO.Action[](0); + uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); + + if (randomSigner == alice) { + return; + } + + vm.startPrank(randomSigner); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, randomSigner)); + multisig.approve(pid, false); + + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, randomSigner)); + multisig.approve(pid, true); + } + function test_WhenCallingApproveWithTryExecutionAndAlmostPassedBeingOpen() external givenTheProposalIsOpen { // It approve should also execute the proposal // It approve should emit an Executed event @@ -1198,6 +1416,36 @@ contract MultisigTest is AragonTest { } modifier givenTheProposalWasApprovedByTheAddress() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + // 0x1234: unlisted and unappointed on creation + + // Create proposal 0 + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + vm.startPrank(alice); + signerList.removeSigners(addrs); + + multisig.approve(pid, false); + _; } @@ -1228,12 +1476,91 @@ contract MultisigTest is AragonTest { } modifier givenTheProposalPassed() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + // 0x1234: unlisted and unappointed on creation + + // Create proposal 0 + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + vm.startPrank(alice); + signerList.removeSigners(addrs); + + multisig.approve(pid, false); + + vm.startPrank(bob); + multisig.approve(pid, false); + + vm.startPrank(randomWallet); + multisig.approve(pid, false); + + vm.startPrank(alice); + _; } function test_WhenCallingGetProposalBeingPassed() external givenTheProposalPassed { // It should return the right values vm.skip(true); + + + // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + // signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // // Alice: listed on creation and self appointed + + // // Bob: listed on creation, appointing someone else now + // vm.startPrank(bob); + // encryptionRegistry.appointWallet(randomWallet); + + // // Random Wallet: appointed by a listed signer on creation + + // // 0x1234: unlisted and unappointed on creation + + // // Create proposal + // IDAO.Action[] memory actions = new IDAO.Action[](1); + // actions[0].value = 1 ether; + // actions[0].to = address(bob); + // actions[0].data = hex"00112233"; + // bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + // bytes32 actionsHash = eMultisig.hashActions(actions); + // eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + + // // Remove (later) + // vm.roll(block.number + 50); + // address[] memory addrs = new address[](2); + // addrs[0] = alice; + // addrs[1] = bob; + + // vm.startPrank(alice); + // signerList.removeSigners(addrs); + + // eMultisig.approve(0); + + // vm.startPrank(bob); + // eMultisig.approve(0); + + // vm.startPrank(randomWallet); + // eMultisig.approve(0); } function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { @@ -1270,12 +1597,75 @@ contract MultisigTest is AragonTest { } modifier givenTheProposalIsAlreadyExecuted() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + // 0x1234: unlisted and unappointed on creation + + // Create proposal 0 + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + vm.startPrank(alice); + signerList.removeSigners(addrs); + + multisig.approve(pid, false); + + vm.startPrank(bob); + multisig.approve(pid, false); + + vm.startPrank(randomWallet); + multisig.approve(pid, false); + + multisig.execute(pid); + + vm.startPrank(alice); + _; } function test_WhenCallingGetProposalBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It should return the right values vm.skip(true); + + + // ( + // executed, + // approvals, + // parameters, + // encryptedPayloadURI, + // publicMetadataUriHash, + // destinationActionsHash, + // destinationPlugin + // ) = eMultisig.getProposal(pid); + + // assertEq(executed, true, "Should be executed"); + // assertEq(approvals, 3, "Should be 3"); + + // assertEq(parameters.minApprovals, 3); + // assertEq(parameters.snapshotBlock, block.number - 1); + // assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + // assertEq(encryptedPayloadURI, "ipfs://12340000"); + // assertEq(publicMetadataUriHash, metadataUriHash); + // assertEq(destinationActionsHash, actionsHash); + // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); + } function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { @@ -1308,6 +1698,46 @@ contract MultisigTest is AragonTest { } modifier givenTheProposalExpired() { + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + + // Random Wallet: appointed by a listed signer on creation + // 0x1234: unlisted and unappointed on creation + + // Create proposal 0 + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex"00112233"; + uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + + // Remove (later) + vm.roll(block.number + 50); + address[] memory addrs = new address[](2); + addrs[0] = alice; + addrs[1] = bob; + vm.startPrank(alice); + signerList.removeSigners(addrs); + + multisig.approve(pid, false); + + vm.startPrank(bob); + multisig.approve(pid, false); + + vm.startPrank(randomWallet); + multisig.approve(pid, false); + + vm.roll(block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + + vm.startPrank(alice); + _; } diff --git a/test/MultisigTree.t.yaml b/test/MultisigTree.t.yaml index 1b59a59..cad10df 100644 --- a/test/MultisigTree.t.yaml +++ b/test/MultisigTree.t.yaml @@ -126,14 +126,14 @@ MultisigTest: # Approval - when: calling canApprove or approve [being uncreated] then: - - it: canApprove should return false (when currently listed and self appointed) - - it: approve should revert (when currently listed and self appointed) - - it: canApprove should return false (when currently listed, appointing someone else now) - - it: approve should revert (when currently listed, appointing someone else now) + - it: canApprove should return false (when listed and self appointed) + - it: approve should revert (when listed and self appointed) + - it: canApprove should return false (when listed, appointing someone else now) + - it: approve should revert (when listed, appointing someone else now) - it: canApprove should return false (when appointed by a listed signer) - it: approve should revert (when appointed by a listed signer) - - it: canApprove should return false (when currently unlisted and unappointed) - - it: approve should revert (when currently unlisted and unappointed) + - it: canApprove should return false (when unlisted and unappointed) + - it: approve should revert (when unlisted and unappointed) # Has approved - when: calling hasApproved [being uncreated] then: @@ -141,14 +141,14 @@ MultisigTest: # Execution - when: calling canExecute or execute [being uncreated] then: - - it: canExecute should return false (when currently listed and self appointed) - - it: execute should revert (when currently listed and self appointed) - - it: canExecute should return false (when currently listed, appointing someone else now) - - it: execute should revert (when currently listed, appointing someone else now) + - it: canExecute should return false (when listed and self appointed) + - it: execute should revert (when listed and self appointed) + - it: canExecute should return false (when listed, appointing someone else now) + - it: execute should revert (when listed, appointing someone else now) - it: canExecute should return false (when appointed by a listed signer) - it: execute should revert (when appointed by a listed signer) - - it: canExecute should return false (when currently unlisted and unappointed) - - it: execute should revert (when currently unlisted and unappointed) + - it: canExecute should return false (when unlisted and unappointed) + - it: execute should revert (when unlisted and unappointed) - given: The proposal is open then: From a0e3ae218d27cd5cae64c4d3fd962fb0b092a70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 12 Nov 2024 20:35:54 +0700 Subject: [PATCH 33/45] Optimizing the signer list getters --- src/EmergencyMultisig.sol | 34 +++++++-------- src/Multisig.sol | 34 +++++++-------- src/SignerList.sol | 72 +++++++++++++++++++------------- src/interfaces/ISignerList.sol | 26 +++++++----- test/EmergencyMultisigTree.t.sol | 51 ++++++++++++---------- test/MultisigTree.t.sol | 55 ++++++++++++------------ 6 files changed, 145 insertions(+), 127 deletions(-) diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index ce3d02d..1ea727a 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -178,10 +178,10 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa bool _approveProposal ) external returns (uint256 proposalId) { if (multisigSettings.onlyListed) { - (bool ownerIsListed,) = multisigSettings.signerList.resolveEncryptionAccountStatus(msg.sender); + bool _listedOrAppointedByListed = multisigSettings.signerList.isListedOrAppointedByListed(msg.sender); // Only the account or its appointed address may create proposals - if (!ownerIsListed) { + if (!_listedOrAppointedByListed) { revert ProposalCreationForbidden(msg.sender); } } @@ -237,10 +237,11 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa proposal_.approvals += 1; } - // Register the approval as being made by the owner, the one who isListed() relates to - address _owner = multisigSettings.signerList.resolveEncryptionOwner(_sender); + // Register the approval as being made by the owner + address _owner = multisigSettings.signerList.getOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); proposal_.approvers[_owner] = true; + // We emit the event as the owner's approval emit Approved({proposalId: _proposalId, approver: _owner}); // Automatic execution is intentionally omitted in order to prevent @@ -292,7 +293,7 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa /// @inheritdoc IEmergencyMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { - address _owner = multisigSettings.signerList.resolveEncryptionOwner(_account); + address _owner = multisigSettings.signerList.getCurrentOwner(_account); return proposals[_proposalId].approvers[_owner]; } @@ -351,24 +352,19 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa return false; } - (address _owner, address _appointedWallet) = multisigSettings.signerList.resolveEncryptionAccount(_approver); - - if (_owner == address(0)) { - // Not resolved + // This internally calls `isListedAtBlock`. + // If not listed or resolved, it returns address(0) + (address _resolvedOwner, address _resolvedVoter) = + multisigSettings.signerList.resolveAccountAtBlock(_approver, proposal_.parameters.snapshotBlock); + if (_resolvedOwner == address(0) || _resolvedVoter == address(0)) { + // Not listedAtBlock() nor appointed by a listed owner return false; - } else if (!multisigSettings.signerList.isListedAtBlock(_owner, proposal_.parameters.snapshotBlock)) { - // The owner account had no voting power + } else if (_approver != _resolvedVoter) { + // Only the voter account can vote (owners who appointed, can't) return false; } - // If there is an appointed wallet, only that wallet can approve - else if (_appointedWallet != address(0)) { - // Someone else is appointed - if (_approver != _appointedWallet) return false; - } - - // If _appointedWallet == address(0), then _owner == _approver. No need to check. - if (proposal_.approvers[_owner]) { + if (proposal_.approvers[_resolvedOwner]) { // The account already approved return false; } diff --git a/src/Multisig.sol b/src/Multisig.sol index 883afb9..d75e026 100644 --- a/src/Multisig.sol +++ b/src/Multisig.sol @@ -175,10 +175,10 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { bool _approveProposal ) external returns (uint256 proposalId) { if (multisigSettings.onlyListed) { - (bool ownerIsListed,) = multisigSettings.signerList.resolveEncryptionAccountStatus(msg.sender); + bool _listedOrAppointedByListed = multisigSettings.signerList.isListedOrAppointedByListed(msg.sender); // Only the account or its appointed address may create proposals - if (!ownerIsListed) { + if (!_listedOrAppointedByListed) { revert ProposalCreationForbidden(msg.sender); } } @@ -241,10 +241,11 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { proposal_.approvals += 1; } - // Register the approval as being made by the owner, the one who isListed() relates to - address _owner = multisigSettings.signerList.resolveEncryptionOwner(_sender); + // Register the approval as being made by the owner. isListedAtBlock() relates to it + address _owner = multisigSettings.signerList.getOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); proposal_.approvers[_owner] = true; + // We emit the event as the owner's approval emit Approved({proposalId: _proposalId, approver: _owner}); if (_tryExecution && _canExecute(_proposalId)) { @@ -294,7 +295,7 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { /// @inheritdoc IMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { - address _owner = multisigSettings.signerList.resolveEncryptionOwner(_account); + address _owner = multisigSettings.signerList.getCurrentOwner(_account); return proposals[_proposalId].approvers[_owner]; } @@ -336,24 +337,19 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { return false; } - (address _owner, address _appointedWallet) = multisigSettings.signerList.resolveEncryptionAccount(_approver); - - if (_owner == address(0)) { - // Not resolved + // This internally calls `isListedAtBlock`. + // If not listed or resolved, it returns address(0) + (address _resolvedOwner, address _resolvedVoter) = + multisigSettings.signerList.resolveAccountAtBlock(_approver, proposal_.parameters.snapshotBlock); + if (_resolvedOwner == address(0) || _resolvedVoter == address(0)) { + // Not listedAtBlock() nor appointed by a listed owner return false; - } else if (!multisigSettings.signerList.isListedAtBlock(_owner, proposal_.parameters.snapshotBlock)) { - // The owner account had no voting power + } else if (_approver != _resolvedVoter) { + // Only the voter account can vote (owners who appointed, can't) return false; } - // If there is an appointed wallet, only that wallet can approve - else if (_appointedWallet != address(0)) { - // Someone else is appointed - if (_approver != _appointedWallet) return false; - } - - // If _appointedWallet == address(0), then _owner == _approver. No need to check. - if (proposal_.approvers[_owner]) { + if (proposal_.approvers[_resolvedOwner]) { // The account already approved return false; } diff --git a/src/SignerList.sol b/src/SignerList.sol index 4876e8f..5fbcfeb 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -103,48 +103,60 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza } /// @inheritdoc ISignerList - function resolveEncryptionAccountStatus(address _address) - public - view - returns (bool ownerIsListed, bool isAppointed) - { - if (this.isListed(_address)) { - ownerIsListed = true; - } else if (this.isListed(settings.encryptionRegistry.appointedBy(_address))) { - ownerIsListed = true; - isAppointed = true; + function isListedOrAppointedByListed(address _address) public view returns (bool listedOrAppointedByListed) { + if (isListed(_address)) { + return true; + } else if (isListed(settings.encryptionRegistry.appointedBy(_address))) { + return true; } - // Not found, return blank values + // Not found, return blank (false) } /// @inheritdoc ISignerList - function resolveEncryptionOwner(address _address) public view returns (address owner) { - (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_address); + function getCurrentOwner(address _address) public view returns (address _owner) { + if (isListed(_address)) { + return _address; + } + address _appointer = settings.encryptionRegistry.appointedBy(_address); + if (isListed(_appointer)) { + return _appointer; + } + + // Not found, return a blank address + } - if (!ownerIsListed) { - return address(0); - } else if (isAppointed) { - return settings.encryptionRegistry.appointedBy(_address); + /// @inheritdoc ISignerList + function getOwnerAtBlock(address _address, uint256 _blockNumber) public view returns (address _owner) { + if (isListedAtBlock(_address, _blockNumber)) { + return _address; } - return _address; + address _appointer = settings.encryptionRegistry.appointedBy(_address); + if (isListedAtBlock(_appointer, _blockNumber)) { + return _appointer; + } + + // Not found, return a blank address } /// @inheritdoc ISignerList - function resolveEncryptionAccount(address _address) public view returns (address owner, address appointedWallet) { - (bool ownerIsListed, bool isAppointed) = resolveEncryptionAccountStatus(_address); - - if (ownerIsListed) { - if (isAppointed) { - owner = settings.encryptionRegistry.appointedBy(_address); - appointedWallet = _address; - } else { - owner = _address; - appointedWallet = settings.encryptionRegistry.getAppointedWallet(_address); - } + function resolveAccountAtBlock(address _address, uint256 _blockNumber) + public + view + returns (address _owner, address _voter) + { + if (isListedAtBlock(_address, _blockNumber)) { + // The owner + the voter + return (_address, settings.encryptionRegistry.getAppointedWallet(_address)); + } + + address _appointer = settings.encryptionRegistry.appointedBy(_address); + if (this.isListedAtBlock(_appointer, _blockNumber)) { + // The appointed wallet votes + return (_appointer, _address); } - // Not found, return blank values + // Not found, returning empty addresses } /// @inheritdoc ISignerList diff --git a/src/interfaces/ISignerList.sol b/src/interfaces/ISignerList.sol index eb25e63..520b472 100644 --- a/src/interfaces/ISignerList.sol +++ b/src/interfaces/ISignerList.sol @@ -19,25 +19,29 @@ interface ISignerList { /// @param signers The addresses of the signers to be removed. function removeSigners(address[] calldata signers) external; - /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. + /// @notice Given an address, determines whether it is a listed signer or a wallet appointed by a listed owner. /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. - /// @return ownerIsListed If resolved, whether the given address is currently listed as a member. False otherwise. - /// @return isAppointed If resolved, whether the given address is appointed by the owner. False otherwise. - function resolveEncryptionAccountStatus(address _sender) - external - view - returns (bool ownerIsListed, bool isAppointed); + /// @return listedOrAppointedByListed If resolved, whether the given address is currently listed as a member. False otherwise. + function isListedOrAppointedByListed(address _address) external returns (bool listedOrAppointedByListed); /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. + /// @param sender The address to check within the list of signers or the appointed accounts. /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. - function resolveEncryptionOwner(address _sender) external view returns (address owner); + function getCurrentOwner(address sender) external returns (address owner); /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. - /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. + /// @param sender The address to check within the list of signers or the appointed accounts. + /// @param blockNumber The block at which the list should be checked /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. - /// @return appointedWallet If resolved, it contains the wallet address appointed for decryption, if any. Returns address(0) otherwise. - function resolveEncryptionAccount(address sender) external view returns (address owner, address appointedWallet); + function getOwnerAtBlock(address sender, uint256 blockNumber) external returns (address owner); + + /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. + /// @return owner If listed and resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. + /// @return voter If listed and resolved, it contains the wallet address appointed for decryption, if any. Returns address(0) otherwise. + function resolveAccountAtBlock(address sender, uint256 _blockNumber) + external + returns (address owner, address voter); /// @notice Among the SignerList's members registered on the EncryptionRegistry, return the effective address they use for encryption function getEncryptionRecipients() external view returns (address[] memory); diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index c16d920..5916431 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -63,8 +63,10 @@ contract EmergencyMultisigTest is AragonTest { vm.roll(100); builder = new DaoBuilder(); - (dao,,, eMultisig,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); + (dao, optimisticPlugin,, eMultisig,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice) + .withMultisigMember(bob).withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).withMinDuration( + 0 + ).build(); } modifier givenANewlyDeployedContract() { @@ -1274,11 +1276,13 @@ contract EmergencyMultisigTest is AragonTest { // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1378,15 +1382,17 @@ contract EmergencyMultisigTest is AragonTest { OptimisticTokenVotingPlugin newOptimisticPlugin; (dao, newOptimisticPlugin,, eMultisig,,,,) = builder.build(); + vm.deal(address(dao), 1 ether); { bytes32 metadataUriHash = keccak256("ipfs://another-public-metadata"); + IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = alice; - actions[0].data = hex"00112233"; + actions[0].data = hex""; bytes32 actionsHash = eMultisig.hashActions(actions); eMultisig.createProposal("ipfs://12340000", metadataUriHash, actionsHash, newOptimisticPlugin, true); @@ -1508,11 +1514,13 @@ contract EmergencyMultisigTest is AragonTest { // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1535,7 +1543,7 @@ contract EmergencyMultisigTest is AragonTest { // It should return the right values vm.skip(true); - // vm.startPrank(bob); + // vm.startPrank(randomWallet); // Appointed by Bob // eMultisig.approve(pid); // vm.startPrank(carol); // eMultisig.approve(pid); @@ -1602,11 +1610,13 @@ contract EmergencyMultisigTest is AragonTest { // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); uint256 pid = @@ -1623,9 +1633,6 @@ contract EmergencyMultisigTest is AragonTest { eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(randomWallet); eMultisig.approve(pid); @@ -1652,11 +1659,13 @@ contract EmergencyMultisigTest is AragonTest { // // 0x1234: unlisted and unappointed on creation - // // Create proposal + // vm.deal(address(dao), 1 ether);// + + // Create proposal // IDAO.Action[] memory actions = new IDAO.Action[](1); // actions[0].value = 1 ether; // actions[0].to = address(bob); - // actions[0].data = hex"00112233"; + // actions[0].data = hex""; // bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); // bytes32 actionsHash = eMultisig.hashActions(actions); // eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1672,9 +1681,6 @@ contract EmergencyMultisigTest is AragonTest { // eMultisig.approve(0); - // vm.startPrank(bob); - // eMultisig.approve(0); - // vm.startPrank(randomWallet); // eMultisig.approve(0); } @@ -1734,11 +1740,13 @@ contract EmergencyMultisigTest is AragonTest { // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); uint256 pid = @@ -1755,10 +1763,10 @@ contract EmergencyMultisigTest is AragonTest { eMultisig.approve(pid); - vm.startPrank(bob); + vm.startPrank(randomWallet); eMultisig.approve(pid); - vm.startPrank(randomWallet); + vm.startPrank(carol); eMultisig.approve(pid); eMultisig.execute(pid, "ipfs://the-metadata", actions); @@ -1838,11 +1846,13 @@ contract EmergencyMultisigTest is AragonTest { // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); uint256 pid = @@ -1859,9 +1869,6 @@ contract EmergencyMultisigTest is AragonTest { eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(randomWallet); eMultisig.approve(pid); diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index d4db036..5886d31 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -64,8 +64,8 @@ contract MultisigTest is AragonTest { vm.startPrank(alice); builder = new DaoBuilder(); - (dao,, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); + (dao, optimisticPlugin, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice) + .withMultisigMember(bob).withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); } modifier givenANewlyDeployedContract() { @@ -1253,11 +1253,13 @@ contract MultisigTest is AragonTest { // Random Wallet: appointed by a listed signer on creation // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; + actions[0].value = 0; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; multisig.createProposal("ipfs://", actions, optimisticPlugin, false); // Remove (later) @@ -1429,11 +1431,13 @@ contract MultisigTest is AragonTest { // Random Wallet: appointed by a listed signer on creation // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; + actions[0].value = 0; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); // Remove (later) @@ -1489,11 +1493,13 @@ contract MultisigTest is AragonTest { // Random Wallet: appointed by a listed signer on creation // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; + actions[0].value = 0; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); // Remove (later) @@ -1506,9 +1512,6 @@ contract MultisigTest is AragonTest { multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(randomWallet); multisig.approve(pid, false); @@ -1521,7 +1524,6 @@ contract MultisigTest is AragonTest { // It should return the right values vm.skip(true); - // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); // signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); @@ -1536,11 +1538,13 @@ contract MultisigTest is AragonTest { // // 0x1234: unlisted and unappointed on creation - // // Create proposal + // vm.deal(address(dao), 1 ether); + // + // Create proposal // IDAO.Action[] memory actions = new IDAO.Action[](1); - // actions[0].value = 1 ether; + // actions[0].value = 0; // actions[0].to = address(bob); - // actions[0].data = hex"00112233"; + // actions[0].data = hex""; // bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); // bytes32 actionsHash = eMultisig.hashActions(actions); // eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1610,11 +1614,13 @@ contract MultisigTest is AragonTest { // Random Wallet: appointed by a listed signer on creation // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; + actions[0].value = 0; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); // Remove (later) @@ -1627,10 +1633,10 @@ contract MultisigTest is AragonTest { multisig.approve(pid, false); - vm.startPrank(bob); + vm.startPrank(randomWallet); multisig.approve(pid, false); - vm.startPrank(randomWallet); + vm.startPrank(carol); multisig.approve(pid, false); multisig.execute(pid); @@ -1644,7 +1650,6 @@ contract MultisigTest is AragonTest { // It should return the right values vm.skip(true); - // ( // executed, // approvals, @@ -1665,7 +1670,6 @@ contract MultisigTest is AragonTest { // assertEq(publicMetadataUriHash, metadataUriHash); // assertEq(destinationActionsHash, actionsHash); // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); - } function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { @@ -1711,11 +1715,13 @@ contract MultisigTest is AragonTest { // Random Wallet: appointed by a listed signer on creation // 0x1234: unlisted and unappointed on creation + vm.deal(address(dao), 1 ether); + // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; + actions[0].value = 0; actions[0].to = address(bob); - actions[0].data = hex"00112233"; + actions[0].data = hex""; uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); // Remove (later) @@ -1728,9 +1734,6 @@ contract MultisigTest is AragonTest { multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(randomWallet); multisig.approve(pid, false); From 413c70c8dfef6631960c834bba9451ed47de4c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 12 Nov 2024 21:15:31 +0700 Subject: [PATCH 34/45] Adapted tests (owner at block) --- src/EmergencyMultisig.sol | 5 +- src/Multisig.sol | 5 +- src/SignerList.sol | 15 +-- src/interfaces/ISignerList.sol | 8 +- test/EmergencyMultisigTree.t.sol | 185 +++++++++++++++++++++---------- test/MultisigTree.t.sol | 2 +- 6 files changed, 136 insertions(+), 84 deletions(-) diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index 1ea727a..0e46769 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -238,7 +238,7 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa } // Register the approval as being made by the owner - address _owner = multisigSettings.signerList.getOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); + address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); proposal_.approvers[_owner] = true; // We emit the event as the owner's approval @@ -293,7 +293,8 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa /// @inheritdoc IEmergencyMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { - address _owner = multisigSettings.signerList.getCurrentOwner(_account); + Proposal storage proposal_ = proposals[_proposalId]; + address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_account, proposal_.parameters.snapshotBlock); return proposals[_proposalId].approvers[_owner]; } diff --git a/src/Multisig.sol b/src/Multisig.sol index d75e026..13c1657 100644 --- a/src/Multisig.sol +++ b/src/Multisig.sol @@ -242,7 +242,7 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { } // Register the approval as being made by the owner. isListedAtBlock() relates to it - address _owner = multisigSettings.signerList.getOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); + address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); proposal_.approvers[_owner] = true; // We emit the event as the owner's approval @@ -295,7 +295,8 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { /// @inheritdoc IMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { - address _owner = multisigSettings.signerList.getCurrentOwner(_account); + Proposal storage proposal_ = proposals[_proposalId]; + address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_account, proposal_.parameters.snapshotBlock); return proposals[_proposalId].approvers[_owner]; } diff --git a/src/SignerList.sol b/src/SignerList.sol index 5fbcfeb..adc9cde 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -114,20 +114,7 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza } /// @inheritdoc ISignerList - function getCurrentOwner(address _address) public view returns (address _owner) { - if (isListed(_address)) { - return _address; - } - address _appointer = settings.encryptionRegistry.appointedBy(_address); - if (isListed(_appointer)) { - return _appointer; - } - - // Not found, return a blank address - } - - /// @inheritdoc ISignerList - function getOwnerAtBlock(address _address, uint256 _blockNumber) public view returns (address _owner) { + function getListedOwnerAtBlock(address _address, uint256 _blockNumber) public view returns (address _owner) { if (isListedAtBlock(_address, _blockNumber)) { return _address; } diff --git a/src/interfaces/ISignerList.sol b/src/interfaces/ISignerList.sol index 520b472..5534c9b 100644 --- a/src/interfaces/ISignerList.sol +++ b/src/interfaces/ISignerList.sol @@ -24,17 +24,11 @@ interface ISignerList { /// @return listedOrAppointedByListed If resolved, whether the given address is currently listed as a member. False otherwise. function isListedOrAppointedByListed(address _address) external returns (bool listedOrAppointedByListed); - /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. - /// @dev NOTE: This function will only resolve based on the current state. Do not use it as an alias of `isListedAtBock()`. - /// @param sender The address to check within the list of signers or the appointed accounts. - /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. - function getCurrentOwner(address sender) external returns (address owner); - /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. /// @param sender The address to check within the list of signers or the appointed accounts. /// @param blockNumber The block at which the list should be checked /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. - function getOwnerAtBlock(address sender, uint256 blockNumber) external returns (address owner); + function getListedOwnerAtBlock(address sender, uint256 blockNumber) external returns (address owner); /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. /// @return owner If listed and resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 5916431..5b28fe0 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1356,7 +1356,6 @@ contract EmergencyMultisigTest is AragonTest { // Get proposal returns the right values - vm.warp(10); { ( bool executed, @@ -1371,24 +1370,23 @@ contract EmergencyMultisigTest is AragonTest { assertEq(executed, false); assertEq(approvals, 0); assertEq(parameters.minApprovals, 3); - assertEq(parameters.snapshotBlock, block.number - 1); + assertEq(parameters.snapshotBlock, block.number - 1 - 50); // We made +50 to remove wallets assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - assertEq(encryptedPayloadURI, "ipfs://"); - assertEq(publicMetadataUriHash, hex""); - assertEq(destinationActionsHash, hex""); + assertEq(encryptedPayloadURI, "ipfs://encrypted"); + assertEq(publicMetadataUriHash, hex"538a79dd5d5741d2d66c0b0ec46e102023a64f8e1e3caeacb6aa4b2b14662a0d"); + assertEq(destinationActionsHash, hex"e212a57e4595f81151b46333ea31e2d5043b53bd562141e1efa1b2778cb3c208"); assertEq(address(destinationPlugin), address(optimisticPlugin)); } // new proposal OptimisticTokenVotingPlugin newOptimisticPlugin; (dao, newOptimisticPlugin,, eMultisig,,,,) = builder.build(); - + vm.deal(address(dao), 1 ether); { bytes32 metadataUriHash = keccak256("ipfs://another-public-metadata"); - IDAO.Action[] memory actions = new IDAO.Action[](1); actions[0].value = 1 ether; actions[0].to = alice; @@ -1404,7 +1402,7 @@ contract EmergencyMultisigTest is AragonTest { bytes32 publicMetadataUriHash, bytes32 destinationActionsHash, OptimisticTokenVotingPlugin destinationPlugin - ) = eMultisig.getProposal(1); + ) = eMultisig.getProposal(0); assertEq(executed, false); assertEq(approvals, 1); @@ -1420,16 +1418,35 @@ contract EmergencyMultisigTest is AragonTest { function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { // It canApprove should return true (when listed on creation, self appointed now) + bool canApprove = eMultisig.canApprove(0, alice); + assertEq(canApprove, true, "Alice should be able to approve"); + // It approve should work (when listed on creation, self appointed now) // It approve should emit an event (when listed on creation, self appointed now) + vm.startPrank(alice); + vm.expectEmit(); + emit Approved(0, alice); + eMultisig.approve(0); + // It canApprove should return false (when listed on creation, appointing someone else now) + canApprove = eMultisig.canApprove(0, bob); + assertEq(canApprove, false, "Bob should not be able to approve directly"); + // It approve should revert (when listed on creation, appointing someone else now) + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, bob)); + eMultisig.approve(0); + // It canApprove should return true (when currently appointed by a signer listed on creation) + canApprove = eMultisig.canApprove(0, randomWallet); + assertEq(canApprove, true, "Random wallet should be able to approve as appointed"); + // It approve should work (when currently appointed by a signer listed on creation) // It approve should emit an event (when currently appointed by a signer listed on creation) - // It canApprove should return false (when unlisted on creation, unappointed now) - // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + vm.startPrank(randomWallet); + vm.expectEmit(); + emit Approved(0, bob); // Note: Event shows the owner, not the appointed wallet + eMultisig.approve(0); } function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { @@ -1455,7 +1472,7 @@ contract EmergencyMultisigTest is AragonTest { uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); // ko - if (randomWallet != alice) { + if (randomWallet != alice && randomWallet != bob && randomWallet != carol && randomWallet != david) { assertEq(eMultisig.canApprove(pid, randomWallet), false, "Should be false"); } @@ -1484,19 +1501,45 @@ contract EmergencyMultisigTest is AragonTest { function test_WhenCallingHasApprovedBeingOpen() external givenTheProposalIsOpen { // It hasApproved should return false until approved - vm.skip(true); + assertEq(eMultisig.hasApproved(0, alice), false, "Should be false before approval"); + assertEq(eMultisig.hasApproved(0, bob), false, "Should be false before approval"); + assertEq(eMultisig.hasApproved(0, randomWallet), false, "Should be false before approval"); + + // After approvals + vm.startPrank(alice); + eMultisig.approve(0); + assertEq(eMultisig.hasApproved(0, alice), true, "Should be true after approval"); + + vm.startPrank(randomWallet); + eMultisig.approve(0); + assertEq(eMultisig.hasApproved(0, bob), true, "Should be true after approval by appointed wallet"); } function test_WhenCallingCanExecuteOrExecuteBeingOpen() external givenTheProposalIsOpen { // It canExecute should return false (when listed on creation, self appointed now) + assertEq(eMultisig.canExecute(0), false, "Should not be executable without approvals"); + + vm.deal(address(dao), 1 ether); + // It execute should revert (when listed on creation, self appointed now) - // It canExecute should return false (when listed on creation, appointing someone else now) - // It execute should revert (when listed on creation, appointing someone else now) - // It canExecute should return false (when currently appointed by a signer listed on creation) - // It execute should revert (when currently appointed by a signer listed on creation) - // It canExecute should return false (when unlisted on creation, unappointed now) - // It execute should revert (when unlisted on creation, unappointed now) - vm.skip(true); + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex""; + + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); + + // Get required approvals + eMultisig.approve(0); + vm.startPrank(randomWallet); // Appointed by Bob + eMultisig.approve(0); + vm.startPrank(carol); + eMultisig.approve(0); + + // Now it should be executable + assertEq(eMultisig.canExecute(0), true, "Should be executable after approvals"); } modifier givenTheProposalWasApprovedByTheAddress() { @@ -1514,14 +1557,14 @@ contract EmergencyMultisigTest is AragonTest { // 0x1234: unlisted and unappointed on creation - vm.deal(address(dao), 1 ether); + vm.deal(address(dao), 0.5 ether); // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); + actions[0].value = 0.5 ether; + actions[0].to = address(carol); actions[0].data = hex""; - bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 metadataUriHash = keccak256("ipfs://more-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1541,58 +1584,84 @@ contract EmergencyMultisigTest is AragonTest { function test_WhenCallingGetProposalBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It should return the right values - vm.skip(true); - - // vm.startPrank(randomWallet); // Appointed by Bob - // eMultisig.approve(pid); - // vm.startPrank(carol); - // eMultisig.approve(pid); + uint256 pid = 0; - // ( - // executed, - // approvals, - // parameters, - // encryptedPayloadURI, - // publicMetadataUriHash, - // destinationActionsHash, - // destinationPlugin - // ) = eMultisig.getProposal(pid); - // assertEq(executed, false, "Should not be executed"); - // assertEq(approvals, 3, "Should be 3"); + vm.startPrank(randomWallet); // Appointed by Bob + eMultisig.approve(pid); + vm.startPrank(carol); + eMultisig.approve(pid); - // assertEq(parameters.minApprovals, 3); - // assertEq(parameters.snapshotBlock, block.number - 1); - // assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - // assertEq(encryptedPayloadURI, "ipfs://12340000"); - // assertEq(publicMetadataUriHash, metadataUriHash); - // assertEq(destinationActionsHash, actionsHash); - // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(pid); + assertEq(executed, false, "Should not be executed"); + assertEq(approvals, 3, "Should be 3"); - // // Execute - // vm.startPrank(alice); - // dao.grant(address(newOptimisticPlugin), address(eMultisig), newOptimisticPlugin.PROPOSER_PERMISSION_ID()); - // eMultisig.execute(pid, "ipfs://another-public-metadata", actions); + assertEq(parameters.minApprovals, 3); + assertEq(parameters.snapshotBlock, block.number - 1 - 50); // We made +50 to remove wallets + assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + assertEq(encryptedPayloadURI, "ipfs://encrypted"); + assertEq(publicMetadataUriHash, hex"1f4c56b7231f4b1bd019565da91d099db90671db977444a5f3c231dbd6013b27"); + assertEq(destinationActionsHash, hex"ed2486fa6e91780dba02ea013f95f9e84ae8250dcf4c7b62ea5b99fbcf682ee4"); + assertEq(address(destinationPlugin), address(optimisticPlugin)); } function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canApprove should return false (when listed on creation, self appointed now) + assertEq(eMultisig.canApprove(0, alice), false, "Alice should not be able to approve again"); + // It approve should revert (when listed on creation, self appointed now) - // It canApprove should return false (when currently appointed by a signer listed on creation) - // It approve should revert (when currently appointed by a signer listed on creation) - vm.skip(true); + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, alice)); + eMultisig.approve(0); + + // It canApprove should return true (when currently appointed by a signer listed on creation) + assertEq(eMultisig.canApprove(0, randomWallet), true, "Random wallet should be able to approve"); + + // It approve should work (when currently appointed by a signer listed on creation) + vm.startPrank(randomWallet); + eMultisig.approve(0); } function test_WhenCallingHasApprovedBeingApproved() external givenTheProposalWasApprovedByTheAddress { - // It hasApproved should return false until approved - vm.skip(true); + // It hasApproved should return true for approved addresses + assertEq(eMultisig.hasApproved(0, alice), true, "Should be true for alice"); + assertEq(eMultisig.hasApproved(0, bob), false, "Should be false for bob"); + assertEq(eMultisig.hasApproved(0, randomWallet), false, "Should be false for randomWallet"); + + // After additional approval + vm.startPrank(randomWallet); + eMultisig.approve(0); + assertEq(eMultisig.hasApproved(0, bob), true, "Should be true for bob after appointed wallet approves"); } function test_WhenCallingCanExecuteOrExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canExecute should return false (when listed on creation, self appointed now) + assertEq(eMultisig.canExecute(0), false, "Should not be executable with only one approval"); + // It execute should revert (when listed on creation, self appointed now) + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex""; + + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); + // It canExecute should return false (when currently appointed by a signer listed on creation) + vm.startPrank(randomWallet); + assertEq(eMultisig.canExecute(0), false, "Should not be executable with only one approval"); + // It execute should revert (when currently appointed by a signer listed on creation) - vm.skip(true); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); } modifier givenTheProposalPassed() { diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 5886d31..cdf4392 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -1362,7 +1362,7 @@ contract MultisigTest is AragonTest { uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); // ko - if (randomWallet != alice) { + if (randomWallet != alice && randomWallet != bob && randomWallet != carol && randomWallet != david) { assertEq(multisig.canApprove(pid, randomWallet), false, "Should be false"); } From 927079d1307af19c3e6973bac6d44479b3b6568d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 12 Nov 2024 22:43:48 +0700 Subject: [PATCH 35/45] Improved interfaces and tests --- src/EmergencyMultisig.sol | 8 +- src/Multisig.sol | 8 +- src/SignerList.sol | 8 +- src/interfaces/ISignerList.sol | 4 +- test/SignerListTree.t.sol | 341 +++++++++++++++++++++++---------- test/SignerListTree.t.yaml | 51 +++-- 6 files changed, 288 insertions(+), 132 deletions(-) diff --git a/src/EmergencyMultisig.sol b/src/EmergencyMultisig.sol index 0e46769..6f26bf8 100644 --- a/src/EmergencyMultisig.sol +++ b/src/EmergencyMultisig.sol @@ -238,7 +238,8 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa } // Register the approval as being made by the owner - address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); + address _owner = + multisigSettings.signerList.getListedEncryptionOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); proposal_.approvers[_owner] = true; // We emit the event as the owner's approval @@ -294,7 +295,8 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa /// @inheritdoc IEmergencyMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; - address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_account, proposal_.parameters.snapshotBlock); + address _owner = + multisigSettings.signerList.getListedEncryptionOwnerAtBlock(_account, proposal_.parameters.snapshotBlock); return proposals[_proposalId].approvers[_owner]; } @@ -356,7 +358,7 @@ contract EmergencyMultisig is IEmergencyMultisig, PluginUUPSUpgradeable, Proposa // This internally calls `isListedAtBlock`. // If not listed or resolved, it returns address(0) (address _resolvedOwner, address _resolvedVoter) = - multisigSettings.signerList.resolveAccountAtBlock(_approver, proposal_.parameters.snapshotBlock); + multisigSettings.signerList.resolveEncryptionAccountAtBlock(_approver, proposal_.parameters.snapshotBlock); if (_resolvedOwner == address(0) || _resolvedVoter == address(0)) { // Not listedAtBlock() nor appointed by a listed owner return false; diff --git a/src/Multisig.sol b/src/Multisig.sol index 13c1657..27c529e 100644 --- a/src/Multisig.sol +++ b/src/Multisig.sol @@ -242,7 +242,8 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { } // Register the approval as being made by the owner. isListedAtBlock() relates to it - address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); + address _owner = + multisigSettings.signerList.getListedEncryptionOwnerAtBlock(_sender, proposal_.parameters.snapshotBlock); proposal_.approvers[_owner] = true; // We emit the event as the owner's approval @@ -296,7 +297,8 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { /// @inheritdoc IMultisig function hasApproved(uint256 _proposalId, address _account) public view returns (bool) { Proposal storage proposal_ = proposals[_proposalId]; - address _owner = multisigSettings.signerList.getListedOwnerAtBlock(_account, proposal_.parameters.snapshotBlock); + address _owner = + multisigSettings.signerList.getListedEncryptionOwnerAtBlock(_account, proposal_.parameters.snapshotBlock); return proposals[_proposalId].approvers[_owner]; } @@ -341,7 +343,7 @@ contract Multisig is IMultisig, PluginUUPSUpgradeable, ProposalUpgradeable { // This internally calls `isListedAtBlock`. // If not listed or resolved, it returns address(0) (address _resolvedOwner, address _resolvedVoter) = - multisigSettings.signerList.resolveAccountAtBlock(_approver, proposal_.parameters.snapshotBlock); + multisigSettings.signerList.resolveEncryptionAccountAtBlock(_approver, proposal_.parameters.snapshotBlock); if (_resolvedOwner == address(0) || _resolvedVoter == address(0)) { // Not listedAtBlock() nor appointed by a listed owner return false; diff --git a/src/SignerList.sol b/src/SignerList.sol index adc9cde..bf121d3 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -114,7 +114,11 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza } /// @inheritdoc ISignerList - function getListedOwnerAtBlock(address _address, uint256 _blockNumber) public view returns (address _owner) { + function getListedEncryptionOwnerAtBlock(address _address, uint256 _blockNumber) + public + view + returns (address _owner) + { if (isListedAtBlock(_address, _blockNumber)) { return _address; } @@ -127,7 +131,7 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza } /// @inheritdoc ISignerList - function resolveAccountAtBlock(address _address, uint256 _blockNumber) + function resolveEncryptionAccountAtBlock(address _address, uint256 _blockNumber) public view returns (address _owner, address _voter) diff --git a/src/interfaces/ISignerList.sol b/src/interfaces/ISignerList.sol index 5534c9b..82846fe 100644 --- a/src/interfaces/ISignerList.sol +++ b/src/interfaces/ISignerList.sol @@ -28,12 +28,12 @@ interface ISignerList { /// @param sender The address to check within the list of signers or the appointed accounts. /// @param blockNumber The block at which the list should be checked /// @return owner If resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. - function getListedOwnerAtBlock(address sender, uint256 blockNumber) external returns (address owner); + function getListedEncryptionOwnerAtBlock(address sender, uint256 blockNumber) external returns (address owner); /// @notice Given an address, determines the corresponding (listed) owner account and the appointed wallet, if any. /// @return owner If listed and resolved to an account, it contains the encryption owner's address. Returns address(0) otherwise. /// @return voter If listed and resolved, it contains the wallet address appointed for decryption, if any. Returns address(0) otherwise. - function resolveAccountAtBlock(address sender, uint256 _blockNumber) + function resolveEncryptionAccountAtBlock(address sender, uint256 _blockNumber) external returns (address owner, address voter); diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index 8e24179..7a589bb 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -44,6 +44,8 @@ contract SignerListTest is AragonTest { (dao,, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice).withMultisigMember(bob) .withMultisigMember(carol).withMultisigMember(david).build(); + vm.roll(block.number + 1); + signers = new address[](4); signers[0] = alice; signers[1] = bob; @@ -684,7 +686,7 @@ contract SignerListTest is AragonTest { vm.assertEq(signerList.isListedAtBlock(david, block.number - 1), false, "Should not be a signer"); } - modifier whenCallingResolveEncryptionAccountStatus() { + modifier whenCallingIsListedOrAppointedByListed() { dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); @@ -704,57 +706,42 @@ contract SignerListTest is AragonTest { _; } - function test_GivenTheCallerIsAListedSigner() external whenCallingResolveEncryptionAccountStatus { + function test_GivenTheCallerIsAListedSigner() external whenCallingIsListedOrAppointedByListed { // 1 - (bool ownerIsListed, bool appointed) = signerList.resolveEncryptionAccountStatus(alice); + bool listedOrAppointedByListed = signerList.isListedOrAppointedByListed(alice); - // It ownerIsListed should be true - assertEq(ownerIsListed, true, "The owner should be listed"); - - // It isAppointed should be false - assertEq(appointed, false, "Should not be appointed"); + // It listedOrAppointedByListed should be true + assertEq(listedOrAppointedByListed, true, "listedOrAppointedByListed should be true"); // 2 - (ownerIsListed, appointed) = signerList.resolveEncryptionAccountStatus(bob); - - // It ownerIsListed should be true - assertEq(ownerIsListed, true, "The owner should be listed"); + listedOrAppointedByListed = signerList.isListedOrAppointedByListed(bob); - // It isAppointed should be false - assertEq(appointed, false, "Should not be appointed"); + // It listedOrAppointedByListed should be true + assertEq(listedOrAppointedByListed, true, "listedOrAppointedByListed should be true"); } - function test_GivenTheCallerIsAppointedByASigner() external whenCallingResolveEncryptionAccountStatus { - (bool ownerIsListed, bool appointed) = signerList.resolveEncryptionAccountStatus(david); + function test_GivenTheCallerIsAppointedByASigner() external whenCallingIsListedOrAppointedByListed { + bool listedOrAppointedByListed = signerList.isListedOrAppointedByListed(david); - // It ownerIsListed should be true - assertEq(ownerIsListed, true, "The owner should be listed"); - - // It isAppointed should be true - assertEq(appointed, true, "Should be appointed"); + // It listedOrAppointedByListed should be true + assertEq(listedOrAppointedByListed, true, "listedOrAppointedByListed should be true"); } - function test_GivenTheCallerIsNotListedOrAppointed() external whenCallingResolveEncryptionAccountStatus { + function test_GivenTheCallerIsNotListedOrAppointed() external whenCallingIsListedOrAppointedByListed { // 1 - (bool ownerIsListed, bool appointed) = signerList.resolveEncryptionAccountStatus(carol); - - // It ownerIsListed should be false - assertEq(ownerIsListed, false, "The owner should be listed"); + bool listedOrAppointedByListed = signerList.isListedOrAppointedByListed(carol); - // It isAppointed should be false - assertEq(appointed, false, "Should be appointed"); + // It listedOrAppointedByListed should be false + assertEq(listedOrAppointedByListed, false, "listedOrAppointedByListed should be false"); // 2 - (ownerIsListed, appointed) = signerList.resolveEncryptionAccountStatus(address(1234)); + listedOrAppointedByListed = signerList.isListedOrAppointedByListed(address(1234)); - // It ownerIsListed should be false - assertEq(ownerIsListed, false, "The owner should not be listed"); - - // It isAppointed should be false - assertEq(appointed, false, "Should not be appointed"); + // It listedOrAppointedByListed should be false + assertEq(listedOrAppointedByListed, false, "listedOrAppointedByListed should be false"); } - modifier whenCallingResolveEncryptionOwner() { + modifier whenCallingGetListedEncryptionOwnerAtBlock() { // Alice (owner) appoints address(0x1234) encryptionRegistry.appointWallet(address(0x1234)); @@ -776,55 +763,129 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsTheOwner() external - whenCallingResolveEncryptionOwner + whenCallingGetListedEncryptionOwnerAtBlock givenTheResolvedOwnerIsListedOnResolveEncryptionOwner { address resolvedOwner; // It should return the given address - resolvedOwner = signerList.resolveEncryptionOwner(alice); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(alice, block.number - 1); assertEq(resolvedOwner, alice, "Should be alice"); - resolvedOwner = signerList.resolveEncryptionOwner(bob); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(bob, block.number - 1); assertEq(resolvedOwner, bob, "Should be bob"); - resolvedOwner = signerList.resolveEncryptionOwner(carol); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(carol, block.number - 1); assertEq(resolvedOwner, carol, "Should be carol"); - resolvedOwner = signerList.resolveEncryptionOwner(david); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(david, block.number - 1); assertEq(resolvedOwner, david, "Should be david"); } function test_WhenTheGivenAddressIsAppointedByTheOwner() external - whenCallingResolveEncryptionOwner + whenCallingGetListedEncryptionOwnerAtBlock givenTheResolvedOwnerIsListedOnResolveEncryptionOwner { address resolvedOwner; // It should return the resolved owner - resolvedOwner = signerList.resolveEncryptionOwner(address(0x1234)); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x1234), block.number - 1); assertEq(resolvedOwner, alice, "Should be alice"); - resolvedOwner = signerList.resolveEncryptionOwner(address(0x2345)); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x2345), block.number - 1); assertEq(resolvedOwner, bob, "Should be bob"); } function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionOwner() external - whenCallingResolveEncryptionOwner + whenCallingGetListedEncryptionOwnerAtBlock + { + address resolvedOwner; + + // It should return a zero value + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x3456), block.number - 1); + assertEq(resolvedOwner, address(0), "Should be zero"); + + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x4567), block.number - 1); + assertEq(resolvedOwner, address(0), "Should be zero"); + } + + modifier givenTheResolvedOwnerWasListedOnResolveEncryptionOwner() { + // But not listed now + // Prior appointments are still in place + + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + address[] memory mvSigners = new address[](4); + mvSigners[0] = address(0x5555); + mvSigners[1] = address(0x6666); + mvSigners[2] = address(0x7777); + mvSigners[3] = address(0x8888); + signerList.addSigners(mvSigners); + + vm.roll(block.number + 1); + + mvSigners[0] = alice; + mvSigners[1] = bob; + mvSigners[2] = carol; + mvSigners[3] = david; + signerList.removeSigners(mvSigners); + + _; + } + + function test_WhenTheGivenAddressIsTheOwner2() + external + whenCallingGetListedEncryptionOwnerAtBlock + givenTheResolvedOwnerWasListedOnResolveEncryptionOwner + { + address resolvedOwner; + + // It should return the given address + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(alice, block.number - 1); + assertEq(resolvedOwner, alice, "Should be alice"); + + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(bob, block.number - 1); + assertEq(resolvedOwner, bob, "Should be bob"); + + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(carol, block.number - 1); + assertEq(resolvedOwner, carol, "Should be carol"); + + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(david, block.number - 1); + assertEq(resolvedOwner, david, "Should be david"); + } + + function test_WhenTheGivenAddressIsAppointedByTheOwner2() + external + whenCallingGetListedEncryptionOwnerAtBlock + givenTheResolvedOwnerWasListedOnResolveEncryptionOwner + { + address resolvedOwner; + + // It should return the resolved owner + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x1234), block.number - 1); + assertEq(resolvedOwner, alice, "Should be alice"); + + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x2345), block.number - 1); + assertEq(resolvedOwner, bob, "Should be bob"); + } + + function test_GivenTheResolvedOwnerWasNotListedOnResolveEncryptionOwner() + external + whenCallingGetListedEncryptionOwnerAtBlock { address resolvedOwner; // It should return a zero value - resolvedOwner = signerList.resolveEncryptionOwner(address(0x3456)); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x3456), block.number - 1); assertEq(resolvedOwner, address(0), "Should be zero"); - resolvedOwner = signerList.resolveEncryptionOwner(address(0x4567)); + resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x4567), block.number - 1); assertEq(resolvedOwner, address(0), "Should be zero"); } - modifier whenCallingResolveEncryptionAccount() { + modifier whenCallingResolveEncryptionAccountAtBlock() { // Alice (owner) appoints address(0x1234) encryptionRegistry.appointWallet(address(0x1234)); @@ -844,108 +905,178 @@ contract SignerListTest is AragonTest { _; } + function test_WhenTheGivenAddressIsOwner() + external + whenCallingResolveEncryptionAccountAtBlock + givenTheResolvedOwnerIsListedOnResolveEncryptionAccount + { + address resolvedOwner; + address votingWallet; + + // 1 - owner appoints + + // It owner should be the given address + // It votingWallet should be the resolved appointed wallet + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(alice, block.number - 1); + assertEq(resolvedOwner, alice, "Should be alice"); + assertEq(votingWallet, address(0x1234), "Should be 0x1234"); + + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(bob, block.number - 1); + assertEq(resolvedOwner, bob, "Should be bob"); + assertEq(votingWallet, address(0x2345), "Should be 0x2345"); + + // 2 - No appointed wallet + + // It owner should be the given address + // It votingWallet should be the resolved appointed wallet + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(carol, block.number - 1); + assertEq(resolvedOwner, carol, "Should be carol"); + assertEq(votingWallet, carol, "Should be carol"); + + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(david, block.number - 1); + assertEq(resolvedOwner, david, "Should be david"); + assertEq(votingWallet, david, "Should be david"); + } + function test_WhenTheGivenAddressIsAppointed() external - whenCallingResolveEncryptionAccount + whenCallingResolveEncryptionAccountAtBlock givenTheResolvedOwnerIsListedOnResolveEncryptionAccount { address resolvedOwner; - address appointedWallet; + address votingWallet; // It owner should be the resolved owner - // It appointedWallet should be the given address - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x1234)); + // It votingWallet should be the given address + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x1234), block.number - 1); assertEq(resolvedOwner, alice, "Should be alice"); - assertEq(appointedWallet, address(0x1234), "Should be 0x1234"); + assertEq(votingWallet, address(0x1234), "Should be 0x1234"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x2345)); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x2345), block.number - 1); assertEq(resolvedOwner, bob, "Should be bob"); - assertEq(appointedWallet, address(0x2345), "Should be 0x2345"); + assertEq(votingWallet, address(0x2345), "Should be 0x2345"); } - function test_WhenTheGivenAddressIsNotAppointed() + function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionAccount() external - whenCallingResolveEncryptionAccount - givenTheResolvedOwnerIsListedOnResolveEncryptionAccount + whenCallingResolveEncryptionAccountAtBlock { address resolvedOwner; - address appointedWallet; + address votingWallet; + + // It should return a zero owner + // It should return a zero votingWallet + + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0), block.number - 1); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(votingWallet, address(0), "Should be 0"); + + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x5555), block.number - 1); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(votingWallet, address(0), "Should be 0"); + + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0xaaaa), block.number - 1); + assertEq(resolvedOwner, address(0), "Should be 0"); + assertEq(votingWallet, address(0), "Should be 0"); + } + + modifier givenTheResolvedOwnerWasListedOnResolveEncryptionAccount() { + // But not listed now + // Prior appointments are still in place + + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + + address[] memory mvSigners = new address[](4); + mvSigners[0] = address(0x5555); + mvSigners[1] = address(0x6666); + mvSigners[2] = address(0x7777); + mvSigners[3] = address(0x8888); + signerList.addSigners(mvSigners); + + vm.roll(block.number + 1); + + mvSigners[0] = alice; + mvSigners[1] = bob; + mvSigners[2] = carol; + mvSigners[3] = david; + signerList.removeSigners(mvSigners); + + _; + } + + function test_WhenTheGivenAddressIsOwner2() + external + whenCallingResolveEncryptionAccountAtBlock + givenTheResolvedOwnerWasListedOnResolveEncryptionAccount + { + address resolvedOwner; + address votingWallet; // 1 - owner appoints // It owner should be the given address - // It appointedWallet should be the resolved appointed wallet - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(alice); + // It votingWallet should be the resolved appointed wallet + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(alice, block.number - 1); assertEq(resolvedOwner, alice, "Should be alice"); - assertEq(appointedWallet, address(0x1234), "Should be 0x1234"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x1234)); - assertEq(resolvedOwner, alice, "Should be alice"); - assertEq(appointedWallet, address(0x1234), "Should be 0x1234"); + assertEq(votingWallet, address(0x1234), "Should be 0x1234"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(bob); - assertEq(resolvedOwner, bob, "Should be bob"); - assertEq(appointedWallet, address(0x2345), "Should be 0x2345"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x2345)); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(bob, block.number - 1); assertEq(resolvedOwner, bob, "Should be bob"); - assertEq(appointedWallet, address(0x2345), "Should be 0x2345"); + assertEq(votingWallet, address(0x2345), "Should be 0x2345"); // 2 - No appointed wallet // It owner should be the given address - // It appointedWallet should be the resolved appointed wallet - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(carol); + // It votingWallet should be the resolved appointed wallet + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(carol, block.number - 1); assertEq(resolvedOwner, carol, "Should be carol"); - assertEq(appointedWallet, carol, "Should be carol"); + assertEq(votingWallet, carol, "Should be carol"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(david); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(david, block.number - 1); assertEq(resolvedOwner, david, "Should be david"); - assertEq(appointedWallet, david, "Should be david"); + assertEq(votingWallet, david, "Should be david"); } - function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionAccount() + function test_WhenTheGivenAddressIsAppointed2() external - whenCallingResolveEncryptionAccount + whenCallingResolveEncryptionAccountAtBlock + givenTheResolvedOwnerWasListedOnResolveEncryptionAccount { address resolvedOwner; - address appointedWallet; - - // It should return a zero owner - // It should return a zero appointedWallet + address votingWallet; - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0)); - assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(appointedWallet, address(0), "Should be 0"); - - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x5555)); - assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(appointedWallet, address(0), "Should be 0"); + // It owner should be the resolved owner + // It votingWallet should be the given address + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x1234), block.number - 1); + assertEq(resolvedOwner, alice, "Should be alice"); + assertEq(votingWallet, address(0x1234), "Should be 0x1234"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0xaaaa)); - assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(appointedWallet, address(0), "Should be 0"); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x2345), block.number - 1); + assertEq(resolvedOwner, bob, "Should be bob"); + assertEq(votingWallet, address(0x2345), "Should be 0x2345"); + } - // Formerly a signer - dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); - dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); - signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + function test_GivenTheResolvedOwnerWasNotListedOnResolveEncryptionAccount() + external + whenCallingResolveEncryptionAccountAtBlock + { + address resolvedOwner; + address votingWallet; - // Remove Bob (appointed 0x2345) and David - address[] memory rmSigners = new address[](2); - rmSigners[0] = bob; - rmSigners[1] = david; - signerList.removeSigners(rmSigners); + // It should return a zero owner + // It should return a zero votingWallet - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(bob); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0), block.number - 1); assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(appointedWallet, address(0), "Should be 0"); + assertEq(votingWallet, address(0), "Should be 0"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(address(0x2345)); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x5555), block.number - 1); assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(appointedWallet, address(0), "Should be 0"); + assertEq(votingWallet, address(0), "Should be 0"); - (resolvedOwner, appointedWallet) = signerList.resolveEncryptionAccount(david); + (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0xaaaa), block.number - 1); assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(appointedWallet, address(0), "Should be 0"); + assertEq(votingWallet, address(0), "Should be 0"); } modifier whenCallingGetEncryptionRecipients() { diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index 50dd7b4..ff55638 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -108,24 +108,21 @@ SignerListTest: - it: returns false # Encryption getters - - when: calling resolveEncryptionAccountStatus + - when: calling isListedOrAppointedByListed and: - given: the caller is a listed signer then: - - it: ownerIsListed should be true - - it: isAppointed should be false + - it: listedOrAppointedByListed should be true - given: the caller is appointed by a signer then: - - it: ownerIsListed should be true - - it: isAppointed should be true + - it: listedOrAppointedByListed should be true - given: the caller is not listed or appointed then: - - it: ownerIsListed should be false - - it: isAppointed should be false + - it: listedOrAppointedByListed should be false - - when: calling resolveEncryptionOwner + - when: calling getListedEncryptionOwnerAtBlock and: - - given: the resolved owner is listed [on resolveEncryptionOwner] + - given: the resolved owner is listed [on getListedEncryptionOwnerAtBlock] and: - when: the given address is the owner then: @@ -134,24 +131,44 @@ SignerListTest: then: - it: should return the resolved owner - - given: the resolved owner is not listed [on resolveEncryptionOwner] + - given: the resolved owner was listed [on getListedEncryptionOwnerAtBlock] + and: + - when: the given address is the owner 2 + then: + - it: should return the given address + - when: the given address is appointed by the owner 2 + then: + - it: should return the resolved owner + + - given: the resolved owner is not listed [on getListedEncryptionOwnerAtBlock] then: - it: should return a zero value - - when: calling resolveEncryptionAccount + - when: calling resolveEncryptionAccountAtBlock and: - - given: the resolved owner is listed [on resolveEncryptionAccount] + - given: the resolved owner is listed [on resolveEncryptionAccountAtBlock] and: + - when: the given address is owner + then: + - it: owner should be itself + - it: votingWallet should be the appointed address - when: the given address is appointed then: - it: owner should be the resolved owner - - it: appointedWallet should be the given address - - when: the given address is not appointed + - it: votingWallet should be the given address + + - given: the resolved owner was listed [on resolveEncryptionAccountAtBlock] + and: + - when: the given address is owner 2 then: - - it: owner should be the given address - - it: appointedWallet should be the resolved appointed wallet + - it: owner should be itself + - it: votingWallet should be the appointed address + - when: the given address is appointed 2 + then: + - it: owner should be the resolved owner + - it: votingWallet should be the given address - - given: the resolved owner is not listed [on resolveEncryptionAccount] + - given: the resolved owner is not listed [on resolveEncryptionAccountAtBlock] then: - it: should return a zero owner - it: should return a zero appointedWallet From cd2970dae902b65e853e0811664c4ffb080e5ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 12 Nov 2024 23:51:17 +0700 Subject: [PATCH 36/45] Emergency multisig tests ready --- test/EmergencyMultisigTree.t.sol | 446 +++++++++++++++++++++++++------ 1 file changed, 358 insertions(+), 88 deletions(-) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 5b28fe0..e26bbc0 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1239,7 +1239,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(hasApproved, false, "Should be false"); randomProposalId++; - hasApproved = eMultisig.hasApproved(randomProposalId, address(1234)); + hasApproved = eMultisig.hasApproved(randomProposalId, address(0x5555)); assertEq(hasApproved, false, "Should be false"); } @@ -1504,6 +1504,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(eMultisig.hasApproved(0, alice), false, "Should be false before approval"); assertEq(eMultisig.hasApproved(0, bob), false, "Should be false before approval"); assertEq(eMultisig.hasApproved(0, randomWallet), false, "Should be false before approval"); + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); // After approvals vm.startPrank(alice); @@ -1634,6 +1635,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(eMultisig.hasApproved(0, alice), true, "Should be true for alice"); assertEq(eMultisig.hasApproved(0, bob), false, "Should be false for bob"); assertEq(eMultisig.hasApproved(0, randomWallet), false, "Should be false for randomWallet"); + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); // After additional approval vm.startPrank(randomWallet); @@ -1686,10 +1688,10 @@ contract EmergencyMultisigTest is AragonTest { actions[0].value = 1 ether; actions[0].to = address(bob); actions[0].data = hex""; - bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 metadataUriHash = keccak256("ipfs://the-original-secret-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); uint256 pid = - eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); + eMultisig.createProposal("ipfs://more-encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); // Remove (later) vm.roll(block.number + 50); @@ -1705,6 +1707,9 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(randomWallet); eMultisig.approve(pid); + vm.startPrank(carol); + eMultisig.approve(pid); + vm.startPrank(alice); _; @@ -1712,86 +1717,210 @@ contract EmergencyMultisigTest is AragonTest { function test_WhenCallingGetProposalBeingPassed() external givenTheProposalPassed { // It should return the right values - vm.skip(true); - - // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); - // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); - // signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); - // // Alice: listed on creation and self appointed - - // // Bob: listed on creation, appointing someone else now - // vm.startPrank(bob); - // encryptionRegistry.appointWallet(randomWallet); + // Retrieve the proposal + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(0); - // // Random Wallet: appointed by a listed signer on creation + // Assert the proposal is not executed + assertEq(executed, false, "Proposal should not be executed"); - // // 0x1234: unlisted and unappointed on creation + // Assert the number of approvals + assertEq(approvals, 3, "Approvals should be 3"); - // vm.deal(address(dao), 1 ether);// + // Assert the proposal parameters + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, + block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect expirationDate" + ); - // Create proposal - // IDAO.Action[] memory actions = new IDAO.Action[](1); - // actions[0].value = 1 ether; - // actions[0].to = address(bob); - // actions[0].data = hex""; - // bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); - // bytes32 actionsHash = eMultisig.hashActions(actions); - // eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); - - // // Remove (later) - // vm.roll(block.number + 50); - // address[] memory addrs = new address[](2); - // addrs[0] = alice; - // addrs[1] = bob; + // Assert the encrypted payload URI + assertEq(encryptedPayloadURI, "ipfs://more-encrypted", "Incorrect encryptedPayloadURI"); - // vm.startPrank(alice); - // signerList.removeSigners(addrs); + // Assert the public metadata URI hash + assertEq( + publicMetadataUriHash, keccak256("ipfs://the-original-secret-metadata"), "Incorrect publicMetadataUriHash" + ); - // eMultisig.approve(0); + // Assert the destination actions hash + assertEq( + destinationActionsHash, + hex"e212a57e4595f81151b46333ea31e2d5043b53bd562141e1efa1b2778cb3c208", + "Incorrect destinationActionsHash" + ); - // vm.startPrank(randomWallet); - // eMultisig.approve(0); + // Assert the destination plugin + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); } function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { // It canApprove should return false (when listed on creation, self appointed now) + // vm.startPrank(alice); + assertEq(eMultisig.canApprove(0, alice), false, "Alice should not be able to approve"); // It approve should revert (when listed on creation, self appointed now) + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, alice)); + eMultisig.approve(0); + // It canApprove should return false (when listed on creation, appointing someone else now) + vm.startPrank(bob); + assertEq(eMultisig.canApprove(0, bob), false, "Bob should not be able to approve"); // It approve should revert (when listed on creation, appointing someone else now) + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, bob)); + eMultisig.approve(0); + // It canApprove should return false (when currently appointed by a signer listed on creation) + vm.startPrank(randomWallet); + assertEq(eMultisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve"); // It approve should revert (when currently appointed by a signer listed on creation) + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, randomWallet)); + eMultisig.approve(0); + // It canApprove should return false (when unlisted on creation, unappointed now) + vm.startPrank(address(0x1234)); + assertEq(eMultisig.canApprove(0, address(0x1234)), false, "Random wallet should not be able to approve"); // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, address(0x1234))); + eMultisig.approve(0); } function test_WhenCallingHasApprovedBeingPassed() external givenTheProposalPassed { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(eMultisig.hasApproved(0, alice), true, "Alice should have approved"); + assertEq(eMultisig.hasApproved(0, bob), true, "Bob should have approved"); + assertEq(eMultisig.hasApproved(0, randomWallet), true, "Should be true"); + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); } function test_WhenCallingCanExecuteOrExecuteWithModifiedDataBeingPassed() external givenTheProposalPassed { // It execute should revert with modified metadata + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex""; + + // vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidMetadataUri.selector, 0)); + eMultisig.execute(0, "ipfs://modified-metadata-1234", actions); + // It execute should revert with modified actions + actions[0].value = 2 ether; // Modify action + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidActions.selector, 0)); + eMultisig.execute(0, "ipfs://the-original-secret-metadata", actions); + // It execute should work with matching data - vm.skip(true); + actions[0].value = 1 ether; // Reset action + eMultisig.execute(0, "ipfs://the-original-secret-metadata", actions); } function test_WhenCallingCanExecuteOrExecuteBeingPassed() external givenTheProposalPassed { // It canExecute should return true, always + assertEq(eMultisig.canExecute(0), true, "Proposal should be executable"); + // It execute should work, when called by anyone with the actions + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + actions[0].data = hex""; + // It execute should emit an event, when called by anyone with the actions + vm.expectEmit(); + emit Executed(0); + + // It A ProposalCreated event is emitted on the destination plugin + uint256 targetPid = (block.timestamp << 128) | (block.timestamp << 64); + vm.expectEmit(); + emit ProposalCreated( + targetPid, + address(eMultisig), + uint64(block.timestamp), + uint64(block.timestamp), + "ipfs://the-original-secret-metadata", + actions, + 0 + ); + + eMultisig.execute(0, "ipfs://the-original-secret-metadata", actions); + // It execute recreates the proposal on the destination plugin + + bool open; + bool executed; + OptimisticTokenVotingPlugin.ProposalParameters memory parameters; + uint256 vetoTally; + bytes memory metadataUri; + IDAO.Action[] memory retrievedActions; + uint256 allowFailureMap; + + (open, executed, parameters, vetoTally, metadataUri, retrievedActions, allowFailureMap) = + optimisticPlugin.getProposal(targetPid); + // It The parameters of the recreated proposal match the hash of the executed one - // It A ProposalCreated event is emitted on the destination plugin + assertEq(open, false, "Should not be open"); // It Execution is immediate on the destination plugin - vm.skip(true); + assertEq(executed, true, "Should be executed"); + assertEq(vetoTally, 0, "Should be 0"); + + assertEq(parameters.vetoEndDate, block.timestamp, "Incorrect vetoEndDate"); + assertEq(metadataUri, "ipfs://the-original-secret-metadata", "Incorrect target metadataUri"); + + assertEq(retrievedActions.length, 1, "Should be 3"); + + assertEq(retrievedActions[0].to, bob, "Incorrect to"); + assertEq(retrievedActions[0].value, 1 ether, "Incorrect value"); + assertEq(retrievedActions[0].data, hex"", "Incorrect data"); + + assertEq(allowFailureMap, 0, "Should be 0"); } function test_GivenTaikoL1IsIncompatible() external givenTheProposalPassed { // It executes successfully, regardless - vm.skip(true); + + (dao, optimisticPlugin,, eMultisig,,,,) = builder.withIncompatibleTaikoL1().build(); + + vm.deal(address(dao), 4 ether); + + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1 ether; + actions[0].to = address(bob); + bytes32 metadataUriHash = keccak256("ipfs://"); + bytes32 actionsHash = eMultisig.hashActions(actions); + uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); + + // Alice + eMultisig.approve(pid); + (bool executed,,,,,,) = eMultisig.getProposal(pid); + assertEq(executed, false, "Should not be executed"); + + // Bob + vm.startPrank(bob); + eMultisig.approve(pid); + (executed,,,,,,) = eMultisig.getProposal(pid); + assertEq(executed, false, "Should not be executed"); + + // Carol + vm.startPrank(carol); + eMultisig.approve(pid); + (executed,,,,,,) = eMultisig.getProposal(pid); + assertEq(executed, false, "Should not be executed"); + + vm.startPrank(randomWallet); + eMultisig.execute(pid, "ipfs://", actions); + (executed,,,,,,) = eMultisig.getProposal(pid); + assertEq(executed, true, "Should be executed"); + + assertEq(bob.balance, 1 ether, "Incorrect balance"); + assertEq(address(dao).balance, 3 ether, "Incorrect balance"); } modifier givenTheProposalIsAlreadyExecuted() { @@ -1813,10 +1942,10 @@ contract EmergencyMultisigTest is AragonTest { // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; + actions[0].value = 0.8 ether; actions[0].to = address(bob); actions[0].data = hex""; - bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); + bytes32 metadataUriHash = keccak256("ipfs://the-orig-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); uint256 pid = eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); @@ -1838,7 +1967,7 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(carol); eMultisig.approve(pid); - eMultisig.execute(pid, "ipfs://the-metadata", actions); + eMultisig.execute(pid, "ipfs://the-orig-metadata", actions); vm.startPrank(alice); @@ -1847,57 +1976,111 @@ contract EmergencyMultisigTest is AragonTest { function test_WhenCallingGetProposalBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It should return the right values - vm.skip(true); - - // ( - // executed, - // approvals, - // parameters, - // encryptedPayloadURI, - // publicMetadataUriHash, - // destinationActionsHash, - // destinationPlugin - // ) = eMultisig.getProposal(pid); - - // assertEq(executed, true, "Should be executed"); - // assertEq(approvals, 3, "Should be 3"); - - // assertEq(parameters.minApprovals, 3); - // assertEq(parameters.snapshotBlock, block.number - 1); - // assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - // assertEq(encryptedPayloadURI, "ipfs://12340000"); - // assertEq(publicMetadataUriHash, metadataUriHash); - // assertEq(destinationActionsHash, actionsHash); - // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(0); + + assertEq(executed, true, "Proposal should be executed"); + assertEq(approvals, 3, "Approvals should be 3"); + + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, + block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect expirationDate" + ); + assertEq(encryptedPayloadURI, "ipfs://encrypted"); + assertEq(publicMetadataUriHash, keccak256("ipfs://the-orig-metadata")); + assertEq(destinationActionsHash, hex"c85c954206700a1f89dfd6599c77677611fdbc8dcb7f15d44158e10b46d13391"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); } function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canApprove should return false (when listed on creation, self appointed now) - // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) - // It approve should revert (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) - // It approve should revert (when currently appointed by a signer listed on creation) // It canApprove should return false (when unlisted on creation, unappointed now) + assertEq(eMultisig.canApprove(0, alice), false, "Alice should not be able to approve"); + assertEq(eMultisig.canApprove(0, bob), false, "Bob should not be able to approve"); + assertEq(eMultisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve"); + assertEq(eMultisig.canApprove(0, address(0x890a)), false, "Random wallet should not be able to approve"); + + // It approve should revert (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, appointing someone else now) + // It approve should revert (when currently appointed by a signer listed on creation) // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, alice)); + eMultisig.approve(0); + + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, bob)); + eMultisig.approve(0); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, randomWallet)); + eMultisig.approve(0); + + vm.startPrank(address(0x890a)); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, address(0x890a))); + eMultisig.approve(0); } function test_WhenCallingHasApprovedBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It hasApproved should return false until approved - vm.skip(true); + + // Assert hasApproved returns true for those who approved + assertEq(eMultisig.hasApproved(0, alice), true, "Alice should have approved"); + assertEq(eMultisig.hasApproved(0, bob), true, "Bob should have approved"); + assertEq(eMultisig.hasApproved(0, randomWallet), true, "Random wallet should have approved"); + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); } function test_WhenCallingCanExecuteOrExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canExecute should return false (when listed on creation, self appointed now) - // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) - // It execute should revert (when listed on creation, appointing someone else now) // It canExecute should return false (when currently appointed by a signer listed on creation) - // It execute should revert (when currently appointed by a signer listed on creation) // It canExecute should return false (when unlisted on creation, unappointed now) + // vm.startPrank(alice); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + vm.startPrank(bob); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + vm.startPrank(randomWallet); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + vm.startPrank(address(0x7890)); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + + // It execute should revert (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, appointing someone else now) + // It execute should revert (when currently appointed by a signer listed on creation) // It execute should revert (when unlisted on creation, unappointed now) - vm.skip(true); + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 0.8 ether; + actions[0].to = address(bob); + actions[0].data = hex""; + + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-orig-metadata", actions); + + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-orig-metadata", actions); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-orig-metadata", actions); + + vm.startPrank(address(0x7890)); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-orig-metadata", actions); } modifier givenTheProposalExpired() { @@ -1919,8 +2102,8 @@ contract EmergencyMultisigTest is AragonTest { // Create proposal IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); + actions[0].value = 1.5 ether; + actions[0].to = address(carol); actions[0].data = hex""; bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); bytes32 actionsHash = eMultisig.hashActions(actions); @@ -1941,7 +2124,7 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(randomWallet); eMultisig.approve(pid); - vm.roll(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); vm.startPrank(alice); @@ -1950,35 +2133,122 @@ contract EmergencyMultisigTest is AragonTest { function test_WhenCallingGetProposalBeingExpired() external givenTheProposalExpired { // It should return the right values - vm.skip(true); + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(0); + + // Assert the proposal is not executed + assertEq(executed, false, "Proposal should not be executed"); + + // Assert the number of approvals + assertEq(approvals, 2, "Approvals should be 2"); + + // Assert the proposal parameters + assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshotBlock"); + assertEq( + parameters.expirationDate, + block.timestamp, // we just moved to it + "Incorrect expirationDate" + ); + + // Assert the encrypted payload URI + assertEq(encryptedPayloadURI, "ipfs://encrypted", "Incorrect encryptedPayloadURI"); + + // Assert the public metadata URI hash + assertEq(publicMetadataUriHash, keccak256("ipfs://the-metadata"), "Incorrect publicMetadataUriHash"); + + // Assert the destination actions hash + assertEq(destinationActionsHash, hex"3626b3f254463d63d9bd5ff77ff99d2691b20f0db6347f685befae593d8f4e6f", "Incorrect destinationActionsHash"); + + // Assert the destination plugin + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); } function test_WhenCallingCanApproveAndApproveBeingExpired() external givenTheProposalExpired { // It canApprove should return false (when listed on creation, self appointed now) - // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) - // It approve should revert (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) - // It approve should revert (when currently appointed by a signer listed on creation) // It canApprove should return false (when unlisted on creation, unappointed now) + assertEq(eMultisig.canApprove(0, alice), false, "Alice should not be able to approve"); + assertEq(eMultisig.canApprove(0, bob), false, "Bob should not be able to approve"); + assertEq(eMultisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve"); + assertEq(eMultisig.canApprove(0, address(0x5555)), false, "Random wallet should not be able to approve"); + + // It approve should revert (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, appointing someone else now) + // It approve should revert (when currently appointed by a signer listed on creation) // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, alice)); + eMultisig.approve(0); + + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, bob)); + eMultisig.approve(0); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, randomWallet)); + eMultisig.approve(0); + + vm.startPrank(address(0x5555)); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, 0, address(0x5555))); + eMultisig.approve(0); } function test_WhenCallingHasApprovedBeingExpired() external givenTheProposalExpired { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(eMultisig.hasApproved(0, alice), true, "Alice should have approved"); + assertEq(eMultisig.hasApproved(0, bob), true, "Bob should have approved"); + assertEq(eMultisig.hasApproved(0, randomWallet), true, "Random wallet should have approved"); + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); } function test_WhenCallingCanExecuteOrExecuteBeingExpired() external givenTheProposalExpired { // It canExecute should return false (when listed on creation, self appointed now) - // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) - // It execute should revert (when listed on creation, appointing someone else now) // It canExecute should return false (when currently appointed by a signer listed on creation) - // It execute should revert (when currently appointed by a signer listed on creation) // It canExecute should return false (when unlisted on creation, unappointed now) + + // vm.startPrank(alice); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + vm.startPrank(bob); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + vm.startPrank(randomWallet); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + vm.startPrank(address(0x5555)); + assertEq(eMultisig.canExecute(0), false, "Proposal should not be executable"); + + // It execute should revert (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, appointing someone else now) + // It execute should revert (when currently appointed by a signer listed on creation) // It execute should revert (when unlisted on creation, unappointed now) - vm.skip(true); + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 1.5 ether; + actions[0].to = address(carol); + actions[0].data = hex""; + + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); + + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); + + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); + + vm.startPrank(address(0x5555)); + vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, 0)); + eMultisig.execute(0, "ipfs://the-metadata", actions); } } From ed42c4a63090ae1cbf1f5132eb495a1c43d3f749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Wed, 13 Nov 2024 20:32:03 +0700 Subject: [PATCH 37/45] Multisig test tree WIP --- test/EmergencyMultisig.t.sol | 2233 ------------------------------ test/EmergencyMultisigTree.t.sol | 23 + test/MultisigTree.t.sol | 774 +++++++++-- 3 files changed, 664 insertions(+), 2366 deletions(-) delete mode 100644 test/EmergencyMultisig.t.sol diff --git a/test/EmergencyMultisig.t.sol b/test/EmergencyMultisig.t.sol deleted file mode 100644 index bbdc03c..0000000 --- a/test/EmergencyMultisig.t.sol +++ /dev/null @@ -1,2233 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import {AragonTest} from "./base/AragonTest.sol"; -import {DaoBuilder} from "./helpers/DaoBuilder.sol"; -import {StandardProposalCondition} from "../src/conditions/StandardProposalCondition.sol"; -import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; -import {Multisig} from "../src/Multisig.sol"; -import {EmergencyMultisig} from "../src/EmergencyMultisig.sol"; -import {SignerList, UPDATE_SIGNER_LIST_PERMISSION_ID} from "../src/SignerList.sol"; -import {IEmergencyMultisig} from "../src/interfaces/IEmergencyMultisig.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {PermissionManager} from "@aragon/osx/core/permission/PermissionManager.sol"; -import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; -import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; -import {createProxyAndCall} from "../src/helpers/proxy.sol"; - -uint64 constant EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; - -contract EmergencyMultisigTest is AragonTest { - DaoBuilder builder; - - DAO dao; - EmergencyMultisig eMultisig; - Multisig stdMultisig; - OptimisticTokenVotingPlugin optimisticPlugin; - SignerList signerList; - - // Events/errors to be tested here (duplicate) - event MultisigSettingsUpdated( - bool onlyListed, uint16 indexed minApprovals, SignerList signerList, uint64 proposalExpirationPeriod - ); - event MembersAdded(address[] members); - event MembersRemoved(address[] members); - - error InvalidAddresslistUpdate(address member); - error InvalidActions(uint256 proposalId); - - // Multisig's event - event EmergencyProposalCreated(uint256 indexed proposalId, address indexed creator, bytes encryptedPayloadURI); - - // OptimisticTokenVotingPlugin's event - event ProposalCreated( - uint256 indexed proposalId, - address indexed creator, - uint64 startDate, - uint64 endDate, - bytes metadata, - IDAO.Action[] actions, - uint256 allowFailureMap - ); - event Approved(uint256 indexed proposalId, address indexed approver); - event Executed(uint256 indexed proposalId); - event Upgraded(address indexed implementation); - - function setUp() public { - vm.startPrank(alice); - vm.warp(1 days); - vm.roll(100); - - builder = new DaoBuilder(); - (dao, optimisticPlugin, stdMultisig, eMultisig,, signerList,,) = builder.withMultisigMember(alice) - .withMultisigMember(bob).withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).withMinDuration( - 0 - ).build(); - } - - function test_RevertsIfTryingToReinitialize() public { - // Deploy a new stdMultisig instance - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - // Reinitialize should fail - vm.expectRevert("Initializable: contract is already initialized"); - eMultisig.initialize(dao, settings); - } - - function test_InitializeSetsMinApprovals() public { - // 2 - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 2, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - (, uint16 minApprovals,,) = eMultisig.multisigSettings(); - assertEq(minApprovals, uint16(2), "Incorrect minApprovals"); - - // Redeploy with 1 - settings.minApprovals = 1; - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - (, minApprovals,,) = eMultisig.multisigSettings(); - assertEq(minApprovals, uint16(1), "Incorrect minApprovals"); - } - - function test_InitializeSetsOnlyListed() public { - // Deploy with true - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - (bool onlyListed,,,) = eMultisig.multisigSettings(); - assertEq(onlyListed, true, "Incorrect onlyListed"); - - // Redeploy with false - settings.onlyListed = false; - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - (onlyListed,,,) = eMultisig.multisigSettings(); - assertEq(onlyListed, false, "Incorrect onlyListed"); - } - - function test_InitializeSetsAddresslistSource() public { - // Deploy the default stdMultisig as source - EmergencyMultisig.MultisigSettings memory emSettings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, emSettings)) - ) - ); - - (,, Addresslist givenSignerList,) = eMultisig.multisigSettings(); - assertEq(address(givenSignerList), address(stdMultisig), "Incorrect addresslistSource"); - - // Redeploy with a new addresslist source - (,,,,, signerList,,) = builder.build(); - - emSettings.signerList = signerList; - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, emSettings)) - ) - ); - - (,, signerList,) = eMultisig.multisigSettings(); - assertEq(address(signerList), address(emSettings.signerList), "Incorrect addresslistSource"); - } - - function test_InitializeSetsProposalExpiration() public { - // Deploy with 15 days - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: 15 days - }); - address[] memory signers = new address[](4); - signers[0] = alice; - signers[1] = bob; - signers[2] = carol; - signers[3] = david; - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - (,,, uint64 expirationPeriod) = eMultisig.multisigSettings(); - assertEq(expirationPeriod, 15 days, "Incorrect expirationPeriod"); - - // Redeploy with 3 days - settings.proposalExpirationPeriod = 3 days; - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - (,,, expirationPeriod) = eMultisig.multisigSettings(); - assertEq(expirationPeriod, 3 days, "Incorrect expirationPeriod"); - } - - function test_ShouldEmitMultisigSettingsUpdatedOnInstall() public { - // Deploy with true/3/default - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: 5 days - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(true, uint16(3), signerList, 5 days); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - // Deploy with false/2/new - - (,, Multisig newMultisig,,, SignerList newSignerList,,) = builder.build(); - - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 2, - signerList: newSignerList, - proposalExpirationPeriod: 15 days - }); - vm.expectEmit(); - emit MultisigSettingsUpdated(false, uint16(2), newSignerList, 15 days); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - } - - // INTERFACES - - function test_DoesntSupportTheEmptyInterface() public view { - bool supported = eMultisig.supportsInterface(0); - assertEq(supported, false, "Should not support the empty interface"); - } - - function test_SupportsIERC165Upgradeable() public view { - bool supported = eMultisig.supportsInterface(type(IERC165Upgradeable).interfaceId); - assertEq(supported, true, "Should support IERC165Upgradeable"); - } - - function test_SupportsIPlugin() public view { - bool supported = eMultisig.supportsInterface(type(IPlugin).interfaceId); - assertEq(supported, true, "Should support IPlugin"); - } - - function test_SupportsIProposal() public view { - bool supported = eMultisig.supportsInterface(type(IProposal).interfaceId); - assertEq(supported, true, "Should support IProposal"); - } - - function test_SupportsIEmergencyMultisig() public view { - bool supported = eMultisig.supportsInterface(type(IEmergencyMultisig).interfaceId); - assertEq(supported, true, "Should support IEmergencyMultisig"); - } - - // UPDATE MULTISIG SETTINGS - - function test_ShouldNotAllowMinApprovalsGreaterThanSignerListLength() public { - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 5, - signerList: signerList, // Greater than 4 members - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - // Retry with onlyListed false - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 6, - signerList: signerList, // Greater than 4 members - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 6)); - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - // OK - settings.minApprovals = 4; - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - } - - function test_ShouldEmitMultisigSettingsUpdated() public { - dao.grant(address(eMultisig), address(alice), eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // 1 - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(true, 1, signerList, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - eMultisig.updateMultisigSettings(settings); - - // 2 - (,, Multisig newMultisig,,, SignerList newSignerList,,) = builder.build(); - - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 2, - signerList: newSignerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1 - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(true, 2, newSignerList, EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); - eMultisig.updateMultisigSettings(settings); - - // 3 - (,, newMultisig,,, newSignerList,,) = builder.build(); - - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 3, - signerList: newSignerList, - proposalExpirationPeriod: 4 days - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(false, 3, newSignerList, 4 days); - eMultisig.updateMultisigSettings(settings); - - // 4 - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 4, - signerList: signerList, - proposalExpirationPeriod: 8 days - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(false, 4, signerList, 8 days); - eMultisig.updateMultisigSettings(settings); - } - - function test_UpdateSettingsShouldRevertWithInvalidSignerList() public { - dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // ko - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 1, - signerList: SignerList(address(dao)), - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidSignerList.selector, address(dao))); - eMultisig.updateMultisigSettings(settings); - - // ko 2 - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 1, - signerList: SignerList(address(optimisticPlugin)), - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidSignerList.selector, address(optimisticPlugin))); - eMultisig.updateMultisigSettings(settings); - - // ok - (,,,,, SignerList newSignerList,,) = builder.build(); - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 1, - signerList: newSignerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - eMultisig.updateMultisigSettings(settings); - } - - function test_onlyWalletWithPermissionsCanUpdateSettings() public { - (,,,,, SignerList newSignerList,,) = builder.build(); - - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 1, - signerList: newSignerList, - proposalExpirationPeriod: 3 days - }); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(eMultisig), - alice, - eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - eMultisig.updateMultisigSettings(settings); - - // Nothing changed - (bool onlyListed, uint16 minApprovals, Addresslist currentSource, uint64 expiration) = - eMultisig.multisigSettings(); - assertEq(onlyListed, true); - assertEq(minApprovals, 3); - assertEq(address(currentSource), address(stdMultisig)); - assertEq(expiration, 10 days); - - // Retry with the permission - dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - vm.expectEmit(); - emit MultisigSettingsUpdated(false, 1, newSignerList, 3 days); - eMultisig.updateMultisigSettings(settings); - } - - function test_MinApprovalsBiggerThanTheListReverts() public { - // MinApprovals should be within the boundaries of the list - dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 5, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 4, 5)); - eMultisig.updateMultisigSettings(settings); - - // More signers - - address[] memory signers = new address[](1); - signers[0] = randomWallet; - signerList.addSigners(signers); - - // should not fail now - eMultisig.updateMultisigSettings(settings); - - // More than that, should fail again - settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 6, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.MinApprovalsOutOfBounds.selector, 5, 6)); - eMultisig.updateMultisigSettings(settings); - - // OK - settings.minApprovals = 5; - eMultisig.updateMultisigSettings(settings); - } - - function testFuzz_PermissionedUpdateSettings(address randomAccount) public { - dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - (bool onlyListed, uint16 minApprovals, SignerList givenSignerList, uint64 expiration) = - eMultisig.multisigSettings(); - assertEq(minApprovals, 3, "Should be 3"); - assertEq(onlyListed, true, "Should be true"); - assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); - assertEq(expiration, 10 days, "Should be 10"); - - // in - (,,,,, SignerList newSignerList,,) = builder.build(); - EmergencyMultisig.MultisigSettings memory newSettings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 2, - signerList: newSignerList, - proposalExpirationPeriod: 4 days - }); - eMultisig.updateMultisigSettings(newSettings); - - (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); - assertEq(minApprovals, 2, "Should be 2"); - assertEq(onlyListed, false, "Should be false"); - assertEq(address(givenSignerList), address(newSignerList), "Incorrect signerList"); - assertEq(expiration, 4 days, "Should be 4"); - - // out - newSettings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - signerList: signerList, - proposalExpirationPeriod: 1 days - }); - eMultisig.updateMultisigSettings(newSettings); - (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); - assertEq(minApprovals, 1, "Should be 1"); - assertEq(onlyListed, true, "Should be true"); - assertEq(address(givenSignerList), address(signerList), "Incorrect signerList"); - assertEq(expiration, 1 days, "Should be 1"); - - vm.roll(block.number + 1); - - // someone else - if (randomAccount != alice && randomAccount != address(0)) { - vm.startPrank(randomAccount); - - (,,,,, newSignerList,,) = builder.build(); - newSettings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 4, - signerList: newSignerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(eMultisig), - randomAccount, - eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - eMultisig.updateMultisigSettings(newSettings); - - (onlyListed, minApprovals, givenSignerList, expiration) = eMultisig.multisigSettings(); - assertEq(minApprovals, 1, "Should still be 1"); - assertEq(onlyListed, true, "Should still be true"); - assertEq(address(givenSignerList), address(signerList), "Should still be signerList"); - assertEq(expiration, 1 days, "Should still be 1"); - } - } - - // PROPOSAL CREATION - - function test_IncrementsTheProposalCounter() public { - // increments the proposal counter - assertEq(eMultisig.proposalCount(), 0, "Should have no proposals"); - - // 1 - eMultisig.createProposal( - "ipfs://", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), - optimisticPlugin, - false - ); - - assertEq(eMultisig.proposalCount(), 1, "Should have 1 proposal"); - - // 2 - eMultisig.createProposal( - "ipfs://more", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), - optimisticPlugin, - true - ); - - assertEq(eMultisig.proposalCount(), 2, "Should have 2 proposals"); - } - - function test_CreatesAndReturnsUniqueProposalIds() public { - // creates unique proposal IDs for each proposal - - // 1 - uint256 pid = eMultisig.createProposal( - "", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), - optimisticPlugin, - true - ); - - assertEq(pid, 0, "Should be 0"); - - // 2 - pid = eMultisig.createProposal( - "ipfs://", - bytes32(0x0000567800000000000000000000000000000000000000000000000000000000), - bytes32(0x0000000056780000000000000000000000000000000000000000000000000000), - optimisticPlugin, - false - ); - - assertEq(pid, 1, "Should be 1"); - - // 3 - pid = eMultisig.createProposal( - "ipfs://more", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000000012340000000000000000000000000000000000000000000000000000), - optimisticPlugin, - true - ); - - assertEq(pid, 2, "Should be 2"); - } - - function test_EmitsProposalCreated() public { - // emits the `ProposalCreated` event - - vm.expectEmit(); - emit EmergencyProposalCreated({proposalId: 0, creator: alice, encryptedPayloadURI: ""}); - eMultisig.createProposal( - "", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), - optimisticPlugin, - true - ); - - // 2 - vm.startPrank(bob); - - vm.expectEmit(); - emit EmergencyProposalCreated({proposalId: 1, creator: bob, encryptedPayloadURI: "ipfs://"}); - eMultisig.createProposal( - "ipfs://", - bytes32(0x0000567800000000000000000000000000000000000000000000000000000000), - bytes32(0x0000000056780000000000000000000000000000000000000000000000000000), - optimisticPlugin, - false - ); - } - - function test_RevertsIfSettingsChangedInSameBlock() public { - // reverts if the stdMultisig settings have changed in the same block - - { - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - } - - // Same block - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, alice)); - eMultisig.createProposal("", bytes32(0), bytes32(0), optimisticPlugin, false); - - // Next block - vm.roll(block.number + 1); - eMultisig.createProposal("", bytes32(0), bytes32(0), optimisticPlugin, false); - } - - function test_CreatesWhenUnlistedAccountsAllowed() public { - // creates a proposal when unlisted accounts are allowed - - // Deploy a new instance with custom settings - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withoutOnlyListed().build(); - - vm.startPrank(randomWallet); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.startPrank(carol); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.startPrank(david); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - } - - function test_RevertsWhenOnlyListedAndAnotherWalletCreates() public { - // reverts if the user is not on the list and only listed accounts can create proposals - - vm.startPrank(randomWallet); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, randomWallet)); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.startPrank(taikoBridge); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, taikoBridge)); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - } - - function test_RevertsWhenCreatorWasListedBeforeButNotNow() public { - // reverts if `msg.sender` is not listed although she was listed in the last block - - dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); - - // Remove - address[] memory addrs = new address[](1); - addrs[0] = alice; - signerList.removeSigners(addrs); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, alice)); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - signerList.addSigners(addrs); // Add Alice back - vm.roll(block.number + 1); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Add+remove - addrs[0] = bob; - signerList.removeSigners(addrs); - - vm.startPrank(bob); - - // Bob cannot create now - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalCreationForbidden.selector, bob)); - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.startPrank(alice); - - // Bob can create now - signerList.addSigners(addrs); // Add Bob back - - vm.startPrank(alice); - - eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - } - - function test_CreatesProposalWithoutApprovingIfUnspecified() public { - // creates a proposal successfully and does not approve if not specified - - uint256 pid = eMultisig.createProposal( - "", - 0, - 0, - optimisticPlugin, - false // approveProposal - ); - - assertEq(eMultisig.hasApproved(pid, alice), false, "Should not have approved"); - (, uint16 approvals,,,,,) = eMultisig.getProposal(pid); - assertEq(approvals, 0, "Should be 0"); - - eMultisig.approve(pid); - - assertEq(eMultisig.hasApproved(pid, alice), true, "Should have approved"); - (, approvals,,,,,) = eMultisig.getProposal(pid); - assertEq(approvals, 1, "Should be 1"); - } - - function test_CreatesAndApprovesWhenSpecified() public { - // creates a proposal successfully and approves if specified - - vm.expectEmit(); - emit Approved({proposalId: 0, approver: alice}); - eMultisig.createProposal( - "ipfs://", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), - optimisticPlugin, - true - ); - - uint256 pid = eMultisig.createProposal( - "ipfs://", - bytes32(0x1234000000000000000000000000000000000000000000000000000000000000), - bytes32(0x0000123400000000000000000000000000000000000000000000000000000000), - optimisticPlugin, - true // approveProposal - ); - assertEq(eMultisig.hasApproved(pid, alice), true, "Should have approved"); - (, uint16 approvals,,,,,) = eMultisig.getProposal(pid); - assertEq(approvals, 1, "Should be 1"); - } - - function test_HashActionsReturnsProperData() public view { - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].to = address(dao); - actions[0].value = 1 ether; - actions[0].data = hex"00112233"; - - bytes32 h1 = eMultisig.hashActions(actions); - - // 2 - actions[0].to = bob; - bytes32 h2 = eMultisig.hashActions(actions); - assertNotEq(h1, h2, "Hashes should differ"); - - // 3 - actions[0].value = 2 ether; - bytes32 h3 = eMultisig.hashActions(actions); - assertNotEq(h2, h3, "Hashes should differ"); - - // 4 - actions[0].data = hex"00112235"; - bytes32 h4 = eMultisig.hashActions(actions); - assertNotEq(h3, h4, "Hashes should differ"); - - // 5 - actions = new IDAO.Action[](0); - bytes32 h5 = eMultisig.hashActions(actions); - assertNotEq(h4, h5, "Hashes should differ"); - - // 5' - bytes32 h5b = eMultisig.hashActions(actions); - assertEq(h5, h5b, "Hashes should match"); - } - - // CAN APPROVE - - function testFuzz_CanApproveReturnsfFalseIfNotCreated(uint256 randomProposalId) public view { - // returns `false` if the proposal doesn't exist - - assertEq(eMultisig.canApprove(randomProposalId, alice), false, "Should be false"); - assertEq(eMultisig.canApprove(randomProposalId, bob), false, "Should be false"); - assertEq(eMultisig.canApprove(randomProposalId, carol), false, "Should be false"); - assertEq(eMultisig.canApprove(randomProposalId, david), false, "Should be false"); - } - - function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { - // returns `false` if the approver is not listed - - { - // Leaving the deployment for fuzz efficiency - - // Deploy a new stdMultisig instance - Multisig.MultisigSettings memory mSettings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 1, - destinationProposalDuration: 4 days, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = alice; - - stdMultisig = Multisig( - createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, mSettings))) - ); - // New emergency stdMultisig using the above - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: false, - minApprovals: 1, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - eMultisig = EmergencyMultisig( - createProxyAndCall( - address(EMERGENCY_MULTISIG_BASE), abi.encodeCall(EmergencyMultisig.initialize, (dao, settings)) - ) - ); - - vm.roll(block.number + 1); - } - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // ko - if (randomWallet != alice) { - assertEq(eMultisig.canApprove(pid, randomWallet), false, "Should be false"); - } - - // static ok - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - } - - function test_CanApproveReturnsFalseIfApproved() public { - // returns `false` if the approver has already approved - builder = new DaoBuilder(); - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(4).build(); - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - eMultisig.approve(pid); - assertEq(eMultisig.canApprove(pid, alice), false, "Should be false"); - - // Bob - assertEq(eMultisig.canApprove(pid, bob), true, "Should be true"); - vm.startPrank(bob); - eMultisig.approve(pid); - assertEq(eMultisig.canApprove(pid, bob), false, "Should be false"); - - // Carol - assertEq(eMultisig.canApprove(pid, carol), true, "Should be true"); - vm.startPrank(carol); - eMultisig.approve(pid); - assertEq(eMultisig.canApprove(pid, carol), false, "Should be false"); - - // David - assertEq(eMultisig.canApprove(pid, david), true, "Should be true"); - vm.startPrank(david); - eMultisig.approve(pid); - assertEq(eMultisig.canApprove(pid, david), false, "Should be false"); - } - - function test_CanApproveReturnsFalseIfExpired() public { - // returns `false` if the proposal has ended - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); // expiration time - 1 - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + 1); // expiration time - assertEq(eMultisig.canApprove(pid, alice), false, "Should be false"); - - // Start later - vm.warp(50 days); - pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); // expiration time - 1 - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + 1); // expiration time - assertEq(eMultisig.canApprove(pid, alice), false, "Should be false"); - } - - function test_CanApproveReturnsFalseIfExecuted() public { - // returns `false` if the proposal is already executed - - bool executed; - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); // passed - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - eMultisig.execute(pid, "ipfs://", actions); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - // David cannot approve - assertEq(eMultisig.canApprove(pid, david), false, "Should be false"); - } - - function test_CanApproveReturnsTrueIfListed() public { - // returns `true` if the approver is listed - - vm.warp(10); - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - assertEq(eMultisig.canApprove(pid, bob), true, "Should be true"); - assertEq(eMultisig.canApprove(pid, carol), true, "Should be true"); - assertEq(eMultisig.canApprove(pid, david), true, "Should be true"); - - // new setup - builder = new DaoBuilder(); - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = - builder.withMultisigMember(randomWallet).withMinApprovals(1).withMinDuration(0).build(); - - // now ko - vm.startPrank(randomWallet); - pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - assertEq(eMultisig.canApprove(pid, alice), false, "Should be false"); - assertEq(eMultisig.canApprove(pid, bob), false, "Should be false"); - assertEq(eMultisig.canApprove(pid, carol), false, "Should be false"); - assertEq(eMultisig.canApprove(pid, david), false, "Should be false"); - - // ok - assertEq(eMultisig.canApprove(pid, randomWallet), true, "Should be true"); - } - - // HAS APPROVED - - function test_HasApprovedReturnsFalseWhenNotApproved() public { - // returns `false` if user hasn't approved yet - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - assertEq(eMultisig.hasApproved(pid, alice), false, "Should be false"); - assertEq(eMultisig.hasApproved(pid, bob), false, "Should be false"); - assertEq(eMultisig.hasApproved(pid, carol), false, "Should be false"); - assertEq(eMultisig.hasApproved(pid, david), false, "Should be false"); - } - - function test_HasApprovedReturnsTrueWhenUserApproved() public { - // returns `true` if user has approved - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - assertEq(eMultisig.hasApproved(pid, alice), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, alice), true, "Should be true"); - - // Bob - vm.startPrank(bob); - assertEq(eMultisig.hasApproved(pid, bob), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, bob), true, "Should be true"); - - // Carol - vm.startPrank(carol); - assertEq(eMultisig.hasApproved(pid, carol), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, carol), true, "Should be true"); - - // David - vm.startPrank(david); - assertEq(eMultisig.hasApproved(pid, david), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, david), true, "Should be true"); - } - - // APPROVE - - function testFuzz_ApproveRevertsIfNotCreated(uint256 randomProposalId) public { - // Reverts if the proposal doesn't exist - - vm.startPrank(alice); - vm.expectRevert( - abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, alice) - ); - eMultisig.approve(randomProposalId); - - // 2 - vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, bob)); - eMultisig.approve(randomProposalId); - - // 3 - vm.startPrank(carol); - vm.expectRevert( - abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, carol) - ); - eMultisig.approve(randomProposalId); - - // 4 - vm.startPrank(david); - vm.expectRevert( - abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, randomProposalId, david) - ); - eMultisig.approve(randomProposalId); - } - - function testFuzz_ApproveRevertsIfNotListed(address randomSigner) public { - // Reverts if the signer is not listed - - builder = new DaoBuilder(); - (,,, eMultisig,,,,) = builder.withMultisigMember(alice).withMinApprovals(1).build(); - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - if (randomSigner == alice) { - return; - } - - vm.startPrank(randomSigner); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, randomSigner)); - eMultisig.approve(pid); - } - - function test_ApproveRevertsIfAlreadyApproved() public { - // reverts when approving multiple times - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, alice)); - eMultisig.approve(pid); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, bob)); - eMultisig.approve(pid); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, carol)); - eMultisig.approve(pid); - - vm.startPrank(alice); - } - - function test_ApprovesWithTheSenderAddress() public { - // approves with the msg.sender address - // Same as test_HasApprovedReturnsTrueWhenUserApproved() - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - assertEq(eMultisig.hasApproved(pid, alice), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, alice), true, "Should be true"); - - // Bob - vm.startPrank(bob); - assertEq(eMultisig.hasApproved(pid, bob), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, bob), true, "Should be true"); - - // Carol - vm.startPrank(carol); - assertEq(eMultisig.hasApproved(pid, carol), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, carol), true, "Should be true"); - - // David - vm.startPrank(david); - assertEq(eMultisig.hasApproved(pid, david), false, "Should be false"); - eMultisig.approve(pid); - assertEq(eMultisig.hasApproved(pid, david), true, "Should be true"); - - vm.startPrank(alice); - } - - function test_ApproveRevertsIfExpired() public { - // reverts if the proposal has ended - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, alice)); - eMultisig.approve(pid); - - vm.warp(block.timestamp + 15 days); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, alice)); - eMultisig.approve(pid); - - // 2 - vm.warp(10 days); - pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - assertEq(eMultisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, alice)); - eMultisig.approve(pid); - - vm.warp(block.timestamp + 15 days); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, alice)); - eMultisig.approve(pid); - } - - function test_ApproveRevertsIfExecuted() public { - // reverts if the proposal has ended - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - - eMultisig.execute(pid, "ipfs://", actions); - (bool executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ApprovalCastForbidden.selector, pid, carol)); - eMultisig.approve(pid); - } - - function test_ApprovingProposalsEmits() public { - // Approving a proposal emits the Approved event - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.expectEmit(); - emit Approved(pid, alice); - eMultisig.approve(pid); - - // Bob - vm.startPrank(bob); - vm.expectEmit(); - emit Approved(pid, bob); - eMultisig.approve(pid); - - // Carol - vm.startPrank(carol); - vm.expectEmit(); - emit Approved(pid, carol); - eMultisig.approve(pid); - - // David (even if it already passed) - vm.startPrank(david); - vm.expectEmit(); - emit Approved(pid, david); - eMultisig.approve(pid); - } - - // CAN EXECUTE - - function testFuzz_CanExecuteReturnsFalseIfNotCreated(uint256 randomProposalId) public view { - // returns `false` if the proposal doesn't exist - - assertEq(eMultisig.canExecute(randomProposalId), false, "Should be false"); - } - - function test_CanExecuteReturnsFalseIfBelowMinApprovals() public { - // returns `false` if the proposal has not reached the minimum approvals yet - - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(2).build(); - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.startPrank(alice); - - // More approvals required (4) - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(4).build(); - - pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // David - vm.startPrank(david); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - } - - function test_CanExecuteReturnsFalseIfExpired() public { - // returns `false` if the proposal has expired - - // 1 - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 1); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // 2 - vm.warp(50 days); - - pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.startPrank(alice); - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 1); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - } - - function test_CanExecuteReturnsFalseIfExecuted() public { - // returns `false` if the proposal is already executed - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - eMultisig.execute(pid, "ipfs://", actions); - - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - } - - function test_CanExecuteReturnsTrueWhenAllGood() public { - // returns `true` if the proposal can be executed - - uint256 pid = eMultisig.createProposal("", 0, 0, optimisticPlugin, false); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // Alice - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), false, "Should be false"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - } - - // EXECUTE - - function testFuzz_ExecuteRevertsIfNotCreated(uint256 randomProposalId) public { - // reverts if the proposal doesn't exist - - IDAO.Action[] memory actions = new IDAO.Action[](0); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, randomProposalId)); - eMultisig.execute(randomProposalId, "", actions); - } - - function test_ExecuteRevertsIfBelowMinApprovals() public { - // reverts if minApprovals is not met yet - - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(2).build(); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - eMultisig.execute(pid, "ipfs://", actions); // ok - - vm.startPrank(alice); - - // More approvals required (4) - (dao, optimisticPlugin, stdMultisig, eMultisig,,,,) = builder.withMinApprovals(4).build(); - - pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - eMultisig.approve(pid); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - - // David - vm.startPrank(david); - eMultisig.approve(pid); - eMultisig.execute(pid, "ipfs://", actions); - } - - function test_ExecuteRevertsIfExpired() public { - // reverts if the proposal has expired - - // 1 - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - - vm.warp(100 days); - - // 2 - pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - vm.startPrank(alice); - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - } - - function test_ExecuteRevertsWhenAlreadyExecuted() public { - // executes if the minimum approval is met when stdMultisig with the `tryExecution` option - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - - assertEq(eMultisig.canExecute(pid), true, "Should be true"); - eMultisig.execute(pid, "ipfs://", actions); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.ProposalExecutionForbidden.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - } - - function test_ExecuteEmitsEvents() public { - // emits the `ProposalExecuted` and `ProposalCreated` events - - vm.warp(5 days); - vm.deal(address(dao), 1 ether); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - - // event - vm.expectEmit(); - emit Executed(pid); - vm.expectEmit(); - uint256 targetPid = (5 days << 128) | (5 days << 64); - emit ProposalCreated(targetPid, address(eMultisig), 5 days, 5 days, "ipfs://", actions, 0); - eMultisig.execute(pid, "ipfs://", actions); - - // 2 - vm.warp(20 days); - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - actionsHash = eMultisig.hashActions(actions); - metadataUriHash = keccak256("ipfs://more-metadata-here"); - pid = eMultisig.createProposal("ipfs://", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - eMultisig.approve(pid); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - - // events - vm.expectEmit(); - emit Executed(pid); - vm.expectEmit(); - targetPid = ((20 days << 128) | (20 days << 64)) + 1; - emit ProposalCreated(targetPid, address(eMultisig), 20 days, 20 days, "ipfs://more-metadata-here", actions, 0); - eMultisig.execute(pid, "ipfs://more-metadata-here", actions); - } - - function test_ExecutesWithEnoughApprovalsOnTime() public { - // executes if the minimum approval is met - - vm.deal(address(dao), 1 ether); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - (bool executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - eMultisig.execute(pid, "ipfs://", actions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - // 2 - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - - actionsHash = eMultisig.hashActions(actions); - pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - eMultisig.execute(pid, "ipfs://", actions); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - } - - function test_ExecuteRevertsWhentheGivenMetadaUriDoesntMatchTheHash() public { - vm.deal(address(dao), 1 ether); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = 0; // Wrong hash - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - (bool executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidMetadataUri.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // 2 - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - - metadataUriHash = keccak256("ipfs://correct-metadata-uri"); - actionsHash = eMultisig.hashActions(actions); - pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Fake URI - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidMetadataUri.selector, pid)); - eMultisig.execute(pid, "ipfs://wrong-metadata-uri", actions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // With ok actions - eMultisig.execute(pid, "ipfs://correct-metadata-uri", actions); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - } - - function test_ExecuteRevertsWhenTheGivenActionsDontMatchTheHash() public { - vm.deal(address(dao), 1 ether); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = 0; // invalid hash - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - (bool executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidActions.selector, pid)); - eMultisig.execute(pid, "ipfs://", actions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // 2 - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - - actionsHash = eMultisig.hashActions(actions); - pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Fake actions - IDAO.Action[] memory otherActions = new IDAO.Action[](1); - otherActions[0].value = 10000 ether; - otherActions[0].to = address(carol); - otherActions[0].data = hex"44556677"; - vm.expectRevert(abi.encodeWithSelector(EmergencyMultisig.InvalidActions.selector, pid)); - eMultisig.execute(pid, "ipfs://", otherActions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // With ok actions - eMultisig.execute(pid, "ipfs://", actions); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - } - - function test_ExecuteWhenPassedAndCalledByAnyoneWithTheActions() public { - // executes if the minimum approval is met and can be called by an unlisted accounts - - vm.deal(address(dao), 4 ether); - - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - (bool executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.startPrank(randomWallet); - eMultisig.execute(pid, "ipfs://", actions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - assertEq(bob.balance, 1 ether, "Incorrect balance"); - assertEq(address(dao).balance, 3 ether, "Incorrect balance"); - - // 2 - vm.startPrank(alice); - - actions = new IDAO.Action[](1); - actions[0].value = 3 ether; - actions[0].to = address(carol); - actions[0].data = hex"0011223344556677"; - actionsHash = eMultisig.hashActions(actions); - pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.startPrank(randomWallet); - eMultisig.execute(pid, "ipfs://", actions); - - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - assertEq(carol.balance, 3 ether, "Incorrect balance"); - assertEq(address(dao).balance, 0, "Incorrect balance"); - } - - function test_ExecutesSuccessfullyDespiteIncompatibleTaikoL1() public { - // executes even if the TaikoL1 contract reverts - (dao, optimisticPlugin,, eMultisig,,,,) = builder.withIncompatibleTaikoL1().build(); - - vm.deal(address(dao), 4 ether); - - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - bytes32 metadataUriHash = keccak256("ipfs://"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("", metadataUriHash, actionsHash, optimisticPlugin, false); - - // Alice - eMultisig.approve(pid); - (bool executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - eMultisig.approve(pid); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.startPrank(randomWallet); - eMultisig.execute(pid, "ipfs://", actions); - (executed,,,,,,) = eMultisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - assertEq(bob.balance, 1 ether, "Incorrect balance"); - assertEq(address(dao).balance, 3 ether, "Incorrect balance"); - } - - function test_GetProposalReturnsTheRightValues() public { - // Get proposal returns the right values - - vm.warp(10); - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); - bytes32 actionsHash = eMultisig.hashActions(actions); - uint256 pid = eMultisig.createProposal("ipfs://", metadataUriHash, actionsHash, optimisticPlugin, false); - - ( - bool executed, - uint16 approvals, - EmergencyMultisig.ProposalParameters memory parameters, - bytes memory encryptedPayloadURI, - bytes32 publicMetadataUriHash, - bytes32 destinationActionsHash, - OptimisticTokenVotingPlugin destinationPlugin - ) = eMultisig.getProposal(pid); - - assertEq(executed, false); - assertEq(approvals, 0); - assertEq(parameters.minApprovals, 3); - assertEq(parameters.snapshotBlock, block.number - 1); - assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - assertEq(encryptedPayloadURI, "ipfs://"); - assertEq(publicMetadataUriHash, metadataUriHash); - assertEq(destinationActionsHash, actionsHash); - assertEq(address(destinationPlugin), address(optimisticPlugin)); - - // 2 new proposal - OptimisticTokenVotingPlugin newOptimisticPlugin; - (dao, newOptimisticPlugin, stdMultisig, eMultisig,,,,) = builder.build(); - vm.deal(address(dao), 1 ether); - - metadataUriHash = keccak256("ipfs://another-public-metadata"); - actions[0].to = alice; - actionsHash = eMultisig.hashActions(actions); - pid = eMultisig.createProposal("ipfs://12340000", metadataUriHash, actionsHash, newOptimisticPlugin, true); - - ( - executed, - approvals, - parameters, - encryptedPayloadURI, - publicMetadataUriHash, - destinationActionsHash, - destinationPlugin - ) = eMultisig.getProposal(pid); - - assertEq(executed, false); - assertEq(approvals, 1); - assertEq(parameters.minApprovals, 3); - assertEq(parameters.snapshotBlock, block.number - 1); - assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - assertEq(encryptedPayloadURI, "ipfs://12340000"); - assertEq(publicMetadataUriHash, metadataUriHash); - assertEq(destinationActionsHash, actionsHash); - assertEq(address(destinationPlugin), address(newOptimisticPlugin)); - - // 3 approve - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - - ( - executed, - approvals, - parameters, - encryptedPayloadURI, - publicMetadataUriHash, - destinationActionsHash, - destinationPlugin - ) = eMultisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 3, "Should be 3"); - - assertEq(parameters.minApprovals, 3); - assertEq(parameters.snapshotBlock, block.number - 1); - assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - assertEq(encryptedPayloadURI, "ipfs://12340000"); - assertEq(publicMetadataUriHash, metadataUriHash); - assertEq(destinationActionsHash, actionsHash); - assertEq(address(destinationPlugin), address(newOptimisticPlugin)); - - // Execute - vm.startPrank(alice); - dao.grant(address(newOptimisticPlugin), address(eMultisig), newOptimisticPlugin.PROPOSER_PERMISSION_ID()); - eMultisig.execute(pid, "ipfs://another-public-metadata", actions); - - // 4 execute - ( - executed, - approvals, - parameters, - encryptedPayloadURI, - publicMetadataUriHash, - destinationActionsHash, - destinationPlugin - ) = eMultisig.getProposal(pid); - - assertEq(executed, true, "Should be executed"); - assertEq(approvals, 3, "Should be 3"); - - assertEq(parameters.minApprovals, 3); - assertEq(parameters.snapshotBlock, block.number - 1); - assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - assertEq(encryptedPayloadURI, "ipfs://12340000"); - assertEq(publicMetadataUriHash, metadataUriHash); - assertEq(destinationActionsHash, actionsHash); - assertEq(address(destinationPlugin), address(newOptimisticPlugin)); - } - - function testFuzz_GetProposalReturnsEmptyValuesForNonExistingOnes(uint256 randomProposalId) public view { - ( - bool executed, - uint16 approvals, - EmergencyMultisig.ProposalParameters memory parameters, - bytes memory encryptedPayloadURI, - bytes32 publicMetadataUriHash, - bytes32 destinationActionsHash, - OptimisticTokenVotingPlugin destinationPlugin - ) = eMultisig.getProposal(randomProposalId); - - assertEq(executed, false, "The proposal should not be executed"); - assertEq(approvals, 0, "The tally should be zero"); - assertEq(encryptedPayloadURI, "", "Incorrect encryptedPayloadURI"); - assertEq(parameters.expirationDate, 0, "Incorrect expirationDate"); - assertEq(parameters.snapshotBlock, 0, "Incorrect snapshotBlock"); - assertEq(parameters.minApprovals, 0, "Incorrect minApprovals"); - assertEq(publicMetadataUriHash, 0, "Metadata URI hash should have no items"); - assertEq(destinationActionsHash, 0, "Actions hash should have no items"); - assertEq(address(destinationPlugin), address(0), "Incorrect destination plugin"); - } - - function test_ProxiedProposalHasTheSameSettingsAsTheOriginal() public { - // Recreated proposal has the same settings and actions as registered here - - bool open; - bool executed; - OptimisticTokenVotingPlugin.ProposalParameters memory parameters; - uint256 vetoTally; - bytes memory metadataUri; - IDAO.Action[] memory retrievedActions; - uint256 allowFailureMap; - - vm.warp(10 days); - vm.deal(address(dao), 100 ether); - - IDAO.Action[] memory submittedActions = new IDAO.Action[](3); - submittedActions[0].to = alice; - submittedActions[0].value = 1 ether; - submittedActions[0].data = hex""; - submittedActions[1].to = bob; - submittedActions[1].value = 2 ether; - submittedActions[1].data = hex""; - submittedActions[2].to = carol; - submittedActions[2].value = 3 ether; - submittedActions[2].data = hex""; - uint256 pid = eMultisig.createProposal( - "ipfs://encrypted-metadata", - keccak256("ipfs://target-metadata-uri"), - eMultisig.hashActions(submittedActions), - optimisticPlugin, - false - ); - - // Approve - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - - vm.startPrank(alice); - eMultisig.execute(pid, "ipfs://target-metadata-uri", submittedActions); - - // Check round - (open, executed, parameters, vetoTally, metadataUri, retrievedActions, allowFailureMap) = - optimisticPlugin.getProposal(((uint256(block.timestamp) << 128) | (uint256(block.timestamp) << 64))); - - assertEq(open, false, "Should not be open"); - assertEq(executed, true, "Should be executed"); - assertEq(vetoTally, 0, "Should be 0"); - - assertEq(parameters.vetoEndDate, block.timestamp, "Incorrect vetoEndDate"); - assertEq(metadataUri, "ipfs://target-metadata-uri", "Incorrect target metadataUri"); - - assertEq(retrievedActions.length, 3, "Should be 3"); - - assertEq(retrievedActions[0].to, alice, "Incorrect to"); - assertEq(retrievedActions[0].value, 1 ether, "Incorrect value"); - assertEq(retrievedActions[0].data, hex"", "Incorrect data"); - assertEq(retrievedActions[1].to, bob, "Incorrect to"); - assertEq(retrievedActions[1].value, 2 ether, "Incorrect value"); - assertEq(retrievedActions[1].data, hex"", "Incorrect data"); - assertEq(retrievedActions[2].to, carol, "Incorrect to"); - assertEq(retrievedActions[2].value, 3 ether, "Incorrect value"); - assertEq(retrievedActions[2].data, hex"", "Incorrect data"); - - assertEq(allowFailureMap, 0, "Should be 0"); - - // New proposal - vm.warp(15 days); - - submittedActions = new IDAO.Action[](2); - submittedActions[1].to = address(dao); - submittedActions[1].value = 0; - submittedActions[1].data = abi.encodeWithSelector(DAO.daoURI.selector); - submittedActions[0].to = address(stdMultisig); - submittedActions[0].value = 0; - submittedActions[0].data = abi.encodeWithSelector(Addresslist.addresslistLength.selector); - pid = eMultisig.createProposal( - "ipfs://encrypted-metadata", - keccak256("ipfs://new-metadata-here"), - eMultisig.hashActions(submittedActions), - optimisticPlugin, - false - ); - - // Approve - eMultisig.approve(pid); - vm.startPrank(bob); - eMultisig.approve(pid); - vm.startPrank(carol); - eMultisig.approve(pid); - - vm.startPrank(alice); - eMultisig.execute(pid, "ipfs://new-metadata-here", submittedActions); - - // Check round - (open, executed, parameters, vetoTally, metadataUri, retrievedActions, allowFailureMap) = - optimisticPlugin.getProposal(((uint256(block.timestamp) << 128) | (uint256(block.timestamp) << 64)) + 1); - - assertEq(open, false, "Should not be open"); - assertEq(executed, true, "Should be executed"); - assertEq(vetoTally, 0, "Should be 0"); - - assertEq(parameters.vetoEndDate, 15 days, "Incorrect vetoEndDate"); - assertEq(metadataUri, "ipfs://new-metadata-here", "Incorrect target metadataUri"); - - assertEq(retrievedActions.length, 2, "Should be 2"); - - assertEq(retrievedActions[1].to, address(dao), "Incorrect to"); - assertEq(retrievedActions[1].value, 0, "Incorrect value"); - assertEq(retrievedActions[1].data, abi.encodeWithSelector(DAO.daoURI.selector), "Incorrect data"); - assertEq(retrievedActions[0].to, address(stdMultisig), "Incorrect to"); - assertEq(retrievedActions[0].value, 0, "Incorrect value"); - assertEq( - retrievedActions[0].data, abi.encodeWithSelector(Addresslist.addresslistLength.selector), "Incorrect data" - ); - - assertEq(allowFailureMap, 0, "Should be 0"); - } - - // Upgrade eMultisig - - function test_UpgradeToRevertsWhenCalledFromNonUpgrader() public { - address initialImplementation = eMultisig.implementation(); - address _newImplementation = address(new EmergencyMultisig()); - - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(eMultisig), - alice, - eMultisig.UPGRADE_PLUGIN_PERMISSION_ID() - ) - ); - - eMultisig.upgradeTo(_newImplementation); - - assertEq(eMultisig.implementation(), initialImplementation); - } - - function test_UpgradeToAndCallRevertsWhenCalledFromNonUpgrader() public { - address initialImplementation = eMultisig.implementation(); - dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - address _newImplementation = address(new EmergencyMultisig()); - - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(eMultisig), - alice, - eMultisig.UPGRADE_PLUGIN_PERMISSION_ID() - ) - ); - eMultisig.upgradeToAndCall( - _newImplementation, abi.encodeCall(EmergencyMultisig.updateMultisigSettings, (settings)) - ); - - assertEq(eMultisig.implementation(), initialImplementation); - } - - function test_UpgradeToSucceedsWhenCalledFromUpgrader() public { - dao.grant(address(eMultisig), alice, eMultisig.UPGRADE_PLUGIN_PERMISSION_ID()); - - address _newImplementation = address(new EmergencyMultisig()); - - vm.expectEmit(); - emit Upgraded(_newImplementation); - - eMultisig.upgradeTo(_newImplementation); - - assertEq(eMultisig.implementation(), address(_newImplementation)); - } - - function test_UpgradeToAndCallSucceedsWhenCalledFromUpgrader() public { - dao.grant(address(eMultisig), alice, eMultisig.UPGRADE_PLUGIN_PERMISSION_ID()); - dao.grant(address(eMultisig), alice, eMultisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - address _newImplementation = address(new EmergencyMultisig()); - - vm.expectEmit(); - emit Upgraded(_newImplementation); - - EmergencyMultisig.MultisigSettings memory settings = EmergencyMultisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - signerList: signerList, - proposalExpirationPeriod: EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - eMultisig.upgradeToAndCall( - _newImplementation, abi.encodeCall(EmergencyMultisig.updateMultisigSettings, (settings)) - ); - - assertEq(eMultisig.implementation(), address(_newImplementation)); - } -} diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index e26bbc0..3154d47 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1447,6 +1447,29 @@ contract EmergencyMultisigTest is AragonTest { vm.expectEmit(); emit Approved(0, bob); // Note: Event shows the owner, not the appointed wallet eMultisig.approve(0); + + // Check approval count + (, uint16 approvals,,,,,) = eMultisig.getProposal(0); + assertEq(approvals, 2, "Should have 2 approvals total"); + + vm.startPrank(carol); + assertEq(eMultisig.canApprove(0, carol), true, "Carol should be able to approve"); + eMultisig.approve(0); + + // Should approve, pass but not execute + bool executed; + (executed, approvals,,,,,) = eMultisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 3, "Should have 3 approvals total"); + + // More approvals + vm.startPrank(david); + assertEq(eMultisig.canApprove(0, david), true, "David should be able to approve"); + eMultisig.approve(0); + + (executed, approvals,,,,,) = eMultisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 4, "Should have 4 approvals total"); } function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index cdf4392..0e29300 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -21,6 +21,7 @@ import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; import {IMultisig} from "../src/interfaces/IMultisig.sol"; uint64 constant MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; +uint32 constant DESTINATION_PROPOSAL_DURATION = 9 days; contract MultisigTest is AragonTest { DaoBuilder builder; @@ -44,9 +45,7 @@ contract MultisigTest is AragonTest { SignerList signerList, uint64 proposalExpirationPeriod ); - // Multisig proposal - event ProposalCreated(uint256 indexed proposalId, address indexed creator, bytes encryptedPayloadURI); - // OptimisticTokenVotingPlugin's event + // Multisig and OptimisticTokenVotingPlugin's event event ProposalCreated( uint256 indexed proposalId, address indexed creator, @@ -65,7 +64,9 @@ contract MultisigTest is AragonTest { builder = new DaoBuilder(); (dao, optimisticPlugin, multisig,,, signerList, encryptionRegistry,) = builder.withMultisigMember(alice) - .withMultisigMember(bob).withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); + .withMultisigMember(bob).withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).withDuration( + DESTINATION_PROPOSAL_DURATION + ).build(); } modifier givenANewlyDeployedContract() { @@ -554,7 +555,7 @@ contract MultisigTest is AragonTest { ) = multisig.multisigSettings(); assertEq(onlyListed, true); assertEq(minApprovals, 3); - assertEq(currentDestinationProposalDuration, 10 days); + assertEq(currentDestinationProposalDuration, 9 days); assertEq(address(currentSource), address(signerList)); assertEq(expiration, 10 days); @@ -685,7 +686,7 @@ contract MultisigTest is AragonTest { multisig.multisigSettings(); assertEq(minApprovals, 3, "Should be 3"); assertEq(onlyListed, true, "Should be true"); - assertEq(destMinDuration, 10 days, "Incorrect destMinDuration"); + assertEq(destMinDuration, 9 days, "Incorrect destMinDuration"); assertEq(address(givenSignerList), address(signerList), "Incorrect addresslistSource"); assertEq(expiration, 10 days, "Should be 10"); @@ -786,7 +787,7 @@ contract MultisigTest is AragonTest { creator: alice, metadata: "ipfs://", startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + 10 days, + endDate: uint64(block.timestamp) + DESTINATION_PROPOSAL_DURATION, actions: inputActions, allowFailureMap: 0 }); @@ -823,7 +824,7 @@ contract MultisigTest is AragonTest { creator: bob, metadata: "ipfs://more", startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + 10 days, + endDate: uint64(block.timestamp) + DESTINATION_PROPOSAL_DURATION, actions: inputActions, allowFailureMap: 0 }); @@ -861,7 +862,7 @@ contract MultisigTest is AragonTest { creator: carol, metadata: "ipfs://1234", startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + 10 days, + endDate: uint64(block.timestamp) + DESTINATION_PROPOSAL_DURATION, actions: inputActions, allowFailureMap: 0 }); @@ -1256,11 +1257,14 @@ contract MultisigTest is AragonTest { vm.deal(address(dao), 1 ether); // Create proposal 0 - IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 0; - actions[0].to = address(bob); + IDAO.Action[] memory actions = new IDAO.Action[](2); + actions[0].value = 0.25 ether; + actions[0].to = address(alice); actions[0].data = hex""; - multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + actions[1].value = 0.75 ether; + actions[1].to = address(dao); + actions[1].data = abi.encodeCall(DAO.setMetadata, "ipfs://new-metadata"); + multisig.createProposal("ipfs://pub-metadata", actions, optimisticPlugin, false); // Remove (later) vm.roll(block.number + 50); @@ -1320,21 +1324,114 @@ contract MultisigTest is AragonTest { function test_WhenCallingGetProposalBeingOpen() external givenTheProposalIsOpen { // It should return the right values - vm.skip(true); + + ( + bool executed, + uint16 approvals, + Multisig.ProposalParameters memory parameters, + bytes memory metadataURI, + IDAO.Action[] memory proposalActions, + OptimisticTokenVotingPlugin destinationPlugin + ) = multisig.getProposal(0); + + // Check basic proposal state + assertEq(executed, false, "Should not be executed"); + assertEq(approvals, 0, "Should have no approvals"); + + // Check parameters + assertEq(parameters.minApprovals, 3, "Should require 3 approvals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshot block"); + assertEq( + parameters.expirationDate, + block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, + "Incorrect expiration date" + ); + + // Check metadata and plugin + assertEq(metadataURI, "ipfs://pub-metadata", "Incorrect metadata URI"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destination plugin"); + + // Verify actions + IDAO.Action[] memory actions = new IDAO.Action[](2); + actions[0].value = 0.25 ether; + actions[0].to = address(alice); + actions[0].data = hex""; + actions[1].value = 0.75 ether; + actions[1].to = address(dao); + actions[1].data = abi.encodeCall(DAO.setMetadata, "ipfs://new-metadata"); + + assertEq(proposalActions.length, actions.length, "Actions length should match"); + for (uint256 i = 0; i < actions.length; i++) { + assertEq(proposalActions[i].to, actions[i].to, "Action to should match"); + assertEq(proposalActions[i].value, actions[i].value, "Action value should match"); + assertEq(proposalActions[i].data, actions[i].data, "Action data should match"); + } } function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { // It canApprove should return true (when listed on creation, self appointed now) // It approve should work (when listed on creation, self appointed now) // It approve should emit an event (when listed on creation, self appointed now) + assertEq(multisig.canApprove(0, alice), true, "Alice should be able to approve"); + vm.expectEmit(); + emit Approved(0, alice); + multisig.approve(0, false); + assertEq(multisig.hasApproved(0, alice), true, "Alice's approval should be recorded"); + // It canApprove should return false (when listed on creation, appointing someone else now) // It approve should revert (when listed on creation, appointing someone else now) + assertEq(multisig.canApprove(0, bob), false, "Bob should be able to approve"); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, bob)); + multisig.approve(0, false); + assertEq(multisig.hasApproved(0, bob), false, "Bob's approval should not be recorded"); + // It canApprove should return true (when currently appointed by a signer listed on creation) // It approve should work (when currently appointed by a signer listed on creation) // It approve should emit an event (when currently appointed by a signer listed on creation) + assertEq(multisig.canApprove(0, randomWallet), true, "RandomWallet should be able to approve"); + vm.startPrank(randomWallet); + vm.expectEmit(); + emit Approved(0, randomWallet); + multisig.approve(0, false); + assertEq(multisig.hasApproved(0, randomWallet), true, "RandomWallet's approval should be recorded"); + // It canApprove should return false (when unlisted on creation, unappointed now) // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + assertEq(multisig.canApprove(0, address(0x5555)), false, "Random wallet should not be able to approve"); + vm.startPrank(address(0x5555)); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, address(0x5555))); + multisig.approve(0, false); + + // Check approval count + (, uint16 approvals,,,,) = multisig.getProposal(0); + assertEq(approvals, 2, "Should have 2 approvals total"); + + // Test tryExecution parameter + vm.startPrank(randomWallet); + // Should not be able to approve again even with tryExecution + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); + multisig.approve(0, true); + + // Carol + vm.startPrank(carol); + assertEq(multisig.canApprove(0, carol), true, "Carol should be able to approve"); + multisig.approve(0, false); + + // Should approve, pass but not execute (yet) + bool executed; + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 3, "Should have 3 approvals total"); + + // David should approve and trigger auto execution + vm.startPrank(david); + assertEq(multisig.canApprove(0, david), true, "David should be able to approve"); + multisig.approve(0, true); + + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, true, "Should have executed"); + assertEq(approvals, 4, "Should have 4 approvals total"); } function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { @@ -1391,30 +1488,64 @@ contract MultisigTest is AragonTest { multisig.approve(pid, true); } - function test_WhenCallingApproveWithTryExecutionAndAlmostPassedBeingOpen() external givenTheProposalIsOpen { - // It approve should also execute the proposal - // It approve should emit an Executed event - // It approve recreates the proposal on the destination plugin - // It The parameters of the recreated proposal match those of the approved one - // It A ProposalCreated event is emitted on the destination plugin - vm.skip(true); - } - function test_WhenCallingHasApprovedBeingOpen() external givenTheProposalIsOpen { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(multisig.hasApproved(0, alice), false, "Alice should not have approved"); + assertEq(multisig.hasApproved(0, bob), false, "Bob should not have approved"); + assertEq(multisig.hasApproved(0, carol), false, "Carol should not have approved"); } function test_WhenCallingCanExecuteOrExecuteBeingOpen() external givenTheProposalIsOpen { // It canExecute should return false (when listed on creation, self appointed now) - // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) - // It execute should revert (when listed on creation, appointing someone else now) // It canExecute should return false (when currently appointed by a signer listed on creation) - // It execute should revert (when currently appointed by a signer listed on creation) // It canExecute should return false (when unlisted on creation, unappointed now) + + // vm.startPrank(alice); + assertEq(multisig.canExecute(0), false, "Should not be executable with only 1 approval"); + vm.startPrank(bob); + assertEq(multisig.canExecute(0), false, "Should not be executable with only 1 approval"); + vm.startPrank(randomWallet); + assertEq(multisig.canExecute(0), false, "Should not be executable with only 1 approval"); + vm.startPrank(address(0x5555)); + assertEq(multisig.canExecute(0), false, "Should not be executable with only 1 approval"); + + // It execute should revert (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, appointing someone else now) + // It execute should revert (when currently appointed by a signer listed on creation) // It execute should revert (when unlisted on creation, unappointed now) - vm.skip(true); + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + vm.startPrank(address(0x5555)); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // More approvals + vm.startPrank(randomWallet); + multisig.approve(0, false); + + assertEq(multisig.canExecute(0), false, "Should not be executable with only 2 approvals"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // Add final approval + vm.startPrank(carol); + multisig.approve(0, false); + + assertEq(multisig.canExecute(0), true, "Should be executable with 3 approvals"); + multisig.execute(0); + + // Verify execution + (bool executed,,,,,) = multisig.getProposal(0); + assertEq(executed, true, "Should now be executed"); } modifier givenTheProposalWasApprovedByTheAddress() { @@ -1435,10 +1566,10 @@ contract MultisigTest is AragonTest { // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 0; - actions[0].to = address(bob); + actions[0].value = 0.3 ether; + actions[0].to = address(carol); actions[0].data = hex""; - uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + uint256 pid = multisig.createProposal("ipfs://more-metadata", actions, optimisticPlugin, false); // Remove (later) vm.roll(block.number + 50); @@ -1448,6 +1579,7 @@ contract MultisigTest is AragonTest { vm.startPrank(alice); signerList.removeSigners(addrs); + // Alice approves multisig.approve(pid, false); _; @@ -1455,28 +1587,144 @@ contract MultisigTest is AragonTest { function test_WhenCallingGetProposalBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It should return the right values - vm.skip(true); + + ( + bool executed, + uint16 approvals, + Multisig.ProposalParameters memory parameters, + bytes memory metadataURI, + IDAO.Action[] memory proposalActions, + OptimisticTokenVotingPlugin destinationPlugin + ) = multisig.getProposal(0); + + assertEq(executed, false, "Should not be executed"); + assertEq(approvals, 1, "Should have 1 approval"); + assertEq(parameters.minApprovals, 3, "Should require 3 approvals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshot block"); // -51 due to vm.roll(block.number + 50) + assertEq( + parameters.expirationDate, block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expiration" + ); + assertEq(metadataURI, "ipfs://more-metadata", "Incorrect metadata URI"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destination plugin"); + + // Verify actions + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 0.3 ether; + actions[0].to = address(carol); + actions[0].data = hex""; + + assertEq(proposalActions.length, actions.length, "Actions length should match"); + for (uint256 i = 0; i < actions.length; i++) { + assertEq(proposalActions[i].to, actions[i].to, "Action to should match"); + assertEq(proposalActions[i].value, actions[i].value, "Action value should match"); + assertEq(proposalActions[i].data, actions[i].data, "Action data should match"); + } } function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { + // Approve without executing + vm.startPrank(randomWallet); + multisig.approve(0, false); + // It canApprove should return false (when listed on creation, self appointed now) - // It approve should revert (when listed on creation, self appointed now) + // It canApprove should return false (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) + // It canApprove should return false (when unlisted on creation, unappointed now) + + // It approve should revert (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, appointing someone else now) // It approve should revert (when currently appointed by a signer listed on creation) - vm.skip(true); + // It approve should revert (when unlisted on creation, unappointed now) + + // When listed on creation, self appointed now + assertEq(multisig.canApprove(0, alice), false, "Alice should not be able to approve"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, alice)); + multisig.approve(0, false); + + // When listed on creation, appointing someone else now + assertEq(multisig.canApprove(0, bob), false, "Bob should not be able to approve"); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, bob)); + multisig.approve(0, false); + + // When currently appointed by a signer listed on creation + // RandomWallet should not be able to approve again + assertEq(multisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve again"); + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); + multisig.approve(0, false); + + // When unlisted on creation, unappointed now + assertEq(multisig.canApprove(0, address(0x1234)), false, "Unlisted address should not be able to approve"); + vm.startPrank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, address(0x1234))); + multisig.approve(0, false); } function test_WhenCallingHasApprovedBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(multisig.hasApproved(0, alice), true, "Alice should have approved"); + assertEq(multisig.hasApproved(0, bob), false, "Bob should not have approved"); + assertEq(multisig.hasApproved(0, carol), false, "Carol should not have approved"); + assertEq(multisig.hasApproved(0, david), false, "David should not have approved"); + assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not have approved"); + + vm.startPrank(randomWallet); // Appointed + multisig.approve(0, false); + assertEq(multisig.hasApproved(0, bob), true, "Bob should have approved"); + + vm.startPrank(carol); + multisig.approve(0, false); + assertEq(multisig.hasApproved(0, carol), true, "Carol should have approved"); + + vm.startPrank(david); + multisig.approve(0, false); + assertEq(multisig.hasApproved(0, david), true, "Bob should have approved"); } function test_WhenCallingCanExecuteOrExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { - // It canExecute should return false (when listed on creation, self appointed now) // It execute should revert (when listed on creation, self appointed now) - // It canExecute should return false (when currently appointed by a signer listed on creation) // It execute should revert (when currently appointed by a signer listed on creation) - vm.skip(true); + + // It canExecute should return false (when listed on creation, self appointed now) + // It canExecute should return false (when currently appointed by a signer listed on creation) + + // It canExecute should return false with insufficient approvals + // vm.startPrank(alice); + assertEq(multisig.canExecute(0), false, "Should not be executable with only 1 approval"); + vm.startPrank(randomWallet); + assertEq(multisig.canExecute(0), false, "Should not be executable with only 1 approval"); + + // It execute should revert with insufficient approvals + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // Add remaining approvals + vm.startPrank(randomWallet); + multisig.approve(0, false); + + // It canExecute should return false with insufficient approvals + assertEq(multisig.canExecute(0), false, "Should not be executable with 2 approvals"); + + vm.startPrank(carol); + multisig.approve(0, false); + + // It canExecute should return true with sufficient approvals + assertEq(multisig.canExecute(0), true, "Should be executable with 3 approvals"); + + // It execute should work with sufficient approvals + vm.expectEmit(); + emit Executed(0); + multisig.execute(0); + + // Verify execution + (bool executed,,,,,) = multisig.getProposal(0); + assertEq(executed, true, "Should be executed"); } modifier givenTheProposalPassed() { @@ -1497,10 +1745,10 @@ contract MultisigTest is AragonTest { // Create proposal 0 IDAO.Action[] memory actions = new IDAO.Action[](1); - actions[0].value = 0; - actions[0].to = address(bob); + actions[0].value = 0.65 ether; + actions[0].to = address(david); actions[0].data = hex""; - uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + uint256 pid = multisig.createProposal("ipfs://proposal-metadata-here", actions, optimisticPlugin, false); // Remove (later) vm.roll(block.number + 50); @@ -1515,6 +1763,9 @@ contract MultisigTest is AragonTest { vm.startPrank(randomWallet); multisig.approve(pid, false); + vm.startPrank(carol); + multisig.approve(pid, false); + vm.startPrank(alice); _; @@ -1522,82 +1773,201 @@ contract MultisigTest is AragonTest { function test_WhenCallingGetProposalBeingPassed() external givenTheProposalPassed { // It should return the right values - vm.skip(true); - - // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); - // dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); - // signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); - - // // Alice: listed on creation and self appointed - - // // Bob: listed on creation, appointing someone else now - // vm.startPrank(bob); - // encryptionRegistry.appointWallet(randomWallet); - - // // Random Wallet: appointed by a listed signer on creation - - // // 0x1234: unlisted and unappointed on creation - - // vm.deal(address(dao), 1 ether); - // - // Create proposal - // IDAO.Action[] memory actions = new IDAO.Action[](1); - // actions[0].value = 0; - // actions[0].to = address(bob); - // actions[0].data = hex""; - // bytes32 metadataUriHash = keccak256("ipfs://the-metadata"); - // bytes32 actionsHash = eMultisig.hashActions(actions); - // eMultisig.createProposal("ipfs://encrypted", metadataUriHash, actionsHash, optimisticPlugin, false); - - // // Remove (later) - // vm.roll(block.number + 50); - // address[] memory addrs = new address[](2); - // addrs[0] = alice; - // addrs[1] = bob; - - // vm.startPrank(alice); - // signerList.removeSigners(addrs); - - // eMultisig.approve(0); + ( + bool executed, + uint16 approvals, + Multisig.ProposalParameters memory parameters, + bytes memory metadataURI, + IDAO.Action[] memory proposalActions, + OptimisticTokenVotingPlugin destinationPlugin + ) = multisig.getProposal(0); + + assertEq(executed, false, "Should not be executed yet"); + assertEq(approvals, 3, "Should have 3 approvals"); + assertEq(parameters.minApprovals, 3, "Should require 3 approvals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshot block"); // -51 due to vm.roll(block.number + 50) + assertEq( + parameters.expirationDate, block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expiration" + ); + assertEq(metadataURI, "ipfs://proposal-metadata-here", "Incorrect metadata URI"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destination plugin"); - // vm.startPrank(bob); - // eMultisig.approve(0); + // Verify actions + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 0.65 ether; + actions[0].to = address(david); + actions[0].data = hex""; - // vm.startPrank(randomWallet); - // eMultisig.approve(0); + assertEq(proposalActions.length, actions.length, "Actions length should match"); + for (uint256 i = 0; i < actions.length; i++) { + assertEq(proposalActions[i].to, actions[i].to, "Action to should match"); + assertEq(proposalActions[i].value, actions[i].value, "Action value should match"); + assertEq(proposalActions[i].data, actions[i].data, "Action data should match"); + } } function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { // It canApprove should return false (when listed on creation, self appointed now) - // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) - // It approve should revert (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) - // It approve should revert (when currently appointed by a signer listed on creation) // It canApprove should return false (when unlisted on creation, unappointed now) + + // It approve should revert (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, appointing someone else now) + // It approve should revert (when currently appointed by a signer listed on creation) // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + + // When listed on creation, self appointed now + assertEq(multisig.canApprove(0, alice), false, "Alice should not be able to approve"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, alice)); + multisig.approve(0, false); + + // When listed on creation, appointing someone else now + assertEq(multisig.canApprove(0, bob), false, "Bob should not be able to approve"); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, bob)); + multisig.approve(0, false); + + // When currently appointed by a signer listed on creation + assertEq(multisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve"); + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); + multisig.approve(0, false); + + // When unlisted on creation, unappointed now + assertEq(multisig.canApprove(0, address(0x1234)), false, "Unlisted address should not be able to approve"); + vm.startPrank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, address(0x1234))); + multisig.approve(0, false); } function test_WhenCallingHasApprovedBeingPassed() external givenTheProposalPassed { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(multisig.hasApproved(0, alice), true, "Alice should show as approved"); + assertEq(multisig.hasApproved(0, bob), true, "Bob should show as approved"); + assertEq(multisig.hasApproved(0, carol), true, "Carol should show as approved"); + assertEq(multisig.hasApproved(0, david), false, "David should not show as approved"); + assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not show as approved"); } function test_WhenCallingCanExecuteOrExecuteBeingPassed() external givenTheProposalPassed { - // It canExecute should return true, always - // It execute should work, when called by anyone - // It execute should emit an event, when called by anyone // It execute recreates the proposal on the destination plugin // It The parameters of the recreated proposal match those of the executed one // It The proposal duration on the destination plugin matches the multisig settings // It A ProposalCreated event is emitted on the destination plugin - vm.skip(true); + + // It canExecute should return true, always + // vm.startPrank(alice); + assertEq(multisig.canExecute(0), true, "Should be executable"); + vm.startPrank(randomWallet); + assertEq(multisig.canExecute(0), true, "Should be executable"); + vm.startPrank(carol); + assertEq(multisig.canExecute(0), true, "Should be executable"); + vm.startPrank(address(0x5555)); + assertEq(multisig.canExecute(0), true, "Should be executable"); + + // It execute should work, when called by anyone + vm.expectEmit(); + emit Executed(0); + + // It execute should emit an event, when called by anyone + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 0.65 ether; + actions[0].to = address(david); + actions[0].data = hex""; + + // It execute recreates the proposal on the destination plugin + uint256 targetPid = (block.timestamp << 128) | ((block.timestamp + DESTINATION_PROPOSAL_DURATION) << 64); + vm.expectEmit(); + emit ProposalCreated( + targetPid, + address(multisig), + uint64(block.timestamp), + uint64(block.timestamp + DESTINATION_PROPOSAL_DURATION), + "ipfs://proposal-metadata-here", + actions, + 0 + ); + + multisig.execute(0); + + // Verify execution + (bool executed,,,,,) = multisig.getProposal(0); + assertEq(executed, true, "Should be executed"); + + // Verify proposal recreation in destination plugin + ( + bool open, + bool destExecuted, + , + uint256 vetoTally, + bytes memory metadataUri, + IDAO.Action[] memory destActions, + uint256 allowFailureMap + ) = optimisticPlugin.getProposal(targetPid); + + assertEq(open, true, "Destination proposal should be open"); + assertEq(destExecuted, false, "Destination proposal should not be executed"); + assertEq(vetoTally, 0, "Veto tally should be 0"); + assertEq(metadataUri, "ipfs://proposal-metadata-here", "Metadata URI should match"); + assertEq(destActions.length, actions.length, "Actions should match"); + assertEq(allowFailureMap, 0, "Allow failure map should be 0"); } function test_GivenTaikoL1IsIncompatible() external givenTheProposalPassed { + // Recreate with L1 incompatible + (dao, optimisticPlugin, multisig,,, signerList, encryptionRegistry,) = builder.withIncompatibleTaikoL1().build(); + + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_PERMISSION_ID); + dao.grant(address(signerList), alice, UPDATE_SIGNER_LIST_SETTINGS_PERMISSION_ID); + signerList.updateSettings(SignerList.Settings(encryptionRegistry, 1)); + + // Alice: listed on creation and self appointed + // Bob: listed on creation, appointing someone else now + vm.startPrank(bob); + encryptionRegistry.appointWallet(randomWallet); + // Random Wallet: appointed by a listed signer on creation + // 0x1234: unlisted and unappointed on creation + + vm.deal(address(dao), 1 ether); + + // Create proposal 0 + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 0.65 ether; + actions[0].to = address(david); + actions[0].data = hex""; + uint256 pid = multisig.createProposal("ipfs://proposal-metadata-here", actions, optimisticPlugin, false); + + vm.startPrank(alice); + multisig.approve(pid, false); + vm.startPrank(randomWallet); + multisig.approve(pid, false); + vm.startPrank(carol); + multisig.approve(pid, false); + + vm.startPrank(alice); + // It executes successfully, regardless - vm.skip(true); + vm.expectEmit(); + emit Executed(0); + + vm.expectEmit(); + emit ProposalCreated( + ((uint256(block.timestamp) << 128) | (uint256(block.timestamp + DESTINATION_PROPOSAL_DURATION) << 64)), + address(multisig), + uint64(block.timestamp), + uint64(block.timestamp + DESTINATION_PROPOSAL_DURATION), + "ipfs://proposal-metadata-here", + actions, + 0 + ); + + multisig.execute(0); + + // Verify execution + (bool executed,,,,,) = multisig.getProposal(0); + assertEq(executed, true, "Should be executed"); } modifier givenTheProposalIsAlreadyExecuted() { @@ -1621,7 +1991,7 @@ contract MultisigTest is AragonTest { actions[0].value = 0; actions[0].to = address(bob); actions[0].data = hex""; - uint256 pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); + uint256 pid = multisig.createProposal("ipfs://the-metadata-here", actions, optimisticPlugin, false); // Remove (later) vm.roll(block.number + 50); @@ -1648,57 +2018,117 @@ contract MultisigTest is AragonTest { function test_WhenCallingGetProposalBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It should return the right values - vm.skip(true); - - // ( - // executed, - // approvals, - // parameters, - // encryptedPayloadURI, - // publicMetadataUriHash, - // destinationActionsHash, - // destinationPlugin - // ) = eMultisig.getProposal(pid); - - // assertEq(executed, true, "Should be executed"); - // assertEq(approvals, 3, "Should be 3"); - - // assertEq(parameters.minApprovals, 3); - // assertEq(parameters.snapshotBlock, block.number - 1); - // assertEq(parameters.expirationDate, block.timestamp + EMERGENCY_MULTISIG_PROPOSAL_EXPIRATION_PERIOD); - // assertEq(encryptedPayloadURI, "ipfs://12340000"); - // assertEq(publicMetadataUriHash, metadataUriHash); - // assertEq(destinationActionsHash, actionsHash); - // assertEq(address(destinationPlugin), address(newOptimisticPlugin)); + + ( + bool executed, + uint16 approvals, + Multisig.ProposalParameters memory parameters, + bytes memory metadataURI, + IDAO.Action[] memory proposalActions, + OptimisticTokenVotingPlugin destinationPlugin + ) = multisig.getProposal(0); + + assertEq(executed, true, "Should be executed"); + assertEq(approvals, 3, "Should have 3 approvals"); + assertEq(parameters.minApprovals, 3, "Should require 3 approvals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshot block"); // -51 due to vm.roll(block.number + 50) + assertEq( + parameters.expirationDate, block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expiration" + ); + assertEq(metadataURI, "ipfs://the-metadata-here", "Incorrect metadata URI"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destination plugin"); + + // Verify actions + IDAO.Action[] memory actions = new IDAO.Action[](1); + actions[0].value = 0; + actions[0].to = address(bob); + actions[0].data = hex""; + + assertEq(proposalActions.length, actions.length, "Actions length should match"); + for (uint256 i = 0; i < actions.length; i++) { + assertEq(proposalActions[i].to, actions[i].to, "Action to should match"); + assertEq(proposalActions[i].value, actions[i].value, "Action value should match"); + assertEq(proposalActions[i].data, actions[i].data, "Action data should match"); + } } function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canApprove should return false (when listed on creation, self appointed now) - // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) - // It approve should revert (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) - // It approve should revert (when currently appointed by a signer listed on creation) // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, appointing someone else now) + // It approve should revert (when currently appointed by a signer listed on creation) // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + + // When listed on creation, self appointed now + assertEq(multisig.canApprove(0, alice), false, "Alice should not be able to approve"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, alice)); + multisig.approve(0, false); + + // When listed on creation, appointing someone else now + assertEq(multisig.canApprove(0, bob), false, "Bob should not be able to approve"); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, bob)); + multisig.approve(0, false); + + // When currently appointed by a signer listed on creation + assertEq(multisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve"); + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); + multisig.approve(0, false); + + // When unlisted on creation, unappointed now + assertEq(multisig.canApprove(0, address(0x1234)), false, "Unlisted address should not be able to approve"); + vm.startPrank(address(0x1234)); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, address(0x1234))); + multisig.approve(0, false); } function test_WhenCallingHasApprovedBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(multisig.hasApproved(0, alice), true, "Alice should show as approved"); + assertEq(multisig.hasApproved(0, bob), true, "Bob should show as approved"); + assertEq(multisig.hasApproved(0, carol), true, "Carol should show as approved"); + assertEq(multisig.hasApproved(0, david), false, "David should not show as approved"); + assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not show as approved"); } function test_WhenCallingCanExecuteOrExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canExecute should return false (when listed on creation, self appointed now) - // It execute should revert (when listed on creation, self appointed now) // It canExecute should return false (when listed on creation, appointing someone else now) - // It execute should revert (when listed on creation, appointing someone else now) // It canExecute should return false (when currently appointed by a signer listed on creation) - // It execute should revert (when currently appointed by a signer listed on creation) // It canExecute should return false (when unlisted on creation, unappointed now) + // It execute should revert (when listed on creation, self appointed now) + // It execute should revert (when listed on creation, appointing someone else now) + // It execute should revert (when currently appointed by a signer listed on creation) // It execute should revert (when unlisted on creation, unappointed now) - vm.skip(true); + + // When listed on creation, self appointed now + // vm.startPrank(alice); + assertEq(multisig.canExecute(0), false, "Should not be executable after execution"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // When listed on creation, appointing someone else now + vm.startPrank(bob); + assertEq(multisig.canExecute(0), false, "Should not be executable after execution"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // When currently appointed by a signer listed on creation + vm.startPrank(randomWallet); + assertEq(multisig.canExecute(0), false, "Should not be executable after execution"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // When unlisted on creation, unappointed now + vm.startPrank(address(0x1234)); + assertEq(multisig.canExecute(0), false, "Should not be executable after execution"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); } modifier givenTheProposalExpired() { @@ -1737,7 +2167,7 @@ contract MultisigTest is AragonTest { vm.startPrank(randomWallet); multisig.approve(pid, false); - vm.roll(block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD); + vm.warp(block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD); vm.startPrank(alice); @@ -1746,24 +2176,80 @@ contract MultisigTest is AragonTest { function test_WhenCallingGetProposalBeingExpired() external givenTheProposalExpired { // It should return the right values - vm.skip(true); + + ( + bool executed, + uint16 approvals, + Multisig.ProposalParameters memory parameters, + bytes memory metadataURI, + IDAO.Action[] memory proposalActions, + OptimisticTokenVotingPlugin destinationPlugin + ) = multisig.getProposal(0); + + assertEq(executed, false, "Should not be executed"); + assertEq(approvals, 2, "Should have 2 approvals"); + assertEq(parameters.minApprovals, 3, "Should require 3 approvals"); + assertEq(parameters.snapshotBlock, block.number - 1 - 50, "Incorrect snapshot block"); + assertEq(parameters.expirationDate, block.timestamp, "Should be expired"); + assertEq(metadataURI, "ipfs://", "Incorrect metadata URI"); + assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destination plugin"); + + // Verify actions + assertEq(proposalActions.length, 1, "Should have 1 action"); + assertEq(proposalActions[0].to, address(bob), "Incorrect action target"); + assertEq(proposalActions[0].value, 0, "Incorrect action value"); + assertEq(proposalActions[0].data, "", "Incorrect action data"); } function test_WhenCallingCanApproveAndApproveBeingExpired() external givenTheProposalExpired { // It canApprove should return false (when listed on creation, self appointed now) - // It approve should revert (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) - // It approve should revert (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) - // It approve should revert (when currently appointed by a signer listed on creation) // It canApprove should return false (when unlisted on creation, unappointed now) + // It approve should revert (when listed on creation, self appointed now) + // It approve should revert (when listed on creation, appointing someone else now) + // It approve should revert (when currently appointed by a signer listed on creation) // It approve should revert (when unlisted on creation, unappointed now) - vm.skip(true); + + // When listed on creation, self appointed now + assertEq(multisig.canApprove(0, alice), false, "Alice should not be able to approve expired proposal"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, alice)); + multisig.approve(0, false); + + // When listed on creation, appointing someone else now + assertEq(multisig.canApprove(0, bob), false, "Bob should not be able to approve expired proposal"); + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, bob)); + multisig.approve(0, false); + + // When currently appointed by a signer listed on creation + assertEq( + multisig.canApprove(0, randomWallet), false, "Random wallet should not be able to approve expired proposal" + ); + vm.startPrank(randomWallet); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); + multisig.approve(0, false); + + // When unlisted on creation, unappointed now + address unlistedAddress = address(0x1234); + assertEq( + multisig.canApprove(0, unlistedAddress), + false, + "Unlisted address should not be able to approve expired proposal" + ); + vm.startPrank(unlistedAddress); + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, unlistedAddress)); + multisig.approve(0, false); } function test_WhenCallingHasApprovedBeingExpired() external givenTheProposalExpired { // It hasApproved should return false until approved - vm.skip(true); + + assertEq(multisig.hasApproved(0, alice), true, "Alice should show as approved"); + assertEq(multisig.hasApproved(0, bob), true, "Bob should show as approved"); + assertEq(multisig.hasApproved(0, carol), false, "Carol should not show as approved"); + assertEq(multisig.hasApproved(0, david), false, "David should not show as approved"); + assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not show as approved"); } function test_WhenCallingCanExecuteOrExecuteBeingExpired() external givenTheProposalExpired { @@ -1775,6 +2261,28 @@ contract MultisigTest is AragonTest { // It execute should revert (when currently appointed by a signer listed on creation) // It canExecute should return false (when unlisted on creation, unappointed now) // It execute should revert (when unlisted on creation, unappointed now) - vm.skip(true); + + // When listed on creation, self appointed now + assertEq(multisig.canExecute(0), false, "Should not be executable when expired"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // When listed on creation, appointing someone else now + vm.startPrank(bob); + assertEq(multisig.canExecute(0), false, "Should not be executable when expired"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // When currently appointed by a signer listed on creation + vm.startPrank(randomWallet); + assertEq(multisig.canExecute(0), false, "Should not be executable when expired"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); + + // When unlisted on creation, unappointed now + vm.startPrank(address(0x1234)); + assertEq(multisig.canExecute(0), false, "Should not be executable when expired"); + vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); + multisig.execute(0); } } From 9a70bc7afee7f9580b0e574457965fe1ea4726c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 14 Nov 2024 10:51:26 +0700 Subject: [PATCH 38/45] Tests running clean --- test/EmergencyMultisigTree.t.sol | 12 +++++++++++- test/MultisigTree.t.sol | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 3154d47..84ea693 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1537,6 +1537,9 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(randomWallet); eMultisig.approve(0); assertEq(eMultisig.hasApproved(0, bob), true, "Should be true after approval by appointed wallet"); + assertEq(eMultisig.hasApproved(0, randomWallet), true, "Should be true after approval"); + + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); } function test_WhenCallingCanExecuteOrExecuteBeingOpen() external givenTheProposalIsOpen { @@ -1664,6 +1667,9 @@ contract EmergencyMultisigTest is AragonTest { vm.startPrank(randomWallet); eMultisig.approve(0); assertEq(eMultisig.hasApproved(0, bob), true, "Should be true for bob after appointed wallet approves"); + assertEq(eMultisig.hasApproved(0, randomWallet), true, "Should be true after approval"); + + assertEq(eMultisig.hasApproved(0, address(0x5555)), false, "5555 should not have approved"); } function test_WhenCallingCanExecuteOrExecuteBeingApproved() external givenTheProposalWasApprovedByTheAddress { @@ -2188,7 +2194,11 @@ contract EmergencyMultisigTest is AragonTest { assertEq(publicMetadataUriHash, keccak256("ipfs://the-metadata"), "Incorrect publicMetadataUriHash"); // Assert the destination actions hash - assertEq(destinationActionsHash, hex"3626b3f254463d63d9bd5ff77ff99d2691b20f0db6347f685befae593d8f4e6f", "Incorrect destinationActionsHash"); + assertEq( + destinationActionsHash, + hex"3626b3f254463d63d9bd5ff77ff99d2691b20f0db6347f685befae593d8f4e6f", + "Incorrect destinationActionsHash" + ); // Assert the destination plugin assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 0e29300..042bf37 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -787,7 +787,7 @@ contract MultisigTest is AragonTest { creator: alice, metadata: "ipfs://", startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + DESTINATION_PROPOSAL_DURATION, + endDate: uint64(block.timestamp) + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, actions: inputActions, allowFailureMap: 0 }); @@ -824,7 +824,7 @@ contract MultisigTest is AragonTest { creator: bob, metadata: "ipfs://more", startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + DESTINATION_PROPOSAL_DURATION, + endDate: uint64(block.timestamp) + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, actions: inputActions, allowFailureMap: 0 }); @@ -862,7 +862,7 @@ contract MultisigTest is AragonTest { creator: carol, metadata: "ipfs://1234", startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + DESTINATION_PROPOSAL_DURATION, + endDate: uint64(block.timestamp) + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, actions: inputActions, allowFailureMap: 0 }); @@ -1392,8 +1392,9 @@ contract MultisigTest is AragonTest { assertEq(multisig.canApprove(0, randomWallet), true, "RandomWallet should be able to approve"); vm.startPrank(randomWallet); vm.expectEmit(); - emit Approved(0, randomWallet); + emit Approved(0, bob); multisig.approve(0, false); + assertEq(multisig.hasApproved(0, bob), true, "RandomWallet's approval should be recorded"); assertEq(multisig.hasApproved(0, randomWallet), true, "RandomWallet's approval should be recorded"); // It canApprove should return false (when unlisted on creation, unappointed now) @@ -1528,7 +1529,9 @@ contract MultisigTest is AragonTest { vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, 0)); multisig.execute(0); - // More approvals + // Approvals + vm.startPrank(alice); + multisig.approve(0, false); vm.startPrank(randomWallet); multisig.approve(0, false); @@ -1637,6 +1640,7 @@ contract MultisigTest is AragonTest { // It approve should revert (when unlisted on creation, unappointed now) // When listed on creation, self appointed now + vm.startPrank(alice); assertEq(multisig.canApprove(0, alice), false, "Alice should not be able to approve"); vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, alice)); multisig.approve(0, false); @@ -1846,9 +1850,9 @@ contract MultisigTest is AragonTest { assertEq(multisig.hasApproved(0, alice), true, "Alice should show as approved"); assertEq(multisig.hasApproved(0, bob), true, "Bob should show as approved"); + assertEq(multisig.hasApproved(0, randomWallet), true, "Random wallet should show as approved"); assertEq(multisig.hasApproved(0, carol), true, "Carol should show as approved"); assertEq(multisig.hasApproved(0, david), false, "David should not show as approved"); - assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not show as approved"); } function test_WhenCallingCanExecuteOrExecuteBeingPassed() external givenTheProposalPassed { @@ -2091,9 +2095,9 @@ contract MultisigTest is AragonTest { assertEq(multisig.hasApproved(0, alice), true, "Alice should show as approved"); assertEq(multisig.hasApproved(0, bob), true, "Bob should show as approved"); + assertEq(multisig.hasApproved(0, randomWallet), true, "Random wallet should show as approved"); assertEq(multisig.hasApproved(0, carol), true, "Carol should show as approved"); assertEq(multisig.hasApproved(0, david), false, "David should not show as approved"); - assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not show as approved"); } function test_WhenCallingCanExecuteOrExecuteBeingExecuted() external givenTheProposalIsAlreadyExecuted { @@ -2247,9 +2251,9 @@ contract MultisigTest is AragonTest { assertEq(multisig.hasApproved(0, alice), true, "Alice should show as approved"); assertEq(multisig.hasApproved(0, bob), true, "Bob should show as approved"); + assertEq(multisig.hasApproved(0, randomWallet), true, "Random wallet should show as approved"); assertEq(multisig.hasApproved(0, carol), false, "Carol should not show as approved"); assertEq(multisig.hasApproved(0, david), false, "David should not show as approved"); - assertEq(multisig.hasApproved(0, randomWallet), false, "Random wallet should not show as approved"); } function test_WhenCallingCanExecuteOrExecuteBeingExpired() external givenTheProposalExpired { From f12b40ee2e008353b6467d945212321514764123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 14 Nov 2024 11:54:22 +0700 Subject: [PATCH 39/45] Tests running clean --- test/EmergencyMultisigTree.t.sol | 12 +- test/EncryptionRegistry.t.sol | 2 +- test/Multisig.t.sol | 2126 ------------------------------ test/MultisigTree.t.sol | 77 +- test/SignerListTree.t.sol | 65 +- test/SignerListTree.t.yaml | 4 +- 6 files changed, 92 insertions(+), 2194 deletions(-) delete mode 100644 test/Multisig.t.sol diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 84ea693..7a3d256 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1172,7 +1172,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(address(destinationPlugin), address(0), "Incorrect destinationPlugin"); } - function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanApproveOrApproveBeingUncreated() external givenTheProposalIsNotCreated { uint256 randomProposalId = 1234; bool canApprove; @@ -1416,7 +1416,7 @@ contract EmergencyMultisigTest is AragonTest { } } - function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanApproveOrApproveBeingOpen() external givenTheProposalIsOpen { // It canApprove should return true (when listed on creation, self appointed now) bool canApprove = eMultisig.canApprove(0, alice); assertEq(canApprove, true, "Alice should be able to approve"); @@ -1639,7 +1639,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(address(destinationPlugin), address(optimisticPlugin)); } - function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanApproveOrApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { // It canApprove should return false (when listed on creation, self appointed now) assertEq(eMultisig.canApprove(0, alice), false, "Alice should not be able to approve again"); @@ -1792,7 +1792,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); } - function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { + function test_WhenCallingCanApproveOrApproveBeingPassed() external givenTheProposalPassed { // It canApprove should return false (when listed on creation, self appointed now) // vm.startPrank(alice); assertEq(eMultisig.canApprove(0, alice), false, "Alice should not be able to approve"); @@ -2031,7 +2031,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); } - function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanApproveOrApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canApprove should return false (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) @@ -2204,7 +2204,7 @@ contract EmergencyMultisigTest is AragonTest { assertEq(address(destinationPlugin), address(optimisticPlugin), "Incorrect destinationPlugin"); } - function test_WhenCallingCanApproveAndApproveBeingExpired() external givenTheProposalExpired { + function test_WhenCallingCanApproveOrApproveBeingExpired() external givenTheProposalExpired { // It canApprove should return false (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index 74646bb..252060f 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -439,7 +439,7 @@ contract EncryptionRegistryTest is AragonTest { } function test_ShouldRevertOnSetPublicKeyIfNotAppointed(address appointedWallet) public { - if (Address.isContract(appointedWallet)) return; + if (Address.isContract(appointedWallet) || Address.isContract(address(uint160(appointedWallet) + 1))) return; address addrValue; bytes32 bytesValue; diff --git a/test/Multisig.t.sol b/test/Multisig.t.sol deleted file mode 100644 index 6261a07..0000000 --- a/test/Multisig.t.sol +++ /dev/null @@ -1,2126 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - -import {AragonTest} from "./base/AragonTest.sol"; -import {DaoBuilder} from "./helpers/DaoBuilder.sol"; -import {StandardProposalCondition} from "../src/conditions/StandardProposalCondition.sol"; -import {OptimisticTokenVotingPlugin} from "../src/OptimisticTokenVotingPlugin.sol"; -import {Multisig} from "../src/Multisig.sol"; -import {IMultisig} from "../src/interfaces/IMultisig.sol"; -import {SignerList} from "../src/SignerList.sol"; -import {DAO} from "@aragon/osx/core/dao/DAO.sol"; -import {IDAO} from "@aragon/osx/core/dao/IDAO.sol"; -import {PermissionManager} from "@aragon/osx/core/permission/PermissionManager.sol"; -import {IProposal} from "@aragon/osx/core/plugin/proposal/IProposal.sol"; -import {IPlugin} from "@aragon/osx/core/plugin/IPlugin.sol"; -import {Addresslist} from "@aragon/osx/plugins/utils/Addresslist.sol"; -import {DaoUnauthorized} from "@aragon/osx/core/utils/auth.sol"; -import {IERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; -import {createProxyAndCall} from "../src/helpers/proxy.sol"; - -uint64 constant MULTISIG_PROPOSAL_EXPIRATION_PERIOD = 10 days; - -contract MultisigTestOld is AragonTest { - DaoBuilder builder; - - DAO dao; - Multisig multisig; - OptimisticTokenVotingPlugin optimisticPlugin; - SignerList signerList; - - // Events/errors to be tested here (duplicate) - event MultisigSettingsUpdated( - bool onlyListed, - uint16 indexed minApprovals, - uint64 destinationProposalDuration, - SignerList signerList, - uint64 proposalExpirationPeriod - ); - event MembersAdded(address[] members); - event MembersRemoved(address[] members); - - error InvalidAddresslistUpdate(address member); - - event ProposalCreated( - uint256 indexed proposalId, - address indexed creator, - uint64 startDate, - uint64 endDate, - bytes metadata, - IDAO.Action[] actions, - uint256 allowFailureMap - ); - event Approved(uint256 indexed proposalId, address indexed approver); - event Executed(uint256 indexed proposalId); - event Upgraded(address indexed implementation); - - function setUp() public { - vm.startPrank(alice); - vm.warp(1 days); - vm.roll(100); - - builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,, signerList,,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(3).build(); - } - - function test_RevertsIfTryingToReinitialize() public { - // Deploy a new multisig instance - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 4 days, - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - // Reinitialize should fail - vm.expectRevert("Initializable: contract is already initialized"); - multisig.initialize(dao, settings); - } - - function test_InitializeSetsMinApprovals() public { - // 2 - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 2, - destinationProposalDuration: 4 days, - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (, uint16 minApprovals,,,,,) = multisig.multisigSettings(); - assertEq(minApprovals, uint16(2), "Incorrect minApprovals"); - - // Redeploy with 1 - settings.minApprovals = 1; - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (, minApprovals,,,,,) = multisig.multisigSettings(); - assertEq(minApprovals, uint16(1), "Incorrect minApprovals"); - } - - function test_InitializeSetsOnlyListed() public { - // Deploy with true - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 4 days, - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (bool onlyListed,,,,,) = multisig.multisigSettings(); - assertEq(onlyListed, true, "Incorrect onlyListed"); - - // Redeploy with false - settings.onlyListed = false; - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (onlyListed,,,,,) = multisig.multisigSettings(); - assertEq(onlyListed, false, "Incorrect onlyListed"); - } - - function test_InitializeSetsDestinationProposalDuration() public { - // Deploy with 5 days - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 5 days, - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (,, uint64 minDuration,,,) = multisig.multisigSettings(); - assertEq(minDuration, 5 days, "Incorrect minDuration"); - - // Redeploy with 3 days - settings.destinationProposalDuration = 3 days; - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (,, minDuration,,,) = multisig.multisigSettings(); - assertEq(minDuration, 3 days, "Incorrect minDuration"); - } - - function test_InitializeSetsProposalExpiration() public { - // Deploy with 15 days - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 5 days, - proposalExpirationPeriod: 15 days - }); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (,,, uint64 expirationPeriod,) = multisig.multisigSettings(); - assertEq(expirationPeriod, 15 days, "Incorrect expirationPeriod"); - - // Redeploy with 3 days - settings.proposalExpirationPeriod = 3 days; - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - (,,, expirationPeriod,) = multisig.multisigSettings(); - assertEq(expirationPeriod, 3 days, "Incorrect expirationPeriod"); - } - - function test_InitializeEmitsMultisigSettingsUpdatedOnInstall1() public { - // Deploy with true/3/2 - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 3, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: 6 days - }); - vm.expectEmit(); - emit MultisigSettingsUpdated(true, uint16(3), 4 days, 6 days); - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - } - - function test_InitializeEmitsMultisigSettingsUpdatedOnInstall2() public { - // Deploy with false/2/7 - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 2, - destinationProposalDuration: 7 days, - proposalExpirationPeriod: 8 days - }); - vm.expectEmit(); - emit MultisigSettingsUpdated(false, uint16(2), 7 days, 8 days); - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - } - - // INTERFACES - - function test_DoesntSupportTheEmptyInterface() public view { - bool supported = multisig.supportsInterface(0); - assertEq(supported, false, "Should not support the empty interface"); - } - - function test_SupportsIERC165Upgradeable() public view { - bool supported = multisig.supportsInterface(type(IERC165Upgradeable).interfaceId); - assertEq(supported, true, "Should support IERC165Upgradeable"); - } - - function test_SupportsIPlugin() public view { - bool supported = multisig.supportsInterface(type(IPlugin).interfaceId); - assertEq(supported, true, "Should support IPlugin"); - } - - function test_SupportsIProposal() public view { - bool supported = multisig.supportsInterface(type(IProposal).interfaceId); - assertEq(supported, true, "Should support IProposal"); - } - - function test_SupportsIMultisig() public view { - bool supported = multisig.supportsInterface(type(IMultisig).interfaceId); - assertEq(supported, true, "Should support IMultisig"); - } - - // UPDATE MULTISIG SETTINGS - - function test_ShouldNotAllowMinApprovalsGreaterThanSignerListLength() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 5, - destinationProposalDuration: 4 days, // Greater than 4 members - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - // Retry with onlyListed false - settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 6, - destinationProposalDuration: 4 days, // Greater than 4 members - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 6)); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - // OK - settings.minApprovals = 4; - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - } - - function test_ShouldNotAllowMinApprovalsZero() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 0, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); - - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - // Retry with onlyListed false - settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 0, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 1, 0)); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - } - - function test_EmitsMultisigSettingsUpdated() public { - dao.grant(address(multisig), address(alice), multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - // 1 - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: 2 days - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(true, 1, 4 days, 2 days); - multisig.updateMultisigSettings(settings); - - // 2 - settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 2, - destinationProposalDuration: 5 days, - proposalExpirationPeriod: 9 days - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(true, 2, 5 days, 9 days); - multisig.updateMultisigSettings(settings); - - // 3 - settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 3, - destinationProposalDuration: 0, - proposalExpirationPeriod: 7 days - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(false, 3, 0, 7 days); - multisig.updateMultisigSettings(settings); - - // 4 - settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 4, - destinationProposalDuration: 1 days, - proposalExpirationPeriod: 0 - }); - - vm.expectEmit(); - emit MultisigSettingsUpdated(false, 4, 1 days, 0); - multisig.updateMultisigSettings(settings); - } - - function test_onlyWalletWithPermissionsCanUpdateSettings() public { - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 3 days, - proposalExpirationPeriod: 5 days - }); - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.updateMultisigSettings(settings); - - // Nothing changed - (bool onlyListed, uint16 minApprovals, uint64 destinationProposalDuration, uint64 expiration,) = - multisig.multisigSettings(); - assertEq(onlyListed, true); - assertEq(minApprovals, 3); - assertEq(destinationProposalDuration, 10 days); - assertEq(expiration, 10 days); - - // Retry with the permission - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - vm.expectEmit(); - emit MultisigSettingsUpdated(true, 1, 3 days, 5 days); - multisig.updateMultisigSettings(settings); - } - - function test_MinApprovalsBiggerThanTheListReverts() public { - // MinApprovals should be within the boundaries of the list - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 5, - destinationProposalDuration: 4 days, // More than 4 - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 4, 5)); - multisig.updateMultisigSettings(settings); - - // More signers - - address[] memory signers = new address[](1); - signers[0] = randomWallet; - multisig.addAddresses(signers); - - // should not fail now - multisig.updateMultisigSettings(settings); - - // More than that, should fail again - settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 6, - destinationProposalDuration: 4 days, // More than 5 - signerList: signerList, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - vm.expectRevert(abi.encodeWithSelector(Multisig.MinApprovalsOutOfBounds.selector, 5, 6)); - multisig.updateMultisigSettings(settings); - - // OK - settings.minApprovals = 5; - multisig.updateMultisigSettings(settings); - } - - function testFuzz_PermissionedUpdateSettings(address randomAccount) public { - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - (bool onlyListed, uint16 minApprovals, uint64 destMinDuration, uint64 expiration,) = multisig.multisigSettings(); - assertEq(minApprovals, 3, "Should be 3"); - assertEq(onlyListed, true, "Should be true"); - assertEq(destMinDuration, 10 days, "Incorrect destMinDuration A"); - assertEq(expiration, 10 days, "Incorrect expiration A"); - - // in - Multisig.MultisigSettings memory newSettings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 2, - destinationProposalDuration: 5 days, - proposalExpirationPeriod: 6 days - }); - multisig.updateMultisigSettings(newSettings); - - (onlyListed, minApprovals, destMinDuration, expiration) = multisig.multisigSettings(); - assertEq(minApprovals, 2, "Should be 2"); - assertEq(onlyListed, false, "Should be false"); - assertEq(destMinDuration, 5 days, "Incorrect destMinDuration B"); - assertEq(expiration, 6 days, "Incorrect expiration B"); - - // out - newSettings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 6 days, - proposalExpirationPeriod: 9 days - }); - multisig.updateMultisigSettings(newSettings); - (onlyListed, minApprovals, destMinDuration, expiration) = multisig.multisigSettings(); - assertEq(minApprovals, 1, "Should be 1"); - assertEq(onlyListed, true, "Should be true"); - assertEq(destMinDuration, 6 days, "Incorrect destMinDuration C"); - assertEq(expiration, 9 days, "Incorrect expiration C"); - - vm.roll(block.number + 1); - - // someone else - if (randomAccount != alice) { - vm.startPrank(randomAccount); - - newSettings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 4, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: 1 days - }); - - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - randomAccount, - multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID() - ) - ); - multisig.updateMultisigSettings(newSettings); - - (onlyListed, minApprovals, destMinDuration, expiration) = multisig.multisigSettings(); - assertEq(minApprovals, 1, "Should still be 1"); - assertEq(onlyListed, true, "Should still be true"); - assertEq(destMinDuration, 6 days, "Should still be 6 days"); - assertEq(expiration, 9 days, "Should still be 9 days"); - } - - vm.startPrank(alice); - } - - // PROPOSAL CREATION - - function test_IncrementsTheProposalCounter() public { - // increments the proposal counter - - assertEq(multisig.proposalCount(), 0, "Should have no proposals"); - - // 1 - IDAO.Action[] memory actions = new IDAO.Action[](0); - multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(multisig.proposalCount(), 1, "Should have 1 proposal"); - - // 2 - multisig.createProposal("ipfs://", actions, optimisticPlugin, true); - - assertEq(multisig.proposalCount(), 2, "Should have 2 proposals"); - } - - function test_CreatesAndReturnsUniqueProposalIds() public { - // creates unique proposal IDs for each proposal - - // 1 - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(pid, 0, "Should be 0"); - - // 2 - pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, true); - - assertEq(pid, 1, "Should be 1"); - - // 3 - pid = multisig.createProposal("ipfs://more", actions, optimisticPlugin, true); - - assertEq(pid, 2, "Should be 2"); - } - - function test_EmitsProposalCreated() public { - // emits the `ProposalCreated` event - - IDAO.Action[] memory actions = new IDAO.Action[](0); - vm.expectEmit(); - emit ProposalCreated({ - proposalId: 0, - creator: alice, - metadata: "", - startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + 10 days, - actions: actions, - allowFailureMap: 0 - }); - multisig.createProposal("", actions, optimisticPlugin, true); - - // 2 - vm.startPrank(bob); - - actions = new IDAO.Action[](1); - actions[0].to = carol; - actions[0].value = 1 ether; - address[] memory addrs = new address[](1); - actions[0].data = abi.encodeCall(Multisig.addAddresses, (addrs)); - - vm.expectEmit(); - emit ProposalCreated({ - proposalId: 1, - creator: bob, - metadata: "ipfs://", - startDate: uint64(block.timestamp), - endDate: uint64(block.timestamp) + 10 days, - actions: actions, - allowFailureMap: 0 - }); - multisig.createProposal("ipfs://", actions, optimisticPlugin, false); - } - - function test_RevertsIfSettingsChangedInSameBlock() public { - // reverts if the multisig settings have changed in the same block - - // Deploy a new multisig instance - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 3, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig = - Multisig(createProxyAndCall(address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, settings)))); - - // 1 - IDAO.Action[] memory actions = new IDAO.Action[](0); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, alice)); - multisig.createProposal("", actions, optimisticPlugin, false); - - // Next block - vm.roll(block.number + 1); - multisig.createProposal("", actions, optimisticPlugin, false); - } - - function test_CreatesWhenUnlistedAccountsAllowed() public { - // creates a proposal when unlisted accounts are allowed - - builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,,) = builder.withMultisigMember(alice).withoutOnlyListed().build(); - - vm.startPrank(randomWallet); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - multisig.createProposal("", actions, optimisticPlugin, false); - } - - function test_RevertsWhenOnlyListedAndTheWalletIsNotListed() public { - // reverts if the user is not on the list and only listed accounts can create proposals - - vm.startPrank(randomWallet); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, randomWallet)); - multisig.createProposal("", actions, optimisticPlugin, false); - } - - function test_RevertsWhenCreatorWasListedBeforeButNotNow() public { - // reverts if `msg.sender` is not listed although she was listed in the last block - - dao.grant(address(signerList), alice, signerList.UPDATE_SIGNER_LIST_PERMISSION_ID()); - - // Remove - address[] memory addrs = new address[](1); - addrs[0] = alice; - signerList.removeSigners(addrs); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, alice)); - multisig.createProposal("", 0, 0, optimisticPlugin, false); - - signerList.addSigners(addrs); // Add Alice back - vm.roll(block.number + 1); - multisig.createProposal("", 0, 0, optimisticPlugin, false); - - // Add+remove - addrs[0] = bob; - signerList.removeSigners(addrs); - - vm.startPrank(bob); - - // Bob cannot create now - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalCreationForbidden.selector, bob)); - multisig.createProposal("", 0, 0, optimisticPlugin, false); - - vm.startPrank(alice); - - // Bob can create now - signerList.addSigners(addrs); // Add Bob back - - vm.startPrank(alice); - - multisig.createProposal("", 0, 0, optimisticPlugin, false); - } - - function test_CreatesProposalWithoutApprovingIfUnspecified() public { - // creates a proposal successfully and does not approve if not specified - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal( - "", - actions, - optimisticPlugin, - false // approveProposal - ); - - assertEq(multisig.hasApproved(pid, alice), false, "Should not have approved"); - (, uint16 approvals,,,,) = multisig.getProposal(pid); - assertEq(approvals, 0, "Should be 0"); - - multisig.approve(pid, false); - - assertEq(multisig.hasApproved(pid, alice), true, "Should have approved"); - (, approvals,,,,) = multisig.getProposal(pid); - assertEq(approvals, 1, "Should be 1"); - } - - function test_CreatesAndApprovesWhenSpecified() public { - // creates a proposal successfully and approves if specified - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal( - "", - actions, - optimisticPlugin, - true // approveProposal - ); - assertEq(multisig.hasApproved(pid, alice), true, "Should have approved"); - (, uint16 approvals,,,,) = multisig.getProposal(pid); - assertEq(approvals, 1, "Should be 1"); - } - - // CAN APPROVE - - function testFuzz_CanApproveReturnsfFalseIfNotCreated(uint256 randomProposalId) public view { - // returns `false` if the proposal doesn't exist - - assertEq(multisig.canApprove(randomProposalId, alice), false, "Should be false"); - assertEq(multisig.canApprove(randomProposalId, bob), false, "Should be false"); - assertEq(multisig.canApprove(randomProposalId, carol), false, "Should be false"); - assertEq(multisig.canApprove(randomProposalId, david), false, "Should be false"); - } - - function testFuzz_CanApproveReturnsfFalseIfNotListed(address randomWallet) public { - // returns `false` if the approver is not listed - - { - // Deploy a new multisig instance (more efficient than the builder for fuzz testing) - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: true, - minApprovals: 1, - destinationProposalDuration: 4 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - address[] memory signers = new address[](1); - signers[0] = alice; - - multisig = Multisig( - createProxyAndCall( - address(MULTISIG_BASE), abi.encodeCall(Multisig.initialize, (dao, signers, settings)) - ) - ); - vm.roll(block.number + 1); - } - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // ko - if (randomWallet != alice) { - assertEq(multisig.canApprove(pid, randomWallet), false, "Should be false"); - } - - // static ok - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - } - - function test_CanApproveReturnsFalseIfApproved() public { - // returns `false` if the approver has already approved - - builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,,) = builder.withMultisigMember(alice).withMultisigMember(bob) - .withMultisigMember(carol).withMultisigMember(david).withMinApprovals(4).build(); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - multisig.approve(pid, false); - assertEq(multisig.canApprove(pid, alice), false, "Should be false"); - - // Bob - assertEq(multisig.canApprove(pid, bob), true, "Should be true"); - vm.startPrank(bob); - multisig.approve(pid, false); - assertEq(multisig.canApprove(pid, bob), false, "Should be false"); - - // Carol - assertEq(multisig.canApprove(pid, carol), true, "Should be true"); - vm.startPrank(carol); - multisig.approve(pid, false); - assertEq(multisig.canApprove(pid, carol), false, "Should be false"); - - // David - assertEq(multisig.canApprove(pid, david), true, "Should be true"); - vm.startPrank(david); - multisig.approve(pid, false); - assertEq(multisig.canApprove(pid, david), false, "Should be false"); - } - - function test_CanApproveReturnsFalseIfExpired() public { - // returns `false` if the proposal has ended - - uint64 startDate = 10; - vm.warp(startDate); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - (,, Multisig.ProposalParameters memory parameters,,,) = multisig.getProposal(pid); - assertEq(parameters.expirationDate, startDate + MULTISIG_PROPOSAL_EXPIRATION_PERIOD, "Incorrect expiration"); - - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(startDate + MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); // multisig expiration time - 1 - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(startDate + MULTISIG_PROPOSAL_EXPIRATION_PERIOD); // multisig expiration time - assertEq(multisig.canApprove(pid, alice), false, "Should be false"); - - // Start later - startDate = 5 days; - vm.warp(startDate); - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + MULTISIG_PROPOSAL_EXPIRATION_PERIOD - 1); // expiration time - 1 - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(block.timestamp + 1); // expiration time - assertEq(multisig.canApprove(pid, alice), false, "Should be false"); - } - - function test_CanApproveReturnsFalseIfExecuted() public { - // returns `false` if the proposal is already executed - - dao.grant(address(optimisticPlugin), address(multisig), optimisticPlugin.PROPOSER_PERMISSION_ID()); - - bool executed; - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, true); // auto execute - - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - // David cannot approve - assertEq(multisig.canApprove(pid, david), false, "Should be false"); - - vm.startPrank(alice); - } - - function test_CanApproveReturnsTrueIfListed() public { - // returns `true` if the approver is listed - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - assertEq(multisig.canApprove(pid, bob), true, "Should be true"); - assertEq(multisig.canApprove(pid, carol), true, "Should be true"); - assertEq(multisig.canApprove(pid, david), true, "Should be true"); - - // new instance - builder = new DaoBuilder(); - (dao, optimisticPlugin, multisig,,,) = builder.withMultisigMember(randomWallet).withoutOnlyListed().build(); - - // now ko - actions = new IDAO.Action[](0); - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(multisig.canApprove(pid, alice), false, "Should be false"); - assertEq(multisig.canApprove(pid, bob), false, "Should be false"); - assertEq(multisig.canApprove(pid, carol), false, "Should be false"); - assertEq(multisig.canApprove(pid, david), false, "Should be false"); - - // ok - assertEq(multisig.canApprove(pid, randomWallet), true, "Should be true"); - } - - // HAS APPROVED - - function test_HasApprovedReturnsFalseWhenNotApproved() public { - // returns `false` if user hasn't approved yet - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - assertEq(multisig.hasApproved(pid, alice), false, "Should be false"); - assertEq(multisig.hasApproved(pid, bob), false, "Should be false"); - assertEq(multisig.hasApproved(pid, carol), false, "Should be false"); - assertEq(multisig.hasApproved(pid, david), false, "Should be false"); - } - - function test_HasApprovedReturnsTrueWhenUserApproved() public { - // returns `true` if user has approved - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - assertEq(multisig.hasApproved(pid, alice), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, alice), true, "Should be true"); - - // Bob - vm.startPrank(bob); - assertEq(multisig.hasApproved(pid, bob), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, bob), true, "Should be true"); - - // Carol - vm.startPrank(carol); - assertEq(multisig.hasApproved(pid, carol), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, carol), true, "Should be true"); - - // David - vm.startPrank(david); - assertEq(multisig.hasApproved(pid, david), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, david), true, "Should be true"); - } - - // APPROVE - - function testFuzz_ApproveRevertsIfNotCreated(uint256 randomProposalId) public { - // Reverts if the proposal doesn't exist - - vm.startPrank(alice); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, alice)); - multisig.approve(randomProposalId, false); - - // 2 - vm.startPrank(bob); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, bob)); - multisig.approve(randomProposalId, false); - - // 3 - vm.startPrank(carol); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, carol)); - multisig.approve(randomProposalId, true); - - // 4 - vm.startPrank(david); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, randomProposalId, david)); - multisig.approve(randomProposalId, true); - } - - function testFuzz_ApproveRevertsIfNotListed(address randomSigner) public { - // Reverts if the signer is not listed - - builder = new DaoBuilder(); - (,, multisig,,,) = builder.withMultisigMember(alice).withMinApprovals(1).build(); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - if (randomSigner == alice) { - return; - } - - vm.startPrank(randomSigner); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, randomSigner)); - multisig.approve(pid, false); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, randomSigner)); - multisig.approve(pid, true); - } - - function test_ApproveRevertsIfAlreadyApproved() public { - // reverts when approving multiple times - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, true); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, alice)); - multisig.approve(pid, true); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, true); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, bob)); - multisig.approve(pid, false); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, carol)); - multisig.approve(pid, true); - } - - function test_ApprovesWithTheSenderAddress() public { - // approves with the msg.sender address - // Same as test_HasApprovedReturnsTrueWhenUserApproved() - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - assertEq(multisig.hasApproved(pid, alice), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, alice), true, "Should be true"); - - // Bob - vm.startPrank(bob); - assertEq(multisig.hasApproved(pid, bob), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, bob), true, "Should be true"); - - // Carol - vm.startPrank(carol); - assertEq(multisig.hasApproved(pid, carol), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, carol), true, "Should be true"); - - // David - vm.startPrank(david); - assertEq(multisig.hasApproved(pid, david), false, "Should be false"); - multisig.approve(pid, false); - assertEq(multisig.hasApproved(pid, david), true, "Should be true"); - } - - function test_ApproveRevertsIfExpired() public { - // reverts if the proposal has ended - - uint64 expirationTime = uint64(block.timestamp) + 10 days; - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(expirationTime); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, alice)); - multisig.approve(pid, false); - - vm.warp(expirationTime + 15 days); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, alice)); - multisig.approve(pid, false); - - // 2 - vm.warp(1000); - expirationTime = uint64(block.timestamp) + 10 days; - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - assertEq(multisig.canApprove(pid, alice), true, "Should be true"); - - vm.warp(expirationTime); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, alice)); - multisig.approve(pid, true); - - vm.warp(expirationTime + 500); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, alice)); - multisig.approve(pid, true); - } - - function test_ApproveRevertsIfExecuted() public { - // reverts if the proposal has ended - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - - multisig.execute(pid); - (bool executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, carol)); - multisig.approve(pid, false); - vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, pid, carol)); - multisig.approve(pid, true); - } - - function test_ApprovingProposalsEmits() public { - // Approving a proposal emits the Approved event - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - vm.expectEmit(); - emit Approved(pid, alice); - multisig.approve(pid, false); - - // Bob - vm.startPrank(bob); - vm.expectEmit(); - emit Approved(pid, bob); - multisig.approve(pid, false); - - // Carol - vm.startPrank(carol); - vm.expectEmit(); - emit Approved(pid, carol); - multisig.approve(pid, false); - - // David (even if it already passed) - vm.startPrank(david); - vm.expectEmit(); - emit Approved(pid, david); - multisig.approve(pid, false); - } - - // CAN EXECUTE - - function testFuzz_CanExecuteReturnsFalseIfNotCreated(uint256 randomProposalId) public view { - // returns `false` if the proposal doesn't exist - - assertEq(multisig.canExecute(randomProposalId), false, "Should be false"); - } - - function test_CanExecuteReturnsFalseIfBelowMinApprovals() public { - // returns `false` if the proposal has not reached the minimum approvals yet - (dao, optimisticPlugin, multisig,,,) = builder.withMinApprovals(2).build(); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.startPrank(alice); - - // More approvals required (4) - (dao, optimisticPlugin, multisig,,,) = builder.withMinApprovals(4).build(); - - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // David - vm.startPrank(david); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), true, "Should be true"); - } - - function test_CanExecuteReturnsFalseIfExpired() public { - // returns `false` if the proposal has ended - - // 1 - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 10 days - 1); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 1); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // 2 - vm.warp(50 days); - actions = new IDAO.Action[](0); - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - vm.startPrank(alice); - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 10 days - 1); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 1); - assertEq(multisig.canExecute(pid), false, "Should be false"); - } - - function test_CanExecuteReturnsFalseIfExecuted() public { - // returns `false` if the proposal is already executed - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - - assertEq(multisig.canExecute(pid), true, "Should be true"); - multisig.execute(pid); - - assertEq(multisig.canExecute(pid), false, "Should be false"); - } - - function test_CanExecuteReturnsTrueWhenAllGood() public { - // returns `true` if the proposal can be executed - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // Alice - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), false, "Should be false"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - - assertEq(multisig.canExecute(pid), true, "Should be true"); - } - - // EXECUTE - - function testFuzz_ExecuteRevertsIfNotCreated(uint256 randomProposalId) public { - // reverts if the proposal doesn't exist - - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, randomProposalId)); - multisig.execute(randomProposalId); - } - - function test_ExecuteRevertsIfBelowMinApprovals() public { - // reverts if minApprovals is not met yet - - (dao, optimisticPlugin, multisig,,,) = builder.withMinApprovals(2).build(); - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - multisig.execute(pid); // ok - - vm.startPrank(alice); - - // More approvals required (4) - (dao, optimisticPlugin, multisig,,,) = builder.withMinApprovals(4).build(); - - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - multisig.approve(pid, false); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - - // David - vm.startPrank(david); - multisig.approve(pid, false); - multisig.execute(pid); - } - - function test_ExecuteRevertsIfExpired() public { - // reverts if the proposal has expired - - // 1 - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 10 days); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - - vm.warp(100 days); - - // 2 - pid = multisig.createProposal("", actions, optimisticPlugin, false); - - vm.startPrank(alice); - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - assertEq(multisig.canExecute(pid), true, "Should be true"); - - vm.warp(block.timestamp + 10 days); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - - vm.startPrank(alice); - } - - function test_ExecuteRevertsWhenAlreadyExecuted() public { - // executes if the minimum approval is met when multisig with the `tryExecution` option - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - - assertEq(multisig.canExecute(pid), true, "Should be true"); - multisig.execute(pid); - - vm.expectRevert(abi.encodeWithSelector(Multisig.ProposalExecutionForbidden.selector, pid)); - multisig.execute(pid); - } - - function test_ExecuteEmitsEvents() public { - // emits the `ProposalExecuted` and `ProposalCreated` events - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - - // event - vm.expectEmit(); - emit Executed(pid); - uint256 targetPid = (uint256(block.timestamp) << 128) | (uint256(block.timestamp + 10 days) << 64); - vm.expectEmit(); - emit ProposalCreated( - targetPid, address(multisig), uint64(block.timestamp), uint64(block.timestamp) + 10 days, "", actions, 0 - ); - multisig.execute(pid); - - // 2 - (dao, optimisticPlugin, multisig,,,) = builder.withDuration(50 days).build(); - - vm.warp(20 days); - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - multisig.approve(pid, false); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - - // events - vm.expectEmit(); - emit Executed(pid); - targetPid = ((uint256(block.timestamp) << 128) | (uint256(block.timestamp + 50 days) << 64)); - vm.expectEmit(); - emit ProposalCreated( - targetPid, address(multisig), uint64(block.timestamp), 20 days + 50 days, "ipfs://", actions, 0 - ); - multisig.execute(pid); - } - - function test_ExecutesWhenApprovingWithTryExecutionAndEnoughApprovals() public { - // executes if the minimum approval is met when multisig with the `tryExecution` option - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - (bool executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Alice - multisig.approve(pid, true); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, true); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, true); - - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - } - - function test_ExecuteEmitsWhenAutoExecutedFromApprove() public { - // emits the `Approved`, `ProposalExecuted`, and `ProposalCreated` events if execute is called inside the `approve` method - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, true); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, true); - - // Carol - vm.startPrank(carol); - vm.expectEmit(); - emit Approved(pid, carol); - vm.expectEmit(); - emit Executed(pid); - - uint256 targetPid = ((uint256(block.timestamp) << 128) | (uint256(block.timestamp + 10 days) << 64)); - vm.expectEmit(); - emit ProposalCreated( - targetPid, address(multisig), uint64(block.timestamp), uint64(block.timestamp) + 10 days, "", actions, 0 - ); - multisig.approve(pid, true); - - // 2 - vm.warp(5 days); - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - multisig.approve(pid, true); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, true); - - // Carol - vm.startPrank(carol); - vm.expectEmit(); - emit Approved(pid, carol); - vm.expectEmit(); - emit Executed(pid); - - targetPid = ((uint256(5 days) << 128) | (uint256(5 days + 10 days) << 64)) + 1; - vm.expectEmit(); - emit ProposalCreated( - targetPid, // foreign pid - address(multisig), - uint64(block.timestamp), - uint64(block.timestamp) + 10 days, - "ipfs://", - actions, - 0 - ); - multisig.approve(pid, true); - - // 3 - (dao, optimisticPlugin, multisig,,,) = builder.withDuration(50 days).build(); - - vm.warp(7 days); - actions = new IDAO.Action[](1); - actions[0].value = 5 ether; - actions[0].to = address(carol); - actions[0].data = hex"44556677"; - pid = multisig.createProposal("ipfs://...", actions, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - multisig.approve(pid, true); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, true); - - // Carol - vm.startPrank(carol); - vm.expectEmit(); - emit Approved(pid, carol); - vm.expectEmit(); - emit Executed(pid); - - targetPid = ((uint256(7 days) << 128) | (uint256(7 days + 50 days) << 64)); - vm.expectEmit(); - emit ProposalCreated(targetPid, address(multisig), 7 days, 7 days + 50 days, "ipfs://...", actions, 0); - multisig.approve(pid, true); - } - - function test_ExecutesWithEnoughApprovalsOnTime() public { - // executes if the minimum approval is met - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - (bool executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - multisig.execute(pid); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - // 2 - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); - - // Alice - vm.startPrank(alice); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - multisig.execute(pid); - - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - } - - function test_ExecuteWhenPassedAndCalledByAnyone() public { - // executes if the minimum approval is met and can be called by an unlisted accounts - - IDAO.Action[] memory actions = new IDAO.Action[](0); - uint256 pid = multisig.createProposal("", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - (bool executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.startPrank(randomWallet); - multisig.execute(pid); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - - // 2 - vm.startPrank(alice); - - actions = new IDAO.Action[](1); - actions[0].value = 1 ether; - actions[0].to = address(bob); - actions[0].data = hex"00112233"; - pid = multisig.createProposal("ipfs://", actions, optimisticPlugin, false); - - // Alice - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Bob - vm.startPrank(bob); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - // Carol - vm.startPrank(carol); - multisig.approve(pid, false); - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, false, "Should not be executed"); - - vm.startPrank(randomWallet); - multisig.execute(pid); - - (executed,,,,,) = multisig.getProposal(pid); - assertEq(executed, true, "Should be executed"); - } - - function test_GetProposalReturnsTheRightValues() public { - // Get proposal returns the right values - - bool executed; - uint16 approvals; - Multisig.ProposalParameters memory parameters; - bytes memory metadataURI; - IDAO.Action[] memory actions; - OptimisticTokenVotingPlugin destPlugin; - - vm.warp(5 days); - - IDAO.Action[] memory createActions = new IDAO.Action[](3); - createActions[0].to = alice; - createActions[0].value = 1 ether; - createActions[0].data = hex"001122334455"; - createActions[1].to = bob; - createActions[1].value = 2 ether; - createActions[1].data = hex"112233445566"; - createActions[2].to = carol; - createActions[2].value = 3 ether; - createActions[2].data = hex"223344556677"; - - uint256 pid = multisig.createProposal("ipfs://metadata", createActions, optimisticPlugin, false); - assertEq(pid, 0, "PID should be 0"); - - // Check round 1 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 0, "Should be 0"); - - assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 3, "Should be 3"); - - assertEq(actions[0].to, alice, "Incorrect to"); - assertEq(actions[0].value, 1 ether, "Incorrect value"); - assertEq(actions[0].data, hex"001122334455", "Incorrect data"); - assertEq(actions[1].to, bob, "Incorrect to"); - assertEq(actions[1].value, 2 ether, "Incorrect value"); - assertEq(actions[1].data, hex"112233445566", "Incorrect data"); - assertEq(actions[2].to, carol, "Incorrect to"); - assertEq(actions[2].value, 3 ether, "Incorrect value"); - assertEq(actions[2].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // Approve - multisig.approve(pid, false); - - // Check round 2 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 1, "Should be 1"); - - assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 3, "Should be 3"); - - assertEq(actions[0].to, alice, "Incorrect to"); - assertEq(actions[0].value, 1 ether, "Incorrect value"); - assertEq(actions[0].data, hex"001122334455", "Incorrect data"); - assertEq(actions[1].to, bob, "Incorrect to"); - assertEq(actions[1].value, 2 ether, "Incorrect value"); - assertEq(actions[1].data, hex"112233445566", "Incorrect data"); - assertEq(actions[2].to, carol, "Incorrect to"); - assertEq(actions[2].value, 3 ether, "Incorrect value"); - assertEq(actions[2].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // Approve - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - - // Check round 3 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 3, "Should be 3"); - - assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 3, "Should be 3"); - - assertEq(actions[0].to, alice, "Incorrect to"); - assertEq(actions[0].value, 1 ether, "Incorrect value"); - assertEq(actions[0].data, hex"001122334455", "Incorrect data"); - assertEq(actions[1].to, bob, "Incorrect to"); - assertEq(actions[1].value, 2 ether, "Incorrect value"); - assertEq(actions[1].data, hex"112233445566", "Incorrect data"); - assertEq(actions[2].to, carol, "Incorrect to"); - assertEq(actions[2].value, 3 ether, "Incorrect value"); - assertEq(actions[2].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // Execute - vm.startPrank(alice); - multisig.execute(pid); - - // Check round 4 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, true, "Should be executed"); - assertEq(approvals, 3, "Should be 3"); - - assertEq(parameters.minApprovals, 3, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 3, "Should be 3"); - - assertEq(actions[0].to, alice, "Incorrect to"); - assertEq(actions[0].value, 1 ether, "Incorrect value"); - assertEq(actions[0].data, hex"001122334455", "Incorrect data"); - assertEq(actions[1].to, bob, "Incorrect to"); - assertEq(actions[1].value, 2 ether, "Incorrect value"); - assertEq(actions[1].data, hex"112233445566", "Incorrect data"); - assertEq(actions[2].to, carol, "Incorrect to"); - assertEq(actions[2].value, 3 ether, "Incorrect value"); - assertEq(actions[2].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // New multisig, new settings - vm.startPrank(alice); - - // Deploy new instances - (dao, optimisticPlugin, multisig,,,) = builder.withMinApprovals(2).build(); - - createActions = new IDAO.Action[](2); - createActions[1].to = alice; - createActions[1].value = 1 ether; - createActions[1].data = hex"001122334455"; - createActions[0].to = carol; - createActions[0].value = 3 ether; - createActions[0].data = hex"223344556677"; - - vm.warp(15 days); - - pid = multisig.createProposal("ipfs://different-metadata", createActions, optimisticPlugin, true); - assertEq(pid, 0, "PID should be 0"); - - // Check round 1 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 1, "Should be 1"); - - assertEq(parameters.minApprovals, 2, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 2, "Should be 2"); - - assertEq(actions[1].to, alice, "Incorrect to"); - assertEq(actions[1].value, 1 ether, "Incorrect value"); - assertEq(actions[1].data, hex"001122334455", "Incorrect data"); - assertEq(actions[0].to, carol, "Incorrect to"); - assertEq(actions[0].value, 3 ether, "Incorrect value"); - assertEq(actions[0].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://different-metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // Approve - vm.startPrank(bob); - multisig.approve(pid, false); - - // Check round 2 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 2, "Should be 2"); - - assertEq(parameters.minApprovals, 2, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 2, "Should be 2"); - - assertEq(actions[1].to, alice, "Incorrect to"); - assertEq(actions[1].value, 1 ether, "Incorrect value"); - assertEq(actions[1].data, hex"001122334455", "Incorrect data"); - assertEq(actions[0].to, carol, "Incorrect to"); - assertEq(actions[0].value, 3 ether, "Incorrect value"); - assertEq(actions[0].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://different-metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // Approve - vm.startPrank(carol); - multisig.approve(pid, false); - - // Check round 3 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, false, "Should not be executed"); - assertEq(approvals, 3, "Should be 3"); - - assertEq(parameters.minApprovals, 2, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 2, "Should be 2"); - - assertEq(actions[1].to, alice, "Incorrect to"); - assertEq(actions[1].value, 1 ether, "Incorrect value"); - assertEq(actions[1].data, hex"001122334455", "Incorrect data"); - assertEq(actions[0].to, carol, "Incorrect to"); - assertEq(actions[0].value, 3 ether, "Incorrect value"); - assertEq(actions[0].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://different-metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - - // Execute - vm.startPrank(alice); - multisig.execute(pid); - - // Check round 4 - (executed, approvals, parameters, metadataURI, actions, destPlugin) = multisig.getProposal(pid); - - assertEq(executed, true, "Should be executed"); - assertEq(approvals, 3, "Should be 3"); - - assertEq(parameters.minApprovals, 2, "Incorrect minApprovals"); - assertEq(parameters.snapshotBlock, block.number - 1, "Incorrect snapshotBlock"); - assertEq(parameters.expirationDate, block.timestamp + 10 days, "Incorrect expirationDate"); - - assertEq(actions.length, 2, "Should be 2"); - - assertEq(actions[1].to, alice, "Incorrect to"); - assertEq(actions[1].value, 1 ether, "Incorrect value"); - assertEq(actions[1].data, hex"001122334455", "Incorrect data"); - assertEq(actions[0].to, carol, "Incorrect to"); - assertEq(actions[0].value, 3 ether, "Incorrect value"); - assertEq(actions[0].data, hex"223344556677", "Incorrect data"); - - assertEq(metadataURI, "ipfs://different-metadata", "Incorrect metadata URI"); - assertEq(address(destPlugin), address(optimisticPlugin), "Incorrect destPlugin"); - } - - function testFuzz_GetProposalReturnsEmptyValuesForNonExistingOnes(uint256 randomProposalId) public view { - ( - bool executed, - uint16 approvals, - Multisig.ProposalParameters memory parameters, - bytes memory metadataURI, - IDAO.Action[] memory destinationActions, - OptimisticTokenVotingPlugin destinationPlugin - ) = multisig.getProposal(randomProposalId); - - assertEq(executed, false, "The proposal should not be executed"); - assertEq(approvals, 0, "The tally should be zero"); - assertEq(metadataURI, "", "Incorrect metadataURI"); - assertEq(parameters.expirationDate, 0, "Incorrect expirationDate"); - assertEq(parameters.snapshotBlock, 0, "Incorrect snapshotBlock"); - assertEq(parameters.minApprovals, 0, "Incorrect minApprovals"); - assertEq(destinationActions.length, 0, "Actions has should have 0 items"); - assertEq(address(destinationPlugin), address(0), "Incorrect destination plugin"); - } - - function test_ProxiedProposalHasTheSameSettingsAsTheOriginal() public { - // Recreated proposal has the same settings and actions as registered here - - bool open; - bool executed; - bytes memory metadataUri; - OptimisticTokenVotingPlugin.ProposalParameters memory parameters; - uint256 vetoTally; - IDAO.Action[] memory actions; - uint256 allowFailureMap; - - vm.warp(2 days); - - IDAO.Action[] memory createActions = new IDAO.Action[](3); - createActions[0].to = alice; - createActions[0].value = 1 ether; - createActions[0].data = hex"001122334455"; - createActions[1].to = bob; - createActions[1].value = 2 ether; - createActions[1].data = hex"112233445566"; - createActions[2].to = carol; - createActions[2].value = 3 ether; - createActions[2].data = hex"223344556677"; - - uint256 pid = multisig.createProposal("ipfs://metadata", createActions, optimisticPlugin, false); - - // Approve - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - - vm.startPrank(alice); - multisig.execute(pid); - - // Check round - // start=1d, end=10d, counter=0 - (open, executed, parameters, vetoTally, metadataUri, actions, allowFailureMap) = - optimisticPlugin.getProposal((uint256(2 days) << 128) | (uint256(2 days + 10 days) << 64)); - - assertEq(open, true, "Should be open"); - assertEq(executed, false, "Should not be executed"); - assertEq(vetoTally, 0, "Should be 0"); - - assertEq(metadataUri, "ipfs://metadata", "Incorrect target metadataUri"); - assertEq(parameters.vetoEndDate, 2 days + 10 days, "Incorrect target vetoEndDate"); - - assertEq(actions.length, 3, "Should be 3"); - - assertEq(actions[0].to, alice, "Incorrect to"); - assertEq(actions[0].value, 1 ether, "Incorrect value"); - assertEq(actions[0].data, hex"001122334455", "Incorrect data"); - assertEq(actions[1].to, bob, "Incorrect to"); - assertEq(actions[1].value, 2 ether, "Incorrect value"); - assertEq(actions[1].data, hex"112233445566", "Incorrect data"); - assertEq(actions[2].to, carol, "Incorrect to"); - assertEq(actions[2].value, 3 ether, "Incorrect value"); - assertEq(actions[2].data, hex"223344556677", "Incorrect data"); - - assertEq(allowFailureMap, 0, "Should be 0"); - - // New proposal - vm.warp(3 days); - - createActions = new IDAO.Action[](2); - createActions[1].to = alice; - createActions[1].value = 1 ether; - createActions[1].data = hex"001122334455"; - createActions[0].to = carol; - createActions[0].value = 3 ether; - createActions[0].data = hex"223344556677"; - - pid = multisig.createProposal("ipfs://more-metadata", createActions, optimisticPlugin, false); - - // Approve - multisig.approve(pid, false); - vm.startPrank(bob); - multisig.approve(pid, false); - vm.startPrank(carol); - multisig.approve(pid, false); - - vm.startPrank(alice); - multisig.execute(pid); - - // Check round - (open, executed, parameters, vetoTally, metadataUri, actions, allowFailureMap) = - optimisticPlugin.getProposal(((uint256(3 days) << 128) | (uint256(3 days + 10 days) << 64)) + 1); - - assertEq(open, true, "Should be open"); - assertEq(executed, false, "Should not be executed"); - assertEq(vetoTally, 0, "Should be 0"); - - assertEq(metadataUri, "ipfs://more-metadata", "Incorrect target metadataUri"); - assertEq(parameters.vetoEndDate, 3 days + 10 days, "Incorrect target vetoEndDate"); - - assertEq(actions.length, 2, "Should be 2"); - - assertEq(actions[1].to, alice, "Incorrect to"); - assertEq(actions[1].value, 1 ether, "Incorrect value"); - assertEq(actions[1].data, hex"001122334455", "Incorrect data"); - assertEq(actions[0].to, carol, "Incorrect to"); - assertEq(actions[0].value, 3 ether, "Incorrect value"); - assertEq(actions[0].data, hex"223344556677", "Incorrect data"); - - assertEq(allowFailureMap, 0, "Should be 0"); - } - - // Upgrade multisig - - function test_UpgradeToRevertsWhenCalledFromNonUpgrader() public { - address initialImplementation = multisig.implementation(); - address _newImplementation = address(new Multisig()); - - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPGRADE_PLUGIN_PERMISSION_ID() - ) - ); - - multisig.upgradeTo(_newImplementation); - - assertEq(multisig.implementation(), initialImplementation); - } - - function test_UpgradeToAndCallRevertsWhenCalledFromNonUpgrader() public { - address initialImplementation = multisig.implementation(); - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - address _newImplementation = address(new Multisig()); - - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 2, - destinationProposalDuration: 14 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - - vm.expectRevert( - abi.encodeWithSelector( - DaoUnauthorized.selector, - address(dao), - address(multisig), - alice, - multisig.UPGRADE_PLUGIN_PERMISSION_ID() - ) - ); - multisig.upgradeToAndCall(_newImplementation, abi.encodeCall(Multisig.updateMultisigSettings, (settings))); - - assertEq(multisig.implementation(), initialImplementation); - } - - function test_UpgradeToSucceedsWhenCalledFromUpgrader() public { - dao.grant(address(multisig), alice, multisig.UPGRADE_PLUGIN_PERMISSION_ID()); - - address _newImplementation = address(new Multisig()); - - vm.expectEmit(); - emit Upgraded(_newImplementation); - - multisig.upgradeTo(_newImplementation); - - assertEq(multisig.implementation(), address(_newImplementation)); - } - - function test_UpgradeToAndCallSucceedsWhenCalledFromUpgrader() public { - dao.grant(address(multisig), alice, multisig.UPGRADE_PLUGIN_PERMISSION_ID()); - dao.grant(address(multisig), alice, multisig.UPDATE_MULTISIG_SETTINGS_PERMISSION_ID()); - - address _newImplementation = address(new Multisig()); - - vm.expectEmit(); - emit Upgraded(_newImplementation); - - Multisig.MultisigSettings memory settings = Multisig.MultisigSettings({ - onlyListed: false, - minApprovals: 2, - destinationProposalDuration: 14 days, - proposalExpirationPeriod: MULTISIG_PROPOSAL_EXPIRATION_PERIOD - }); - multisig.upgradeToAndCall(_newImplementation, abi.encodeCall(Multisig.updateMultisigSettings, (settings))); - - assertEq(multisig.implementation(), address(_newImplementation)); - } -} diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 042bf37..014dfc8 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -1129,7 +1129,7 @@ contract MultisigTest is AragonTest { assertEq(address(destinationPlugin), address(0), "Incorrect destinationPlugin"); } - function test_WhenCallingCanApproveAndApproveBeingUncreated() external givenTheProposalIsNotCreated { + function test_WhenCallingCanApproveOrApproveBeingUncreated() external givenTheProposalIsNotCreated { uint256 randomProposalId = 1234; bool canApprove; @@ -1368,7 +1368,7 @@ contract MultisigTest is AragonTest { } } - function test_WhenCallingCanApproveAndApproveBeingOpen() external givenTheProposalIsOpen { + function test_WhenCallingCanApproveOrApproveBeingOpen() external givenTheProposalIsOpen { // It canApprove should return true (when listed on creation, self appointed now) // It approve should work (when listed on creation, self appointed now) // It approve should emit an event (when listed on creation, self appointed now) @@ -1408,7 +1408,67 @@ contract MultisigTest is AragonTest { (, uint16 approvals,,,,) = multisig.getProposal(0); assertEq(approvals, 2, "Should have 2 approvals total"); - // Test tryExecution parameter + // Try to approve again + vm.startPrank(randomWallet); + // Should not be able to approve again + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); + multisig.approve(0, false); + + // Carol + vm.startPrank(carol); + assertEq(multisig.canApprove(0, carol), true, "Carol should be able to approve"); + multisig.approve(0, false); + + // Should approve, pass but not execute + bool executed; + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 3, "Should have 3 approvals total"); + + // David should approve + vm.startPrank(david); + assertEq(multisig.canApprove(0, david), true, "David should be able to approve"); + multisig.approve(0, false); + + // Should approve, pass but not execute + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 4, "Should have 4 approvals total"); + } + + function test_WhenCallingApproveWithTryExecutionAndAlmostPassedBeingOpen() external givenTheProposalIsOpen { + // It approve should also execute the proposal + // It approve should emit an Executed event + // It approve recreates the proposal on the destination plugin + // It The parameters of the recreated proposal match those of the approved one + // It A ProposalCreated event is emitted on the destination plugin + + bool executed; + uint16 approvals; + + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 0, "Should have 0 approvals total"); + + // Approve with tryExecute on + multisig.approve(0, true); + // Should not be able to approve again even with tryExecution + vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, alice)); + multisig.approve(0, true); + + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 1, "Should have 1 approvals total"); + + vm.startPrank(randomWallet); + multisig.approve(0, true); + assertEq(multisig.hasApproved(0, bob), true, "RandomWallet's approval should be recorded for Bob"); + assertEq(multisig.hasApproved(0, randomWallet), true, "RandomWallet's approval should be recorded"); + + (executed, approvals,,,,) = multisig.getProposal(0); + assertEq(executed, false, "Should not have executed"); + assertEq(approvals, 2, "Should have 2 approvals total"); + vm.startPrank(randomWallet); // Should not be able to approve again even with tryExecution vm.expectRevert(abi.encodeWithSelector(Multisig.ApprovalCastForbidden.selector, 0, randomWallet)); @@ -1420,11 +1480,12 @@ contract MultisigTest is AragonTest { multisig.approve(0, false); // Should approve, pass but not execute (yet) - bool executed; (executed, approvals,,,,) = multisig.getProposal(0); assertEq(executed, false, "Should not have executed"); assertEq(approvals, 3, "Should have 3 approvals total"); + assertEq(multisig.canExecute(0), true, "Should be already executable"); + // David should approve and trigger auto execution vm.startPrank(david); assertEq(multisig.canApprove(0, david), true, "David should be able to approve"); @@ -1624,7 +1685,7 @@ contract MultisigTest is AragonTest { } } - function test_WhenCallingCanApproveAndApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { + function test_WhenCallingCanApproveOrApproveBeingApproved() external givenTheProposalWasApprovedByTheAddress { // Approve without executing vm.startPrank(randomWallet); multisig.approve(0, false); @@ -1810,7 +1871,7 @@ contract MultisigTest is AragonTest { } } - function test_WhenCallingCanApproveAndApproveBeingPassed() external givenTheProposalPassed { + function test_WhenCallingCanApproveOrApproveBeingPassed() external givenTheProposalPassed { // It canApprove should return false (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) @@ -2056,7 +2117,7 @@ contract MultisigTest is AragonTest { } } - function test_WhenCallingCanApproveAndApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { + function test_WhenCallingCanApproveOrApproveBeingExecuted() external givenTheProposalIsAlreadyExecuted { // It canApprove should return false (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) @@ -2205,7 +2266,7 @@ contract MultisigTest is AragonTest { assertEq(proposalActions[0].data, "", "Incorrect action data"); } - function test_WhenCallingCanApproveAndApproveBeingExpired() external givenTheProposalExpired { + function test_WhenCallingCanApproveOrApproveBeingExpired() external givenTheProposalExpired { // It canApprove should return false (when listed on creation, self appointed now) // It canApprove should return false (when listed on creation, appointing someone else now) // It canApprove should return false (when currently appointed by a signer listed on creation) diff --git a/test/SignerListTree.t.sol b/test/SignerListTree.t.sol index 7a589bb..e0b7e53 100644 --- a/test/SignerListTree.t.sol +++ b/test/SignerListTree.t.sol @@ -757,14 +757,14 @@ contract SignerListTest is AragonTest { _; } - modifier givenTheResolvedOwnerIsListedOnResolveEncryptionOwner() { + modifier givenTheResolvedOwnerIsListedOnGetListedEncryptionOwnerAtBlock() { _; } function test_WhenTheGivenAddressIsTheOwner() external whenCallingGetListedEncryptionOwnerAtBlock - givenTheResolvedOwnerIsListedOnResolveEncryptionOwner + givenTheResolvedOwnerIsListedOnGetListedEncryptionOwnerAtBlock { address resolvedOwner; @@ -785,7 +785,7 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsAppointedByTheOwner() external whenCallingGetListedEncryptionOwnerAtBlock - givenTheResolvedOwnerIsListedOnResolveEncryptionOwner + givenTheResolvedOwnerIsListedOnGetListedEncryptionOwnerAtBlock { address resolvedOwner; @@ -797,21 +797,7 @@ contract SignerListTest is AragonTest { assertEq(resolvedOwner, bob, "Should be bob"); } - function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionOwner() - external - whenCallingGetListedEncryptionOwnerAtBlock - { - address resolvedOwner; - - // It should return a zero value - resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x3456), block.number - 1); - assertEq(resolvedOwner, address(0), "Should be zero"); - - resolvedOwner = signerList.getListedEncryptionOwnerAtBlock(address(0x4567), block.number - 1); - assertEq(resolvedOwner, address(0), "Should be zero"); - } - - modifier givenTheResolvedOwnerWasListedOnResolveEncryptionOwner() { + modifier givenTheResolvedOwnerWasListedOnGetListedEncryptionOwnerAtBlock() { // But not listed now // Prior appointments are still in place @@ -838,7 +824,7 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsTheOwner2() external whenCallingGetListedEncryptionOwnerAtBlock - givenTheResolvedOwnerWasListedOnResolveEncryptionOwner + givenTheResolvedOwnerWasListedOnGetListedEncryptionOwnerAtBlock { address resolvedOwner; @@ -859,7 +845,7 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsAppointedByTheOwner2() external whenCallingGetListedEncryptionOwnerAtBlock - givenTheResolvedOwnerWasListedOnResolveEncryptionOwner + givenTheResolvedOwnerWasListedOnGetListedEncryptionOwnerAtBlock { address resolvedOwner; @@ -871,7 +857,7 @@ contract SignerListTest is AragonTest { assertEq(resolvedOwner, bob, "Should be bob"); } - function test_GivenTheResolvedOwnerWasNotListedOnResolveEncryptionOwner() + function test_GivenTheResolvedOwnerWasNotListedOnGetListedEncryptionOwnerAtBlock() external whenCallingGetListedEncryptionOwnerAtBlock { @@ -901,14 +887,14 @@ contract SignerListTest is AragonTest { _; } - modifier givenTheResolvedOwnerIsListedOnResolveEncryptionAccount() { + modifier givenTheResolvedOwnerIsListedOnResolveEncryptionAccountAtBlock() { _; } function test_WhenTheGivenAddressIsOwner() external whenCallingResolveEncryptionAccountAtBlock - givenTheResolvedOwnerIsListedOnResolveEncryptionAccount + givenTheResolvedOwnerIsListedOnResolveEncryptionAccountAtBlock { address resolvedOwner; address votingWallet; @@ -941,7 +927,7 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsAppointed() external whenCallingResolveEncryptionAccountAtBlock - givenTheResolvedOwnerIsListedOnResolveEncryptionAccount + givenTheResolvedOwnerIsListedOnResolveEncryptionAccountAtBlock { address resolvedOwner; address votingWallet; @@ -957,30 +943,7 @@ contract SignerListTest is AragonTest { assertEq(votingWallet, address(0x2345), "Should be 0x2345"); } - function test_GivenTheResolvedOwnerIsNotListedOnResolveEncryptionAccount() - external - whenCallingResolveEncryptionAccountAtBlock - { - address resolvedOwner; - address votingWallet; - - // It should return a zero owner - // It should return a zero votingWallet - - (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0), block.number - 1); - assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(votingWallet, address(0), "Should be 0"); - - (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0x5555), block.number - 1); - assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(votingWallet, address(0), "Should be 0"); - - (resolvedOwner, votingWallet) = signerList.resolveEncryptionAccountAtBlock(address(0xaaaa), block.number - 1); - assertEq(resolvedOwner, address(0), "Should be 0"); - assertEq(votingWallet, address(0), "Should be 0"); - } - - modifier givenTheResolvedOwnerWasListedOnResolveEncryptionAccount() { + modifier givenTheResolvedOwnerWasListedOnResolveEncryptionAccountAtBlock() { // But not listed now // Prior appointments are still in place @@ -1007,7 +970,7 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsOwner2() external whenCallingResolveEncryptionAccountAtBlock - givenTheResolvedOwnerWasListedOnResolveEncryptionAccount + givenTheResolvedOwnerWasListedOnResolveEncryptionAccountAtBlock { address resolvedOwner; address votingWallet; @@ -1040,7 +1003,7 @@ contract SignerListTest is AragonTest { function test_WhenTheGivenAddressIsAppointed2() external whenCallingResolveEncryptionAccountAtBlock - givenTheResolvedOwnerWasListedOnResolveEncryptionAccount + givenTheResolvedOwnerWasListedOnResolveEncryptionAccountAtBlock { address resolvedOwner; address votingWallet; @@ -1056,7 +1019,7 @@ contract SignerListTest is AragonTest { assertEq(votingWallet, address(0x2345), "Should be 0x2345"); } - function test_GivenTheResolvedOwnerWasNotListedOnResolveEncryptionAccount() + function test_GivenTheResolvedOwnerWasNotListedOnResolveEncryptionAccountAtBlock() external whenCallingResolveEncryptionAccountAtBlock { diff --git a/test/SignerListTree.t.yaml b/test/SignerListTree.t.yaml index ff55638..24644cc 100644 --- a/test/SignerListTree.t.yaml +++ b/test/SignerListTree.t.yaml @@ -140,7 +140,7 @@ SignerListTest: then: - it: should return the resolved owner - - given: the resolved owner is not listed [on getListedEncryptionOwnerAtBlock] + - given: the resolved owner was not listed [on getListedEncryptionOwnerAtBlock] then: - it: should return a zero value @@ -168,7 +168,7 @@ SignerListTest: - it: owner should be the resolved owner - it: votingWallet should be the given address - - given: the resolved owner is not listed [on resolveEncryptionAccountAtBlock] + - given: the resolved owner was not listed [on resolveEncryptionAccountAtBlock] then: - it: should return a zero owner - it: should return a zero appointedWallet From 601efd2087b748c4a5f8cb140042ff2cbcf3fcff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 14 Nov 2024 12:20:55 +0700 Subject: [PATCH 40/45] Final touches --- README.md | 34 ++++++++++++++++++++++++++++++-- test/EmergencyMultisigTree.t.sol | 22 +++++++++++++++++++++ test/MultisigTree.t.sol | 20 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 14c8cdb..b18140d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ The governance settings need to be defined when the plugin is installed but the It allows the Security Council members to create and approve proposals. After a certain minimum of approvals is met, proposals can be relayed to the [Optimistic Token Voting plugin](#optimistic-token-voting-plugin) only. +The list of signers for this plugin is taken from SignerList contract. Any changes on it will effect both plugin instances. + The ability to relay proposals to the [Optimistic Token Voting plugin](#optimistic-token-voting-plugin) is restricted by a [permission condition](src/conditions/StandardProposalCondition.sol), which ensures that a minimum veto period is defined as part of the parameters. ![Standard proposal flow](./img/std-proposal-flow.png) @@ -52,7 +54,7 @@ The ability to relay proposals to the [Optimistic Token Voting plugin](#optimist Like before, this plugin allows Security Council members to create and approve proposals. If a super majority approves, proposals can be relayed to the [Optimistic Token Voting plugin](#optimistic-token-voting-plugin) with a delay period of potentially 0. This is, being executed immediately. -The address list of this plugin is taken from the standard Multisig plugin. Any changes on the former will effect both plugin instances. +The list of signers for this plugin is taken from SignerList contract. Any changes on it will effect both plugin instances. There are two key differences with the standard Multisig: 1. The proposal's metadata and the actions to execute are encrypted, only the Security Council members have the means to decrypt them @@ -69,9 +71,37 @@ The Emergency Multisig settings are the same as for the standard Multisig. - The plugin can only create proposals on the [Optimistic Token Voting plugin](#optimistic-token-voting-plugin) provided that the `duration` is equal or greater than the minimum defined - The DAO can update the plugin settings +## Signer List + +Both multisigs relate to this contract to determine if an address was listed at a certain block. It allows to read the state and manage the address list given that the appropriate permissions are granted. + +It also plays an important role regarding encryption, this is why it is coupled with the Encryption Registry (see below). + +It offers convenience methods to determine 3 potential states for a given address: +- An address was a listed signer at a given past block (owner) +- An address is appointed by another address, listed at a past block (appointed) +- An address not listed or appointed + +### The encryption challenge + +Smart wallets cannot possibly generate a private key, which means that encryption and decryption is unviable. To this end, the [EncryptionRegistry](#encryption-registry) (see below) allows listed signers to **appoint** an EOA to act on behalf of them. + +This means that the Security Council could include a member who was an organization, and such organiation could have a smart wallet. This smart wallet would then appoint one of its members' EOA, so that emergency proposals could be reviewed, approved and eventually executed. + +If at any point, the member's EOA became compromised or the member left the team, the smart wallet could then appoint a new EOA and continue without impacting the rest of the Security Council. + +What it means: +- Owners (listed signers) + - Can always create emergency multisig proposals + - Can only approve if they are not appointing another address +- Addresses appointed by a listed signer + - Can create emergency proposals + - Can approve + - Can execute (they can decrypt the actions and the metadata) + ## Encryption Registry -This is a helper contract that allows Security Council members to register the public key of their deterministic ephemeral wallet. The available public keys will be used to encrypt the proposal metadata and actions. +This is a helper contract that allows Security Council members ([SignerList](#signer-list) addresses) to register the public key of their deterministic ephemeral wallet. The available public keys will be used to encrypt the proposal metadata and actions. Given that smart contracts cannot possibly sign or decrypt data, the encryption registry allows to appoint an EOA as the end target for encryption purposes. This is useful for organizations not wanting to rely on just a single wallet. diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisigTree.t.sol index 7a3d256..86c3e76 100644 --- a/test/EmergencyMultisigTree.t.sol +++ b/test/EmergencyMultisigTree.t.sol @@ -1172,6 +1172,28 @@ contract EmergencyMultisigTest is AragonTest { assertEq(address(destinationPlugin), address(0), "Incorrect destinationPlugin"); } + function testFuzz_GetProposalReturnsEmptyValuesForNonExistingOnes(uint256 randomProposalId) public view { + ( + bool executed, + uint16 approvals, + EmergencyMultisig.ProposalParameters memory parameters, + bytes memory encryptedPayloadURI, + bytes32 publicMetadataUriHash, + bytes32 destinationActionsHash, + OptimisticTokenVotingPlugin destinationPlugin + ) = eMultisig.getProposal(randomProposalId); + + assertEq(executed, false, "The proposal should not be executed"); + assertEq(approvals, 0, "The tally should be zero"); + assertEq(encryptedPayloadURI, "", "Incorrect encryptedPayloadURI"); + assertEq(parameters.expirationDate, 0, "Incorrect expirationDate"); + assertEq(parameters.snapshotBlock, 0, "Incorrect snapshotBlock"); + assertEq(parameters.minApprovals, 0, "Incorrect minApprovals"); + assertEq(publicMetadataUriHash, 0, "Metadata URI hash should have no items"); + assertEq(destinationActionsHash, 0, "Actions hash should have no items"); + assertEq(address(destinationPlugin), address(0), "Incorrect destination plugin"); + } + function test_WhenCallingCanApproveOrApproveBeingUncreated() external givenTheProposalIsNotCreated { uint256 randomProposalId = 1234; bool canApprove; diff --git a/test/MultisigTree.t.sol b/test/MultisigTree.t.sol index 014dfc8..04f2a38 100644 --- a/test/MultisigTree.t.sol +++ b/test/MultisigTree.t.sol @@ -1489,6 +1489,26 @@ contract MultisigTest is AragonTest { // David should approve and trigger auto execution vm.startPrank(david); assertEq(multisig.canApprove(0, david), true, "David should be able to approve"); + + // It execute recreates the proposal on the destination plugin + uint256 targetPid = (block.timestamp << 128) | ((block.timestamp + DESTINATION_PROPOSAL_DURATION) << 64); + IDAO.Action[] memory actions = new IDAO.Action[](2); + actions[0].value = 0.25 ether; + actions[0].to = address(alice); + actions[0].data = hex""; + actions[1].value = 0.75 ether; + actions[1].to = address(dao); + actions[1].data = abi.encodeCall(DAO.setMetadata, "ipfs://new-metadata"); + vm.expectEmit(); + emit ProposalCreated( + targetPid, + address(multisig), + uint64(block.timestamp), + uint64(block.timestamp + DESTINATION_PROPOSAL_DURATION), + "ipfs://pub-metadata", + actions, + 0 + ); multisig.approve(0, true); (executed, approvals,,,,) = multisig.getProposal(0); From c837b8709500b5668c39c1e31f4954a53f90bb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 14 Nov 2024 12:23:52 +0700 Subject: [PATCH 41/45] Renaming test files --- test/{EmergencyMultisigTree.t.sol => EmergencyMultisig.t.sol} | 0 test/{EmergencyMultisigTree.t.yaml => EmergencyMultisig.t.yaml} | 0 test/{MultisigTree.t.sol => Multisig.t.sol} | 0 test/{MultisigTree.t.yaml => Multisig.t.yaml} | 0 test/{SignerListTree.t.sol => SignerList.t.sol} | 0 test/{SignerListTree.t.yaml => SignerList.t.yaml} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename test/{EmergencyMultisigTree.t.sol => EmergencyMultisig.t.sol} (100%) rename test/{EmergencyMultisigTree.t.yaml => EmergencyMultisig.t.yaml} (100%) rename test/{MultisigTree.t.sol => Multisig.t.sol} (100%) rename test/{MultisigTree.t.yaml => Multisig.t.yaml} (100%) rename test/{SignerListTree.t.sol => SignerList.t.sol} (100%) rename test/{SignerListTree.t.yaml => SignerList.t.yaml} (100%) diff --git a/test/EmergencyMultisigTree.t.sol b/test/EmergencyMultisig.t.sol similarity index 100% rename from test/EmergencyMultisigTree.t.sol rename to test/EmergencyMultisig.t.sol diff --git a/test/EmergencyMultisigTree.t.yaml b/test/EmergencyMultisig.t.yaml similarity index 100% rename from test/EmergencyMultisigTree.t.yaml rename to test/EmergencyMultisig.t.yaml diff --git a/test/MultisigTree.t.sol b/test/Multisig.t.sol similarity index 100% rename from test/MultisigTree.t.sol rename to test/Multisig.t.sol diff --git a/test/MultisigTree.t.yaml b/test/Multisig.t.yaml similarity index 100% rename from test/MultisigTree.t.yaml rename to test/Multisig.t.yaml diff --git a/test/SignerListTree.t.sol b/test/SignerList.t.sol similarity index 100% rename from test/SignerListTree.t.sol rename to test/SignerList.t.sol diff --git a/test/SignerListTree.t.yaml b/test/SignerList.t.yaml similarity index 100% rename from test/SignerListTree.t.yaml rename to test/SignerList.t.yaml From 702c38e0dad51677a5aa365095f66066b68697d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Thu, 14 Nov 2024 12:26:32 +0700 Subject: [PATCH 42/45] Showing the signer list on deploy --- script/Deploy.s.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index f5066ca..1d09918 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -68,17 +68,19 @@ contract Deploy is Script { console.log("- Optimistic token voting plugin:", address(daoDeployment.optimisticTokenVotingPlugin)); console.log(""); + console.log("Helpers"); + console.log("- Signer list", address(daoDeployment.signerList)); + console.log("- Encryption registry", address(daoDeployment.encryptionRegistry)); + console.log("- Delegation wall", address(delegationWall)); + + console.log(""); + console.log("Plugin repositories"); console.log("- Multisig plugin repository:", address(daoDeployment.multisigPluginRepo)); console.log("- Emergency multisig plugin repository:", address(daoDeployment.emergencyMultisigPluginRepo)); console.log( "- Optimistic token voting plugin repository:", address(daoDeployment.optimisticTokenVotingPluginRepo) ); - console.log(""); - - console.log("Helpers"); - console.log("- Encryption registry", address(daoDeployment.encryptionRegistry)); - console.log("- Delegation wall", address(delegationWall)); } function getProductionSettings() internal view returns (TaikoDaoFactory.DeploymentSettings memory settings) { From 324ecc8fd5731db9f1450d9457d16117e3423b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 22 Nov 2024 18:11:23 +0700 Subject: [PATCH 43/45] Ensuring that self appointing resets the appointed address --- src/EncryptionRegistry.sol | 35 +++- src/SignerList.sol | 6 +- src/interfaces/IEncryptionRegistry.sol | 5 +- test/EncryptionRegistry.t.sol | 275 +++++++++++++++++++------ 4 files changed, 246 insertions(+), 75 deletions(-) diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index 32df6a9..bebf0f4 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -24,7 +24,7 @@ contract EncryptionRegistry is IEncryptionRegistry, ERC165 { mapping(address => AccountEntry) public accounts; /// @notice A reference to the account that appointed each wallet - mapping(address => address) public appointedBy; + mapping(address => address) public appointerOf; /// @dev The contract to check whether the caller is a multisig member Addresslist addresslist; @@ -39,25 +39,44 @@ contract EncryptionRegistry is IEncryptionRegistry, ERC165 { /// @inheritdoc IEncryptionRegistry function appointWallet(address _newWallet) public { + // Appointing ourselves is the same as unappointing + if (_newWallet == msg.sender) _newWallet = address(0); + if (!addresslist.isListed(msg.sender)) { revert MustBeListed(); } else if (Address.isContract(_newWallet)) { revert CannotAppointContracts(); - } else if (appointedBy[_newWallet] != address(0)) { + } else if (addresslist.isListed(_newWallet)) { + // Appointing an already listed signer is not allowed, as votes would be locked + revert AlreadyListed(); + } else if (_newWallet == accounts[msg.sender].appointedWallet) { + return; // done + } else if (appointerOf[_newWallet] != address(0)) { revert AlreadyAppointed(); } + bool exists; + for (uint256 i = 0; i < registeredAccounts.length;) { + if (registeredAccounts[i] == msg.sender) { + exists = true; + break; + } + unchecked { + i++; + } + } + // New account? - if (accounts[msg.sender].appointedWallet == address(0) && accounts[msg.sender].publicKey == bytes32(0)) { + if (!exists) { registeredAccounts.push(msg.sender); } // Existing account else { - // Clear the old appointedBy[], if needed + // Clear the current appointerOf[], if needed if (accounts[msg.sender].appointedWallet != address(0)) { - appointedBy[accounts[msg.sender].appointedWallet] = address(0); + appointerOf[accounts[msg.sender].appointedWallet] = address(0); } - // Clear the old public key, if needed + // Clear the current public key, if needed if (accounts[msg.sender].publicKey != bytes32(0)) { // The old appointed wallet should no longer be able to see new content accounts[msg.sender].publicKey = bytes32(0); @@ -65,7 +84,9 @@ contract EncryptionRegistry is IEncryptionRegistry, ERC165 { } accounts[msg.sender].appointedWallet = _newWallet; - appointedBy[_newWallet] = msg.sender; + if (_newWallet != address(0)) { + appointerOf[_newWallet] = msg.sender; + } emit WalletAppointed(msg.sender, _newWallet); } diff --git a/src/SignerList.sol b/src/SignerList.sol index bf121d3..1904e1c 100644 --- a/src/SignerList.sol +++ b/src/SignerList.sol @@ -106,7 +106,7 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza function isListedOrAppointedByListed(address _address) public view returns (bool listedOrAppointedByListed) { if (isListed(_address)) { return true; - } else if (isListed(settings.encryptionRegistry.appointedBy(_address))) { + } else if (isListed(settings.encryptionRegistry.appointerOf(_address))) { return true; } @@ -122,7 +122,7 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza if (isListedAtBlock(_address, _blockNumber)) { return _address; } - address _appointer = settings.encryptionRegistry.appointedBy(_address); + address _appointer = settings.encryptionRegistry.appointerOf(_address); if (isListedAtBlock(_appointer, _blockNumber)) { return _appointer; } @@ -141,7 +141,7 @@ contract SignerList is ISignerList, Addresslist, ERC165Upgradeable, DaoAuthoriza return (_address, settings.encryptionRegistry.getAppointedWallet(_address)); } - address _appointer = settings.encryptionRegistry.appointedBy(_address); + address _appointer = settings.encryptionRegistry.appointerOf(_address); if (this.isListedAtBlock(_appointer, _blockNumber)) { // The appointed wallet votes return (_appointer, _address); diff --git a/src/interfaces/IEncryptionRegistry.sol b/src/interfaces/IEncryptionRegistry.sol index e096e46..7413300 100644 --- a/src/interfaces/IEncryptionRegistry.sol +++ b/src/interfaces/IEncryptionRegistry.sol @@ -15,6 +15,9 @@ interface IEncryptionRegistry { /// @notice Raised when attempting to register a contract instead of a wallet error CannotAppointContracts(); + /// @notice Raised when attempting to appoint an address which is already a listed signer + error AlreadyListed(); + /// @notice Raised when attempting to appoint an already appointed address error AlreadyAppointed(); @@ -42,7 +45,7 @@ interface IEncryptionRegistry { /// @notice Returns the address of the account that appointed the given wallet, if any. /// @return appointerAddress The address of the appointer account or zero. - function appointedBy(address wallet) external returns (address appointerAddress); + function appointerOf(address wallet) external returns (address appointerAddress); /// @notice Returns the address of the account registered at the given index function registeredAccounts(uint256) external view returns (address); diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index 252060f..bc5a562 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -146,10 +146,14 @@ contract EncryptionRegistryTest is AragonTest { } function testFuzz_ShouldRegisterMemberPublicKeys(address appointedWallet) public { - if (Address.isContract(appointedWallet)) return; - else if (Address.isContract(address(uint160(appointedWallet) + 1))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 2))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 3))) return; + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david + ) return; + else if (Address.isContract(appointedWallet)) return; + else if (Address.isContract(address(uint160(appointedWallet) + 100))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 200))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 300))) return; address addrValue; bytes32 bytesValue; @@ -170,59 +174,62 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); - registry.appointWallet(address(uint160(appointedWallet) + 1)); - vm.startPrank(address(uint160(appointedWallet) + 1)); + registry.appointWallet(address(uint160(appointedWallet) + 100)); + vm.startPrank(address(uint160(appointedWallet) + 100)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); - registry.appointWallet(address(uint160(appointedWallet) + 2)); - vm.startPrank(address(uint160(appointedWallet) + 2)); + registry.appointWallet(address(uint160(appointedWallet) + 200)); + vm.startPrank(address(uint160(appointedWallet) + 200)); registry.setPublicKey(carol, 0x0000000090ab0000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 2)); + assertEq(addrValue, address(uint160(appointedWallet) + 200)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); - registry.appointWallet(address(uint160(appointedWallet) + 3)); - vm.startPrank(address(uint160(appointedWallet) + 3)); + registry.appointWallet(address(uint160(appointedWallet) + 300)); + vm.startPrank(address(uint160(appointedWallet) + 300)); registry.setPublicKey(david, 0x000000000000cdef000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 2)); + assertEq(addrValue, address(uint160(appointedWallet) + 200)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(david); - assertEq(addrValue, address(uint160(appointedWallet) + 3)); + assertEq(addrValue, address(uint160(appointedWallet) + 300)); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } - function test_ShouldClearPublicKeyAfterAppointing(address appointedWallet) public { - if (appointedWallet == address(0)) return; + function testFuzz_ShouldClearPublicKeyAfterAppointing(address appointedWallet) public { + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david + ) return; else if (Address.isContract(appointedWallet)) return; - else if (Address.isContract(address(uint160(appointedWallet) + 1))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 2))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 3))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 100))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 200))) return; + else if (Address.isContract(address(uint160(appointedWallet) + 300))) return; address addrValue; bytes32 bytesValue; @@ -249,13 +256,13 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 1)); + registry.appointWallet(address(uint160(appointedWallet) + 100)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol @@ -264,16 +271,16 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(carol); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 2)); + registry.appointWallet(address(uint160(appointedWallet) + 200)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 2)); + assertEq(addrValue, address(uint160(appointedWallet) + 200)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David @@ -282,19 +289,19 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(david); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 3)); + registry.appointWallet(address(uint160(appointedWallet) + 300)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 2)); + assertEq(addrValue, address(uint160(appointedWallet) + 200)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(david); - assertEq(addrValue, address(uint160(appointedWallet) + 3)); + assertEq(addrValue, address(uint160(appointedWallet) + 300)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } @@ -315,33 +322,163 @@ contract EncryptionRegistryTest is AragonTest { assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // OK - registry.appointWallet(bob); - registry.appointWallet(carol); - registry.appointWallet(david); + registry.appointWallet(address(0x1111)); + registry.appointWallet(address(0x2222)); + registry.appointWallet(address(0x3333)); // KO vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.CannotAppointContracts.selector)); registry.appointWallet(address(dao)); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, david); + assertEq(addrValue, address(0x3333)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // KO vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.CannotAppointContracts.selector)); registry.appointWallet(address(multisig)); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, david); + assertEq(addrValue, address(0x3333)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // KO vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.CannotAppointContracts.selector)); registry.appointWallet(address(registry)); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, david); + assertEq(addrValue, address(0x3333)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + } + + function test_shouldAllowToAppointBackAndForth() public { + address addrValue; + bytes32 bytesValue; + + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + vm.startPrank(alice); + + // Neutral + registry.appointWallet(address(0x0)); + + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0x0)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Repeated appointments + registry.appointWallet(address(0x1234)); + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0x1234)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(address(0x1234)); + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0x1234)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // Bob + registry.appointWallet(address(0x1111)); + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0x1111)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(address(0x1111)); + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0x1111)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + registry.appointWallet(address(0x1111)); + (addrValue, bytesValue) = registry.accounts(alice); + assertEq(addrValue, address(0x1111)); + assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); + + // More + registry.appointWallet(address(0x2222)); + registry.appointWallet(address(0x2222)); + registry.appointWallet(address(0x2222)); + + registry.appointWallet(address(0x3333)); + registry.appointWallet(address(0x3333)); + registry.appointWallet(address(0x3333)); + + // OK again + registry.appointWallet(address(0x1234)); + registry.appointWallet(address(0x1111)); + registry.appointWallet(address(0x2222)); + registry.appointWallet(address(0x3333)); + } + + function test_getRegisteredAccountsOnlyReturnsAddressesOnce() public { + uint256 count = registry.getRegisteredAccounts().length; + assertEq(count, 0); + + vm.startPrank(alice); + + // Neutral + registry.appointWallet(address(0x0)); + + count = registry.getRegisteredAccounts().length; + assertEq(count, 0); + + // Appoint + registry.appointWallet(address(0x1111)); + + count = registry.getRegisteredAccounts().length; + assertEq(count, 1); + + registry.appointWallet(address(0x2222)); + + count = registry.getRegisteredAccounts().length; + assertEq(count, 1); + + registry.appointWallet(address(0x3333)); + + count = registry.getRegisteredAccounts().length; + assertEq(count, 1); + } + + function test_shouldRevertIfAppointingAnotherSigner() public { + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyListed.selector)); + registry.appointWallet(bob); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyListed.selector)); + registry.appointWallet(carol); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyListed.selector)); + registry.appointWallet(david); + + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyListed.selector)); + registry.appointWallet(alice); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyListed.selector)); + registry.appointWallet(carol); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyListed.selector)); + registry.appointWallet(david); + + // ok + registry.appointWallet(address(0x5555)); + } + + function test_shouldRevertWhenAlreadyAppointed() public { + vm.startPrank(alice); + registry.appointWallet(address(0x1234)); + + vm.startPrank(bob); + registry.appointWallet(address(0x2345)); + + // Fail + vm.startPrank(alice); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyAppointed.selector)); + registry.appointWallet(address(0x2345)); + + vm.startPrank(bob); + vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.AlreadyAppointed.selector)); + registry.appointWallet(address(0x1234)); + + // ok + registry.appointWallet(address(0x5555)); } - function test_ShouldRevertIfNotListed(address appointedWallet) public { + function testFuzz_AppointShouldRevertIfNotListed(address appointedWallet) public { if (Address.isContract(appointedWallet)) return; SignerList signerList; @@ -350,6 +487,7 @@ contract EncryptionRegistryTest is AragonTest { // Only Alice (,, multisig,,, signerList, registry,) = new DaoBuilder().withMultisigMember(alice).build(); + if (signerList.isListed(appointedWallet)) return; (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, address(0)); @@ -367,12 +505,12 @@ contract EncryptionRegistryTest is AragonTest { assertEq(bytesValue, 0x5678000000000000000000000000000000000000000000000000000000000000); // Appoint self - registry.appointWallet(alice); - vm.startPrank(alice); + registry.appointWallet(appointedWallet); + vm.startPrank(appointedWallet); registry.setPublicKey(alice, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, alice); + assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); // NOT OK @@ -380,15 +518,15 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(address(uint160(appointedWallet) + 1)); + registry.appointWallet(address(uint160(appointedWallet) + 100)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(address(uint160(appointedWallet) + 1)); + vm.startPrank(address(uint160(appointedWallet) + 100)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(bob, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, alice); + assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); @@ -397,15 +535,15 @@ contract EncryptionRegistryTest is AragonTest { // Carol vm.startPrank(carol); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(address(uint160(appointedWallet) + 2)); + registry.appointWallet(address(uint160(appointedWallet) + 200)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(address(uint160(appointedWallet) + 2)); + vm.startPrank(address(uint160(appointedWallet) + 200)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(carol, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, alice); + assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); @@ -417,15 +555,15 @@ contract EncryptionRegistryTest is AragonTest { // David vm.startPrank(david); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(address(uint160(appointedWallet) + 3)); + registry.appointWallet(address(uint160(appointedWallet) + 300)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(address(uint160(appointedWallet) + 3)); + vm.startPrank(address(uint160(appointedWallet) + 300)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(david, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, alice); + assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); assertEq(addrValue, address(0)); @@ -438,8 +576,8 @@ contract EncryptionRegistryTest is AragonTest { assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } - function test_ShouldRevertOnSetPublicKeyIfNotAppointed(address appointedWallet) public { - if (Address.isContract(appointedWallet) || Address.isContract(address(uint160(appointedWallet) + 1))) return; + function testFuzz_ShouldRevertOnSetPublicKeyIfNotAppointed(address appointedWallet) public { + if (Address.isContract(appointedWallet) || Address.isContract(address(uint160(appointedWallet) + 100))) return; address addrValue; bytes32 bytesValue; @@ -474,19 +612,22 @@ contract EncryptionRegistryTest is AragonTest { assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 1)); + registry.appointWallet(address(uint160(appointedWallet) + 100)); // Appointed - vm.startPrank(address(uint160(appointedWallet) + 1)); + vm.startPrank(address(uint160(appointedWallet) + 100)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 1)); + assertEq(addrValue, address(uint160(appointedWallet) + 100)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); } - function test_ShouldRevertIfOwnerNotAppointed(address appointedWallet) public { - if (appointedWallet == address(0)) return; + function testFuzz_ShouldRevertOnSetOwnPublicKeyIfOwnerIsAppointing(address appointedWallet) public { + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david + ) return; else if (Address.isContract(appointedWallet)) return; address addrValue; @@ -507,7 +648,7 @@ contract EncryptionRegistryTest is AragonTest { registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); - assertEq(addrValue, alice); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); // Bob @@ -525,7 +666,7 @@ contract EncryptionRegistryTest is AragonTest { registry.setOwnPublicKey(0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, bob); + assertEq(addrValue, address(0)); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); } @@ -602,7 +743,8 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(carol); registry.appointWallet(address(0x90ab)); assertEq(registry.getRegisteredAccounts().length, 3, "Incorrect length"); - registry.appointWallet(carol); + registry.appointWallet(address(0x6666)); + vm.startPrank(address(0x6666)); registry.setPublicKey(carol, bytes32(uint256(3456))); assertEq(registry.getRegisteredAccounts().length, 3, "Incorrect length"); @@ -610,7 +752,8 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(david); registry.appointWallet(address(0xcdef)); assertEq(registry.getRegisteredAccounts().length, 4, "Incorrect length"); - registry.appointWallet(david); + registry.appointWallet(address(0x7777)); + vm.startPrank(address(0x7777)); registry.setPublicKey(david, bytes32(uint256(4567))); assertEq(registry.getRegisteredAccounts().length, 4, "Incorrect length"); } @@ -638,7 +781,8 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(carol); registry.appointWallet(address(0x90ab)); assertEq(registry.registeredAccounts(2), carol); - registry.appointWallet(carol); + registry.appointWallet(address(0x6666)); + vm.startPrank(address(0x6666)); registry.setPublicKey(carol, bytes32(uint256(3456))); assertEq(registry.registeredAccounts(2), carol); @@ -646,7 +790,8 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(david); registry.appointWallet(address(0xcdef)); assertEq(registry.registeredAccounts(3), david); - registry.appointWallet(david); + registry.appointWallet(address(0x7777)); + vm.startPrank(address(0x7777)); registry.setPublicKey(david, bytes32(uint256(4567))); assertEq(registry.registeredAccounts(3), david); @@ -681,7 +826,8 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(carol); registry.appointWallet(address(0x90ab)); assertEq(registry.registeredAccounts(2), carol); - registry.appointWallet(carol); + registry.appointWallet(address(0x6666)); + vm.startPrank(address(0x6666)); registry.setPublicKey(carol, bytes32(uint256(3456))); assertEq(registry.registeredAccounts(2), carol); @@ -689,7 +835,8 @@ contract EncryptionRegistryTest is AragonTest { vm.startPrank(david); registry.appointWallet(address(0xcdef)); assertEq(registry.registeredAccounts(3), david); - registry.appointWallet(david); + registry.appointWallet(address(0x7777)); + vm.startPrank(address(0x7777)); registry.setPublicKey(david, bytes32(uint256(4567))); assertEq(registry.registeredAccounts(3), david); From 6b3bfd19b23179e43212e007cef7a355a66f9992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Fri, 22 Nov 2024 19:10:27 +0700 Subject: [PATCH 44/45] Clearer fuzz test values to skip --- test/EncryptionRegistry.t.sol | 58 ++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index bc5a562..5f67f2a 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -146,14 +146,7 @@ contract EncryptionRegistryTest is AragonTest { } function testFuzz_ShouldRegisterMemberPublicKeys(address appointedWallet) public { - if ( - appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob - || appointedWallet == carol || appointedWallet == david - ) return; - else if (Address.isContract(appointedWallet)) return; - else if (Address.isContract(address(uint160(appointedWallet) + 100))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 200))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 300))) return; + if (skipAppointedWallet(appointedWallet)) return; address addrValue; bytes32 bytesValue; @@ -222,14 +215,7 @@ contract EncryptionRegistryTest is AragonTest { } function testFuzz_ShouldClearPublicKeyAfterAppointing(address appointedWallet) public { - if ( - appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob - || appointedWallet == carol || appointedWallet == david - ) return; - else if (Address.isContract(appointedWallet)) return; - else if (Address.isContract(address(uint160(appointedWallet) + 100))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 200))) return; - else if (Address.isContract(address(uint160(appointedWallet) + 300))) return; + if (skipAppointedWallet(appointedWallet)) return; address addrValue; bytes32 bytesValue; @@ -577,7 +563,7 @@ contract EncryptionRegistryTest is AragonTest { } function testFuzz_ShouldRevertOnSetPublicKeyIfNotAppointed(address appointedWallet) public { - if (Address.isContract(appointedWallet) || Address.isContract(address(uint160(appointedWallet) + 100))) return; + if (skipAppointedWallet(appointedWallet)) return; address addrValue; bytes32 bytesValue; @@ -624,11 +610,7 @@ contract EncryptionRegistryTest is AragonTest { } function testFuzz_ShouldRevertOnSetOwnPublicKeyIfOwnerIsAppointing(address appointedWallet) public { - if ( - appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob - || appointedWallet == carol || appointedWallet == david - ) return; - else if (Address.isContract(appointedWallet)) return; + if (skipAppointedWallet(appointedWallet)) return; address addrValue; bytes32 bytesValue; @@ -861,4 +843,36 @@ contract EncryptionRegistryTest is AragonTest { function supportsInterface(bytes4) public pure returns (bool) { return false; } + + // Internal helpers + + function skipAppointedWallet(address appointedWallet) internal view returns (bool) { + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) + ) return true; + + appointedWallet = address(uint160(appointedWallet) + 100); + + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) + ) return true; + + appointedWallet = address(uint160(appointedWallet) + 100); + + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) + ) return true; + + appointedWallet = address(uint160(appointedWallet) + 100); + + if ( + appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob + || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) + ) return true; + + return false; + } } From a49b8ff4895f767890a8f32a74d0258d57a59e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8r=E2=88=82=C2=A1?= Date: Tue, 26 Nov 2024 17:04:02 +0700 Subject: [PATCH 45/45] Amendment --- src/EncryptionRegistry.sol | 14 ++- test/EncryptionRegistry.t.sol | 179 +++++++++++++++++++++++++--------- 2 files changed, 143 insertions(+), 50 deletions(-) diff --git a/src/EncryptionRegistry.sol b/src/EncryptionRegistry.sol index bebf0f4..58e9b79 100644 --- a/src/EncryptionRegistry.sol +++ b/src/EncryptionRegistry.sol @@ -142,8 +142,18 @@ contract EncryptionRegistry is IEncryptionRegistry, ERC165 { // Internal helpers function _setPublicKey(address _account, bytes32 _publicKey) internal { - if (accounts[_account].appointedWallet == address(0) && accounts[_account].publicKey == bytes32(0)) { - // New member + bool exists; + for (uint256 i = 0; i < registeredAccounts.length;) { + if (registeredAccounts[i] == _account) { + exists = true; + break; + } + unchecked { + i++; + } + } + if (!exists) { + // New account registeredAccounts.push(_account); } diff --git a/test/EncryptionRegistry.t.sol b/test/EncryptionRegistry.t.sol index 5f67f2a..b501c27 100644 --- a/test/EncryptionRegistry.t.sol +++ b/test/EncryptionRegistry.t.sol @@ -167,50 +167,50 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); - registry.appointWallet(address(uint160(appointedWallet) + 100)); - vm.startPrank(address(uint160(appointedWallet) + 100)); + registry.appointWallet(address(uint160(appointedWallet) + 10)); + vm.startPrank(address(uint160(appointedWallet) + 10)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); // Carol vm.startPrank(carol); - registry.appointWallet(address(uint160(appointedWallet) + 200)); - vm.startPrank(address(uint160(appointedWallet) + 200)); + registry.appointWallet(address(uint160(appointedWallet) + 20)); + vm.startPrank(address(uint160(appointedWallet) + 20)); registry.setPublicKey(carol, 0x0000000090ab0000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 200)); + assertEq(addrValue, address(uint160(appointedWallet) + 20)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); // David vm.startPrank(david); - registry.appointWallet(address(uint160(appointedWallet) + 300)); - vm.startPrank(address(uint160(appointedWallet) + 300)); + registry.appointWallet(address(uint160(appointedWallet) + 30)); + vm.startPrank(address(uint160(appointedWallet) + 30)); registry.setPublicKey(david, 0x000000000000cdef000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x1234000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 200)); + assertEq(addrValue, address(uint160(appointedWallet) + 20)); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(david); - assertEq(addrValue, address(uint160(appointedWallet) + 300)); + assertEq(addrValue, address(uint160(appointedWallet) + 30)); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); } @@ -242,13 +242,13 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(bob); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 100)); + registry.appointWallet(address(uint160(appointedWallet) + 10)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // Carol @@ -257,16 +257,16 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(carol); assertEq(bytesValue, 0x0000000090ab0000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 200)); + registry.appointWallet(address(uint160(appointedWallet) + 20)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 200)); + assertEq(addrValue, address(uint160(appointedWallet) + 20)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); // David @@ -275,19 +275,19 @@ contract EncryptionRegistryTest is AragonTest { (addrValue, bytesValue) = registry.accounts(david); assertEq(bytesValue, 0x000000000000cdef000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 300)); + registry.appointWallet(address(uint160(appointedWallet) + 30)); (addrValue, bytesValue) = registry.accounts(alice); assertEq(addrValue, appointedWallet); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(carol); - assertEq(addrValue, address(uint160(appointedWallet) + 200)); + assertEq(addrValue, address(uint160(appointedWallet) + 20)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(david); - assertEq(addrValue, address(uint160(appointedWallet) + 300)); + assertEq(addrValue, address(uint160(appointedWallet) + 30)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); } @@ -395,32 +395,112 @@ contract EncryptionRegistryTest is AragonTest { } function test_getRegisteredAccountsOnlyReturnsAddressesOnce() public { - uint256 count = registry.getRegisteredAccounts().length; - assertEq(count, 0); + (address ad1,) = getWallet("wallet 1"); + (address ad2,) = getWallet("wallet 2"); + (address ad3,) = getWallet("wallet 3"); + + assertEq(registry.getRegisteredAccounts().length, 0); vm.startPrank(alice); - // Neutral + // No appointment registry.appointWallet(address(0x0)); + assertEq(registry.getRegisteredAccounts().length, 0, "Incorrect count"); - count = registry.getRegisteredAccounts().length; - assertEq(count, 0); + // Appoint + define pubKey's + registry.appointWallet(ad1); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); - // Appoint - registry.appointWallet(address(0x1111)); + vm.startPrank(ad1); + registry.setPublicKey(alice, hex"cdeef70d62f3a538739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + vm.startPrank(alice); + registry.appointWallet(ad2); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); - count = registry.getRegisteredAccounts().length; - assertEq(count, 1); + vm.startPrank(ad2); + registry.setPublicKey(alice, hex"00eef70d62f3a538739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); - registry.appointWallet(address(0x2222)); + vm.startPrank(alice); + registry.appointWallet(ad3); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); - count = registry.getRegisteredAccounts().length; - assertEq(count, 1); + vm.startPrank(ad3); + registry.setPublicKey(alice, hex"0000f70d62f3a538739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); - registry.appointWallet(address(0x3333)); + // Appoint self back + vm.startPrank(alice); + registry.appointWallet(address(0)); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + // Set own public key + registry.setOwnPublicKey(hex"1deef70d62f3a538739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + // Appoint + define pubKey's (2) + registry.appointWallet(ad1); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + vm.startPrank(ad1); + registry.setPublicKey(alice, hex"cdeef70d62f3a538739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + // Appoint self back + vm.startPrank(alice); + registry.appointWallet(address(0)); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + // BOB + + vm.startPrank(bob); + + // No appointment + registry.appointWallet(address(0x0)); + assertEq(registry.getRegisteredAccounts().length, 1, "Incorrect count"); + + // Appoint + define pubKey's + registry.appointWallet(ad1); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + vm.startPrank(ad1); + registry.setPublicKey(bob, hex"cdeef70d00000038739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + vm.startPrank(bob); + registry.appointWallet(ad2); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + vm.startPrank(ad2); + registry.setPublicKey(bob, hex"00eef70d00000038739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); - count = registry.getRegisteredAccounts().length; - assertEq(count, 1); + vm.startPrank(bob); + registry.appointWallet(ad3); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + vm.startPrank(ad3); + registry.setPublicKey(bob, hex"0000f70d00000038739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + // Appoint self back + vm.startPrank(bob); + registry.appointWallet(address(0)); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + // Set own public key + registry.setOwnPublicKey(hex"1deef70d00000038739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + // Appoint + define pubKey's (2) + registry.appointWallet(ad1); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); + + vm.startPrank(ad1); + registry.setPublicKey(bob, hex"cdeef70d00000038739fb51629eeca7d7cd4852b26a5b469f16af187fdbc7152"); + assertEq(registry.getRegisteredAccounts().length, 2, "Incorrect count"); } function test_shouldRevertIfAppointingAnotherSigner() public { @@ -504,10 +584,10 @@ contract EncryptionRegistryTest is AragonTest { // Bob vm.startPrank(bob); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(address(uint160(appointedWallet) + 100)); + registry.appointWallet(address(uint160(appointedWallet) + 10)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(address(uint160(appointedWallet) + 100)); + vm.startPrank(address(uint160(appointedWallet) + 10)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(bob, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -521,10 +601,10 @@ contract EncryptionRegistryTest is AragonTest { // Carol vm.startPrank(carol); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(address(uint160(appointedWallet) + 200)); + registry.appointWallet(address(uint160(appointedWallet) + 20)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(address(uint160(appointedWallet) + 200)); + vm.startPrank(address(uint160(appointedWallet) + 20)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(carol, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -541,10 +621,10 @@ contract EncryptionRegistryTest is AragonTest { // David vm.startPrank(david); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); - registry.appointWallet(address(uint160(appointedWallet) + 300)); + registry.appointWallet(address(uint160(appointedWallet) + 30)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setOwnPublicKey(0x0000567800000000000000000000000000000000000000000000000000000000); - vm.startPrank(address(uint160(appointedWallet) + 300)); + vm.startPrank(address(uint160(appointedWallet) + 30)); vm.expectRevert(abi.encodeWithSelector(IEncryptionRegistry.MustBeListed.selector)); registry.setPublicKey(david, 0x1234000000000000000000000000000000000000000000000000000000000000); @@ -598,14 +678,14 @@ contract EncryptionRegistryTest is AragonTest { assertEq(addrValue, address(0)); assertEq(bytesValue, 0x0000000000000000000000000000000000000000000000000000000000000000); - registry.appointWallet(address(uint160(appointedWallet) + 100)); + registry.appointWallet(address(uint160(appointedWallet) + 10)); // Appointed - vm.startPrank(address(uint160(appointedWallet) + 100)); + vm.startPrank(address(uint160(appointedWallet) + 10)); registry.setPublicKey(bob, 0x0000567800000000000000000000000000000000000000000000000000000000); (addrValue, bytesValue) = registry.accounts(bob); - assertEq(addrValue, address(uint160(appointedWallet) + 100)); + assertEq(addrValue, address(uint160(appointedWallet) + 10)); assertEq(bytesValue, 0x0000567800000000000000000000000000000000000000000000000000000000); } @@ -847,26 +927,29 @@ contract EncryptionRegistryTest is AragonTest { // Internal helpers function skipAppointedWallet(address appointedWallet) internal view returns (bool) { + // Avoid fuzz tests overflowing + if (appointedWallet >= address(uint160(0xFFFfFFfFfFFffFFfFFFffffFfFfFffFFfFFFFF00))) return true; + if ( appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) ) return true; - appointedWallet = address(uint160(appointedWallet) + 100); + appointedWallet = address(uint160(appointedWallet) + 10); if ( appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) ) return true; - appointedWallet = address(uint160(appointedWallet) + 100); + appointedWallet = address(uint160(appointedWallet) + 10); if ( appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob || appointedWallet == carol || appointedWallet == david || Address.isContract(appointedWallet) ) return true; - appointedWallet = address(uint160(appointedWallet) + 100); + appointedWallet = address(uint160(appointedWallet) + 10); if ( appointedWallet == address(0) || appointedWallet == alice || appointedWallet == bob