Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect leverage in subgraph #626

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions frontend/app/src/tx-flows/openLeveragePosition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ export const openLeveragePosition: FlowDeclaration<Request, Step> = {
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)) {
Expand All @@ -316,14 +316,6 @@ export const openLeveragePosition: FlowDeclaration<Request, Step> = {
while (true) {
const { trove } = await graphQuery(TroveByIdQuery, { id: prefixedTroveId });
if (trove !== null) {
storedState.setState(({ loanModes }) => {
return {
loanModes: {
...loanModes,
[prefixedTroveId]: "leverage",
},
};
});
return;
}
}
Expand Down
274 changes: 158 additions & 116 deletions subgraph/src/TroveManager.mapping.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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<i32>(totalCollaterals)).fill(0);
borrowerInfo.lastTroveIdByCollateral = (new Array<i32>(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
Expand All @@ -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);
Expand All @@ -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<i32>(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:
Expand Down