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] 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: