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

feat: porting b-protocol and compound to studio #449

Merged
merged 5 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
50 changes: 50 additions & 0 deletions src/apps/b-protocol/adapters/compound.b-protocol-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Inject } from '@nestjs/common';

import { ZERO_ADDRESS } from '~app-toolkit/constants/address';
import { CompoundContractFactory } from '~apps/compound/contracts';
import { CompoundLendingBalanceHelper } from '~apps/compound/helper/compound.lending.balance-helper';
import { CompoundLendingMetaHelper } from '~apps/compound/helper/compound.lending.meta-helper';
import { ProductItem } from '~balance/balance-fetcher.interface';
import { Network } from '~types/network.interface';

import { BProtocolContractFactory } from '../contracts';

const network = Network.ETHEREUM_MAINNET;

export class CompoundBProtocolAdapter {
constructor(
@Inject(CompoundLendingBalanceHelper) private readonly compoundLendingBalanceHelper: CompoundLendingBalanceHelper,
@Inject(CompoundContractFactory) private readonly compoundContractFactory: CompoundContractFactory,
@Inject(CompoundLendingMetaHelper) private readonly compoundLendingMetaHelper: CompoundLendingMetaHelper,
@Inject(BProtocolContractFactory) private readonly bProtocolContractFactory: BProtocolContractFactory,
) {}

async getBalances(address: string): Promise<ProductItem | null> {
const registry = this.bProtocolContractFactory.bProtocolCompoundRegistry({
address: '0xbf698df5591caf546a7e087f5806e216afed666a',
network,
});

const avatarAddress = await registry.avatarOf(address);
if (avatarAddress === ZERO_ADDRESS) return null;

const lendingBalances = await this.compoundLendingBalanceHelper.getBalances({
address: avatarAddress,
appId: 'compound',
supplyGroupId: 'supply',
borrowGroupId: 'borrow',
wpoulin marked this conversation as resolved.
Show resolved Hide resolved
wpoulin marked this conversation as resolved.
Show resolved Hide resolved
network,
getTokenContract: ({ address, network }) => this.compoundContractFactory.compoundCToken({ address, network }),
getBalanceRaw: ({ contract, address, multicall }) => multicall.wrap(contract).balanceOf(address),
getBorrowBalanceRaw: ({ contract, address, multicall }) => multicall.wrap(contract).borrowBalanceCurrent(address),
});

const meta = this.compoundLendingMetaHelper.getMeta({ balances: lendingBalances });

return {
label: 'Compound',
assets: lendingBalances,
meta,
};
}
}
79 changes: 79 additions & 0 deletions src/apps/b-protocol/adapters/liquity.b-protocol-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Inject } from '@nestjs/common';
import _ from 'lodash';

import { drillBalance } from '~app-toolkit';
import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { buildDollarDisplayItem } from '~app-toolkit/helpers/presentation/display-item.present';
import { getTokenImg } from '~app-toolkit/helpers/presentation/image.present';
import { LIQUITY_DEFINITION } from '~apps/liquity';
import { ProductItem } from '~balance/balance-fetcher.interface';
import { ContractType } from '~position/contract.interface';
import { ContractPositionBalance } from '~position/position-balance.interface';
import { Network } from '~types/network.interface';

import { BProtocolContractFactory } from '../contracts';

const network = Network.ETHEREUM_MAINNET;

export class LiquityBProtocolAdapter {
private readonly bammPools = [
'0x0d3abaa7e088c2c82f54b2f47613da438ea8c598',
'0x54bc9113f1f55cdbdf221daf798dc73614f6d972',
];

constructor(
@Inject(BProtocolContractFactory) private readonly bProtocolContractFactory: BProtocolContractFactory,
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
) {}

async getBalances(address: string): Promise<ProductItem | null> {
const multicall = this.appToolkit.getMulticall(network);
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);

const bammLensAddress = '0xfae2e2d3f11bab10ee0ddd0332f6dfe957414ccb';
const contract = this.bProtocolContractFactory.bProtocolBammLens({ address: bammLensAddress, network });
const wrappedContract = multicall.wrap(contract);

const liquityBalancesRaw = await Promise.all(
this.bammPools.map(async pool => {
const userDeposits = await wrappedContract.getUserDeposit(address, pool);
const lusdToken = baseTokens.find(p => p.symbol === 'LUSD');
const ethToken = baseTokens.find(p => p.symbol === 'ETH');
if (!lusdToken || !ethToken) return null;

return [
{ token: lusdToken, balanceRaw: userDeposits.lusd },
{ token: ethToken, balanceRaw: userDeposits.eth },
].map(({ token, balanceRaw }) => {
const tokenBalance = drillBalance(token, balanceRaw.toString());

const contractPositionBalance: ContractPositionBalance = {
type: ContractType.POSITION,
address: bammLensAddress,
appId: LIQUITY_DEFINITION.id,
groupId: LIQUITY_DEFINITION.groups.trove.id,
network,
tokens: [tokenBalance],
balanceUSD: tokenBalance.balanceUSD,
dataProps: {},
displayProps: {
label: `Deposited ${token.symbol} in Liquity`,
secondaryLabel: buildDollarDisplayItem(token.price),
images: [getTokenImg(token.address, network)],
},
};

return contractPositionBalance;
});
}),
);

const liquityBalances = _.compact(liquityBalancesRaw);

return {
label: 'Liquity',
assets: liquityBalances.flat(),
meta: [],
};
}
}
143 changes: 143 additions & 0 deletions src/apps/b-protocol/adapters/maker.b-protocol.adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { Inject } from '@nestjs/common';
import _ from 'lodash';
import { sumBy } from 'lodash';

import { drillBalance } from '~app-toolkit';
import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { getImagesFromToken } from '~app-toolkit/helpers/presentation/image.present';
import { MAKER_DEFINITION } from '~apps/maker';
import { ProductItem } from '~balance/balance-fetcher.interface';
import { ContractType } from '~position/contract.interface';
import { ContractPositionBalance } from '~position/position-balance.interface';
import { Network } from '~types/network.interface';

import { BProtocolContractFactory } from '../contracts';

const network = Network.ETHEREUM_MAINNET;

export class MakerBProtocolAdapter {
private readonly BCDP_MANGER = '0x3f30c2381CD8B917Dd96EB2f1A4F96D91324BBed';
private readonly CDP_MANAGER = '0x5ef30b9986345249bc32d8928B7ee64DE9435E39';
private readonly GET_CDPS = '0x36a724Bd100c39f0Ea4D3A20F7097eE01A8Ff573';
private readonly MCD_VAT = '0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B';
private readonly MCD_SPOT = '0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3';
private readonly PROXY_REGISTRY = '0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4';
private readonly JAR = '0x3C36cCf03dAB88c1b1AC1eb9C3Fb5dB0b6763cFF';
private readonly ILKs = [
{
name: 'ETH-A',
code: '0x4554482d41000000000000000000000000000000000000000000000000000000',
},
{
name: 'ETH-B',
code: '0x4554482d42000000000000000000000000000000000000000000000000000000',
},
{
name: 'ETH-C',
code: '0x4554482d43000000000000000000000000000000000000000000000000000000',
},
{
name: 'WBTC-A',
code: '0x574254432d410000000000000000000000000000000000000000000000000000',
},
];

constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(BProtocolContractFactory) private readonly bProtocolContractFactory: BProtocolContractFactory,
) {}

async getBalances(address: string): Promise<ProductItem | null> {
const baseTokens = await this.appToolkit.getBaseTokenPrices(network);
const contract = this.bProtocolContractFactory.bProtocolGetInfo({
address: '0x468960199c8045dedcf6aeb33e28dc57346ad3ff',
network,
});

const makerBalancesRaw = await Promise.all(
this.ILKs.map(async ilk => {
const userInfo = await contract.callStatic.getInfo(
address,
ilk.code,
this.BCDP_MANGER,
this.CDP_MANAGER,
this.GET_CDPS,
this.MCD_VAT,
this.MCD_SPOT,
this.PROXY_REGISTRY,
this.JAR,
);

// Data Props
const vaultID = Number(userInfo.bCdpInfo.cdp);
const tokenSymbol = ilk.name.split('-')[0];
const collateralToken = baseTokens.find(p => p.symbol === tokenSymbol);
const debtToken = baseTokens.find(p => p.symbol === 'DAI');
if (!collateralToken || !debtToken) return null;

const collateral = drillBalance(collateralToken, userInfo.bCdpInfo.ethDeposit.toString());
const debt = drillBalance(debtToken, userInfo.bCdpInfo.daiDebt.toString(), { isDebt: true });
const tokens = [collateral, debt].filter(v => Math.abs(v.balanceUSD) > 0);
const balanceUSD = sumBy(tokens, v => v.balanceUSD);
const cRatio = debt.balanceUSD === 0 ? 0 : (collateral.balanceUSD / Math.abs(debt.balanceUSD)) * 100;

// Display Props
const label = `Maker Vault #${vaultID}`;
const images = tokens.map(v => getImagesFromToken(v)).flat();

const position: ContractPositionBalance = {
type: ContractType.POSITION,
address: this.BCDP_MANGER,
appId: MAKER_DEFINITION.id,
groupId: MAKER_DEFINITION.groups.vault.id,
network,
tokens,
balanceUSD,

dataProps: {
cRatio,
},

displayProps: {
label,
images,
},
};

return position;
}),
);

const makerBalances = _.compact(makerBalancesRaw);

const [collateral, debt] = _.partition(
makerBalances.flatMap(v => v.tokens),
v => v.balanceUSD >= 0,
);
const collateralUSD = sumBy(collateral, a => a.balanceUSD);
const debtUSD = sumBy(debt, a => a.balanceUSD);
const cRatio = debtUSD === 0 ? 0 : (collateralUSD / Math.abs(debtUSD)) * 100;

return {
label: 'Maker',
assets: makerBalances.flat(),
meta: [
{
label: 'Collateral',
value: collateralUSD,
type: 'dollar',
},
{
label: 'Debt',
value: debtUSD,
type: 'dollar',
},
{
label: 'C-Ratio',
value: cRatio,
type: 'pct',
},
],
};
}
}
Binary file added src/apps/b-protocol/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions src/apps/b-protocol/b-protocol.definition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Register } from '~app-toolkit/decorators';
import { appDefinition, AppDefinition } from '~app/app.definition';
import { AppAction, AppTag, GroupType } from '~app/app.interface';
import { Network } from '~types/network.interface';

export const B_PROTOCOL_DEFINITION = appDefinition({
id: 'b-protocol',
name: 'B.Protocol',
description: `B.Protocol is a backstop protocol which BPRO holders govern. Users of the protocol have access to all the benefits of MakerDAO and Compound (soon Aave), with the additional benefit of splitting liquidation proceeds according to proportional usage of the protocol.`,
groups: {
deposit: { id: 'deposit', type: GroupType.POSITION, label: 'Lending' },
},
url: 'https://www.bprotocol.org/',
links: {
twitter: 'https://twitter.com/bprotocoleth',
discord: 'https://discord.com/invite/bJ4guuw',
medium: 'https://medium.com/b-protocol',
github: 'https://github.com/backstop-protocol',
},
tags: [AppTag.LENDING],
supportedNetworks: {
[Network.ETHEREUM_MAINNET]: [AppAction.VIEW],
},
});

@Register.AppDefinition(B_PROTOCOL_DEFINITION.id)
export class BProtocolAppDefinition extends AppDefinition {
constructor() {
super(B_PROTOCOL_DEFINITION);
}
}

export default B_PROTOCOL_DEFINITION;
25 changes: 25 additions & 0 deletions src/apps/b-protocol/b-protocol.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Register } from '~app-toolkit/decorators';
import { AbstractApp } from '~app/app.dynamic-module';
import { CompoundAppModule } from '~apps/compound/compound.module';

import { CompoundBProtocolAdapter } from './adapters/compound.b-protocol-adapter';
import { LiquityBProtocolAdapter } from './adapters/liquity.b-protocol-adapter';
import { MakerBProtocolAdapter } from './adapters/maker.b-protocol.adapter';
import { BProtocolContractFactory } from './contracts';
import { EthereumBProtocolBalanceFetcher } from './ethereum/b-protocol.balance-fetcher';

import { BProtocolAppDefinition, B_PROTOCOL_DEFINITION } from '.';

@Register.AppModule({
appId: B_PROTOCOL_DEFINITION.id,
imports: [CompoundAppModule],
providers: [
BProtocolAppDefinition,
BProtocolContractFactory,
CompoundBProtocolAdapter,
LiquityBProtocolAdapter,
MakerBProtocolAdapter,
EthereumBProtocolBalanceFetcher,
],
})
export class BProtocolAppModule extends AbstractApp() {}
31 changes: 31 additions & 0 deletions src/apps/b-protocol/contracts/abis/b-protocol-bamm-lens.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "contract BAMMLike",
"name": "bamm",
"type": "address"
}
],
"name": "getUserDeposit",
"outputs": [
{
"internalType": "uint256",
"name": "lusd",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "eth",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
Loading