From 4d898325b27ad1c33b6c364b40fa37a94e3f748b Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Mon, 2 Dec 2024 12:48:53 +0000 Subject: [PATCH] Detect leverage in subgraph (#626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Subgraph: reorganize the operations handling + fix the leverage detection - openLeveragePosition tx flow: don’t store the leverage mode in storage (rely on the subgraph instead) --- .../app/src/tx-flows/openLeveragePosition.tsx | 10 +- subgraph/src/TroveManager.mapping.ts | 274 ++++++++++-------- 2 files changed, 159 insertions(+), 125 deletions(-) diff --git a/frontend/app/src/tx-flows/openLeveragePosition.tsx b/frontend/app/src/tx-flows/openLeveragePosition.tsx index 80adacb8..5c5e9a3d 100644 --- a/frontend/app/src/tx-flows/openLeveragePosition.tsx +++ b/frontend/app/src/tx-flows/openLeveragePosition.tsx @@ -305,7 +305,7 @@ export const openLeveragePosition: FlowDeclaration = { throw new Error("Not implemented"); }, - async postFlowCheck({ request, steps, storedState }) { + async postFlowCheck({ request, steps }) { const lastStep = steps?.at(-1); if (lastStep?.txStatus !== "post-check" || !isTroveId(lastStep.txReceiptData)) { @@ -316,14 +316,6 @@ export const openLeveragePosition: FlowDeclaration = { while (true) { const { trove } = await graphQuery(TroveByIdQuery, { id: prefixedTroveId }); if (trove !== null) { - storedState.setState(({ loanModes }) => { - return { - loanModes: { - ...loanModes, - [prefixedTroveId]: "leverage", - }, - }; - }); return; } } diff --git a/subgraph/src/TroveManager.mapping.ts b/subgraph/src/TroveManager.mapping.ts index 1606598f..8d329e45 100644 --- a/subgraph/src/TroveManager.mapping.ts +++ b/subgraph/src/TroveManager.mapping.ts @@ -1,4 +1,4 @@ -import { Address, BigInt, ByteArray, crypto, dataSource } from "@graphprotocol/graph-ts"; +import { Address, BigInt, Bytes, dataSource } from "@graphprotocol/graph-ts"; import { BorrowerInfo, Collateral, InterestBatch, InterestRateBracket, Trove } from "../generated/schema"; import { BatchUpdated as BatchUpdatedEvent, @@ -7,6 +7,8 @@ import { } from "../generated/templates/TroveManager/TroveManager"; import { TroveNFT as TroveNFTContract } from "../generated/templates/TroveManager/TroveNFT"; +// decides whether to update the flag indicating +// that a trove might be leveraged or not. enum LeverageUpdate { yes = 0, no = 1, @@ -27,90 +29,88 @@ let OP_OPEN_TROVE_AND_JOIN_BATCH = 7; let OP_SET_INTEREST_BATCH_MANAGER = 8; let OP_REMOVE_FROM_BATCH = 9; -let FLASH_LOAN_TOPIC_HASH = crypto.keccak256( - ByteArray.fromUTF8("FlashLoan(address,address,uint256,uint256)"), -).toHexString(); +let FLASH_LOAN_TOPIC = Bytes.fromHexString( + // keccak256("FlashLoan(address,address,uint256,uint256)") + "0x0d7d75e01ab95780d3cd1c8ec0dd6c2ce19e3a20427eec8bf53283b6fb8e95f0", +); export function handleTroveOperation(event: TroveOperationEvent): void { let timestamp = event.block.timestamp; let troveId = event.params._troveId; let collId = dataSource.context().getString("collId"); - let troveFullId = collId + ":" + troveId.toHexString(); let operation = event.params._operation; let tm = TroveManagerContract.bind(event.address); - let leverageUpdate = getLeverageUpdate(event); - let trove = Trove.load(troveFullId); - - switch (operation) { - case OP_OPEN_TROVE: - case OP_ADJUST_TROVE: - case OP_ADJUST_TROVE_INTEREST_RATE: - case OP_APPLY_PENDING_DEBT: - updateTrove(tm, troveId, timestamp, leverageUpdate); - break; - - case OP_OPEN_TROVE_AND_JOIN_BATCH: - updateTrove(tm, troveId, timestamp, leverageUpdate); - enterBatch(collId, troveId, tm.Troves(troveId).getInterestBatchManager()); - break; - - case OP_SET_INTEREST_BATCH_MANAGER: - enterBatch(collId, troveId, tm.Troves(troveId).getInterestBatchManager()); - break; - - case OP_REMOVE_FROM_BATCH: - leaveBatch(collId, troveId, event.params._annualInterestRate); - break; - - case OP_REDEEM_COLLATERAL: - updateTrove(tm, troveId, timestamp, leverageUpdate); - - if (!trove) { - throw new Error("Trove not found: " + troveFullId); - } - trove.status = "redeemed"; - trove.save(); - break; + let trove: Trove | null = null; - case OP_CLOSE_TROVE: - updateTrove(tm, troveId, timestamp, leverageUpdate); + if (operation === OP_OPEN_TROVE) { + updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), true); + return; + } - if (!trove) { - throw new Error("Trove not found: " + troveFullId); - } + if (operation === OP_ADJUST_TROVE) { + updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), true); + return; + } - if (trove.interestBatch !== null) { - leaveBatch(collId, troveId, BigInt.fromI32(0)); - } + if (operation === OP_APPLY_PENDING_DEBT) { + updateTrove(tm, troveId, timestamp, LeverageUpdate.unchanged, false); + return; + } - trove.closedAt = timestamp; - trove.status = "closed"; - trove.save(); - break; + if (operation === OP_OPEN_TROVE_AND_JOIN_BATCH) { + updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), true); + enterBatch(collId, troveId, tm.Troves(troveId).getInterestBatchManager()); + return; + } - case OP_LIQUIDATE: - updateTrove(tm, troveId, timestamp, leverageUpdate); + if (operation === OP_ADJUST_TROVE_INTEREST_RATE) { + updateTrove(tm, troveId, timestamp, LeverageUpdate.unchanged, false); + return; + } - if (!trove) { - throw new Error("Trove not found: " + troveFullId); - } + if (operation === OP_SET_INTEREST_BATCH_MANAGER) { + enterBatch(collId, troveId, tm.Troves(troveId).getInterestBatchManager()); + return; + } - if (trove.interestBatch !== null) { - leaveBatch(collId, troveId, BigInt.fromI32(0)); - } + if (operation === OP_REMOVE_FROM_BATCH) { + leaveBatch(collId, troveId, event.params._annualInterestRate); + return; + } - trove.debt = event.params._debtIncreaseFromRedist; - trove.deposit = event.params._collIncreaseFromRedist; + if (operation === OP_REDEEM_COLLATERAL) { + trove = updateTrove(tm, troveId, timestamp, LeverageUpdate.unchanged, false); + trove.status = "redeemed"; + trove.save(); + return; + } - trove.closedAt = timestamp; - trove.status = "liquidated"; - trove.save(); - break; + if (operation === OP_CLOSE_TROVE) { + trove = updateTrove(tm, troveId, timestamp, LeverageUpdate.unchanged, false); + if (trove.interestBatch !== null) { + leaveBatch(collId, troveId, BigInt.fromI32(0)); + } + trove.closedAt = timestamp; + trove.status = "closed"; + trove.save(); + return; + } - default: - throw new Error("Unsupported operation: " + operation.toString()); + if (operation === OP_LIQUIDATE) { + trove = updateTrove(tm, troveId, timestamp, LeverageUpdate.unchanged, false); + if (trove.interestBatch !== null) { + leaveBatch(collId, troveId, BigInt.fromI32(0)); + } + trove.debt = event.params._debtIncreaseFromRedist; + trove.deposit = event.params._collIncreaseFromRedist; + trove.closedAt = timestamp; + trove.status = "liquidated"; + trove.save(); + return; } + + throw new Error("Unsupported operation: " + operation.toString()); } function getLeverageUpdate(event: TroveOperationEvent): LeverageUpdate { @@ -127,7 +127,7 @@ function getLeverageUpdate(event: TroveOperationEvent): LeverageUpdate { let receipt = event.receipt; let logs = receipt ? receipt.logs : []; for (let i = 0; i < logs.length; i++) { - if (logs[i].topics[0].toHexString() === FLASH_LOAN_TOPIC_HASH) { + if (logs[i].topics[0].equals(FLASH_LOAN_TOPIC)) { return LeverageUpdate.yes; } } @@ -259,6 +259,73 @@ function updateRateBracketDebt( newRateBracket.save(); } +function createTrove( + troveId: BigInt, + debt: BigInt, + deposit: BigInt, + stake: BigInt, + interestRate: BigInt, + timestamp: BigInt, + mightBeLeveraged: boolean, +): Trove { + let collId = dataSource.context().getString("collId"); + let troveFullId = collId + ":" + troveId.toHexString(); + + let collateral = Collateral.load(collId); + if (!collateral) { + throw new Error("Non-existent collateral: " + collId); + } + + let trove = Trove.load(troveFullId); + if (trove) { + throw new Error("Trove already exists: " + troveFullId); + } + + let borrower = TroveNFTContract.bind(Address.fromBytes( + 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); + borrowerInfo.lastTroveIdByCollateral = (new Array(totalCollaterals)).fill(0); + } + + // update borrower info + let trovesByColl = borrowerInfo.trovesByCollateral; + trovesByColl[collateral.collIndex] += 1; + borrowerInfo.trovesByCollateral = trovesByColl; + borrowerInfo.troves += 1; + borrowerInfo.save(); + + // create trove + trove = new Trove(troveFullId); + trove.borrower = borrower; + trove.collateral = collId; + trove.createdAt = timestamp; + trove.debt = debt; + trove.deposit = deposit; + trove.stake = stake; + trove.status = "active"; + trove.troveId = troveId.toHexString(); + trove.updatedAt = timestamp; + + // batches are handled separately, not + // when creating the trove but right after + trove.interestRate = interestRate; + trove.interestBatch = null; + trove.mightBeLeveraged = mightBeLeveraged; + + trove.save(); + + return trove; +} + // When a trove gets updated (either on TroveUpdated or BatchedTroveUpdated): // 1. update the collateral (branch) total deposited & debt // 2. create the trove entity if it doesn't exist @@ -270,8 +337,9 @@ function updateTrove( troveManagerContract: TroveManagerContract, troveId: BigInt, timestamp: BigInt, - mightBeLeveragedUpdate: LeverageUpdate, -): void { + leverageUpdate: LeverageUpdate, + createIfMissing: boolean, +): Trove { let collId = dataSource.context().getString("collId"); let collateral = Collateral.load(collId); @@ -298,69 +366,43 @@ function updateTrove( // create trove if needed if (!trove) { - let borrower = TroveNFTContract.bind(Address.fromBytes( - 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); + if (!createIfMissing) { + throw new Error("Trove not found: " + troveFullId); } + let trove = createTrove( + troveId, + newDebt, + newDeposit, + newStake, + newInterestRate, + timestamp, + leverageUpdate === LeverageUpdate.yes, + ); - // update borrower info - let trovesByColl = borrowerInfo.trovesByCollateral; - trovesByColl[collateral.collIndex] += 1; - borrowerInfo.trovesByCollateral = trovesByColl; - borrowerInfo.troves += 1; - borrowerInfo.save(); - - // create trove - trove = new Trove(troveFullId); - trove.borrower = borrower; - trove.collateral = collId; - trove.createdAt = timestamp; - trove.debt = newDebt; - trove.deposit = newDeposit; - trove.interestRate = trove.interestBatch === null - ? newInterestRate - : BigInt.fromI32(0); - trove.stake = newStake; - trove.status = "active"; - trove.troveId = troveId.toHexString(); - trove.updatedAt = timestamp; - trove.mightBeLeveraged = mightBeLeveragedUpdate === LeverageUpdate.yes; - trove.save(); + // update interest rate brackets (no need to check if the trove + // is in a batch as this is done after calling updateTrove()) + updateRateBracketDebt(collId, prevInterestRate, newInterestRate, prevDebt, newDebt); + return trove; } // update interest rate brackets for non-batched troves if (trove.interestBatch === null) { - updateRateBracketDebt( - collId, - prevInterestRate, - newInterestRate, - prevDebt, - newDebt, - ); + updateRateBracketDebt(collId, prevInterestRate, newInterestRate, prevDebt, newDebt); } trove.debt = newDebt; trove.deposit = newDeposit; - trove.interestRate = trove.interestBatch === null - ? newInterestRate - : BigInt.fromI32(0); + trove.interestRate = trove.interestBatch === null ? newInterestRate : BigInt.fromI32(0); trove.stake = newStake; - if (mightBeLeveragedUpdate !== LeverageUpdate.unchanged) { - trove.mightBeLeveraged = mightBeLeveragedUpdate === LeverageUpdate.yes; + if (leverageUpdate !== LeverageUpdate.unchanged) { + trove.mightBeLeveraged = leverageUpdate === LeverageUpdate.yes; } trove.updatedAt = timestamp; trove.save(); + + return trove; } // when a batch gets updated: