Skip to content

Commit

Permalink
feat: getPersonas and callAsPersona
Browse files Browse the repository at this point in the history
  • Loading branch information
DanThomp507 committed Dec 8, 2022
1 parent 6b0c0a1 commit 8ac20e9
Show file tree
Hide file tree
Showing 11 changed files with 464 additions and 69 deletions.
2 changes: 1 addition & 1 deletion docs/assets/search.js

Large diffs are not rendered by default.

82 changes: 48 additions & 34 deletions docs/classes/Pod.html

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions docs/classes/Proposal.html

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions docs/modules.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions scripts/approve-superproposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ async function main() {
let data;
try {
if (isMember) {
data = superPod.populateBurn(dummyAccount);
data = superPod.burnMember(dummyAccount);
} else {
data = superPod.populateMint(dummyAccount);
data = superPod.mintMember(dummyAccount);
}
await superPod.propose(data, subPod.safe);
} catch (err) {
Expand Down
5 changes: 3 additions & 2 deletions scripts/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ const multiPodInput = [
async function main() {
const { walletOne } = setup(5);

const pod = await getPod(25);

const pod = await getPod('1-member-pods.pod.eth');
console.log('pod', pod);
const personas = await pod.getPersonas('0x85760ef61c0ccB7BCC4C7A0116d80D59D92e736d');
console.log('personas', personas);
}

main();
112 changes: 110 additions & 2 deletions src/Pod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ethers } from 'ethers';
import ENS, { labelhash } from '@ensdomains/ensjs';
import { getControllerByAddress, getDeployment } from '@orcaprotocol/contracts';
import axios from 'axios';
import { getPersonas } from './pod-utils';
import { config } from './config';
import { getPodFetchersByAddressOrEns, getPodFetchersById } from './fetchers';
import {
Expand Down Expand Up @@ -187,6 +188,12 @@ export default class Pod {
*/
memberEOAs?: string[];

/**
* @ignore
* @property Admin pod object
*/
adminPod?: Pod;

/**
* @ignore
* @property Array of Pod objects for any member pods
Expand Down Expand Up @@ -326,7 +333,9 @@ export default class Pod {
}

// All safe transactions that have a given nonce.
const safeTransactions = await getSafeTransactionsBySafe(this.safe, { nonce });
const safeTransactions = await getSafeTransactionsBySafe(this.safe, {
nonce,
});
if (safeTransactions.length === 0) throw new Error('Could not find a related safe transaction');
if (safeTransactions.length === 1) return new Proposal(this, this.nonce, safeTransactions[0]);
if (safeTransactions.length === 2)
Expand Down Expand Up @@ -418,6 +427,99 @@ export default class Pod {
return this.memberPods;
};

/**
* Calls a Pod method via a persona.
* The persona object can be fetched by using `Pod.getPersonas(address)`
* E.g.,
* ```
* callAsPersona(
* pod.mintMember,
* [newMember],
* { type: 'admin', address: userAddress },
* signer,
* )
* ```
* The sender must be a signer in the admin case, or the address of the sender.
* The sender must be a member of the relevant pod.
*
* @param method
* @param args
* @param persona
* @param sender
* @returns
*/
callAsPersona = async (
method: any,
args: Array<any>,
persona: { type: string; address: string },
sender?: ethers.Signer | string,
) => {
switch (persona.type) {
case 'admin':
if (!sender) throw new Error(`Expected sender to be signer, but received ${sender}`);
args.push(sender);
return method.apply(this, args);
case 'member':
return this.propose(await method.apply(this, args), persona.address);
case 'adminPodMember': {
let adminPod;
if (this.adminPod) adminPod = this.adminPod;
else {
adminPod = await new Pod(this.admin);
this.adminPod = adminPod;
}
let senderAddress = sender;
if (sender instanceof ethers.Signer) senderAddress = await sender.getAddress();
let result;
try {
result = await adminPod.propose(await method.apply(this, args), senderAddress);
} catch (err) {
// Make the error message more specific
if (err.message.includes('Sender must be a member of')) {
throw new Error('Sender must be a member of the admin pod');
}
}
return result;
}
case 'subPodMember': {
const subPod = await new Pod(persona.address);

const isSubPodMember = await this.isMember(subPod.safe);

if (!isSubPodMember) throw new Error('Sub pod was not a member of this pod');

const senderAddress = sender instanceof ethers.Signer ? await sender.getAddress() : sender;
let result;
try {
result = await subPod.propose(
await this.propose(await method.apply(this, args), subPod.safe),
senderAddress,
);
} catch (err) {
// Make the error message more specific
if (err.message.includes('Sender must be a member of')) {
throw new Error('Sender must be a member of the sub pod');
}
}
return result;
}
default:
throw new Error(`${persona.type} was not a valid persona`);
}
};

/**
* Fetches all personas for a given address related to this pod.
* All personas return as an object indicating the type of the persona and the address of the persona.
* For members and admins, the persona address is the user's address.
* For admin pods and sub pods, the persona address is the pod address.
* @param address
*/
getPersonas(address: string) {
// Function declared separately out for test/mocking purposes.
return getPersonas(this, address);
}

/**
* Checks if user is a member of this pod
* @param address
Expand All @@ -444,7 +546,13 @@ export default class Pod {
isAdminPodMember = async (address: string): Promise<boolean> => {
const checkedAddress = checkAddress(address);
if (!this.admin) return false;
const adminPod = await new Pod(this.admin);
let adminPod;
if (this.adminPod) adminPod = this.adminPod;
else {
adminPod = await new Pod(this.admin);
this.adminPod = adminPod;
}

if (!adminPod) return false;
return adminPod.isMember(checkedAddress);
};
Expand Down
20 changes: 20 additions & 0 deletions src/pod-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type Pod from './Pod';

// Functions are put here so that they can be mocked for testing purposes.

// eslint-disable-next-line import/prefer-default-export
export async function getPersonas(
pod: Pod,
address: string,
): Promise<Array<{ type: string; address: string }>> {
const personas = [];
if (pod.isAdmin(address)) personas.push({ type: 'admin', address });
if (await pod.isAdminPodMember(address)) personas.push({ type: 'adminPodMember', address });
if (await pod.isMember(address)) personas.push({ type: 'member', address });

const memberSubPods = await pod.getSubPodsByMember(address);
memberSubPods.forEach(subPod => {
personas.push({ type: 'subPodMember', address: subPod.safe });
});
return personas;
}
2 changes: 2 additions & 0 deletions test/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export const artNautPod = {
],
};

export const gmPod = {};

/**
* Turns arrays into the stupid format that GQL returns
*/
Expand Down
File renamed without changes.
Loading

0 comments on commit 8ac20e9

Please sign in to comment.