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

Commit

Permalink
feat(pancakeswap): Add new CAKE syrup pool and new Pancakeswap LP far…
Browse files Browse the repository at this point in the history
…ms (#398)
  • Loading branch information
immasandwich authored May 9, 2022
1 parent 33f9ac0 commit fed00bf
Show file tree
Hide file tree
Showing 16 changed files with 7,181 additions and 10 deletions.
74 changes: 72 additions & 2 deletions src/apps/pancakeswap/binance/pancakeswap.balance-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { Network } from '~types/network.interface';
import {
PancakeswapCakeChef,
PancakeswapChef,
PancakeswapChefV2,
PancakeswapContractFactory,
PancakeswapIfoChef,
PancakeswapSmartChef,
PancakeswapSyrupCake,
} from '../contracts';
import { PANCAKESWAP_DEFINITION } from '../pancakeswap.definition';

Expand All @@ -36,7 +38,7 @@ export class BinanceSmartChainPancakeSwapBalanceFetcher implements BalanceFetche
});
}

private async getFarmBalances(address: string) {
private async getLegacyFarmBalances(address: string) {
// LP and Manual Cake Farms
return this.appToolkit.helpers.masterChefContractPositionBalanceHelper.getBalances<PancakeswapChef>({
address,
Expand All @@ -59,6 +61,29 @@ export class BinanceSmartChainPancakeSwapBalanceFetcher implements BalanceFetche
});
}

private async getFarmBalances(address: string) {
// LP and Manual Cake Farms
return this.appToolkit.helpers.masterChefContractPositionBalanceHelper.getBalances<PancakeswapChefV2>({
address,
appId,
network,
groupId: PANCAKESWAP_DEFINITION.groups.farmV2.id,
resolveChefContract: ({ contractAddress }) =>
this.contractFactory.pancakeswapChefV2({ network, address: contractAddress }),
resolveStakedTokenBalance: this.appToolkit.helpers.masterChefDefaultStakedBalanceStrategy.build({
resolveStakedBalance: ({ multicall, contract, contractPosition }) =>
multicall
.wrap(contract)
.userInfo(contractPosition.dataProps.poolIndex, address)
.then(v => v.amount),
}),
resolveClaimableTokenBalances: this.appToolkit.helpers.masterChefDefaultClaimableBalanceStrategy.build({
resolveClaimableBalance: ({ multicall, contract, contractPosition }) =>
multicall.wrap(contract).pendingCake(contractPosition.dataProps.poolIndex, address),
}),
});
}

private async getIfoCakeBalances(address: string) {
// IFO Cake Farm
return this.appToolkit.helpers.masterChefContractPositionBalanceHelper.getBalances<PancakeswapIfoChef>({
Expand Down Expand Up @@ -113,6 +138,33 @@ export class BinanceSmartChainPancakeSwapBalanceFetcher implements BalanceFetche
});
}

private async getSyrupCakeBalances(address: string) {
// Autocompounding Cake Farm
return this.appToolkit.helpers.masterChefContractPositionBalanceHelper.getBalances<PancakeswapSyrupCake>({
address,
appId,
network,
groupId: PANCAKESWAP_DEFINITION.groups.syrupCake.id,
resolveChefContract: ({ contractAddress }) =>
this.contractFactory.pancakeswapSyrupCake({ network, address: contractAddress }),
resolveStakedTokenBalance: this.appToolkit.helpers.masterChefDefaultStakedBalanceStrategy.build({
resolveStakedBalance: async ({ multicall, contract, address }) => {
const [userInfo, pricePerShareRaw] = await Promise.all([
multicall.wrap(contract).userInfo(address),
multicall.wrap(contract).getPricePerFullShare(),
]);

const shares = userInfo.shares.toString();
const pricePerShare = Number(pricePerShareRaw) / 10 ** 18;
return new BigNumber(shares).times(pricePerShare).toFixed(0);
},
}),
resolveClaimableTokenBalances: this.appToolkit.helpers.masterChefDefaultClaimableBalanceStrategy.build({
resolveClaimableBalance: async () => 0, // Autocompounding
}),
});
}

private async getSyrupPoolBalances(address: string) {
// Syrup Pools (single-staking)
return this.appToolkit.helpers.masterChefContractPositionBalanceHelper.getBalances<PancakeswapSmartChef>({
Expand All @@ -136,10 +188,20 @@ export class BinanceSmartChainPancakeSwapBalanceFetcher implements BalanceFetche
}

async getBalances(address: string) {
const [poolBalances, farmBalances, autoCakeBalances, ifoCakeBalances, syrupPoolBalances] = await Promise.all([
const [
poolBalances,
legacyFarmBalances,
farmBalances,
autoCakeBalances,
syrupCakeBalances,
ifoCakeBalances,
syrupPoolBalances,
] = await Promise.all([
this.getPoolBalances(address),
this.getLegacyFarmBalances(address),
this.getFarmBalances(address),
this.getAutoCakeBalances(address),
this.getSyrupCakeBalances(address),
this.getIfoCakeBalances(address),
this.getSyrupPoolBalances(address),
]);
Expand All @@ -157,10 +219,18 @@ export class BinanceSmartChainPancakeSwapBalanceFetcher implements BalanceFetche
label: 'IFO CAKE',
assets: ifoCakeBalances,
},
{
label: 'Staked CAKE',
assets: syrupCakeBalances,
},
{
label: 'Farms',
assets: farmBalances,
},
{
label: 'Legacy Farms',
assets: legacyFarmBalances,
},
{
label: 'Syrup Pools',
assets: syrupPoolBalances,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Inject } from '@nestjs/common';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { RewardRateUnit } from '~app-toolkit/helpers/master-chef/master-chef.contract-position-helper';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { PancakeswapChefV2, PancakeswapContractFactory } from '../contracts';
import { PANCAKESWAP_DEFINITION } from '../pancakeswap.definition';

const appId = PANCAKESWAP_DEFINITION.id;
const groupId = PANCAKESWAP_DEFINITION.groups.farmV2.id;
const network = Network.BINANCE_SMART_CHAIN_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class BinanceSmartChainPancakeswapFarmV2ContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(PancakeswapContractFactory) private readonly contractFactory: PancakeswapContractFactory,
) {}

getPositions() {
return this.appToolkit.helpers.masterChefContractPositionHelper.getContractPositions<PancakeswapChefV2>({
network,
groupId,
appId,
minimumTvl: 10000,
address: '0xa5f8c5dbd5f286960b9d90548680ae5ebff07652',
dependencies: [{ appId, groupIds: [PANCAKESWAP_DEFINITION.groups.pool.id], network }],
resolveContract: opts => this.contractFactory.pancakeswapChefV2(opts),
resolvePoolLength: async ({ multicall, contract }) => multicall.wrap(contract).poolLength(),
resolveDepositTokenAddress: ({ multicall, contract, poolIndex }) => multicall.wrap(contract).lpToken(poolIndex),
resolveRewardTokenAddresses: ({ multicall, contract }) => multicall.wrap(contract).CAKE(),
rewardRateUnit: RewardRateUnit.BLOCK,
resolveRewardRate: this.appToolkit.helpers.masterChefDefaultRewardsPerBlockStrategy.build({
resolvePoolAllocPoints: ({ multicall, contract, poolIndex }) =>
multicall
.wrap(contract)
.poolInfo(poolIndex)
.then(i => i.allocPoint),
resolveTotalAllocPoints: async ({ multicall, contract, poolIndex }) => {
const poolInfo = await multicall.wrap(contract).poolInfo(poolIndex);
return poolInfo.isRegular
? multicall.wrap(contract).totalRegularAllocPoint()
: multicall.wrap(contract).totalSpecialAllocPoint();
},
resolveTotalRewardRate: async ({ multicall, contract, poolIndex }) => {
const poolInfo = await multicall.wrap(contract).poolInfo(poolIndex);
return multicall.wrap(contract).cakePerBlock(poolInfo.isRegular);
},
}),
});
}
}
52 changes: 50 additions & 2 deletions src/apps/pancakeswap/binance/pancakeswap.pool.cache-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,53 @@ export class BinanceSmartChainPancakeswapPoolAddressCacheManager {
return compact(allAddresses);
}

@CacheOnInterval({
key: `apps-v3:${PANCAKESWAP_DEFINITION.id}:chef-v2-pool-addresses`,
timeout: 15 * 60 * 1000,
})
private async getChefV2PoolAddresses() {
const network = Network.BINANCE_SMART_CHAIN_MAINNET;
const chefAddress = '0xa5f8c5dbd5f286960b9d90548680ae5ebff07652';
const chefContract = this.contractFactory.pancakeswapChefV2({ address: chefAddress, network });

const provider = this.appToolkit.getNetworkProvider(network);
const multicall = this.appToolkit.getMulticall(network);
const numPools = await multicall.wrap(chefContract).poolLength();

const allAddresses = await Promise.all(
range(0, Number(numPools)).map(async v => {
const lpTokenAddressRaw = await multicall.wrap(chefContract).lpToken(v);
const lpTokenAddress = lpTokenAddressRaw.toLowerCase();
const lpTokenContract = this.contractFactory.pancakeswapPair({ address: lpTokenAddress, network });

// Some EOAs exist on the MasterChef contract; calling these breaks multicall
const code = await provider.getCode(lpTokenAddress);
if (code === '0x') return false;

const [symbol, factoryAddressRaw] = await Promise.all([
multicall
.wrap(lpTokenContract)
.symbol()
.catch(_err => ''),
multicall
.wrap(lpTokenContract)
.factory()
.catch(_err => ''),
]);

// We've deprecated V1 support since the liquidities are low now (also our zap does not support V1)
const V2_FACTORY_ADDRESS = '0xca143ce32fe78f1f7019d7d551a6402fc5350c73';
const isV2Pair = factoryAddressRaw.toLowerCase() === V2_FACTORY_ADDRESS;
const isCakeLp = symbol === 'Cake-LP';
if (!isV2Pair || !isCakeLp) return null;

return lpTokenAddress;
}),
);

return compact(allAddresses);
}

@CacheOnInterval({
key: `apps-v3:${PANCAKESWAP_DEFINITION.id}:static-pool-addresses`,
timeout: 15 * 60 * 1000,
Expand All @@ -81,12 +128,13 @@ export class BinanceSmartChainPancakeswapPoolAddressCacheManager {
}

async getPoolAddresses(): Promise<string[]> {
const [topPoolAddresses, chefPoolAddresses, staticPoolAddresses] = await Promise.all([
const [topPoolAddresses, chefPoolAddresses, chefV2PoolAddresses, staticPoolAddresses] = await Promise.all([
this.getTopPoolAddresses(),
this.getChefPoolAddresses(),
this.getChefV2PoolAddresses(),
this.getStaticPoolAddresses(),
]);

return uniq([...topPoolAddresses, ...chefPoolAddresses, ...staticPoolAddresses]);
return uniq([...topPoolAddresses, ...chefPoolAddresses, ...chefV2PoolAddresses, ...staticPoolAddresses]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 0xa5f8c5dbd5f286960b9d90548680ae5ebff07652 => LP Staking

import { Inject } from '@nestjs/common';
import { BigNumber } from 'ethers';

import { APP_TOOLKIT, IAppToolkit } from '~app-toolkit/app-toolkit.interface';
import { Register } from '~app-toolkit/decorators';
import { RewardRateUnit } from '~app-toolkit/helpers/master-chef/master-chef.contract-position-helper';
import { PositionFetcher } from '~position/position-fetcher.interface';
import { ContractPosition } from '~position/position.interface';
import { Network } from '~types/network.interface';

import { PancakeswapContractFactory, PancakeswapSyrupCake } from '../contracts';
import { PANCAKESWAP_DEFINITION } from '../pancakeswap.definition';

const appId = PANCAKESWAP_DEFINITION.id;
const groupId = PANCAKESWAP_DEFINITION.groups.syrupCake.id;
const network = Network.BINANCE_SMART_CHAIN_MAINNET;

@Register.ContractPositionFetcher({ appId, groupId, network })
export class BinanceSmartChainPancakeswapSyrupCakeContractPositionFetcher implements PositionFetcher<ContractPosition> {
constructor(
@Inject(APP_TOOLKIT) private readonly appToolkit: IAppToolkit,
@Inject(PancakeswapContractFactory)
private readonly contractFactory: PancakeswapContractFactory,
) {}

getPositions() {
return this.appToolkit.helpers.masterChefContractPositionHelper.getContractPositions<PancakeswapSyrupCake>({
network,
groupId,
appId,
address: '0x45c54210128a065de780c4b0df3d16664f7f859e',
rewardRateUnit: RewardRateUnit.BLOCK,
resolveContract: opts => this.contractFactory.pancakeswapSyrupCake(opts),
resolvePoolLength: async () => BigNumber.from(1),
resolveDepositTokenAddress: ({ multicall, contract }) => multicall.wrap(contract).token(),
resolveRewardTokenAddresses: ({ multicall, contract }) => multicall.wrap(contract).token(),
resolveTotalValueLocked: ({ multicall, contract }) => multicall.wrap(contract).available(),
resolveRewardRate: async ({ multicall, network }) => {
// The auto-compounding CAKE rewards are harvested from the main MasterChef V2 contract on PID 0
const masterChefV2Address = '0xa5f8c5dbd5f286960b9d90548680ae5ebff07652';
const masterChefV2Contract = this.contractFactory.pancakeswapChefV2({ address: masterChefV2Address, network });
const poolInfo = await multicall.wrap(masterChefV2Contract).poolInfo(0);
const cakePerBlock = await multicall.wrap(masterChefV2Contract).cakePerBlock(poolInfo.isRegular);
const poolAllocPoints = poolInfo.allocPoint;
const totalAllocPoints = poolInfo.isRegular
? masterChefV2Contract.totalRegularAllocPoint()
: masterChefV2Contract.totalSpecialAllocPoint();

const poolShare = Number(poolAllocPoints) / Number(totalAllocPoints);
const rewardPerBlock = poolShare * Number(cakePerBlock);
return rewardPerBlock;
},
});
}
}
Loading

0 comments on commit fed00bf

Please sign in to comment.