Skip to content

Commit

Permalink
Add token stats
Browse files Browse the repository at this point in the history
  • Loading branch information
prevostc committed Aug 31, 2024
1 parent 61de2f5 commit 3b52404
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 126 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"build": "graph build",
"format": "prettier . --write",
"test": "yarn run --silent test:lint && yarn run --silent test:unit",
"test:unit": "graph test",
"test:unit": "echo 'No unit tests defined'",
"test:lint": "prettier . --check",
"infra:start": "docker compose up -d",
"infra:stop": "docker compose down",
Expand Down
43 changes: 34 additions & 9 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,57 @@ type Token @entity(immutable: true) {
name: String
"The number of decimals the token uses"
decimals: BigInt!
"Holder list of the token"
balances: [TokenBalance!]! @derivedFrom(field: "token")
}

"""
Token statistics. This entity is updated by the subgraph.
"""
type TokenStatistic @entity {
"Token address"
id: Bytes!

"Token"
token: Token!

"The total supply of the token"
totalSupply: BigInt!

"How many holders this token has. With balance > 0"
holderCount: BigInt!
}

"""
A contract where transfers from/to are ignored
This is used to account for contracts that are not ERC20 compliant
but allow staking one token like beefy boosts
"""
type IgnoredContract @entity {
type IgnoredContract @entity(immutable: true) {
id: Bytes!
}

# account details
type Account @entity {
#account address
"""
An holder account. Can be a user or a contract
"""
type Account @entity(immutable: true) {
"Account address"
id: Bytes!
#balances
"Account balance"
balances: [TokenBalance!]! @derivedFrom(field: "account")
}

# token balance details
"""
A token balance represents the amount of a specific token that an account holds
"""
type TokenBalance @entity {
"Account address + Token address"
id: Bytes!
#token

"The token that this balance is for"
token: Token!
#account
"Account that holds the token"
account: Account!
#amount
"The amount of the token this account holds"
amount: BigInt!
}
5 changes: 2 additions & 3 deletions src/classic/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { BoostDeployed as BoostCreated } from "../../generated/ClassicBoostFacto
import { ClassicVault as ClassicVaultContract } from "../../generated/ClassicVaultFactory/ClassicVault"
import { BeefyERC20Product as BeefyERC20ProductTemplate, ClassicVault as ClassicVaultTemplate } from "../../generated/templates"
import { fetchAndSaveTokenData } from "../common/utils/token"
import { getIgnoredContract } from "../common/entity/ignored"
import { addIgnoredContract } from "../common/entity/ignored"
import { RANDOM } from "../random"

export function handleClassicVaultOrStrategyCreated(event: VaultOrStrategyCreated): void {
Expand All @@ -29,8 +29,7 @@ export function handleClassicVaultOrStrategyCreated(event: VaultOrStrategyCreate

export function handleClassicBoostCreated(event: BoostCreated): void {
const address = event.params.boost
const ignored = getIgnoredContract(address)
ignored.save()
addIgnoredContract(address)
}

export function handleClassicVaultInitialized(event: VaultInitialized): void {
Expand Down
3 changes: 2 additions & 1 deletion src/common/entity/account.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Bytes } from "@graphprotocol/graph-ts"
import { Account } from "../../../generated/schema"

export function getAccount(accountAddress: Bytes): Account {
export function createAccount(accountAddress: Bytes): Account {
let account = Account.load(accountAddress)
if (!account) {
account = new Account(accountAddress)
account.save()
}

return account
Expand Down
5 changes: 2 additions & 3 deletions src/common/entity/ignored.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ export function shouldIgnoreContract(contractAddress: Bytes): boolean {
return IgnoredContract.load(contractAddress) !== null
}

export function getIgnoredContract(contractAddress: Bytes): IgnoredContract {
export function addIgnoredContract(contractAddress: Bytes): void {
let ignoredcontract = IgnoredContract.load(contractAddress)
if (!ignoredcontract) {
ignoredcontract = new IgnoredContract(contractAddress)
ignoredcontract.save()
}

return ignoredcontract
}
15 changes: 14 additions & 1 deletion src/common/entity/token.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BigInt, Bytes } from "@graphprotocol/graph-ts"
import { Token } from "../../../generated/schema"
import { Token, TokenStatistic } from "../../../generated/schema"
import { ADDRESS_ZERO } from "../utils/address"
import { ZERO_BI } from "../utils/decimal"

export function isNullToken(token: Token): boolean {
return token.id.equals(ADDRESS_ZERO)
Expand Down Expand Up @@ -31,3 +32,15 @@ export function getToken(tokenAddress: Bytes): Token {
}
return token
}

export function getTokenStatistic(tokenAddress: Bytes): TokenStatistic {
let tokenStatistic = TokenStatistic.load(tokenAddress)
if (!tokenStatistic) {
tokenStatistic = new TokenStatistic(tokenAddress)
tokenStatistic.token = tokenAddress
tokenStatistic.totalSupply = ZERO_BI
tokenStatistic.holderCount = ZERO_BI
tokenStatistic.save()
}
return tokenStatistic
}
57 changes: 49 additions & 8 deletions src/common/interaction.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,73 @@
import { BigInt, Bytes, log } from "@graphprotocol/graph-ts"
import { Transfer as TransferEvent } from "../../generated/templates/BeefyERC20Product/IERC20"
import { BURN_ADDRESS, SHARE_TOKEN_MINT_ADDRESS } from "../config"
import { getAccount } from "./entity/account"
import { createAccount } from "./entity/account"
import { getTokenBalance } from "./entity/balance"
import { getToken } from "./entity/token"
import { getToken, getTokenStatistic } from "./entity/token"
import { shouldIgnoreContract } from "./entity/ignored"
import { ZERO_BI } from "./utils/decimal"

export function handleProductTransfer(event: TransferEvent): void {
if (event.params.value.equals(ZERO_BI)) {
log.debug("Ignoring transfer with zero value: {}", [event.transaction.hash.toHexString()])
return
}

if (shouldIgnoreContract(event.params.from) || shouldIgnoreContract(event.params.to)) {
log.debug("Ignoring transfer from/to ignored contract: {}", [event.transaction.hash.toHexString()])
return
}

const tokenAddress = event.address
const statistic = getTokenStatistic(tokenAddress)

if (event.params.from.notEqual(SHARE_TOKEN_MINT_ADDRESS) && event.params.from.notEqual(BURN_ADDRESS)) {
updateAccountBalance(event.address, event.params.from, event.params.value.neg())
const balDiff = updateAccountBalance(tokenAddress, event.params.from, event.params.value.neg())
statistic.holderCount = statistic.holderCount.plus(balDiff.holderCountChange())
}

if (event.params.to.notEqual(SHARE_TOKEN_MINT_ADDRESS) && event.params.to.notEqual(BURN_ADDRESS)) {
updateAccountBalance(event.address, event.params.to, event.params.value)
const balDiff = updateAccountBalance(tokenAddress, event.params.to, event.params.value)
statistic.holderCount = statistic.holderCount.plus(balDiff.holderCountChange())
}

if (event.params.from.equals(BURN_ADDRESS) || event.params.from.equals(SHARE_TOKEN_MINT_ADDRESS)) {
statistic.totalSupply = statistic.totalSupply.plus(event.params.value)
}
if (event.params.to.equals(BURN_ADDRESS) || event.params.to.equals(SHARE_TOKEN_MINT_ADDRESS)) {
statistic.totalSupply = statistic.totalSupply.minus(event.params.value)
}

statistic.save()
}

function updateAccountBalance(tokenAddress: Bytes, accountAddress: Bytes, amountDiff: BigInt): void {
const account = getAccount(accountAddress)
account.save()
function updateAccountBalance(tokenAddress: Bytes, accountAddress: Bytes, amountDiff: BigInt): BalanceDiff {
const account = createAccount(accountAddress)
const token = getToken(tokenAddress)
token.save()

const balance = getTokenBalance(token, account)
balance.amount = balance.amount.plus(amountDiff)
const before = balance.amount
const after = balance.amount.plus(amountDiff)
balance.amount = after
balance.save()

return new BalanceDiff(before, balance.amount)
}

class BalanceDiff {
constructor(
public before: BigInt,
public after: BigInt,
) {}

public holderCountChange(): BigInt {
if (this.before.equals(ZERO_BI) && this.after.notEqual(ZERO_BI)) {
return BigInt.fromI32(1)
}
if (this.before.notEqual(ZERO_BI) && this.after.equals(ZERO_BI)) {
return BigInt.fromI32(-1)
}
return ZERO_BI
}
}
49 changes: 0 additions & 49 deletions src/common/utils/time.ts

This file was deleted.

51 changes: 0 additions & 51 deletions tests/utils/time.test.ts

This file was deleted.

0 comments on commit 3b52404

Please sign in to comment.