Skip to content
This repository has been archived by the owner on Jan 24, 2024. It is now read-only.

Commit

Permalink
(feat) Gravita: Make vessel positions a token pair
Browse files Browse the repository at this point in the history
Use AdminContract to fetch collateral types instead of hardcoding
Separate each token (plus GRAI) into their own position definitions
  • Loading branch information
Will Robertson authored and Will Robertson committed Sep 30, 2023
1 parent 9d8e2ed commit 9031af9
Show file tree
Hide file tree
Showing 15 changed files with 8,980 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,5 @@ import { GravitaVesselContractPositionFetcher } from '../common/gravita.vessel.c

@PositionTemplate()
export class ArbitrumGravitaVesselContractPositionFetcher extends GravitaVesselContractPositionFetcher {
vesselManagerAddress = '0x6adaa3eba85c77e8566b73aefb4c2f39df4046ca';
borrowerOperationsAddress = '0x89f1eccf2644902344db02788a790551bb070351';
collateralTokenAddresses = [
'0x5979d7b546e38e414f7e9822514be443a4800529', // wstETH
'0x82af49447d8a07e3bd95bd0d56f35241523fbab1', // WETH
'0x8ffdf2de812095b1d19cb146e4c004587c0a0692', // LUSD
'0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8', // rETH
];
}
133 changes: 52 additions & 81 deletions src/apps/gravita/common/gravita.vessel.contract-position-fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { Inject } from '@nestjs/common';
import { BigNumber } from 'ethers';
import { sumBy } from 'lodash';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { drillBalance } from '~app-toolkit/helpers/drill-balance.helper';
import { getLabelFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { MetaType } from '~position/position.interface';
import { isSupplied } from '~position/position.utils';
import { ContractPositionTemplatePositionFetcher } from '~position/template/contract-position.template.position-fetcher';
import {
DefaultContractPositionDefinition,
GetDefinitionsParams,
GetTokenDefinitionsParams,
UnderlyingTokenDefinition,
} from '~position/template/contract-position.template.types';

import { GravitaContractFactory, VesselManager } from '../contracts';
import { AdminContract, BorrowerOperations, GravitaContractFactory, PriceFeed, VesselManager } from '../contracts';

export abstract class GravitaVesselContractPositionFetcher extends ContractPositionTemplatePositionFetcher<VesselManager> {
export type GravitaVesselDefinition = {
address: string;
collateralAddress: string;
};

export abstract class GravitaVesselContractPositionFetcher extends ContractPositionTemplatePositionFetcher<BorrowerOperations> {
groupLabel = 'Vessel';
abstract vesselManagerAddress: string;
abstract borrowerOperationsAddress: string;
abstract collateralTokenAddresses: string[];

constructor(
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
Expand All @@ -27,98 +29,67 @@ export abstract class GravitaVesselContractPositionFetcher extends ContractPosit
super(appToolkit);
}

getContract(address: string): VesselManager {
return this.gravitaContractFactory.vesselManager({ address, network: this.network });
// Using BorrowerOperations, rather than VesselManager
// As per https://studio.zapper.fi/docs/concepts/contract-positions#what-address-should-i-use-for-a-contractposition
getContract(address: string): BorrowerOperations {
return this.gravitaContractFactory.borrowerOperations({ address, network: this.network });
}

// Using BorrowerOperations, rather than VesselManager - per https://studio.zapper.fi/docs/concepts/contract-positions#what-address-should-i-use-for-a-contractposition
async getDefinitions(): Promise<DefaultContractPositionDefinition[]> {
return [{ address: this.borrowerOperationsAddress }];
getVesselManager(address: string): VesselManager {
return this.gravitaContractFactory.vesselManager({ address, network: this.network });
}

async _getDebtToken(contract: VesselManager): Promise<UnderlyingTokenDefinition> {
return {
metaType: MetaType.BORROWED,
address: await contract.debtToken(),
network: this.network,
};
getAdminContract(address: string): AdminContract {
return this.gravitaContractFactory.adminContract({ address, network: this.network });
}

async _getCollateralTokens(): Promise<UnderlyingTokenDefinition[]> {
return this.collateralTokenAddresses.map(collateralAddress => ({
metaType: MetaType.SUPPLIED,
address: collateralAddress,
network: this.network,
}));
getPriceFeed(address: string): PriceFeed {
return this.gravitaContractFactory.priceFeed({ address, network: this.network });
}

async getPositionsForBalances() {
const defaultContractPositions = await this.appToolkit.getAppContractPositions({
appId: this.appId,
async getDefinitions({ multicall }: GetDefinitionsParams): Promise<GravitaVesselDefinition[]> {
const contract = this.gravitaContractFactory.borrowerOperations({
address: this.borrowerOperationsAddress,
network: this.network,
groupIds: [this.groupId],
});
const adminContractAddress = await multicall.wrap(contract).adminContract();
const adminContract = this.getAdminContract(adminContractAddress);
const allCollaterals = await multicall.wrap(adminContract).getValidCollateral();
return allCollaterals.map(collateralAddress => ({ address: this.borrowerOperationsAddress, collateralAddress }));
}

return this.collateralTokenAddresses.map(collateralToken => {
return {
...defaultContractPositions[0],
collateralToken,
};
});
async _getDebtToken(contract: BorrowerOperations): Promise<UnderlyingTokenDefinition> {
return {
metaType: MetaType.BORROWED,
address: await contract.debtToken(),
network: this.network,
};
}

async getTokenDefinitions({
contract,
}: GetTokenDefinitionsParams<VesselManager>): Promise<UnderlyingTokenDefinition[]> {
definition,
}: GetTokenDefinitionsParams<BorrowerOperations, GravitaVesselDefinition>): Promise<UnderlyingTokenDefinition[]> {
const debtToken = await this._getDebtToken(contract);
const collateralTokens = await this._getCollateralTokens();
return [debtToken, ...collateralTokens];
}

async getLabel(): Promise<string> {
// async getLabel({ contractPosition }): Promise<string> {
// FIXME: Can't label the Vessel with collateral type, because getLabel is called before getPositionsForBalances,
// and so collateralToken class variable isn't available yet.
// const collateralToken = contractPosition.tokens.find(t => t.address === contractPosition.collateralToken)!;
// return `${getLabelFromToken(collateralToken)} Vessel`;
return 'Gravita Vessel';
const collateralToken = {
metaType: MetaType.SUPPLIED,
address: definition.collateralAddress,
network: this.network,
};
return [debtToken, collateralToken];
}

async getBalances(_address: string) {
const multicall = this.appToolkit.getMulticall(this.network);
const address = await this.getAccountAddress(_address);

const contractPositions = await this.getPositionsForBalances();
const filteredPositions = await this.filterPositionsForAddress(address, contractPositions);

const contract = multicall.wrap(this.getContract(this.vesselManagerAddress));
const balances = await Promise.all(
filteredPositions.map(async contractPosition => {
const balancesRaw = await this.getTokenBalancesPerPosition({ address, contractPosition, contract });
const allTokens = contractPosition.tokens.map((cp, idx) =>
drillBalance(cp, balancesRaw[idx]?.toString() ?? '0', { isDebt: cp.metaType === MetaType.BORROWED }),
);

const tokens = allTokens.filter(v => Math.abs(v.balanceUSD) > 0.01);
const balanceUSD = sumBy(tokens, t => t.balanceUSD);

const balance = { ...contractPosition, tokens, balanceUSD };
return balance;
}),
);

return balances;
async getLabel({ contractPosition }): Promise<string> {
const collateralToken = contractPosition.tokens.find(isSupplied)!;
return `${getLabelFromToken(collateralToken)} Vessel`;
}

getTokenBalancesPerPosition({ address, contractPosition, contract }) {
return Promise.all(
contractPosition.tokens.map(token => {
if (token.metaType == MetaType.BORROWED)
return contract.getVesselDebt(contractPosition.collateralToken, address);
else if (token.address == contractPosition.collateralToken)
return contract.getVesselColl(contractPosition.collateralToken, address);
else return BigNumber.from(0);
}),
);
async getTokenBalancesPerPosition({ address, contractPosition, contract }) {
const vesselManager = this.getVesselManager(await contract.vesselManager());
const collateral = contractPosition.tokens.find(isSupplied)!;
return Promise.all([
vesselManager.getVesselDebt(collateral.address, address),
vesselManager.getVesselColl(collateral.address, address),
]);
}
}
Loading

0 comments on commit 9031af9

Please sign in to comment.