diff --git a/subgraphs/venus/schema.graphql b/subgraphs/venus/schema.graphql index ca95039c..f3990b9d 100644 --- a/subgraphs/venus/schema.graphql +++ b/subgraphs/venus/schema.graphql @@ -67,6 +67,15 @@ type Market @entity { totalXvsDistributedMantissa: BigInt! "vToken decimal length" vTokenDecimals: Int! + + "Number of accounts currently supplying to this market" + supplierCount: BigInt! + + "Number of accounts with reasonable borrow positions in this market (excludes accounts with dust (potentially left over from liquidations))" + borrowerCountAdjusted: BigInt! + + "Number of accounts currently borrowing from this market" + borrowerCount: BigInt! } """ @@ -84,17 +93,6 @@ type Account @entity { countLiquidator: Int! "True if user has ever borrowed" hasBorrowed: Boolean! - - # The following values are added by the JS Wrapper, and must be calculated with the most up - # to date values based on the block delta for market.exchangeRate and market.borrowIndex - # They do not need to be in the schema, but they will show up in the explorer playground - - # "If less than 1, the account can be liquidated - # health: BigDecimal! - # "Total assets supplied by user" - # totalBorrowValueInEth: BigDecimal! - # "Total assets borrowed from user" - # totalCollateralValueInEth: BigDecimal! } """ @@ -131,22 +129,6 @@ type AccountVToken @entity { totalUnderlyingRepaid: BigDecimal! "Current borrow balance stored in contract (exclusive of interest since accrualBlockNumber)" storedBorrowBalance: BigDecimal! - - # The following values are added by the JS Wrapper, and must be calculated with the most up - # to date values based on the block delta for market.exchangeRate and market.borrowIndex - # They do not need to be in the schema, but they will show up in the explorer playground - - # supplyBalanceUnderlying: BigDecimal! - # FORMULA: supplyBalanceUnderlying = vTokenBalance * market.exchangeRate - - # lifetimeSupplyInterestAccrued: BigDecimal! - # FORMULA: lifetimeSupplyInterestAccrued = supplyBalanceUnderlying - totalUnderlyingSupplied + totalUnderlyingRedeemed - - # borrowBalanceUnderlying: BigDecimal! - # FORMULA: borrowBalanceUnderlying = storedBorrowBalance * market.borrowIndex / accountBorrowIndex - - # lifetimeBorrowInterestAccrued: BigDecimal! - # FORMULA: lifetimeBorrowInterestAccrued = borrowBalanceUnderlying - totalUnderlyingBorrowed + totalUnderlyingRepaid } """ diff --git a/subgraphs/venus/src/mappings/vToken.ts b/subgraphs/venus/src/mappings/vToken.ts index 35b3a129..d4b7e2ff 100644 --- a/subgraphs/venus/src/mappings/vToken.ts +++ b/subgraphs/venus/src/mappings/vToken.ts @@ -1,5 +1,7 @@ /* eslint-disable prefer-const */ // to satisfy AS compiler +import { BigInt } from '@graphprotocol/graph-ts'; + import { Account, BorrowEvent, @@ -21,12 +23,14 @@ import { RepayBorrow, Transfer, } from '../../generated/templates/VToken/VToken'; +import { oneBigInt, zeroBigInt32 } from '../constants'; import { nullAddress } from '../constants/addresses'; import { createAccount } from '../operations/create'; import { createMarket } from '../operations/create'; import { updateCommonVTokenStats } from '../operations/update'; import { updateMarket } from '../operations/update'; import { exponentToBigDecimal } from '../utilities/exponentToBigDecimal'; +import { getMarketId, getTransactionId } from '../utilities/ids'; /* Account supplies assets into market and receives vTokens in exchange * @@ -46,10 +50,7 @@ export const handleMint = (event: Mint): void => { if (!market) { market = createMarket(event.address.toHexString()); } - let mintID = event.transaction.hash - .toHexString() - .concat('-') - .concat(event.transactionLogIndex.toString()); + let mintId = getTransactionId(event.transaction.hash, event.transactionLogIndex); const vTokenDecimals = market.vTokenDecimals; @@ -62,7 +63,7 @@ export const handleMint = (event: Mint): void => { .div(exponentToBigDecimal(market.underlyingDecimals)) .truncate(market.underlyingDecimals); - let mint = new MintEvent(mintID); + let mint = new MintEvent(mintId); mint.amount = vTokenAmount; mint.to = event.params.minter; mint.from = event.address; @@ -71,6 +72,10 @@ export const handleMint = (event: Mint): void => { mint.vTokenSymbol = market.symbol; mint.underlyingAmount = underlyingAmount; mint.save(); + + if (event.params.mintTokens.equals(event.params.totalSupply)) { + market.supplierCount = market.supplierCount.plus(oneBigInt); + } }; /* Account supplies vTokens into market and receives underlying asset in exchange @@ -114,6 +119,12 @@ export const handleRedeem = (event: Redeem): void => { redeem.vTokenSymbol = market.symbol; redeem.underlyingAmount = underlyingAmount; redeem.save(); + + if (event.params.totalSupply.equals(zeroBigInt32)) { + // if the current balance is 0 then the user has withdrawn all their assets from this market + market.supplierCount = market.supplierCount.minus(oneBigInt); + market.save(); + } }; /* Borrow assets from the protocol. All values either BNB or BEP20 @@ -186,6 +197,13 @@ export const handleBorrow = (event: Borrow): void => { borrow.blockTime = event.block.timestamp.toI32(); borrow.underlyingSymbol = market.underlyingSymbol; borrow.save(); + + if (event.params.accountBorrows == event.params.borrowAmount) { + // if both the accountBorrows and the borrowAmount are the same, it means the account is a new borrower + market.borrowerCount = market.borrowerCount.plus(oneBigInt); + market.borrowerCountAdjusted = market.borrowerCountAdjusted.plus(oneBigInt); + market.save(); + } }; /* Repay some amount borrowed. Anyone can repay anyones balance @@ -262,6 +280,17 @@ export const handleRepayBorrow = (event: RepayBorrow): void => { repay.underlyingSymbol = market.underlyingSymbol; repay.payer = event.params.payer; repay.save(); + + if (event.params.accountBorrows.equals(zeroBigInt32)) { + market.borrowerCount = market.borrowerCount.minus(oneBigInt); + market.borrowerCountAdjusted = market.borrowerCountAdjusted.minus(oneBigInt); + market.save(); + } else if (event.params.accountBorrows.le(new BigInt(10))) { + // Sometimes a liquidator will leave dust behind. If this happens we'll adjust count + // because the position only exists due to a technicality + market.borrowerCountAdjusted = market.borrowerCountAdjusted.minus(oneBigInt); + market.save(); + } }; /* @@ -353,10 +382,10 @@ export const handleLiquidateBorrow = (event: LiquidateBorrow): void => { export const handleTransfer = (event: Transfer): void => { // We only updateMarket() if accrual block number is not up to date. This will only happen // with normal transfers, since mint, redeem, and seize transfers will already run updateMarket() - let marketID = event.address.toHexString(); - let market = Market.load(marketID); + let marketId = getMarketId(event.address); + let market = Market.load(marketId); if (!market) { - market = createMarket(marketID); + market = createMarket(marketId); } if (market.accrualBlockNumber != event.block.number.toI32()) { market = updateMarket(event.address, event.block.number.toI32(), event.block.timestamp.toI32()); @@ -366,11 +395,11 @@ export const handleTransfer = (event: Transfer): void => { // Checking if the tx is FROM the vToken contract (i.e. this will not run when minting) // If so, it is a mint, and we don't need to run these calculations - let accountFromID = event.params.from.toHex(); - if (accountFromID != nullAddress.toHex()) { - let accountFrom = Account.load(accountFromID); + let accountFromId = event.params.from.toHex(); + if (accountFromId != nullAddress.toHex()) { + let accountFrom = Account.load(accountFromId); if (accountFrom == null) { - createAccount(accountFromID); + createAccount(accountFromId); } // Update vTokenStats common for all events, and return the stats to update unique @@ -378,7 +407,7 @@ export const handleTransfer = (event: Transfer): void => { let vTokenStatsFrom = updateCommonVTokenStats( market.id, market.symbol, - accountFromID, + accountFromId, event.transaction.hash, event.block.timestamp, event.block.number, @@ -401,11 +430,11 @@ export const handleTransfer = (event: Transfer): void => { // If so, we ignore it. this leaves an edge case, where someone who accidentally sends // vTokens to a vToken contract, where it will not get recorded. Right now it would // be messy to include, so we are leaving it out for now TODO fix this in future - let accountToID = event.params.to.toHex(); - if (accountToID != marketID) { - let accountTo = Account.load(accountToID); + let accountToId = event.params.to.toHex(); + if (accountToId != marketId) { + let accountTo = Account.load(accountToId); if (accountTo == null) { - createAccount(accountToID); + createAccount(accountToId); } // Update vTokenStats common for all events, and return the stats to update unique @@ -413,7 +442,7 @@ export const handleTransfer = (event: Transfer): void => { let vTokenStatsTo = updateCommonVTokenStats( market.id, market.symbol, - accountToID, + accountToId, event.transaction.hash, event.block.timestamp, event.block.number, @@ -432,12 +461,9 @@ export const handleTransfer = (event: Transfer): void => { vTokenStatsTo.save(); } - let transferID = event.transaction.hash - .toHexString() - .concat('-') - .concat(event.transactionLogIndex.toString()); + let transferId = getTransactionId(event.transaction.hash, event.transactionLogIndex); - let transfer = new TransferEvent(transferID); + let transfer = new TransferEvent(transferId); transfer.amount = event.params.amount.toBigDecimal().div(exponentToBigDecimal(vTokenDecimals)); transfer.to = event.params.to; transfer.from = event.params.from; diff --git a/subgraphs/venus/src/operations/create.ts b/subgraphs/venus/src/operations/create.ts index f975b5be..422ff0d1 100644 --- a/subgraphs/venus/src/operations/create.ts +++ b/subgraphs/venus/src/operations/create.ts @@ -100,5 +100,9 @@ export function createMarket(marketAddress: string): Market { market.reserveFactor = reserveFactor.reverted ? BigInt.fromI32(0) : reserveFactor.value; market.totalXvsDistributedMantissa = zeroBigInt32; + market.supplierCount = zeroBigInt32; + market.borrowerCount = zeroBigInt32; + market.borrowerCountAdjusted = zeroBigInt32; + return market; } diff --git a/subgraphs/venus/src/utilities/ids.ts b/subgraphs/venus/src/utilities/ids.ts index ca39c4f7..fc3748ea 100644 --- a/subgraphs/venus/src/utilities/ids.ts +++ b/subgraphs/venus/src/utilities/ids.ts @@ -12,6 +12,9 @@ export const getMarketId = (vTokenAddress: Address): string => export const getAccountVTokenId = (marketAddress: Address, accountAddress: Address): string => joinIds([marketAddress.toHexString(), accountAddress.toHexString()]); +export const getTransactionId = (transactionHash: Bytes, logIndex: BigInt): string => + joinIds([transactionHash.toHexString(), logIndex.toString()]); + export const getAccountVTokenTransactionId = ( accountAddress: Address, transactionHash: Bytes, diff --git a/subgraphs/venus/tests/VToken/index.test.ts b/subgraphs/venus/tests/VToken/index.test.ts index 8a704b78..3fc099a1 100644 --- a/subgraphs/venus/tests/VToken/index.test.ts +++ b/subgraphs/venus/tests/VToken/index.test.ts @@ -449,6 +449,8 @@ describe('VToken', () => { handleMint(mintEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'supplierCount', '2'); + const supplier02 = user2Address; mintEvent = createMintEvent( aaaTokenAddress, @@ -460,6 +462,7 @@ describe('VToken', () => { createAccountVTokenBalanceOfMock(aaaTokenAddress, supplier02, mintTokens); handleMint(mintEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'supplierCount', '3'); let redeemEvent = createRedeemEvent( aaaTokenAddress, @@ -471,6 +474,7 @@ describe('VToken', () => { createAccountVTokenBalanceOfMock(aaaTokenAddress, supplier02, zeroBigInt32); handleRedeem(redeemEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'supplierCount', '2'); redeemEvent = createRedeemEvent( aaaTokenAddress, @@ -482,6 +486,7 @@ describe('VToken', () => { createAccountVTokenBalanceOfMock(aaaTokenAddress, supplier01, halfMintTokens); handleRedeem(redeemEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'supplierCount', '1'); redeemEvent = createRedeemEvent( aaaTokenAddress, @@ -493,6 +498,7 @@ describe('VToken', () => { createAccountVTokenBalanceOfMock(aaaTokenAddress, supplier01, zeroBigInt32); handleRedeem(redeemEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'supplierCount', '0'); }); test('registers increase and decrease in the market borrower count', () => { @@ -515,6 +521,8 @@ describe('VToken', () => { ); handleBorrow(borrowEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCount', '2'); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCountAdjusted', '2'); const borrower02 = user2Address; borrowEvent = createBorrowEvent( @@ -526,6 +534,8 @@ describe('VToken', () => { ); handleBorrow(borrowEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCount', '3'); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCountAdjusted', '3'); let repayEvent = createRepayBorrowEvent( aaaTokenAddress, @@ -537,6 +547,8 @@ describe('VToken', () => { ); handleRepayBorrow(repayEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCount', '2'); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCountAdjusted', '2'); repayEvent = createRepayBorrowEvent( aaaTokenAddress, @@ -548,17 +560,21 @@ describe('VToken', () => { ); handleRepayBorrow(repayEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCount', '1'); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCountAdjusted', '1'); repayEvent = createRepayBorrowEvent( aaaTokenAddress, borrower01, borrower01, halfBorrowAmountTokens, - zeroBigInt32, - zeroBigInt32, + oneBigInt, + oneBigInt, ); handleRepayBorrow(repayEvent); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCount', '1'); + assert.fieldEquals('Market', aaaTokenAddress.toHex(), 'borrowerCountAdjusted', '1'); }); }); });