Skip to content

Commit

Permalink
Merge pull request #4 from vocdoni/f/plugin_members_subgraph
Browse files Browse the repository at this point in the history
Subgraph plugin members support
  • Loading branch information
emmdim authored Nov 15, 2023
2 parents 2c7143f + 07ce120 commit a4d9452
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/js-client/src/internal/graphql-queries/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// add your grapql queries here and export them with this file
export * from './settings';
export * from './proposal';
export * from './members';
26 changes: 26 additions & 0 deletions packages/js-client/src/internal/graphql-queries/members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { gql } from 'graphql-request';

export const QueryPluginMembers = gql`
query PluginMembers($address: String!, $block: Block_height) {
pluginMembers(block: $block, where: { pluginAddress: $address }) {
id
address
balance
votingPower
plugin {
id
}
proposals {
id
}
delegatee {
id
address
}
delegators {
id
address
}
}
}
`;
23 changes: 22 additions & 1 deletion packages/subgraph/manifest/subgraph.placeholder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,25 @@ templates:
- event: TallyApproval(indexed uint256,indexed address)
handler: handleTallyApproval
file: ./src/plugin/plugin.ts

- name: GovernanceERC20
kind: ethereum/contract
network: {{network}}
source:
abi: GovernanceERC20
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- PluginMember
abis:
- name: GovernanceERC20
file: $PLUGIN_MODULE/artifacts/@aragon/osx/token/ERC20/governance/GovernanceERC20.sol/GovernanceERC20.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
- event: DelegateChanged(indexed address,indexed address,indexed address)
handler: handleDelegateChanged
- event: DelegateVotesChanged(indexed address,uint256,uint256)
handler: handleDelegateVotesChanged
file: ./src/plugin/governance-erc20.ts
8 changes: 7 additions & 1 deletion packages/subgraph/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,14 @@ type Plugin implements PluginInstallation @entity {
type PluginMember @entity {
id: ID! # plugin_address + member_address
address: String # address as string to facilitate filtering by address on the UI
proposals: [PluginProposalMember!]! @derivedFrom(field: "approver")
balance: BigInt!
plugin: Plugin!
pluginAddress: String! # address as string to facilitate filtering by address on the UI
proposals: [PluginProposalMember!]! @derivedFrom(field: "approver")
delegatee: PluginMember
votingPower: BigInt
# we assume token owners and/or delegatees are members
delegators: [PluginMember!]! @derivedFrom(field: "delegatee")
}

type PluginProposalMember @entity(immutable: true) {
Expand Down
112 changes: 112 additions & 0 deletions packages/subgraph/src/plugin/governance-erc20.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {PluginMember} from '../../generated/schema';
import {
DelegateChanged,
DelegateVotesChanged,
Transfer,
} from '../../generated/templates/GovernanceERC20/GovernanceERC20';
import {GovernanceERC20 as GovernanceERC20Contract} from '../../generated/templates/GovernanceERC20/GovernanceERC20';
import {Address, BigInt, dataSource, store} from '@graphprotocol/graph-ts';

function getOrCreateMember(user: Address, pluginId: string): PluginMember {
let id = [user.toHexString(), pluginId].join('_');
let member = PluginMember.load(id);
if (!member) {
member = new PluginMember(id);
member.address = user.toHexString();
member.balance = BigInt.zero();
member.plugin = pluginId;
member.pluginAddress = dataSource.address().toHexString();
member.delegatee = null;
member.votingPower = BigInt.zero();
}

return member;
}

export function handleTransfer(event: Transfer): void {
let context = dataSource.context();
let pluginId = context.getString('pluginId');

if (event.params.from != Address.zero()) {
let fromMember = getOrCreateMember(event.params.from, pluginId);
fromMember.balance = fromMember.balance.minus(event.params.value);
fromMember.save();
}

if (event.params.to != Address.zero()) {
let toMember = getOrCreateMember(event.params.to, pluginId);
toMember.balance = toMember.balance.plus(event.params.value);
toMember.save();
}
}

export function handleDelegateChanged(event: DelegateChanged): void {
let context = dataSource.context();
let pluginId = context.getString('pluginId');
const toDelegate = event.params.toDelegate;

// make sure `fromDelegate` & `toDelegate`are members
if (event.params.fromDelegate != Address.zero()) {
let fromMember = getOrCreateMember(event.params.fromDelegate, pluginId);
fromMember.save();
}
if (toDelegate != Address.zero()) {
let toMember = getOrCreateMember(toDelegate, pluginId);
toMember.save();
}

// make sure `delegator` is member and set delegatee
if (event.params.delegator != Address.zero()) {
let delegator = getOrCreateMember(event.params.delegator, pluginId);

// set delegatee
let delegatee: string | null = null;
if (toDelegate != Address.zero()) {
delegatee = [toDelegate.toHexString(), pluginId].join('_');

delegator.delegatee = delegatee;
}

delegator.save();
}
}

export function handleDelegateVotesChanged(event: DelegateVotesChanged): void {
const delegate = event.params.delegate;
if (delegate == Address.zero()) return;
const newVotingPower = event.params.newBalance;

const context = dataSource.context();
const pluginId = context.getString('pluginId');
let member = getOrCreateMember(delegate, pluginId);

if (isZeroBalanceAndVotingPower(member.balance, newVotingPower)) {
if (shouldRemoveMember(event.address, delegate)) {
store.remove('PluginMember', member.id);
return;
}
}
member.votingPower = newVotingPower;
member.save();
}

function isZeroBalanceAndVotingPower(
memberBalance: BigInt,
votingPower: BigInt
): boolean {
return (
memberBalance.equals(BigInt.zero()) && votingPower.equals(BigInt.zero())
);
}

function shouldRemoveMember(
contractAddress: Address,
delegate: Address
): boolean {
const governanceERC20Contract = GovernanceERC20Contract.bind(contractAddress);
const delegates = governanceERC20Contract.try_delegates(delegate);
if (!delegates.reverted) {
return delegates.value == delegate || delegates.value == Address.zero();
}
return false;
}
11 changes: 10 additions & 1 deletion packages/subgraph/src/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PluginProposal,
TallyElement,
} from '../../generated/schema';
import {GovernanceERC20} from '../../generated/templates';
import {
ExecutionMultisigMembersAdded,
ExecutionMultisigMembersRemoved,
Expand All @@ -14,7 +15,7 @@ import {
TallyApproval,
TallySet,
} from '../../generated/templates/Plugin/VocdoniVoting';
import {Address, dataSource} from '@graphprotocol/graph-ts';
import {Address, DataSourceContext, dataSource} from '@graphprotocol/graph-ts';

export function handlePluginSettingsUpdated(
event: PluginSettingsUpdated
Expand Down Expand Up @@ -45,6 +46,14 @@ export function handlePluginSettingsUpdated(
pluginEntity.censusStrategyURI = event.params.censusStrategyURI;
pluginEntity.save();
}

// Create template
const pluginContext = new DataSourceContext();
pluginContext.setString('pluginId', installationId.toHexString());
GovernanceERC20.createWithContext(
event.params.daoTokenAddress,
pluginContext
);
}
}

Expand Down

0 comments on commit a4d9452

Please sign in to comment.