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

Commit

Permalink
feat(aura): Migrate Aura to template (#1628)
Browse files Browse the repository at this point in the history
  • Loading branch information
wpoulin authored Oct 28, 2022
1 parent e6f1988 commit 395c37f
Show file tree
Hide file tree
Showing 27 changed files with 6,109 additions and 927 deletions.
10 changes: 5 additions & 5 deletions src/apps/aura/aura.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export const AURA_DEFINITION = appDefinition({
label: 'auraBAL',
},

staking: {
id: 'staking',
auraBalStaking: {
id: 'aura-bal-staking',
type: GroupType.POSITION,
label: 'Staked auraBAL',
},
Expand All @@ -35,10 +35,10 @@ export const AURA_DEFINITION = appDefinition({
label: 'Deposit',
},

pool: {
id: 'pool',
lpFarm: {
id: 'lp-farm',
type: GroupType.POSITION,
label: 'Balancer Pools',
label: 'Liquidity Pool Staking',
},

locker: {
Expand Down
25 changes: 9 additions & 16 deletions src/apps/aura/aura.module.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,30 @@
import { Register } from '~app-toolkit/decorators';
import { AbstractApp } from '~app/app.dynamic-module';
import { BalancerV2AppModule } from '~apps/balancer-v2';
import { SynthetixAppModule } from '~apps/synthetix';

import { AuraAppDefinition, AURA_DEFINITION } from './aura.definition';
import { AuraBalancerPoolResolver } from './common/aura.balancer-pool.resolver';
import { AuraContractFactory } from './contracts';
import { EthereumAuraBalTokenFetcher } from './ethereum/aura.aura-bal.token-fetcher';
import { EthereumAuraBalanceFetcher } from './ethereum/aura.balance-fetcher';
import { EthereumAuraAuraBalStakingContractPositionFetcher } from './ethereum/aura.aura-bal-staking.contract-position-fetcher';
import { EthereumAuraAuraBalTokenFetcher } from './ethereum/aura.aura-bal.token-fetcher';
import { EthereumAuraChefContractPositionFetcher } from './ethereum/aura.chef.contract-position-fetcher';
import { EthereumAuraDepositTokenFetcher } from './ethereum/aura.deposit.token-fetcher';
import { EthereumAuraLockerContractPositionFetcher } from './ethereum/aura.locker.contract-position-fetcher';
import { EthereumAuraPoolContractPositionFetcher } from './ethereum/aura.pool.contract-position-fetcher';
import { EthereumAuraStakingContractPositionFetcher } from './ethereum/aura.staking.contract-position-fetcher';
import { AuraBalancerPoolsHelper } from './helpers/aura.balancer-pools-helper';
import { AuraBaseRewardPoolHelper } from './helpers/aura.base-reward-pool-helper';
import { EthereumAuraLpFarmContractPositionFetcher } from './ethereum/aura.lp-farm.contract-position-fetcher';

@Register.AppModule({
appId: AURA_DEFINITION.id,
imports: [SynthetixAppModule, BalancerV2AppModule],
providers: [
AuraAppDefinition,
AuraContractFactory,
// helpers
AuraBalancerPoolResolver,
// Ethereum
EthereumAuraBalanceFetcher,
EthereumAuraBalTokenFetcher,
EthereumAuraAuraBalTokenFetcher,
EthereumAuraDepositTokenFetcher,
EthereumAuraChefContractPositionFetcher,
EthereumAuraPoolContractPositionFetcher,
EthereumAuraLpFarmContractPositionFetcher,
EthereumAuraLockerContractPositionFetcher,
EthereumAuraStakingContractPositionFetcher,
// Helpers
AuraBalancerPoolsHelper,
AuraBaseRewardPoolHelper,
EthereumAuraAuraBalStakingContractPositionFetcher,
],
})
export class AuraAppModule extends AbstractApp() {}
25 changes: 0 additions & 25 deletions src/apps/aura/aura.types.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,31 @@ import { gql } from 'graphql-request';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { Cache } from '~cache/cache.decorator';
import { Network } from '~types';

import { AURA_DEFINITION } from '../aura.definition';
import { BalancerPool } from '../aura.types';
import { AuraContractFactory } from '../index';
import { Network } from '~types/network.interface';

import AURA_DEFINITION from '../aura.definition';
import { AuraContractFactory } from '../contracts';

export type BalancerPool = {
id: string;
address: string;
name: string;
poolType: string;
swapFee: number;
symbol: string;
tokensList: string;
totalLiquidity: number;
totalSwapVolume: number;
totalSwapFee: number;
totalShares: number;
tokens: {
address: string;
symbol: string;
decimals: number;
balance: number;
weight: number;
}[];
};

type GetPoolResponse = {
pool: {
Expand All @@ -30,7 +50,7 @@ type GetPoolResponse = {
balance: string;
weight: string;
}[];
} | null;
};
};

type GetBPTOutParams = { balancerPool: BalancerPool; maxAmountsIn: BigNumber[]; sender?: string; recipient?: string };
Expand Down Expand Up @@ -61,30 +81,32 @@ const GET_POOL_QUERY = gql`
`;

const BALANCER_VAULT = '0xba12222222228d8ba445958a75a0704d566bf2c8';

const network = Network.ETHEREUM_MAINNET;

@Injectable()
export class AuraBalancerPoolsHelper {
export class AuraBalancerPoolResolver {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(AuraContractFactory) private readonly auraContractFactory: AuraContractFactory,
@Inject(AuraContractFactory) private readonly contractFactory: AuraContractFactory,
) {}

@Cache({
key: (poolId: string) => `apps-v3:${AURA_DEFINITION.id}:balancer-pools-${poolId}`,
ttl: 15 * 60, // 15 minutes
key: (poolId: string) => `studio:${AURA_DEFINITION.id}:balancer-pools-${poolId}`,
ttl: 15 * 60,
})
async getBalancerPool(poolId: string): Promise<BalancerPool | null> {
private async getBalancerPoolData(poolId: string) {
const endpoint = `https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2`;
const { pool } = await this.appToolkit.helpers.theGraphHelper.request<GetPoolResponse>({
endpoint: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2',
endpoint,
query: GET_POOL_QUERY,
variables: { id: poolId },
});

if (!pool) {
return null;
}
return pool;
}

async getBalancerPool(poolId: string): Promise<BalancerPool> {
const balancerPoolData = await this.getBalancerPoolData(poolId);

const {
id,
Expand All @@ -99,7 +121,7 @@ export class AuraBalancerPoolsHelper {
totalSwapFee,
totalShares,
tokens,
} = pool;
} = balancerPoolData;

return {
id,
Expand Down Expand Up @@ -131,7 +153,7 @@ export class AuraBalancerPoolsHelper {
}: GetBPTOutParams) {
const { id, tokens } = balancerPool;

const balancerHelpers = this.auraContractFactory.auraBalancerHelpers({
const balancerHelpers = this.contractFactory.auraBalancerHelpers({
address: '0x5addcca35b7a0d07c74063c48700c8590e87864e',
network,
});
Expand Down
166 changes: 166 additions & 0 deletions src/apps/aura/common/aura.farm.contract-position-fetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { Inject } from '@nestjs/common';
import { BigNumber, BigNumberish } from 'ethers';
import { range } from 'lodash';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { isClaimable } from '~position/position.utils';
import {
GetDataPropsParams,
GetTokenBalancesParams,
GetTokenDefinitionsParams,
} from '~position/template/contract-position.template.types';
import {
SingleStakingFarmDataProps,
SingleStakingFarmDynamicTemplateContractPositionFetcher,
} from '~position/template/single-staking.dynamic.template.contract-position-fetcher';

import { AuraBaseRewardPool, AuraContractFactory } from '../contracts';

export type AuraPoolSingleStakingFarmDataProps = {
liquidity: number;
apy: number;
isActive: boolean;
extraRewards: {
address: string;
rewardToken: string;
}[];
};

// AURA is minted whenever BAL is claimed
// See: https://docs.convexfinance.com/convexfinanceintegration/cvx-minting
export const claimedBalToMintedAura = (claimedBalAmount: string, currentAuraSupply: string) => {
const claimedBalAmountBN = BigNumber.from(claimedBalAmount);
const currentAuraSupplyBN = BigNumber.from(currentAuraSupply);

// All values are static/immutable
const maxSupply = BigNumber.from(10).pow(26);
const initMintAmount = BigNumber.from(10).pow(25).mul(5);
const emissionsMaxSupply = BigNumber.from(10).pow(25).mul(5);
const totalCliffs = BigNumber.from(500);
const reductionPerCliff = emissionsMaxSupply.div(totalCliffs);

const emissionsMinted = currentAuraSupplyBN.sub(initMintAmount);

// e.g. reductionPerCliff = 5e25 / 500 = 1e23
// e.g. cliff = 1e25 / 1e23 = 100
const cliff = emissionsMinted.div(reductionPerCliff);

// e.g. 100 < 500
if (cliff.lt(totalCliffs)) {
// e.g. (new) reduction = (500 - 100) * 2.5 + 700 = 1700;
// e.g. (new) reduction = (500 - 250) * 2.5 + 700 = 1325;
// e.g. (new) reduction = (500 - 400) * 2.5 + 700 = 950;
const reduction = totalCliffs.sub(cliff).mul(5).div(2).add(700);
// e.g. (new) amount = 1e19 * 1700 / 500 = 34e18;
// e.g. (new) amount = 1e19 * 1325 / 500 = 26.5e18;
// e.g. (new) amount = 1e19 * 950 / 500 = 19e17;
let amount = claimedBalAmountBN.mul(reduction).div(totalCliffs);

// e.g. amtTillMax = 5e25 - 1e25 = 4e25
const amtTillMax = maxSupply.sub(emissionsMinted);
if (amount.gt(amtTillMax)) {
amount = amtTillMax;
}

return amount;
}

return BigNumber.from(0);
};

export abstract class AuraFarmContractPositionFetcher extends SingleStakingFarmDynamicTemplateContractPositionFetcher<AuraBaseRewardPool> {
constructor(
@Inject(APP_TOOLKIT) protected readonly appToolkit: IAppToolkit,
@Inject(AuraContractFactory)
protected readonly contractFactory: AuraContractFactory,
) {
super(appToolkit);
}

getContract(address: string): AuraBaseRewardPool {
return this.contractFactory.auraBaseRewardPool({ network: this.network, address });
}

async getStakedTokenAddress({ contract }: GetTokenDefinitionsParams<AuraBaseRewardPool>) {
return contract.stakingToken();
}

async getRewardTokenAddresses({ contract, multicall }: GetTokenDefinitionsParams<AuraBaseRewardPool>) {
// BAL rewarded, AURA minted
const primaryRewardTokenAddresses = [
'0xba100000625a3754423978a60c9317c58a424e3d', // BAL
'0xc0c293ce456ff0ed870add98a0828dd4d2903dbf', // AURA
];

// Extra rewards
const extraRewardTokenAddresses = await Promise.all(
range(0, Number(await contract.extraRewardsLength())).map(async v => {
const vbpAddress = await contract.extraRewards(v);
const vbp = this.contractFactory.auraVirtualBalanceRewardPool({ address: vbpAddress, network: this.network });
return multicall.wrap(vbp).rewardToken();
}),
);

return [...primaryRewardTokenAddresses, ...extraRewardTokenAddresses];
}

async getRewardRates({
contract,
multicall,
contractPosition,
}: GetDataPropsParams<AuraBaseRewardPool, SingleStakingFarmDataProps>): Promise<BigNumberish | BigNumberish[]> {
const auraToken = contractPosition.tokens.find(v => v.symbol === 'AURA')!;
const auraTokenContract = this.contractFactory.erc20(auraToken);
const auraSupplyRaw = await multicall.wrap(auraTokenContract).totalSupply();

const balRewardRate = await multicall.wrap(contract).rewardRate();
const auraRewardRate = claimedBalToMintedAura(balRewardRate.toString(), auraSupplyRaw.toString());

const numExtraRewards = await multicall.wrap(contract).extraRewardsLength().then(Number);
const extraRewardRates = await Promise.all(
range(0, numExtraRewards).map(async v => {
const vbpAddress = await multicall.wrap(contract).extraRewards(v);
const vbp = this.contractFactory.auraVirtualBalanceRewardPool({ address: vbpAddress, network: this.network });
return multicall.wrap(vbp).rewardRate();
}),
);

return [balRewardRate, auraRewardRate, ...extraRewardRates];
}

getStakedTokenBalance({ address, contract }: GetTokenBalancesParams<AuraBaseRewardPool>) {
return contract.balanceOf(address);
}

async getRewardTokenBalances({
address,
contract,
contractPosition,
multicall,
}: GetTokenBalancesParams<AuraBaseRewardPool>) {
const rewardTokens = contractPosition.tokens.filter(isClaimable);
const [, auraRewardToken, ...extraRewards] = rewardTokens;

const auraTokenContract = multicall.wrap(this.contractFactory.erc20(auraRewardToken));
const currentAuraSupply = await auraTokenContract.totalSupply();

const balBalanceBN = await contract.earned(address);
const balBalanceRaw = balBalanceBN.toString();
const auraBalanceRaw = claimedBalToMintedAura(balBalanceRaw, currentAuraSupply.toString());

const extraRewardBalances = await Promise.all(
extraRewards.map(async (_, i) => {
const extraRewardAddress = await contract.extraRewards(i);
const extraRewardContract = this.contractFactory.auraVirtualBalanceRewardPool({
address: extraRewardAddress,
network: this.network,
});

const earnedBN = await multicall.wrap(extraRewardContract).earned(address);
return earnedBN.toString();
}),
);

return [balBalanceRaw, auraBalanceRaw, ...extraRewardBalances];
}
}
Loading

0 comments on commit 395c37f

Please sign in to comment.