Skip to content

Commit

Permalink
feat: handle liquidation seize events
Browse files Browse the repository at this point in the history
  • Loading branch information
coreyar committed Sep 6, 2024
1 parent d03056b commit 9317220
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 34 deletions.
92 changes: 65 additions & 27 deletions subgraphs/isolated-pools/src/mappings/vToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
NewAccessControlManager,
NewMarketInterestRateModel,
NewReserveFactor,
ProtocolSeize,
Redeem,
RepayBorrow,
ReservesAdded,
SpreadReservesReduced,
Transfer,
} from '../../generated/PoolRegistry/VToken';
import { VToken as VTokenContract } from '../../generated/PoolRegistry/VToken';
import { nullAddress } from '../constants/addresses';
import { oneBigInt, zeroBigInt32 } from '../constants/index';
import {
Expand All @@ -26,13 +28,17 @@ import {
createRepayBorrowTransaction,
createTransferTransaction,
} from '../operations/create';
import { getOrCreateAccount, getOrCreateMarket } from '../operations/getOrCreate';
import { getMarket } from '../operations/get';
import {
getOrCreateAccount,
getOrCreateAccountVToken,
getOrCreateMarket,
} from '../operations/getOrCreate';
import { recordLiquidatorAsBorrower } from '../operations/recordLiquidatorAsBorrower';
import {
updateAccountVTokenBorrow,
updateAccountVTokenRepayBorrow,
updateAccountVTokenSupply,
updateAccountVTokenTransferFrom,
updateAccountVTokenTransferTo,
updateMarket,
} from '../operations/update';

Expand Down Expand Up @@ -190,6 +196,28 @@ export function handleLiquidateBorrow(event: LiquidateBorrow): void {
const borrower = getOrCreateAccount(event.params.borrower);
borrower.countLiquidated = borrower.countLiquidated + 1;
borrower.save();
const collateralContract = VTokenContract.bind(event.params.vTokenCollateral);
const collateralBalance = collateralContract.balanceOf(event.params.borrower);
const market = getMarket(event.address)!;
// Update balance
const borrowerAccountVToken = getOrCreateAccountVToken(
event.params.borrower,
Address.fromBytes(market.pool),
event.address,
);
const accountFromVToken = borrowerAccountVToken.entity;

// Creation updates balance
if (!borrowerAccountVToken.created) {
accountFromVToken.accountVTokenSupplyBalanceMantissa =
accountFromVToken.accountVTokenSupplyBalanceMantissa.minus(event.params.seizeTokens);
}
accountFromVToken.save();
// Check if borrower is still supplying liquidated asset
if (collateralBalance.equals(zeroBigInt32)) {
market.supplierCount = market.supplierCount.plus(oneBigInt);
market.save();
}

createLiquidateBorrowTransaction(event);
}
Expand All @@ -205,6 +233,10 @@ export function handleNewReserveFactor(event: NewReserveFactor): void {
market.save();
}

export function handleProtocolSeize(event: ProtocolSeize): void {
recordLiquidatorAsBorrower(event);
}

/* Transferring of vTokens
*
* event.params.from = sender of vTokens
Expand All @@ -221,44 +253,50 @@ export function handleNewReserveFactor(event: NewReserveFactor): void {
* events. So for those events, we do not update vToken balances.
*/
export function handleTransfer(event: Transfer): void {
const vTokenAddress = event.address;
const accountFromAddress = event.params.from;
const accountToAddress = event.params.to;

// 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()
const market = updateMarket(
event.address,
event.block.number.toI32(),
event.block.timestamp.toI32(),
);

// 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
if (accountFromAddress.toHex() != nullAddress.toHex()) {
const accountFromAddress = event.params.from;
const accountToAddress = event.params.to;
// Checking if the event is FROM the vToken contract or null (i.e. this will not run when minting)
// Checking if the event is TO the vToken contract (i.e. this will not run when redeeming)
// @TODO Edge case where someone who accidentally sends vTokens to a vToken contract, where it will not get recorded.
if (
accountFromAddress.notEqual(event.address) &&
accountFromAddress.notEqual(nullAddress) &&
accountToAddress.notEqual(event.address)
) {
getOrCreateAccount(accountFromAddress);

updateAccountVTokenTransferFrom(
const resultFrom = getOrCreateAccountVToken(
accountFromAddress,
Address.fromBytes(market.pool),
vTokenAddress,
event.block.number,
event.params.amount,
market.exchangeRateMantissa,
event.address,
);
}
const accountFromVToken = resultFrom.entity;

// Checking if the tx is TO the vToken contract (i.e. this will not run when redeeming)
// 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
if (accountToAddress.toHex() != vTokenAddress.toHex()) {
// Creation updates balance
if (!resultFrom.created) {
accountFromVToken.accountVTokenSupplyBalanceMantissa =
accountFromVToken.accountVTokenSupplyBalanceMantissa.minus(event.params.amount);
}
accountFromVToken.save();
getOrCreateAccount(accountToAddress);

updateAccountVTokenTransferTo(
const resultTo = getOrCreateAccountVToken(
accountToAddress,
Address.fromBytes(market.pool),
vTokenAddress,
event.block.number,
event.address,
);
const accountToVToken = resultTo.entity;
// Creation updates balance
if (!resultTo.created) {
accountToVToken.accountVTokenSupplyBalanceMantissa =
accountToVToken.accountVTokenSupplyBalanceMantissa.plus(event.params.amount);
accountToVToken.save();
}
}

createTransferTransaction(event);
Expand Down
20 changes: 16 additions & 4 deletions subgraphs/isolated-pools/src/operations/getOrCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,36 +68,48 @@ export const getOrCreateAccountPool = (
return accountPool;
};

export class GetOrCreateAccountVTokenReturn {
entity: AccountVToken;
created: boolean;
}

export const getOrCreateAccountVToken = (
accountAddress: Address,
poolAddress: Address,
marketAddress: Address,
enteredMarket: boolean = false, // eslint-disable-line @typescript-eslint/no-inferrable-types
): AccountVToken => {
): GetOrCreateAccountVTokenReturn => {
const accountVTokenId = getAccountVTokenId(marketAddress, accountAddress);
let accountVToken = AccountVToken.load(accountVTokenId);
let created = false;
if (!accountVToken) {
created = true;
accountVToken = new AccountVToken(accountVTokenId);
accountVToken.account = accountAddress;
accountVToken.accountPool = getOrCreateAccountPool(accountAddress, poolAddress).id;
accountVToken.market = marketAddress;
accountVToken.enteredMarket = enteredMarket;
accountVToken.accrualBlockNumber = zeroBigInt32;
// we need to set an initial real onchain value to this otherwise it will never
// be accurate
const vTokenContract = VToken.bind(marketAddress);
const accountSnapshot = vTokenContract.getAccountSnapshot(accountAddress);

const suppliedAmountMantissa = accountSnapshot.value1;
const borrowedAmountMantissa = accountSnapshot.value2;

accountVToken.accountVTokenSupplyBalanceMantissa = suppliedAmountMantissa;
accountVToken.accountBorrowBalanceMantissa = borrowedAmountMantissa;
// @TODO
// accountVToken.vTokenBalanceMantissa = vTokenContract.balanceOf(accountId);
// accountVToken.storedBorrowBalanceMantissa = zeroBigInt32;
// accountVToken.borrowIndex = vTokenContract.borrowIndex();

accountVToken.totalUnderlyingRedeemedMantissa = zeroBigInt32;
accountVToken.accountBorrowIndexMantissa = zeroBigInt32;
accountVToken.totalUnderlyingRepaidMantissa = zeroBigInt32;
accountVToken.enteredMarket = false;
accountVToken.save();
}
return accountVToken;
return { entity: accountVToken, created };
};

export const getOrCreateRewardSpeed = (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Address, ByteArray, Bytes, crypto, ethereum } from '@graphprotocol/graph-ts';

import { VToken as VTokenContract } from '../../generated/PoolRegistry/VToken';
import { ProtocolSeize } from '../../generated/PoolRegistry/VToken';
import { oneBigInt } from '../constants';
import { getMarket } from '../operations/get';
import { getOrCreateAccountVToken } from '../operations/getOrCreate';

class LiquidatorTransferMarker {
current: i32;
vTokenAddress: Address;

constructor(vTokenAddress: Address) {
this.vTokenAddress = vTokenAddress;
}
}

export const recordLiquidatorAsBorrower = (event: ProtocolSeize): void => {
if (event.receipt) {
const reversedLogs = event.receipt!.logs.reverse();
reversedLogs.reduce<LiquidatorTransferMarker>((acc, curr, idx) => {
const protocolSeizeTopic = Bytes.fromByteArray(
crypto.keccak256(ByteArray.fromUTF8('ProtocolSeize(address,address,uint256)')),
);

if (curr.topics.includes(protocolSeizeTopic)) {
acc.current = idx - 1;
}
if (idx == acc.current) {
const transferTuple = ethereum.decode('(address,address,uint256)', curr.data)!.toTuple();
const liquidator = transferTuple[1].toAddress();
const amount = transferTuple[2].toBigInt();
// Register a Transfer to liquidator +1 supplier count
const collateralContract = VTokenContract.bind(acc.vTokenAddress);
const collateralBalanceLiquidator = collateralContract.balanceOf(liquidator);
const market = getMarket(acc.vTokenAddress)!;
// Update balance
const borrowerAccountVToken = getOrCreateAccountVToken(
liquidator,
Address.fromBytes(market.pool),
acc.vTokenAddress,
);
const accountFromVToken = borrowerAccountVToken.entity;

// Creation updates balance
if (!borrowerAccountVToken.created) {
accountFromVToken.accountVTokenSupplyBalanceMantissa =
accountFromVToken.accountVTokenSupplyBalanceMantissa.minus(amount);
}
accountFromVToken.save();
// If the transfer amount equals balance it was a funding transfer
if (collateralBalanceLiquidator.equals(amount)) {
market.supplierCount = market.supplierCount.plus(oneBigInt);
market.save();
}
}
return acc;
}, new LiquidatorTransferMarker(event.address));
}
};
4 changes: 2 additions & 2 deletions subgraphs/isolated-pools/src/operations/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const updateAccountVToken = (
): AccountVToken => {
getOrCreateAccount(accountAddress);
const accountVToken = getOrCreateAccountVToken(accountAddress, poolAddress, marketAddress, false);
accountVToken.accrualBlockNumber = blockNumber;
return accountVToken as AccountVToken;
accountVToken.entity.accrualBlockNumber = blockNumber;
return accountVToken.entity as AccountVToken;
};

export const updateAccountVTokenSupply = (
Expand Down
3 changes: 2 additions & 1 deletion subgraphs/isolated-pools/src/operations/updateOrCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ export const updateOrCreateAccountVToken = (
enteredMarketBool = enteredMarket.value;
}

const accountVToken = getOrCreateAccountVToken(
const result = getOrCreateAccountVToken(
accountAddress,
poolAddress,
marketAddress,
enteredMarketBool,
);
const accountVToken = result.entity;
accountVToken.enteredMarket = enteredMarketBool;
accountVToken.accrualBlockNumber = blockNumber;
accountVToken.save();
Expand Down
2 changes: 2 additions & 0 deletions subgraphs/isolated-pools/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ templates:
handler: handleRepayBorrow
- event: LiquidateBorrow(indexed address,indexed address,uint256,indexed address,uint256)
handler: handleLiquidateBorrow
- event: ProtocolSeize(indexed address,indexed address,uint256)
handler: handleProtocolSeize
- event: AccrueInterest(uint256,uint256,uint256,uint256)
handler: handleAccrueInterest
- event: NewReserveFactor(uint256,uint256)
Expand Down

0 comments on commit 9317220

Please sign in to comment.