Skip to content

Commit

Permalink
Testing work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
brickpop committed Jun 4, 2024
1 parent 87873e5 commit a1b0f45
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 43 deletions.
8 changes: 7 additions & 1 deletion packages/contracts/src/governance/MainVotingPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {MajorityVotingBase} from "./base/MajorityVotingBase.sol";
import {IMembers} from "../base/IMembers.sol";
import {IEditors} from "../base/IEditors.sol";
import {Addresslist} from "./base/Addresslist.sol";
import {MemberAccessPlugin} from "./MemberAccessPlugin.sol";
import {MemberAccessPlugin, MEMBER_ACCESS_INTERFACE_ID} from "./MemberAccessPlugin.sol";
import {SpacePlugin} from "../space/SpacePlugin.sol";

// The [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface ID of the contract.
Expand Down Expand Up @@ -74,6 +74,9 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers
/// @notice Raised when a content proposal is called with empty data
error EmptyContent();

/// @notice Thrown when the given contract doesn't support a required interface.
error InvalidInterface(address);

/// @notice Raised when a non-editor attempts to call a restricted function.
error Unauthorized();

Expand Down Expand Up @@ -111,6 +114,9 @@ contract MainVotingPlugin is Addresslist, MajorityVotingBase, IEditors, IMembers
_addAddresses(_initialEditors);
emit EditorsAdded(_initialEditors);

if (!_memberAccessPlugin.supportsInterface(MEMBER_ACCESS_INTERFACE_ID)) {
revert InvalidInterface(address(_memberAccessPlugin));
}
memberAccessPlugin = _memberAccessPlugin;
}

Expand Down
22 changes: 14 additions & 8 deletions packages/contracts/src/governance/MemberAccessPlugin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade
error ProposalExecutionForbidden(uint256 proposalId);

/// @notice Thrown when called from an incompatible contract.
error InvalidCallerInterface();
error InvalidInterface();

/// @notice Emitted when a proposal is approved by an editor.
/// @param proposalId The ID of the proposal.
Expand Down Expand Up @@ -167,7 +167,7 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade
!MainVotingPlugin(msg.sender).supportsInterface(type(IEditors).interfaceId) ||
!MainVotingPlugin(msg.sender).supportsInterface(type(Addresslist).interfaceId)
) {
revert InvalidCallerInterface();
revert InvalidInterface();
}

// Build the list of actions
Expand Down Expand Up @@ -233,17 +233,22 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade
}

// If the creator is an editor, we assume that the editor approves
approve(proposalId);
_approve(proposalId, _proposer);
} else {
proposal_.parameters.minApprovals = MIN_APPROVALS_WHEN_CREATED_BY_NON_EDITOR;
}
}

/// @inheritdoc IMultisig
/// @dev The second parameter is left empty to keep compatibility with the existing multisig interface
/// @param _proposalId The Id of the proposal to approve.
function approve(uint256 _proposalId) public {
if (!_canApprove(_proposalId, msg.sender)) {
revert ApprovalCastForbidden(_proposalId, msg.sender);
_approve(_proposalId, msg.sender);
}

/// @notice Internal implementation, allowing proposeAddMember() to specify the proposer.
function _approve(uint256 _proposalId, address _approver) internal {
if (!_canApprove(_proposalId, _approver)) {
revert ApprovalCastForbidden(_proposalId, _approver);
}

Proposal storage proposal_ = proposals[_proposalId];
Expand All @@ -254,16 +259,17 @@ contract MemberAccessPlugin is IMultisig, PluginUUPSUpgradeable, ProposalUpgrade
proposal_.approvals += 1;
}

proposal_.approvers[msg.sender] = true;
proposal_.approvers[_approver] = true;

emit Approved({proposalId: _proposalId, editor: msg.sender});
emit Approved({proposalId: _proposalId, editor: _approver});

if (_canExecute(_proposalId)) {
_execute(_proposalId);
}
}

/// @notice Rejects the given proposal immediately.
/// @param _proposalId The Id of the proposal to reject.
function reject(uint256 _proposalId) public {
if (!_canApprove(_proposalId, msg.sender)) {
revert ApprovalCastForbidden(_proposalId, msg.sender);
Expand Down
45 changes: 37 additions & 8 deletions packages/contracts/test/unit-testing/main-voting-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,25 +156,54 @@ describe('Main Voting Plugin', function () {
describe('initialize', async () => {
it('reverts if trying to re-initialize', async () => {
await expect(
memberAccessPlugin.initialize(dao.address, {
proposalDuration: 60 * 60 * 24 * 5,
})
mainVotingPlugin.initialize(
dao.address,
defaultMainVotingSettings,
[alice.address],
memberAccessPlugin.address
)
).to.be.revertedWith('Initializable: contract is already initialized');
});

it('Fails to initialize with an incompatible main voting plugin', async () => {
// ok
mainVotingPlugin = await deployWithProxy<MainVotingPlugin>(
new MainVotingPlugin__factory(alice)
);
await expect(
mainVotingPlugin.initialize(
dao.address,
defaultMainVotingSettings,
[alice.address],
memberAccessPlugin.address
)
).to.be.revertedWith('Initializable: contract is already initialized');
).to.not.be.reverted;

// not ok
mainVotingPlugin = await deployWithProxy<MainVotingPlugin>(
new MainVotingPlugin__factory(alice)
);
await expect(
spacePlugin.initialize(
mainVotingPlugin.initialize(
dao.address,
defaultInput.contentUri,
ADDRESS_ZERO
defaultMainVotingSettings,
[alice.address],
bob.address
)
).to.be.revertedWith('Initializable: contract is already initialized');
).to.be.reverted;

// not ok
mainVotingPlugin = await deployWithProxy<MainVotingPlugin>(
new MainVotingPlugin__factory(alice)
);
await expect(
mainVotingPlugin.initialize(
dao.address,
defaultMainVotingSettings,
[alice.address],
spacePlugin.address
)
).to.be.reverted;
});

it('The plugin has one editor after created', async () => {
Expand Down
35 changes: 9 additions & 26 deletions packages/contracts/test/unit-testing/member-access-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
EMPTY_DATA,
EXECUTE_PERMISSION_ID,
mineBlock,
PROPOSER_PERMISSION_ID,
ROOT_PERMISSION_ID,
UPDATE_ADDRESSES_PERMISSION_ID,
UPDATE_MULTISIG_SETTINGS_PERMISSION_ID,
Expand Down Expand Up @@ -142,6 +143,12 @@ describe('Member Access Plugin', function () {
);
// The DAO is ROOT on itself
await dao.grant(dao.address, dao.address, ROOT_PERMISSION_ID);
// The plugin can propose members on the member access helper
await dao.grant(
memberAccessPlugin.address,
mainVotingPlugin.address,
PROPOSER_PERMISSION_ID
);
// Alice can make the DAO execute arbitrary stuff (test)
await dao.grant(dao.address, alice.address, EXECUTE_PERMISSION_ID);

Expand All @@ -153,36 +160,12 @@ describe('Member Access Plugin', function () {
});

describe('initialize', () => {
it('Fails to initialize with an incompatible main voting plugin', async () => {
// ok
memberAccessPlugin = await deployWithProxy<MemberAccessPlugin>(
new MemberAccessPlugin__factory(alice)
);
await expect(
memberAccessPlugin.initialize(dao.address, {
proposalDuration: 60 * 60 * 24 * 5,
})
).to.not.be.reverted;

// not ok
memberAccessPlugin = await deployWithProxy<MemberAccessPlugin>(
new MemberAccessPlugin__factory(alice)
);
await expect(
memberAccessPlugin.initialize(dao.address, {
proposalDuration: 60 * 60 * 24 * 5,
})
).to.be.reverted;

// not ok
memberAccessPlugin = await deployWithProxy<MemberAccessPlugin>(
new MemberAccessPlugin__factory(alice)
);
it('reverts if trying to re-initialize', async () => {
await expect(
memberAccessPlugin.initialize(dao.address, {
proposalDuration: 60 * 60 * 24 * 5,
})
).to.be.reverted;
).to.be.revertedWith('Initializable: contract is already initialized');
});
});

Expand Down

0 comments on commit a1b0f45

Please sign in to comment.