Skip to content

Commit

Permalink
Subgraph: address issues pointed out by review (#687)
Browse files Browse the repository at this point in the history
This commit addresses some issues pointed out by @danielattilasimon’s review.

- [x] Consistent `status` updates (fix troves being stuck in redeemed mode)
- [x] Simplify `getLeverageUpdate()`
- [x] `enterBatch()` and `leaveBatch()` now return the Trove
- [x] NFT transfers support
- [x] ~Fix collateral calculations~ removed, these were not used
  • Loading branch information
bpierre authored Jan 14, 2025
1 parent 81824a6 commit ee9a029
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 56 deletions.
3 changes: 0 additions & 3 deletions subgraph/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ type Collateral @entity {
addresses: CollateralAddresses! @derivedFrom(field: "collateral")
stabilityPoolDeposits: [StabilityPoolDeposit!]!
@derivedFrom(field: "collateral")
totalDeposited: BigInt!
totalDebt: BigInt!
price: BigInt!
}

type Token @entity(immutable: true) {
Expand Down
10 changes: 6 additions & 4 deletions subgraph/src/BoldToken.mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { CollateralRegistry as CollateralRegistryContract } from "../generated/B
import { ERC20 as ERC20Contract } from "../generated/BoldToken/ERC20";
import { TroveManager as TroveManagerContract } from "../generated/BoldToken/TroveManager";
import { Collateral, CollateralAddresses, StabilityPoolEpochScale, Token } from "../generated/schema";
import { StabilityPool as StabilityPoolTemplate, TroveManager as TroveManagerTemplate } from "../generated/templates";
import {
StabilityPool as StabilityPoolTemplate,
TroveManager as TroveManagerTemplate,
TroveNFT as TroveNFTTemplate,
} from "../generated/templates";

function addCollateral(
collIndex: i32,
Expand All @@ -20,9 +24,6 @@ function addCollateral(
let collateral = new Collateral(collId);
collateral.collIndex = collIndex;
collateral.token = collId;
collateral.totalDebt = BigInt.fromI32(0);
collateral.totalDeposited = BigInt.fromI32(0);
collateral.price = BigInt.fromI32(0);

let token = new Token(collId);
let tokenContract = ERC20Contract.bind(tokenAddress);
Expand Down Expand Up @@ -68,6 +69,7 @@ function addCollateral(
context.setI32("totalCollaterals", totalCollaterals);

TroveManagerTemplate.createWithContext(troveManagerAddress, context);
TroveNFTTemplate.createWithContext(Address.fromBytes(addresses.troveNft), context);
StabilityPoolTemplate.createWithContext(Address.fromBytes(addresses.stabilityPool), context);
}

Expand Down
79 changes: 30 additions & 49 deletions subgraph/src/TroveManager.mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { TroveNFT as TroveNFTContract } from "../generated/templates/TroveManage
// decides whether to update the flag indicating
// that a trove might be leveraged or not.
enum LeverageUpdate {
yes = 0,
no = 1,
unchanged = 2,
yes,
no,
unchanged,
}

// see Operation enum in
Expand Down Expand Up @@ -49,7 +49,9 @@ export function handleTroveOperation(event: TroveOperationEvent): void {
}

if (operation === OP_ADJUST_TROVE) {
updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), true);
trove = updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), false);
trove.status = "active";
trove.save();
return;
}

Expand All @@ -65,17 +67,23 @@ export function handleTroveOperation(event: TroveOperationEvent): void {
}

if (operation === OP_ADJUST_TROVE_INTEREST_RATE) {
updateTrove(tm, troveId, timestamp, LeverageUpdate.unchanged, false);
trove = updateTrove(tm, troveId, timestamp, getLeverageUpdate(event), false);
trove.status = "active";
trove.save();
return;
}

if (operation === OP_SET_INTEREST_BATCH_MANAGER) {
enterBatch(collId, troveId, tm.Troves(troveId).getInterestBatchManager());
trove = enterBatch(collId, troveId, tm.Troves(troveId).getInterestBatchManager());
trove.status = "active";
trove.save();
return;
}

if (operation === OP_REMOVE_FROM_BATCH) {
leaveBatch(collId, troveId, event.params._annualInterestRate);
trove = leaveBatch(collId, troveId, event.params._annualInterestRate);
trove.status = "active";
trove.save();
return;
}

Expand Down Expand Up @@ -114,27 +122,14 @@ export function handleTroveOperation(event: TroveOperationEvent): void {
}

function getLeverageUpdate(event: TroveOperationEvent): LeverageUpdate {
let operation = event.params._operation;

// check if the operation involved a flash loan,
// which would indicate a potential leveraged update
if (
operation === OP_OPEN_TROVE
|| operation === OP_OPEN_TROVE_AND_JOIN_BATCH
|| operation === OP_ADJUST_TROVE
|| operation === OP_ADJUST_TROVE_INTEREST_RATE
) {
let receipt = event.receipt;
let logs = receipt ? receipt.logs : [];
for (let i = 0; i < logs.length; i++) {
if (logs[i].topics[0].equals(FLASH_LOAN_TOPIC)) {
return LeverageUpdate.yes;
}
let receipt = event.receipt;
let logs = receipt ? receipt.logs : [];
for (let i = 0; i < logs.length; i++) {
if (logs[i].topics[0].equals(FLASH_LOAN_TOPIC)) {
return LeverageUpdate.yes;
}
return LeverageUpdate.no;
}

return LeverageUpdate.unchanged;
return LeverageUpdate.no;
}

function floorToDecimals(value: BigInt, decimals: u8): BigInt {
Expand All @@ -150,25 +145,15 @@ function getRateFloored(rate: BigInt): BigInt {
// 1. set the interest batch on the trove
// 2. set the interest rate to 0 (indicating that the trove is in a batch)
// 3. remove its debt from its rate bracket (handled at the batch level)
function enterBatch(collId: string, troveId: BigInt, batchManager: Address): void {
function enterBatch(collId: string, troveId: BigInt, batchManager: Address): Trove {
let troveFullId = collId + ":" + troveId.toHexString();
let batchId = collId + ":" + batchManager.toHexString();

let trove = Trove.load(troveFullId);
if (!trove) {
if (trove === null) {
throw new Error("Trove not found: " + troveFullId);
}

// leave the previous batch if needed
if (trove.interestBatch !== null) {
leaveBatch(collId, troveId, BigInt.fromI32(0));
}

let batch = InterestBatch.load(batchId);
if (!batch) {
throw new Error("Batch not found: " + batchId);
}

updateRateBracketDebt(
collId,
trove.interestRate,
Expand All @@ -180,30 +165,26 @@ function enterBatch(collId: string, troveId: BigInt, batchManager: Address): voi
trove.interestBatch = batchId;
trove.interestRate = BigInt.fromI32(0);
trove.save();

return trove;
}

// When a trove leaves a batch:
// 1. remove the interest batch on the trove
// 2. set the interest rate to the new rate
// 3. add its debt to the rate bracket of the current rate
function leaveBatch(collId: string, troveId: BigInt, interestRate: BigInt): void {
function leaveBatch(collId: string, troveId: BigInt, interestRate: BigInt): Trove {
let troveFullId = collId + ":" + troveId.toHexString();

let trove = Trove.load(troveFullId);
if (trove === null) {
throw new Error("Trove not found: " + troveFullId);
}

let batchId = trove.interestBatch;
if (batchId === null) {
if (trove.interestBatch === null) {
throw new Error("Trove is not in a batch: " + troveFullId);
}

let batch = InterestBatch.load(batchId);
if (batch === null) {
throw new Error("Batch not found: " + batchId);
}

updateRateBracketDebt(
collId,
BigInt.fromI32(0), // coming from rate 0 (in batch)
Expand All @@ -214,7 +195,10 @@ function leaveBatch(collId: string, troveId: BigInt, interestRate: BigInt): void

trove.interestBatch = null;
trove.interestRate = interestRate;
trove.status = "active"; // always reset the status when leaving a batch
trove.save();

return trove;
}

function loadOrCreateInterestRateBracket(
Expand Down Expand Up @@ -350,7 +334,6 @@ function updateTrove(
let trove = Trove.load(troveFullId);

let prevDebt = trove ? trove.debt : BigInt.fromI32(0);
let prevDeposit = trove ? trove.deposit : BigInt.fromI32(0);
let prevInterestRate = trove ? trove.interestRate : null;

let troveData = troveManagerContract.getLatestTroveData(troveId);
Expand All @@ -359,8 +342,6 @@ function updateTrove(
let newInterestRate = troveData.annualInterestRate;
let newStake = troveManagerContract.Troves(troveId).getStake();

collateral.totalDeposited = collateral.totalDeposited.minus(prevDeposit).plus(newDeposit);
collateral.totalDebt = collateral.totalDebt.minus(prevDebt).plus(newDebt);
collateral.save();

// create trove if needed
Expand Down
68 changes: 68 additions & 0 deletions subgraph/src/TroveNFT.mapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Address, BigInt, dataSource } from "@graphprotocol/graph-ts";
import { BorrowerInfo, 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<i32>(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 = <i32> parseInt(trove.collateral);
trovesByColl[collIndex] += diff;

borrowerInfo.trovesByCollateral = trovesByColl;
borrowerInfo.troves += diff;
borrowerInfo.save();
}

export function handleTransfer(event: TransferEvent): void {
// Minting doesn’t need to be handled as we are already
// handling OP_OPEN_TROVE & OP_OPEN_TROVE_AND_JOIN_BATCH
// in TroveManager.mapping.ts.
if (event.params.from.equals(Address.zero())) {
return;
}

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

// update BorrowerInfo for the new owner (including zero address)
updateBorrowerInfo(event.params.to, troveFullId, BorrowerInfoUpdate.add);

// update the trove borrower
let trove = Trove.load(troveFullId);
if (trove) {
trove.borrower = event.params.to;
trove.save();
}
}
21 changes: 21 additions & 0 deletions subgraph/subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dataSources:
file: ../contracts/out/ERC20.sol/ERC20.json
- name: TroveManager
file: ../contracts/out/TroveManager.sol/TroveManager.json
- name: TroveNFT
file: ../contracts/out/TroveNFT.sol/TroveNFT.json
eventHandlers:
- event: CollateralRegistryAddressChanged(address)
handler: handleCollateralRegistryAddressChanged
Expand Down Expand Up @@ -99,6 +101,25 @@ templates:
- event: BatchUpdated(indexed
address,uint8,uint256,uint256,uint256,uint256,uint256,uint256)
handler: handleBatchUpdated
- name: TroveNFT
kind: ethereum/contract
network: sepolia
source:
abi: TroveNFT
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
file: ./src/TroveNFT.mapping.ts
entities:
- BorrowerInfo
- Trove
abis:
- name: TroveNFT
file: ../contracts/out/TroveNFT.sol/TroveNFT.json
eventHandlers:
- event: Transfer(indexed address,indexed address,indexed uint256)
handler: handleTransfer
- name: StabilityPool
kind: ethereum/contract
network: sepolia
Expand Down

0 comments on commit ee9a029

Please sign in to comment.