diff --git a/frontend/app/src/graphql/gql.ts b/frontend/app/src/graphql/gql.ts index 988951313..b8845060d 100644 --- a/frontend/app/src/graphql/gql.ts +++ b/frontend/app/src/graphql/gql.ts @@ -15,7 +15,7 @@ import * as types from './graphql'; * Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size */ const documents = { - "\n query TrovesCount($id: ID!) {\n borrowerInfo(id: $id) {\n troves\n trovesByCollateral\n }\n }\n": types.TrovesCountDocument, + "\n query BorrowerInfo($id: ID!) {\n borrowerInfo(id: $id) {\n nextOwnerIndexes\n troves\n trovesByCollateral\n }\n }\n": types.BorrowerInfoDocument, "\n fragment FullTroveFragment on Trove {\n id\n borrower\n closedAt\n createdAt\n debt\n deposit\n interestRate\n mightBeLeveraged\n stake\n status\n troveId\n updatedAt\n collateral {\n id\n token {\n symbol\n name\n }\n minCollRatio\n collIndex\n }\n interestBatch {\n id\n annualInterestRate\n annualManagementFee\n batchManager\n }\n }\n": types.FullTroveFragmentFragmentDoc, "\n query TrovesByAccount($account: Bytes!) {\n troves(\n where: {\n borrower: $account,\n status_in: [active,redeemed,liquidated],\n }\n orderBy: updatedAt\n orderDirection: desc\n ) {\n id\n borrower\n closedAt\n createdAt\n debt\n deposit\n interestRate\n mightBeLeveraged\n stake\n status\n troveId\n updatedAt\n collateral {\n id\n token {\n symbol\n name\n }\n minCollRatio\n collIndex\n }\n interestBatch {\n id\n annualInterestRate\n annualManagementFee\n batchManager\n }\n }\n }\n": types.TrovesByAccountDocument, "\n query TroveById($id: ID!) {\n trove(id: $id) {\n id\n borrower\n closedAt\n createdAt\n debt\n deposit\n interestRate\n mightBeLeveraged\n stake\n status\n troveId\n updatedAt\n collateral {\n id\n token {\n symbol\n name\n }\n minCollRatio\n collIndex\n }\n interestBatch {\n id\n annualInterestRate\n annualManagementFee\n batchManager\n }\n }\n }\n": types.TroveByIdDocument, @@ -35,7 +35,7 @@ const documents = { /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "\n query TrovesCount($id: ID!) {\n borrowerInfo(id: $id) {\n troves\n trovesByCollateral\n }\n }\n"): typeof import('./graphql').TrovesCountDocument; +export function graphql(source: "\n query BorrowerInfo($id: ID!) {\n borrowerInfo(id: $id) {\n nextOwnerIndexes\n troves\n trovesByCollateral\n }\n }\n"): typeof import('./graphql').BorrowerInfoDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/app/src/graphql/graphql.ts b/frontend/app/src/graphql/graphql.ts index 14a3a57cf..088c1a10f 100644 --- a/frontend/app/src/graphql/graphql.ts +++ b/frontend/app/src/graphql/graphql.ts @@ -47,6 +47,7 @@ export type Block_Height = { export type BorrowerInfo = { __typename?: 'BorrowerInfo'; id: Scalars['ID']['output']; + nextOwnerIndexes: Array; troves: Scalars['Int']['output']; trovesByCollateral: Array; }; @@ -63,6 +64,12 @@ export type BorrowerInfo_Filter = { id_lte?: InputMaybe; id_not?: InputMaybe; id_not_in?: InputMaybe>; + nextOwnerIndexes?: InputMaybe>; + nextOwnerIndexes_contains?: InputMaybe>; + nextOwnerIndexes_contains_nocase?: InputMaybe>; + nextOwnerIndexes_not?: InputMaybe>; + nextOwnerIndexes_not_contains?: InputMaybe>; + nextOwnerIndexes_not_contains_nocase?: InputMaybe>; or?: InputMaybe>>; troves?: InputMaybe; trovesByCollateral?: InputMaybe>; @@ -82,6 +89,7 @@ export type BorrowerInfo_Filter = { export enum BorrowerInfo_OrderBy { Id = 'id', + NextOwnerIndexes = 'nextOwnerIndexes', Troves = 'troves', TrovesByCollateral = 'trovesByCollateral' } @@ -138,11 +146,8 @@ export type Collateral = { collIndex: Scalars['Int']['output']; id: Scalars['ID']['output']; minCollRatio: Scalars['BigInt']['output']; - price: Scalars['BigInt']['output']; stabilityPoolDeposits: Array; token: Token; - totalDebt: Scalars['BigInt']['output']; - totalDeposited: Scalars['BigInt']['output']; troves: Array; }; @@ -278,9 +283,6 @@ export enum CollateralAddresses_OrderBy { CollateralCollIndex = 'collateral__collIndex', CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', - CollateralPrice = 'collateral__price', - CollateralTotalDebt = 'collateral__totalDebt', - CollateralTotalDeposited = 'collateral__totalDeposited', Id = 'id', SortedTroves = 'sortedTroves', StabilityPool = 'stabilityPool', @@ -319,14 +321,6 @@ export type Collateral_Filter = { minCollRatio_not?: InputMaybe; minCollRatio_not_in?: InputMaybe>; or?: InputMaybe>>; - price?: InputMaybe; - price_gt?: InputMaybe; - price_gte?: InputMaybe; - price_in?: InputMaybe>; - price_lt?: InputMaybe; - price_lte?: InputMaybe; - price_not?: InputMaybe; - price_not_in?: InputMaybe>; stabilityPoolDeposits_?: InputMaybe; token?: InputMaybe; token_?: InputMaybe; @@ -349,22 +343,6 @@ export type Collateral_Filter = { token_not_starts_with_nocase?: InputMaybe; token_starts_with?: InputMaybe; token_starts_with_nocase?: InputMaybe; - totalDebt?: InputMaybe; - totalDebt_gt?: InputMaybe; - totalDebt_gte?: InputMaybe; - totalDebt_in?: InputMaybe>; - totalDebt_lt?: InputMaybe; - totalDebt_lte?: InputMaybe; - totalDebt_not?: InputMaybe; - totalDebt_not_in?: InputMaybe>; - totalDeposited?: InputMaybe; - totalDeposited_gt?: InputMaybe; - totalDeposited_gte?: InputMaybe; - totalDeposited_in?: InputMaybe>; - totalDeposited_lt?: InputMaybe; - totalDeposited_lte?: InputMaybe; - totalDeposited_not?: InputMaybe; - totalDeposited_not_in?: InputMaybe>; troves_?: InputMaybe; }; @@ -380,15 +358,12 @@ export enum Collateral_OrderBy { CollIndex = 'collIndex', Id = 'id', MinCollRatio = 'minCollRatio', - Price = 'price', StabilityPoolDeposits = 'stabilityPoolDeposits', Token = 'token', TokenDecimals = 'token__decimals', TokenId = 'token__id', TokenName = 'token__name', TokenSymbol = 'token__symbol', - TotalDebt = 'totalDebt', - TotalDeposited = 'totalDeposited', Troves = 'troves' } @@ -848,9 +823,6 @@ export enum InterestBatch_OrderBy { CollateralCollIndex = 'collateral__collIndex', CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', - CollateralPrice = 'collateral__price', - CollateralTotalDebt = 'collateral__totalDebt', - CollateralTotalDeposited = 'collateral__totalDeposited', Debt = 'debt', Id = 'id', Troves = 'troves' @@ -921,9 +893,6 @@ export enum InterestRateBracket_OrderBy { CollateralCollIndex = 'collateral__collIndex', CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', - CollateralPrice = 'collateral__price', - CollateralTotalDebt = 'collateral__totalDebt', - CollateralTotalDeposited = 'collateral__totalDeposited', Id = 'id', Rate = 'rate', TotalDebt = 'totalDebt' @@ -1441,9 +1410,6 @@ export enum StabilityPoolDeposit_OrderBy { CollateralCollIndex = 'collateral__collIndex', CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', - CollateralPrice = 'collateral__price', - CollateralTotalDebt = 'collateral__totalDebt', - CollateralTotalDeposited = 'collateral__totalDeposited', Deposit = 'deposit', Depositor = 'depositor', Id = 'id', @@ -1957,9 +1923,6 @@ export enum Token_OrderBy { CollateralCollIndex = 'collateral__collIndex', CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', - CollateralPrice = 'collateral__price', - CollateralTotalDebt = 'collateral__totalDebt', - CollateralTotalDeposited = 'collateral__totalDeposited', Decimals = 'decimals', Id = 'id', Name = 'name', @@ -2149,9 +2112,6 @@ export enum Trove_OrderBy { CollateralCollIndex = 'collateral__collIndex', CollateralId = 'collateral__id', CollateralMinCollRatio = 'collateral__minCollRatio', - CollateralPrice = 'collateral__price', - CollateralTotalDebt = 'collateral__totalDebt', - CollateralTotalDeposited = 'collateral__totalDeposited', CreatedAt = 'createdAt', Debt = 'debt', Deposit = 'deposit', @@ -2207,12 +2167,12 @@ export enum _SubgraphErrorPolicy_ { Deny = 'deny' } -export type TrovesCountQueryVariables = Exact<{ +export type BorrowerInfoQueryVariables = Exact<{ id: Scalars['ID']['input']; }>; -export type TrovesCountQuery = { __typename?: 'Query', borrowerInfo?: { __typename?: 'BorrowerInfo', troves: number, trovesByCollateral: Array } | null }; +export type BorrowerInfoQuery = { __typename?: 'Query', borrowerInfo?: { __typename?: 'BorrowerInfo', nextOwnerIndexes: Array, troves: number, trovesByCollateral: Array } | null }; export type FullTroveFragmentFragment = { __typename?: 'Trove', id: string, borrower: string, closedAt?: bigint | null, createdAt: bigint, debt: bigint, deposit: bigint, interestRate: bigint, mightBeLeveraged: boolean, stake: bigint, status: TroveStatus, troveId: string, updatedAt: bigint, collateral: { __typename?: 'Collateral', id: string, minCollRatio: bigint, collIndex: number, token: { __typename?: 'Token', symbol: string, name: string } }, interestBatch?: { __typename?: 'InterestBatch', id: string, annualInterestRate: bigint, annualManagementFee: bigint, batchManager: string } | null } & { ' $fragmentName'?: 'FullTroveFragmentFragment' }; @@ -2360,14 +2320,15 @@ export const StabilityPoolDepositFragmentFragmentDoc = new TypedDocumentString(` } } `, {"fragmentName":"StabilityPoolDepositFragment"}) as unknown as TypedDocumentString; -export const TrovesCountDocument = new TypedDocumentString(` - query TrovesCount($id: ID!) { +export const BorrowerInfoDocument = new TypedDocumentString(` + query BorrowerInfo($id: ID!) { borrowerInfo(id: $id) { + nextOwnerIndexes troves trovesByCollateral } } - `) as unknown as TypedDocumentString; + `) as unknown as TypedDocumentString; export const TrovesByAccountDocument = new TypedDocumentString(` query TrovesByAccount($account: Bytes!) { troves( diff --git a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx index 276bc5344..4ef7cd4be 100644 --- a/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx +++ b/frontend/app/src/screens/BorrowScreen/BorrowScreen.tsx @@ -26,7 +26,7 @@ import { getLiquidationRisk, getLoanDetails, getLtv } from "@/src/liquity-math"; import { useAccount, useBalance } from "@/src/services/Ethereum"; import { usePrice } from "@/src/services/Prices"; import { useTransactionFlow } from "@/src/services/TransactionFlow"; -import { useTrovesCount } from "@/src/subgraph-hooks"; +import { useNextOwnerIndex } from "@/src/subgraph-hooks"; import { isCollIndex } from "@/src/types"; import { infoTooltipProps } from "@/src/uikit-utils"; import { css } from "@/styled-system/css"; @@ -113,7 +113,7 @@ export function BorrowScreen() { throw new Error(`Unknown collateral symbol: ${collateral.symbol}`); } - const troveCount = useTrovesCount(account.address ?? null, collIndex); + const nextOwnerIndex = useNextOwnerIndex(account.address ?? null, collIndex); const loanDetails = getLoanDetails( deposit.isEmpty ? null : deposit.parsed, @@ -396,7 +396,12 @@ export function BorrowScreen() { size="large" wide onClick={() => { - if (deposit.parsed && debt.parsed && account.address) { + if ( + deposit.parsed + && debt.parsed + && account.address + && typeof nextOwnerIndex.data === "number" + ) { txFlow.start({ flowId: "openBorrowPosition", backLink: ["/borrow", "Back to editing"], @@ -405,7 +410,7 @@ export function BorrowScreen() { collIndex, owner: account.address, - ownerIndex: troveCount.data ?? 0, + ownerIndex: nextOwnerIndex.data, collAmount: deposit.parsed, boldAmount: debt.parsed, upperHint: dnum18(0), diff --git a/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx b/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx index 481ef960b..7b6d739e4 100644 --- a/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx +++ b/frontend/app/src/screens/LeverageScreen/LeverageScreen.tsx @@ -29,7 +29,7 @@ import { getCollIndexFromSymbol } from "@/src/liquity-utils"; import { useAccount, useBalance } from "@/src/services/Ethereum"; import { usePrice } from "@/src/services/Prices"; import { useTransactionFlow } from "@/src/services/TransactionFlow"; -import { useTrovesCount } from "@/src/subgraph-hooks"; +import { useNextOwnerIndex } from "@/src/subgraph-hooks"; import { infoTooltipProps } from "@/src/uikit-utils"; import { css } from "@/styled-system/css"; import { @@ -85,7 +85,8 @@ export function LeverageScreen() { [symbol, useBalance(account.address, symbol)] as const ))); - const troveCount = useTrovesCount(account.address ?? null, collIndex); + const nextOwnerIndex = useNextOwnerIndex(account.address ?? null, collIndex); + const collPrice = usePrice(collToken.symbol); const maxCollDeposit = MAX_COLLATERAL_DEPOSITS[collSymbol] ?? null; @@ -147,7 +148,7 @@ export function LeverageScreen() { collIndex, initialDeposit: depositPreLeverage.parsed, leverageFactor: leverageField.leverageFactor, - ownerIndex: troveCount.data ?? null, + ownerIndex: nextOwnerIndex.data ?? null, }); const leverageSlippageElements = useSlippageElements( @@ -373,14 +374,19 @@ export function LeverageScreen() { size="large" wide onClick={() => { - if (depositPreLeverage.parsed && leverageField.debt && account.address) { + if ( + depositPreLeverage.parsed + && leverageField.debt + && account.address + && typeof nextOwnerIndex.data === "number" + ) { txFlow.start({ flowId: "openLeveragePosition", backLink: ["/multiply", "Back to editing"], successLink: ["/", "Go to the Dashboard"], successMessage: "The leveraged position has been created successfully.", - ownerIndex: troveCount.data ?? 0, + ownerIndex: nextOwnerIndex.data, leverageFactor: leverageField.leverageFactor, loan: newLoan, }); diff --git a/frontend/app/src/subgraph-hooks.ts b/frontend/app/src/subgraph-hooks.ts index 65f8fc857..b514e6762 100644 --- a/frontend/app/src/subgraph-hooks.ts +++ b/frontend/app/src/subgraph-hooks.ts @@ -15,6 +15,7 @@ import { isAddress, shortenAddress } from "@liquity2/uikit"; import { useQuery } from "@tanstack/react-query"; import * as dn from "dnum"; import { + BorrowerInfoQuery, GovernanceInitiatives, GovernanceStats, GovernanceUser, @@ -27,7 +28,6 @@ import { StabilityPoolQuery, TroveByIdQuery, TrovesByAccountQuery, - TrovesCountQuery, } from "./subgraph-queries"; type Options = { @@ -41,22 +41,23 @@ function prepareOptions(options?: Options) { }; } -export function useTrovesCount( +export function useNextOwnerIndex( borrower: null | Address, - collIndex?: CollIndex, + collIndex: null | CollIndex, options?: Options, ) { let queryFn = async () => { - if (!borrower) { + if (!borrower || collIndex === null) { return null; } + const { borrowerInfo } = await graphQuery( - TrovesCountQuery, + BorrowerInfoQuery, { id: borrower.toLowerCase() }, ); - return collIndex === undefined - ? borrowerInfo?.troves ?? 0 - : borrowerInfo?.trovesByCollateral[collIndex] ?? null; + + // if borrowerInfo doesn’t exist, start at 0 + return borrowerInfo?.nextOwnerIndexes[collIndex] ?? 0; }; if (DEMO_MODE) { @@ -70,7 +71,7 @@ export function useTrovesCount( } return useQuery({ - queryKey: ["TrovesCount", borrower, collIndex], + queryKey: ["NextTroveId", borrower, collIndex], queryFn, ...prepareOptions(options), }); diff --git a/frontend/app/src/subgraph-queries.ts b/frontend/app/src/subgraph-queries.ts index 97150653f..7b48b8617 100644 --- a/frontend/app/src/subgraph-queries.ts +++ b/frontend/app/src/subgraph-queries.ts @@ -32,9 +32,10 @@ export async function graphQuery( return result.data as TResult; } -export const TrovesCountQuery = graphql(` - query TrovesCount($id: ID!) { +export const BorrowerInfoQuery = graphql(` + query BorrowerInfo($id: ID!) { borrowerInfo(id: $id) { + nextOwnerIndexes troves trovesByCollateral } diff --git a/subgraph/networks.json b/subgraph/networks.json index 8633818cb..5170a869e 100644 --- a/subgraph/networks.json +++ b/subgraph/networks.json @@ -19,12 +19,12 @@ }, "sepolia": { "BoldToken": { - "address": "0x6ecf96d1f30cd98c0b58d6b12ea8ae014c52464d", - "startBlock": 7279144 + "address": "0x0a69fa2a565bd6fa9c3890ecaa30b149aaf99136", + "startBlock": 7314166 }, "Governance": { - "address": "0xd7eae81178b97e49e6471f398f0486a7c2e43721", - "startBlock": 7279317 + "address": "0x7eedda08119826757c98a4680e94f3b5e1f33af6", + "startBlock": 7314318 } } } diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 390116c07..5d9751189 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -68,6 +68,7 @@ type BorrowerInfo @entity { id: ID! # "borrowerAddress", e.g. "0x0000000000000000000000000000000000000000" troves: Int! trovesByCollateral: [Int!]! + nextOwnerIndexes: [Int!]! } type StabilityPool @entity { diff --git a/subgraph/src/TroveManager.mapping.ts b/subgraph/src/TroveManager.mapping.ts index 3dc02efe0..6f23443a0 100644 --- a/subgraph/src/TroveManager.mapping.ts +++ b/subgraph/src/TroveManager.mapping.ts @@ -1,11 +1,12 @@ import { Address, BigInt, Bytes, dataSource } from "@graphprotocol/graph-ts"; -import { BorrowerInfo, Collateral, InterestBatch, InterestRateBracket, Trove } from "../generated/schema"; +import { Collateral, InterestBatch, InterestRateBracket, Trove } from "../generated/schema"; import { BatchUpdated as BatchUpdatedEvent, TroveManager as TroveManagerContract, TroveOperation as TroveOperationEvent, } from "../generated/templates/TroveManager/TroveManager"; import { TroveNFT as TroveNFTContract } from "../generated/templates/TroveManager/TroveNFT"; +import { BorrowerTrovesCountUpdate, updateBorrowerTrovesCount } from "./shared"; // decides whether to update the flag indicating // that a trove might be leveraged or not. @@ -38,13 +39,18 @@ export function handleTroveOperation(event: TroveOperationEvent): void { let timestamp = event.block.timestamp; let troveId = event.params._troveId; let collId = dataSource.context().getString("collId"); + let collateral = Collateral.load(collId); + + if (!collateral) { + throw new Error("Collateral not found: " + collId); + } let operation = event.params._operation; let tm = TroveManagerContract.bind(event.address); let trove: Trove | null = null; if (operation === OP_OPEN_TROVE) { - updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), true); + trove = updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), true); return; } @@ -99,6 +105,13 @@ export function handleTroveOperation(event: TroveOperationEvent): void { if (trove.interestBatch !== null) { leaveBatch(collId, troveId, BigInt.fromI32(0)); } + + updateBorrowerTrovesCount( + BorrowerTrovesCountUpdate.remove, + Address.fromBytes(trove.borrower), + collateral.collIndex, + ); + trove.closedAt = timestamp; trove.status = "closed"; trove.save(); @@ -269,22 +282,11 @@ function createTrove( dataSource.context().getBytes("address:troveNft"), )).ownerOf(troveId); - // create borrower info if needed - let borrowerInfo = BorrowerInfo.load(borrower.toHexString()); - if (!borrowerInfo) { - borrowerInfo = new BorrowerInfo(borrower.toHexString()); - borrowerInfo.troves = 0; - - let totalCollaterals = dataSource.context().getI32("totalCollaterals"); - borrowerInfo.trovesByCollateral = (new Array(totalCollaterals)).fill(0); - } - - // update borrower info - let trovesByColl = borrowerInfo.trovesByCollateral; - trovesByColl[collateral.collIndex] += 1; - borrowerInfo.trovesByCollateral = trovesByColl; - borrowerInfo.troves += 1; - borrowerInfo.save(); + updateBorrowerTrovesCount( + BorrowerTrovesCountUpdate.add, + borrower, + collateral.collIndex, + ); // create trove trove = new Trove(troveFullId); diff --git a/subgraph/src/TroveNFT.mapping.ts b/subgraph/src/TroveNFT.mapping.ts index bf3c233fd..f9a0a3155 100644 --- a/subgraph/src/TroveNFT.mapping.ts +++ b/subgraph/src/TroveNFT.mapping.ts @@ -1,46 +1,7 @@ -import { Address, BigInt, dataSource } from "@graphprotocol/graph-ts"; -import { BorrowerInfo, Trove } from "../generated/schema"; +import { Address, dataSource } from "@graphprotocol/graph-ts"; +import { Trove } from "../generated/schema"; import { Transfer as TransferEvent } from "../generated/templates/TroveNFT/TroveNFT"; - -enum BorrowerInfoUpdate { - add, - remove, -} - -function updateBorrowerInfo( - borrowerAddress: Address, - troveFullId: string, - update: BorrowerInfoUpdate, -): void { - let trove = Trove.load(troveFullId); - if (!trove) { - throw new Error("Trove does not exist: " + troveFullId); - } - - let borrowerInfo = BorrowerInfo.load(borrowerAddress.toHexString()); - - if (!borrowerInfo && update == BorrowerInfoUpdate.add) { - borrowerInfo = new BorrowerInfo(borrowerAddress.toHexString()); - borrowerInfo.troves = 0; - - let totalCollaterals = dataSource.context().getI32("totalCollaterals"); - borrowerInfo.trovesByCollateral = (new Array(totalCollaterals)).fill(0); - } - - if (!borrowerInfo) { - throw new Error("BorrowerInfo does not exist: " + borrowerAddress.toHexString()); - } - - let diff = update == BorrowerInfoUpdate.add ? 1 : -1; - - let trovesByColl = borrowerInfo.trovesByCollateral; - let collIndex = parseInt(trove.collateral); - trovesByColl[collIndex] += diff; - - borrowerInfo.trovesByCollateral = trovesByColl; - borrowerInfo.troves += diff; - borrowerInfo.save(); -} +import { BorrowerTrovesCountUpdate, updateBorrowerTrovesCount } from "./shared"; export function handleTransfer(event: TransferEvent): void { // Minting doesn’t need to be handled as we are already @@ -53,16 +14,28 @@ export function handleTransfer(event: TransferEvent): void { let collId = dataSource.context().getString("collId"); let troveFullId = collId + ":" + event.params.tokenId.toHexString(); - // update BorrowerInfo for the previous owner - updateBorrowerInfo(event.params.from, troveFullId, BorrowerInfoUpdate.remove); + let trove = Trove.load(troveFullId); + if (!trove) { + throw new Error("Trove does not exist: " + troveFullId); + } + + let collIndex = parseInt(trove.collateral); - // update BorrowerInfo for the new owner (including zero address) - updateBorrowerInfo(event.params.to, troveFullId, BorrowerInfoUpdate.add); + // update troves count & ownerIndex for the previous owner + updateBorrowerTrovesCount( + BorrowerTrovesCountUpdate.remove, + event.params.from, + collIndex, + ); + + // update troves count & ownerIndex for the current owner (including zero address) + updateBorrowerTrovesCount( + BorrowerTrovesCountUpdate.add, + event.params.to, + collIndex, + ); // update the trove borrower - let trove = Trove.load(troveFullId); - if (trove) { - trove.borrower = event.params.to; - trove.save(); - } + trove.borrower = event.params.to; + trove.save(); } diff --git a/subgraph/src/shared.ts b/subgraph/src/shared.ts new file mode 100644 index 000000000..03e10c8e8 --- /dev/null +++ b/subgraph/src/shared.ts @@ -0,0 +1,44 @@ +import { Address, dataSource } from "@graphprotocol/graph-ts"; +import { BorrowerInfo } from "../generated/schema"; + +export enum BorrowerTrovesCountUpdate { + add, + remove, +} + +export function updateBorrowerTrovesCount( + update: BorrowerTrovesCountUpdate, + borrower: Address, + collIndex: i32, +): BorrowerInfo { + let borrowerId = borrower.toHexString(); + let borrowerInfo = BorrowerInfo.load(borrowerId); + + let collateralsCount = dataSource.context().getI32("totalCollaterals"); + + if (!borrowerInfo) { + borrowerInfo = new BorrowerInfo(borrowerId); + borrowerInfo.troves = 0; + borrowerInfo.trovesByCollateral = (new Array(collateralsCount)).fill(0); + borrowerInfo.nextOwnerIndexes = (new Array(collateralsCount)).fill(0); + } + + // track the amount of troves per collateral + let trovesByCollateral = borrowerInfo.trovesByCollateral; + trovesByCollateral[collIndex] += update === BorrowerTrovesCountUpdate.add ? 1 : -1; + borrowerInfo.trovesByCollateral = trovesByCollateral; + + // track the total amount of troves + borrowerInfo.troves += update === BorrowerTrovesCountUpdate.add ? 1 : -1; + + // nextOwnerIndexes only ever goes up + if (update === BorrowerTrovesCountUpdate.add) { + let nextOwnerIndexes = borrowerInfo.nextOwnerIndexes; + nextOwnerIndexes[collIndex] += 1; + borrowerInfo.nextOwnerIndexes = nextOwnerIndexes; + } + + borrowerInfo.save(); + + return borrowerInfo; +} diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index f46cc0ca3..31d1d112a 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -6,8 +6,8 @@ dataSources: name: BoldToken source: abi: BoldToken - address: "0x6ecf96d1f30cd98c0b58d6b12ea8ae014c52464d" - startBlock: 7279144 + address: "0x0a69fa2a565bd6fa9c3890ecaa30b149aaf99136" + startBlock: 7314166 mapping: kind: ethereum/events apiVersion: 0.0.9 @@ -38,8 +38,8 @@ dataSources: name: Governance source: abi: Governance - address: "0xd7eae81178b97e49e6471f398f0486a7c2e43721" - startBlock: 7279317 + address: "0x7eedda08119826757c98a4680e94f3b5e1f33af6" + startBlock: 7314318 mapping: kind: ethereum/events apiVersion: 0.0.9