Skip to content

Commit

Permalink
PSV plugin setup and metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
brickpop committed Aug 25, 2023
1 parent 5d33538 commit 371e838
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 11 deletions.
17 changes: 9 additions & 8 deletions packages/contracts/src/PersonalSpaceVotingPluginSetup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ contract PersonalSpaceVotingPluginSetup is PluginSetup {
/// @notice The address of the `PersonalSpaceVotingPlugin` plugin logic contract to be cloned.
address private immutable implementation_;

/// @notice Thrown if the admin address is zero.
/// @param admin The admin address.
error EditorAddressInvalid(address admin);
/// @notice Thrown if the editor address is zero.
/// @param editor The initial editor address.
error EditorAddressInvalid(address editor);

/// @notice The constructor setting the `PersonalSpaceVotingPlugin` implementation contract to clone from.
constructor() {
Expand All @@ -34,10 +34,10 @@ contract PersonalSpaceVotingPluginSetup is PluginSetup {
bytes calldata _data
) external returns (address plugin, PreparedSetupData memory preparedSetupData) {
// Decode `_data` to extract the params needed for cloning and initializing the `PersonalSpaceVotingPlugin` plugin.
address admin = abi.decode(_data, (address));
address editor = abi.decode(_data, (address));

if (admin == address(0)) {
revert EditorAddressInvalid({admin: admin});
if (editor == address(0)) {
revert EditorAddressInvalid({editor: editor});
}

// Clone plugin contract.
Expand All @@ -50,11 +50,11 @@ contract PersonalSpaceVotingPluginSetup is PluginSetup {
PermissionLib.MultiTargetPermission[]
memory permissions = new PermissionLib.MultiTargetPermission[](2);

// Grant `ADMIN_EXECUTE_PERMISSION` of the plugin to the admin.
// Grant `ADMIN_EXECUTE_PERMISSION` of the plugin to the editor.
permissions[0] = PermissionLib.MultiTargetPermission(
PermissionLib.Operation.Grant,
plugin,
admin,
editor,
PermissionLib.NO_CONDITION,
PersonalSpaceVotingPlugin(plugin).EDITOR_PERMISSION_ID()
);
Expand All @@ -80,6 +80,7 @@ contract PersonalSpaceVotingPluginSetup is PluginSetup {
// Prepare permissions
permissions = new PermissionLib.MultiTargetPermission[](1);

// Revoke EXECUTE on the DAO
permissions[0] = PermissionLib.MultiTargetPermission(
PermissionLib.Operation.Revoke,
_dao,
Expand Down
22 changes: 22 additions & 0 deletions packages/contracts/src/personal-space-voting-build-metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"ui": {},
"change": "Initial build.",
"pluginSetup": {
"prepareInstallation": {
"description": "The information required for the installation of build 1.",
"inputs": [
{
"name": "_initialEditorAddress",
"type": "address",
"internalType": "address",
"description": "The address of the first address to be granted the editor permission."
}
]
},
"prepareUpdate": {},
"prepareUninstallation": {
"description": "The information required for the uninstallation of build 1.",
"inputs": []
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Personal Space Voting Plugin",
"description": "",
"images": {}
}
2 changes: 1 addition & 1 deletion packages/contracts/src/space-release-metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "SpacePlugin",
"name": "Space Plugin",
"description": "",
"images": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { expect } from "chai";
import { ethers } from "hardhat";

import {
PersonalSpaceVotingPlugin__factory,
PersonalSpaceVotingPluginSetup,
PersonalSpaceVotingPluginSetup__factory,
} from "../../typechain";
import metadata from "../../src/personal-space-voting-build-metadata.json";
import { psvpInterface } from "./personal-space-governance";
import { getNamedTypesFromMetadata, Operation } from "../helpers/types";
import { deployTestDao } from "../helpers/test-dao";
import { getInterfaceID } from "../../utils/interfaces";

const abiCoder = ethers.utils.defaultAbiCoder;
const AddressZero = ethers.constants.AddressZero;
const EMPTY_DATA = "0x";

// Permissions
const EDITOR_PERMISSION_ID = ethers.utils.id(
"EDITOR_PERMISSION",
);
const EXECUTE_PERMISSION_ID = ethers.utils.id("EXECUTE_PERMISSION");

describe("Personal Space Voting Plugin Setup", function () {
let ownerAddress: string;
let signers: any;
let adminSetup: PersonalSpaceVotingPluginSetup;
let implementationAddress: string;
let targetDao: any;
let minimum_data: any;

before(async () => {
signers = await ethers.getSigners();
ownerAddress = await signers[0].getAddress();
targetDao = await deployTestDao(signers[0]);

minimum_data = abiCoder.encode(
getNamedTypesFromMetadata(
metadata.pluginSetup.prepareInstallation.inputs,
),
[ownerAddress],
);

const PersonalSpaceVotingPluginSetup =
new PersonalSpaceVotingPluginSetup__factory(signers[0]);
adminSetup = await PersonalSpaceVotingPluginSetup.deploy();

implementationAddress = await adminSetup.implementation();
});

it("does not support the empty interface", async () => {
expect(await adminSetup.supportsInterface("0xffffffff")).to.be.false;
});

it("creates admin address base with the correct interface", async () => {
const factory = new PersonalSpaceVotingPlugin__factory(signers[0]);
const adminAddressContract = factory.attach(implementationAddress);

expect(
await adminAddressContract.supportsInterface(
getInterfaceID(psvpInterface),
),
).to.be.eq(true);
});

describe("prepareInstallation", async () => {
it("fails if data is empty, or not of minimum length", async () => {
await expect(
adminSetup.prepareInstallation(targetDao.address, EMPTY_DATA),
).to.be.reverted;

await expect(
adminSetup.prepareInstallation(
targetDao.address,
minimum_data.substring(0, minimum_data.length - 2),
),
).to.be.reverted;

await expect(
adminSetup.prepareInstallation(targetDao.address, minimum_data),
).not.to.be.reverted;
});

it("reverts if encoded address in `_data` is zero", async () => {
const dataWithAddressZero = abiCoder.encode(
getNamedTypesFromMetadata(
metadata.pluginSetup.prepareInstallation.inputs,
),
[AddressZero],
);

await expect(
adminSetup.prepareInstallation(targetDao.address, dataWithAddressZero),
)
.to.be.revertedWithCustomError(
adminSetup,
"EditorAddressInvalid",
)
.withArgs(AddressZero);
});

it("correctly returns plugin, helpers and permissions", async () => {
const nonce = await ethers.provider.getTransactionCount(
adminSetup.address,
);
const anticipatedPluginAddress = ethers.utils.getContractAddress({
from: adminSetup.address,
nonce,
});

const {
plugin,
preparedSetupData: { helpers, permissions },
} = await adminSetup.callStatic.prepareInstallation(
targetDao.address,
minimum_data,
);

expect(plugin).to.be.equal(anticipatedPluginAddress);
expect(helpers.length).to.be.equal(0);
expect(permissions.length).to.be.equal(2);
expect(permissions).to.deep.equal([
[
Operation.Grant,
plugin,
ownerAddress,
AddressZero,
EDITOR_PERMISSION_ID,
],
[
Operation.Grant,
targetDao.address,
plugin,
AddressZero,
EXECUTE_PERMISSION_ID,
],
]);
});

it("correctly sets up the plugin", async () => {
const daoAddress = targetDao.address;

const nonce = await ethers.provider.getTransactionCount(
adminSetup.address,
);
const anticipatedPluginAddress = ethers.utils.getContractAddress({
from: adminSetup.address,
nonce,
});

await adminSetup.prepareInstallation(daoAddress, minimum_data);

const factory = new PersonalSpaceVotingPlugin__factory(signers[0]);
const adminAddressContract = factory.attach(anticipatedPluginAddress);

expect(await adminAddressContract.dao()).to.be.equal(daoAddress);
});
});

describe("prepareUninstallation", async () => {
it("correctly returns permissions", async () => {
const plugin = ethers.Wallet.createRandom().address;

const permissions = await adminSetup.callStatic.prepareUninstallation(
targetDao.address,
{
plugin,
currentHelpers: [],
data: EMPTY_DATA,
},
);

expect(permissions.length).to.be.equal(1);
expect(permissions).to.deep.equal([
[
Operation.Revoke,
targetDao.address,
plugin,
AddressZero,
EXECUTE_PERMISSION_ID,
],
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type InitData = { contentUri: string };
export const defaultInitData: InitData = {
contentUri: "ipfs://",
};
export const adminInterface = new ethers.utils.Interface([
export const psvpInterface = new ethers.utils.Interface([
"function initialize(address)",
"function executeProposal(bytes,tuple(address,uint256,bytes)[],uint256)",
]);
Expand Down Expand Up @@ -245,7 +245,7 @@ describe("Personal Geo Browser Space", function () {
it("supports the `Admin` interface", async () => {
expect(
await personalSpaceVotingPlugin.supportsInterface(
getInterfaceID(adminInterface),
getInterfaceID(psvpInterface),
),
).to
.be.true;
Expand Down

0 comments on commit 371e838

Please sign in to comment.