Skip to content

Commit

Permalink
vebal staking and user balances (#539)
Browse files Browse the repository at this point in the history
* vebal staking and user balances

* no need for context
  • Loading branch information
franzns authored Jul 4, 2024
1 parent e113488 commit d8752b4
Show file tree
Hide file tree
Showing 15 changed files with 279 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/tender-pumpkins-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'backend': minor
---

adding vebal as a staking option
34 changes: 34 additions & 0 deletions modules/actions/pool/staking/sync-vebal-staking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { prisma } from '../../../../prisma/prisma-client';
import mainnet from '../../../../config/mainnet';

export const syncVebalStakingForPools = async (): Promise<void> => {
const stakingId = mainnet.veBal!.address;
const chain = 'MAINNET';
const veBalPoolId = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014';

await prisma.prismaPoolStaking.upsert({
where: { id_chain: { id: stakingId, chain: chain } },
create: {
id: stakingId,
chain: chain,
poolId: veBalPoolId,
type: 'VEBAL',
address: stakingId,
},
update: {},
});

await prisma.prismaPoolStakingVebal.upsert({
where: { id_chain: { id: stakingId, chain: chain } },
create: {
id: stakingId,
chain: chain,
stakingId: stakingId,
vebalAddress: stakingId,
},
update: {
stakingId: stakingId,
vebalAddress: stakingId,
},
});
};
7 changes: 6 additions & 1 deletion modules/network/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { env } from '../../app/env';
import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service';
import config from '../../config';
import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service';
import { UserSyncVebalLockBalanceService } from '../user/lib/user-sync-vebal-lock-balance.service';

export const data: NetworkData = config.MAINNET;

Expand All @@ -31,7 +32,11 @@ export const mainnetNetworkConfig: NetworkConfig = {
new VeBalProtocolAprService(data.rpcUrl),
new VeBalVotingAprService(),
],
userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()],
userStakedBalanceServices: [
new UserSyncGaugeBalanceService(),
new UserSyncAuraBalanceService(),
new UserSyncVebalLockBalanceService(),
],
services: {
balancerSubgraphService: new BalancerSubgraphService(data.subgraphs.balancer, 1),
},
Expand Down
7 changes: 7 additions & 0 deletions modules/pool/lib/pool-gql-loader.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,13 @@ export class PoolGqlLoaderService {
reliquary: null,
aura: null,
};
} else if (staking.vebal) {
return {
...staking,
gauge: null,
reliquary: null,
aura: null,
};
}
}

Expand Down
30 changes: 30 additions & 0 deletions modules/pool/pool-debug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { initRequestScopedContext, setRequestScopedContextValue } from '../conte
import { poolService } from '../pool/pool.service';
import { userService } from '../user/user.service';
import { tokenService } from '../token/token.service';
import mainnet from '../../config/mainnet';
describe('pool debugging', () => {
it('sync pools', async () => {
initRequestScopedContext();
Expand Down Expand Up @@ -134,6 +135,35 @@ describe('pool debugging', () => {
// expect(pool.userBalance?.stakedBalances.length).toBeGreaterThan(0);
}, 5000000);

it('debug vebal staking', async () => {
initRequestScopedContext();
setRequestScopedContextValue('chainId', '1');
//only do once before starting to debug
// await poolService.syncAllPoolsFromSubgraph();
// await poolService.loadOnChainDataForAllPools();
// await userService.initWalletBalancesForAllPools();
// await userService.initStakedBalances(['AURA']);
await poolService.reloadStakingForAllPools(['VEBAL'], 'MAINNET');

await userService.syncChangedStakedBalances();

const pool = await poolService.getGqlPool(
'0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014',
'MAINNET',
'0xd86a11b0c859c18bfc1b4acd072c5afe57e79438',
);
expect(pool.staking).toBeDefined();
expect(pool.staking?.vebal).toBeDefined();
expect(pool.staking?.vebal?.vebalAddress).toBe(mainnet.veBal?.address);

expect(pool.userBalance).toBeDefined();
expect(pool.userBalance?.totalBalance).not.toBe('0');
expect(pool.userBalance?.totalBalanceUsd).toBeGreaterThan(0);
expect(pool.userBalance?.walletBalance).not.toBe('0');
expect(pool.userBalance?.walletBalanceUsd).toBeGreaterThan(0);
expect(pool.userBalance?.stakedBalances.length).toBeGreaterThan(0);
}, 5000000);

it('debug user staking', async () => {
initRequestScopedContext();
setRequestScopedContextValue('chainId', '42161');
Expand Down
7 changes: 7 additions & 0 deletions modules/pool/pool.gql
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,7 @@ type GqlPoolStaking {
farm: GqlPoolStakingMasterChefFarm
reliquary: GqlPoolStakingReliquaryFarm
aura: GqlPoolStakingAura
vebal: GqlPoolStakingVebal
}

enum GqlPoolStakingType {
Expand All @@ -1256,6 +1257,7 @@ enum GqlPoolStakingType {
FRESH_BEETS
RELIQUARY
AURA
VEBAL
}

enum GqlPoolStakingGaugeStatus {
Expand Down Expand Up @@ -1311,6 +1313,11 @@ type GqlPoolStakingAura {
apr: Float!
}

type GqlPoolStakingVebal {
id: ID!
vebalAddress: String!
}

type GqlPoolStakingReliquaryFarm {
id: ID!
beetsPerSecond: String!
Expand Down
14 changes: 14 additions & 0 deletions modules/pool/pool.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ model PrismaPoolStaking {
gauge PrismaPoolStakingGauge?
reliquary PrismaPoolStakingReliquaryFarm?
aura PrismaPoolStakingAura?
vebal PrismaPoolStakingVebal?
userStakedBalances PrismaUserStakedBalance[]
}
Expand All @@ -334,6 +335,7 @@ enum PrismaPoolStakingType {
RELIQUARY
FRESH_BEETS
AURA
VEBAL
}

model PrismaPoolStakingMasterChefFarm {
Expand Down Expand Up @@ -394,6 +396,18 @@ model PrismaPoolStakingAura {
isShutdown Boolean
}

model PrismaPoolStakingVebal {
@@id([id, chain])
@@unique([stakingId, chain])
id String
stakingId String
staking PrismaPoolStaking @relation(fields:[stakingId, chain], references: [id, chain], onDelete: Cascade)
chain Chain
vebalAddress String
}

enum PrismaPoolStakingGaugeStatus {
KILLED
ACTIVE
Expand Down
5 changes: 5 additions & 0 deletions modules/pool/pool.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { AllNetworkConfigsKeyedOnChain } from '../network/network-config';
import { GaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph.service';
import { deleteAuraStakingForAllPools, syncAuraStakingForPools } from '../actions/pool/staking/sync-aura-staking';
import { AuraSubgraphService } from '../sources/subgraphs/aura/aura.service';
import { syncVebalStakingForPools } from '../actions/pool/staking/sync-vebal-staking';

export class PoolService {
constructor(
Expand Down Expand Up @@ -283,6 +284,10 @@ export class PoolService {
if (networkconfig.data.subgraphs.aura) {
await syncAuraStakingForPools(chain, new AuraSubgraphService(networkconfig.data.subgraphs.aura));
}

if (chain === 'MAINNET') {
await syncVebalStakingForPools();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ query VotingEscrowLocks(
}
}
}

query VebalGetMeta {
meta: _meta {
block {
number
}
deployment
hasIndexingErrors
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export class VeBalLocksSubgraphService {
return locks;
}

public async getMetadata() {
const { meta } = await this.sdk.VebalGetMeta();

if (!meta) {
throw new Error('Missing meta data');
}
return meta;
}

public get sdk() {
const client = new GraphQLClient(config.subgraphs.gauge ?? '');

Expand Down
113 changes: 113 additions & 0 deletions modules/user/lib/user-sync-vebal-lock-balance.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { UserStakedBalanceService, UserSyncUserBalanceInput } from '../user-types';
import { prisma } from '../../../prisma/prisma-client';
import { getContractAt } from '../../web3/contract';
import _ from 'lodash';
import { prismaBulkExecuteOperations } from '../../../prisma/prisma-util';
import RewardsOnlyGaugeAbi from './abi/RewardsOnlyGauge.json';
import { Multicaller } from '../../web3/multicaller';
import { formatFixed } from '@ethersproject/bignumber';
import { PrismaPoolStakingType } from '@prisma/client';
import { networkContext } from '../../network/network-context.service';
import { GaugeSubgraphService } from '../../subgraphs/gauge-subgraph/gauge-subgraph.service';
import { veBalLocksSubgraphService } from '../../subgraphs/veBal-locks-subgraph/veBal-locks-subgraph.service';
import { BigNumber } from 'ethers';
import VeBalABI from '../../vebal/abi/vebal.json';
import mainnet from '../../../config/mainnet';

export class UserSyncVebalLockBalanceService implements UserStakedBalanceService {
get chain() {
return networkContext.chain;
}

private readonly veBalPoolId = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014';

public async initStakedBalances(stakingTypes: PrismaPoolStakingType[]): Promise<void> {
if (!stakingTypes.includes('VEBAL') && this.chain !== 'MAINNET') {
return;
}

console.log('initStakedVebalBalances: Starting loading users and onchain balances...');
await this.syncBalances(true);

console.log('initStakedVebalBalances: finished...');
}

public async syncChangedStakedBalances(): Promise<void> {
await this.syncBalances(false);
}

private async syncBalances(init: boolean): Promise<void> {
const subgraphVeBalHolders = await veBalLocksSubgraphService.getAllveBalHolders();
const metadata = await veBalLocksSubgraphService.getMetadata();

let operations: any[] = [];
// for mainnet, we get the vebal balance form the vebal contract
const multicall = new Multicaller(networkContext.data.multicall, networkContext.provider, VeBalABI);

let response = {} as {
[userAddress: string]: {
balance: BigNumber;
locked: BigNumber[];
};
};

for (const holder of subgraphVeBalHolders) {
multicall.call(`${holder.user}.locked`, networkContext.data.veBal!.address, 'locked', [holder.user]);

// so if we scheduled more than 100 calls, we execute the batch
if (multicall.numCalls >= 100) {
response = _.merge(response, await multicall.execute());
}
}

if (multicall.numCalls > 0) {
response = _.merge(response, await multicall.execute());
}

operations.push(
prisma.prismaUser.createMany({
data: subgraphVeBalHolders.map((holder) => ({ address: holder.user.toLowerCase() })),
skipDuplicates: true,
}),
);

if (init) {
operations.push(
prisma.prismaUserStakedBalance.deleteMany({ where: { staking: { type: 'VEBAL' }, chain: this.chain } }),
);
}

for (const veBalHolder in response) {
operations.push(
prisma.prismaUserStakedBalance.upsert({
where: { id_chain: { id: `veBal-${veBalHolder.toLowerCase()}`, chain: 'MAINNET' } },
create: {
id: `veBal-${veBalHolder.toLowerCase()}`,
chain: 'MAINNET',
balance: formatFixed(response[veBalHolder].locked[0], 18),
balanceNum: parseFloat(formatFixed(response[veBalHolder].locked[0], 18)),
userAddress: veBalHolder.toLowerCase(),
poolId: this.veBalPoolId,
tokenAddress: mainnet.veBal!.bptAddress,
stakingId: mainnet.veBal!.address,
},
update: {
balance: formatFixed(response[veBalHolder].locked[0], 18),
balanceNum: parseFloat(formatFixed(response[veBalHolder].locked[0], 18)),
},
}),
);
}

operations.push(
prisma.prismaUserBalanceSyncStatus.upsert({
where: { type_chain: { type: 'VEBAL', chain: this.chain } },
create: { type: 'VEBAL', chain: this.chain, blockNumber: metadata.block.number },
update: { blockNumber: metadata.block.number },
}),
);
await prismaBulkExecuteOperations(operations, true, undefined);
}

public async syncUserBalance({ userAddress, poolId, poolAddress, staking }: UserSyncUserBalanceInput) {}
}
1 change: 1 addition & 0 deletions modules/user/user.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum PrismaUserBalanceType {
STAKED
RELIQUARY
AURA
VEBAL
}

model PrismaUserPoolBalanceSnapshot {
Expand Down
21 changes: 21 additions & 0 deletions prisma/migrations/20240704114833_add_vebal_staking/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- AlterEnum
ALTER TYPE "PrismaPoolStakingType" ADD VALUE 'VEBAL';

-- AlterEnum
ALTER TYPE "PrismaUserBalanceType" ADD VALUE 'VEBAL';

-- CreateTable
CREATE TABLE "PrismaPoolStakingVebal" (
"id" TEXT NOT NULL,
"stakingId" TEXT NOT NULL,
"chain" "Chain" NOT NULL,
"vebalAddress" TEXT NOT NULL,

CONSTRAINT "PrismaPoolStakingVebal_pkey" PRIMARY KEY ("id","chain")
);

-- CreateIndex
CREATE UNIQUE INDEX "PrismaPoolStakingVebal_stakingId_chain_key" ON "PrismaPoolStakingVebal"("stakingId", "chain");

-- AddForeignKey
ALTER TABLE "PrismaPoolStakingVebal" ADD CONSTRAINT "PrismaPoolStakingVebal_stakingId_chain_fkey" FOREIGN KEY ("stakingId", "chain") REFERENCES "PrismaPoolStaking"("id", "chain") ON DELETE CASCADE ON UPDATE CASCADE;
2 changes: 2 additions & 0 deletions prisma/prisma-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const prismaPoolWithExpandedNesting = Prisma.validator<Prisma.PrismaPoolA
},
},
aura: true,
vebal: true,
},
},
categories: true,
Expand Down Expand Up @@ -318,6 +319,7 @@ export const prismaPoolMinimal = Prisma.validator<Prisma.PrismaPoolArgs>()({
},
},
aura: true,
vebal: true,
},
},
},
Expand Down
Loading

0 comments on commit d8752b4

Please sign in to comment.