Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subgraph plugin members support #4

Merged
merged 2 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading