Skip to content

Commit

Permalink
Merge pull request #544 from aragon/fix/negative-subgraph-balances
Browse files Browse the repository at this point in the history
Fix: negative subgraph balances
  • Loading branch information
jordaniza authored Mar 8, 2024
2 parents 9d9e764 + 736f455 commit 19f4c42
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 68 deletions.
7 changes: 7 additions & 0 deletions packages/subgraph/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UPCOMING]

## 1.5.0

### Changed

- Fixed bug with negative number balances and missing delegation history for existing ERC20 tokens using `TokenVoting`
- Fixed bugs regarding inconsistent memberIds in various parts of the codebase. This primarily affects delegation.

### Changed

- Removed `createERC1155TokenCalls`, `createTokenCalls`, `createWrappedTokenCalls` and `createDummyActions` and use the equivalent functions `createERC20TokenCalls`, `createERC1155TokenCalls` and `createDummyAction` from `@aragon/sdk-commons-subgraph`
Expand Down
89 changes: 65 additions & 24 deletions packages/subgraph/src/packages/token/governance-erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,71 +6,109 @@ import {
} from '../../../generated/templates/GovernanceERC20/GovernanceERC20';
import {Transfer} from '../../../generated/templates/TokenVoting/ERC20';
import {generateMemberEntityId} from '../../utils/ids';
import {
TokenVotingMemberResult,
getDelegateeId,
getERC20Balance,
getVotingPower,
} from './utils';
import {Address, BigInt, dataSource, store} from '@graphprotocol/graph-ts';

function getOrCreateMember(user: Address, pluginId: string): TokenVotingMember {
function getOrCreateMember(
user: Address,
pluginId: string,
tokenAddress: Address
): TokenVotingMemberResult {
let memberEntityId = generateMemberEntityId(
user,
Address.fromString(pluginId)
Address.fromString(pluginId),
user
);
let createdNew = false;
let member = TokenVotingMember.load(memberEntityId);

if (!member) {
createdNew = true;
member = new TokenVotingMember(memberEntityId);
member.address = user;
member.balance = BigInt.zero();
member.balance = getERC20Balance(user, tokenAddress);
member.plugin = pluginId;

member.delegatee = null;
member.votingPower = BigInt.zero();
member.delegatee = getDelegateeId(user, tokenAddress, pluginId);
member.votingPower = getVotingPower(user, tokenAddress);
}

return member;
return new TokenVotingMemberResult(member, createdNew);
}

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

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

// in the case of an existing member, update the balance
if (!result.createdNew) {
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);
let result = getOrCreateMember(event.params.to, pluginId, tokenAddress);
let toMember = result.entity;

// in the case of an existing member, update the balance
if (!result.createdNew) {
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');
let tokenAddress = event.address;

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();
let resultFromDelegate = getOrCreateMember(
event.params.fromDelegate,
pluginId,
tokenAddress
);
resultFromDelegate.entity.save();
}

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

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

delegator.delegatee = delegatee;
const resultDelegatee = getOrCreateMember(
toDelegate,
pluginId,
tokenAddress
);
const delegatee = resultDelegatee.entity;
const delegateeId = generateMemberEntityId(
Address.fromString(pluginId),
Address.fromBytes(delegatee.address)
);
delegatee.save();
delegator.delegatee = delegateeId;
}

delegator.save();
}
}
Expand All @@ -82,7 +120,10 @@ export function handleDelegateVotesChanged(event: DelegateVotesChanged): void {

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

let result = getOrCreateMember(delegate, pluginId, tokenAddress);
let member = result.entity;

if (isZeroBalanceAndVotingPower(member.balance, newVotingPower)) {
if (shouldRemoveMember(event.address, delegate)) {
Expand Down
1 change: 0 additions & 1 deletion packages/subgraph/src/packages/token/token-voting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ export function handleMembershipContractAnnounced(
return;
}
packageEntity.token = tokenAddress as string;

packageEntity.save();

// Both GovernanceWrappedERC20/GovernanceERC20 use the `Transfer` event, so
Expand Down
60 changes: 60 additions & 0 deletions packages/subgraph/src/packages/token/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {TokenVotingMember} from '../../../generated/schema';
import {GovernanceERC20 as GovernanceERC20Contract} from '../../../generated/templates/GovernanceERC20/GovernanceERC20';
import {ADDRESS_ZERO} from '../../utils/constants';
import {generateMemberEntityId} from '../../utils/ids';
import {Address, BigInt} from '@graphprotocol/graph-ts';

export function getERC20Balance(user: Address, tokenAddress: Address): BigInt {
let contract = GovernanceERC20Contract.bind(tokenAddress);
let balance = contract.balanceOf(user);
return balance;
}

export function getDelegation(
user: Address,
tokenAddress: Address
): string | null {
let contract = GovernanceERC20Contract.bind(tokenAddress);
let delegate = contract.delegates(user);

return delegate === Address.fromString(ADDRESS_ZERO)
? null
: delegate.toHexString();
}

export function getDelegateeId(
user: Address,
tokenAddress: Address,
pluginId: string
): string | null {
let delegatee = getDelegation(user, tokenAddress);
return delegatee
? generateMemberEntityId(
Address.fromString(pluginId),
Address.fromString(user.toHexString())
)
: null;
}

export function getVotingPower(user: Address, tokenAddress: Address): BigInt {
let contract = GovernanceERC20Contract.bind(tokenAddress);
let votingPower = contract.getVotes(user);
return votingPower;
}

/**
* A container for the result of the `getOrCreateMember` function.
* @param entity - The `TokenVotingMember` entity.
* @param createdNew - A boolean indicating whether the entity was created new
* or if false it was previously created. If the entity was created new, it already
* has the latest balance of the user, so no need to then update the balance.
*/
export class TokenVotingMemberResult {
entity: TokenVotingMember;
createdNew: boolean;

constructor(entity: TokenVotingMember, createNew: boolean) {
this.entity = entity;
this.createdNew = createNew;
}
}
3 changes: 3 additions & 0 deletions packages/subgraph/tests/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ export const ADDRESS_THREE = '0x0000000000000000000000000000000000000003';
export const ADDRESS_FOUR = '0x0000000000000000000000000000000000000004';
export const ADDRESS_FIVE = '0x0000000000000000000000000000000000000005';
export const ADDRESS_SIX = '0x0000000000000000000000000000000000000006';
export const ADDRESS_SEVEN = '0x0000000000000000000000000000000000000007';
export const DAO_ADDRESS = '0x00000000000000000000000000000000000000da';
export const CONTRACT_ADDRESS = '0x00000000000000000000000000000000000000Ad';
export const DAO_TOKEN_ADDRESS = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
export const DEFAULT_MOCK_EVENT_ADDRESS =
'0xA16081F360e3847006dB660bae1c6d1b2e17eC2A';

export const ZERO = '0';
export const ONE = '1';
Expand Down
5 changes: 3 additions & 2 deletions packages/subgraph/tests/helpers/method-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,9 @@ class TokenVotingMemberMethods extends TokenVotingMember {
memberAddress: string = ADDRESS_ONE,
pluginAddress: string = CONTRACT_ADDRESS
): TokenVotingMemberMethods {
const plugin = Address.fromHexString(pluginAddress);
let id = memberAddress.concat('_').concat(plugin.toHexString());
const plugin = Address.fromBytes(Bytes.fromHexString(pluginAddress));
const member = Address.fromBytes(Bytes.fromHexString(memberAddress));
let id = generateMemberEntityId(plugin, member);

this.id = id;
this.address = Address.fromHexString(memberAddress);
Expand Down
Loading

0 comments on commit 19f4c42

Please sign in to comment.