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

Fix: negative subgraph balances #544

Merged
merged 20 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
4120c8b
fix: call balanceOf during tokenVoting member init. Set context is us…
jordaniza Feb 26, 2024
ff1a414
fix: added additional tests for delegation
jordaniza Feb 26, 2024
1f7e137
fix: tests with conditional balance fetching
jordaniza Feb 26, 2024
30a9f00
rm: log alert file
jordaniza Feb 26, 2024
a6d1be1
docs: better commenting for the result container
jordaniza Feb 26, 2024
ad96b5a
docs: changelog
jordaniza Feb 27, 2024
4d2140e
fix: extendedTokenVotingMember default ids
jordaniza Feb 27, 2024
b72e3ee
fix: metaprogramming order of ids
jordaniza Feb 27, 2024
a4bd144
feat: created utils folder inside gov-erc20
jordaniza Mar 6, 2024
cd9a14b
fix: removed the context variable for contract in favour of the event…
jordaniza Mar 6, 2024
4ea625a
tests: add the ability to set the event.address when creating transfe…
jordaniza Mar 6, 2024
9143b6d
fix: froze packages to fix node dependency issues
jordaniza Mar 6, 2024
4d2a4a2
Merge branch 'develop' into fix/negative-subgraph-balances
jordaniza Mar 6, 2024
97c89fe
fix: bump osx-commons
jordaniza Mar 6, 2024
df1304c
Revert "fix: bump osx-commons"
jordaniza Mar 6, 2024
273fda1
fix: updated all links for consistence with docusaurus v3 (#543)
banasa44 Feb 22, 2024
a200ca8
Revert "fix: updated all links for consistence with docusaurus v3 (#5…
heueristik Feb 27, 2024
7b06ead
fix: resolved dependency issues by importing working package json and
banasa44 Feb 29, 2024
2e0cbaf
Merge branch 'fix/negative-subgraph-balances' of https://github.com/a…
jordaniza Mar 7, 2024
736f455
lint: prettier on package jsons
jordaniza Mar 7, 2024
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
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
138 changes: 113 additions & 25 deletions packages/subgraph/src/packages/token/governance-erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,157 @@ import {
DelegateVotesChanged,
} from '../../../generated/templates/GovernanceERC20/GovernanceERC20';
import {Transfer} from '../../../generated/templates/TokenVoting/ERC20';
import {ADDRESS_ZERO} from '../../utils/constants';
import {generateMemberEntityId} from '../../utils/ids';
import {Address, BigInt, dataSource, store} from '@graphprotocol/graph-ts';
import {Address, BigInt, dataSource, log, store} from '@graphprotocol/graph-ts';

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

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();
}

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;
}

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.
*/
class TokenVotingMemberResult {
entity: TokenVotingMember;
createdNew: boolean;

constructor(entity: TokenVotingMember, createNew: boolean) {
this.entity = entity;
this.createdNew = createNew;
}
}

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 {
jordaniza marked this conversation as resolved.
Show resolved Hide resolved
let context = dataSource.context();
let pluginId = context.getString('pluginId');
let tokenAddress = Address.fromString(context.getString('tokenAddress'));
jordaniza marked this conversation as resolved.
Show resolved Hide resolved

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 = Address.fromString(context.getString('tokenAddress'));

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 +167,10 @@ export function handleDelegateVotesChanged(event: DelegateVotesChanged): void {

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

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

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

packageEntity.save();

// Both GovernanceWrappedERC20/GovernanceERC20 use the `Transfer` event, so
// It's safe to create the same type of template for them.
let context = new DataSourceContext();
context.setString('pluginId', pluginEntityId);

// add the token address so we can fetch the user's balance on initialization
context.setString('tokenAddress', token.toHexString());
jordaniza marked this conversation as resolved.
Show resolved Hide resolved
GovernanceERC20.createWithContext(event.params.definingContract, context);
}
}
1 change: 1 addition & 0 deletions packages/subgraph/tests/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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';
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
Loading