Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalised exit phantom #521

Merged
merged 9 commits into from
Aug 18, 2023
12 changes: 12 additions & 0 deletions balancer-js/src/lib/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,16 @@ export class Logger {
console.error(`[ERROR] ${message}`);
}
}

time(message: string): void {
if (this.enableLogging) {
console.time(`[TIME] ${message}`);
}
}

timeEnd(message: string): void {
if (this.enableLogging) {
console.timeEnd(`[TIME] ${message}`);
}
}
}
9 changes: 7 additions & 2 deletions balancer-js/src/modules/data/pool/subgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { GraphQLQuery, Pool } from '@/types';
import { Network } from '@/lib/constants/network';
import { PoolsQueryVariables } from '../../subgraph/subgraph';
import { mapType } from './subgraph-helpers';
import { Logger } from '@/lib/utils/logger';

interface PoolsSubgraphRepositoryOptions {
url: string;
Expand Down Expand Up @@ -76,7 +77,8 @@ export class PoolsSubgraphRepository
* @returns Promise resolving to pools list
*/
private async fetchAllPools(): Promise<Pool[]> {
console.time('fetching pools');
const logger = Logger.getInstance();
logger.time('fetching pools');

if (this.blockHeight) {
this.query.args.block = { number: await this.blockHeight() };
Expand All @@ -88,14 +90,16 @@ export class PoolsSubgraphRepository
const { pool0, pool1000, pool2000 } = await this.client.AllPools(
formattedQuery
);
console.timeEnd('fetching pools');
logger.timeEnd('fetching pools');

return [...pool0, ...pool1000, ...pool2000].map((pool) =>
mapType(pool, this.chainId)
);
}

async fetch(options?: PoolsRepositoryFetchOptions): Promise<Pool[]> {
const logger = Logger.getInstance();
logger.time('fetching pools');
if (options?.skip) {
this.query.args.skip = options.skip;
}
Expand All @@ -112,6 +116,7 @@ export class PoolsSubgraphRepository
const { pools } = await this.client.Pools(formattedQuery);

this.skip = (options?.skip || 0) + pools.length;
logger.timeEnd('fetching pools');

return pools.map((pool) => mapType(pool, this.chainId));
}
Expand Down
22 changes: 14 additions & 8 deletions balancer-js/src/modules/data/pool/subgraphOnChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Pool } from '@/types';
import { getOnChainBalances } from '../../../modules/sor/pool-data/onChainData';
import { PoolsSubgraphRepository } from './subgraph';
import { isSameAddress } from '@/lib/utils';
import { Logger } from '@/lib/utils/logger';

interface PoolsSubgraphOnChainRepositoryOptions {
provider: Provider;
Expand Down Expand Up @@ -59,35 +60,40 @@ export class PoolsSubgraphOnChainRepository
* @returns Promise resolving to pools list
*/
private async fetchDefault(): Promise<Pool[]> {
console.time('fetching pools SG');
const pools = await this.poolsSubgraph.all();
console.timeEnd('fetching pools SG');
const filteredPools = this.filterPools(pools);
console.time(`fetching onchain ${filteredPools.length} pools`);

const logger = Logger.getInstance();
logger.time(`fetching onchain ${filteredPools.length} pools`);

const onchainPools = await getOnChainBalances(
filteredPools,
this.multicall,
this.vault,
this.provider
);
console.timeEnd(`fetching onchain ${filteredPools.length} pools`);

logger.timeEnd(`fetching onchain ${filteredPools.length} pools`);

return onchainPools;
}

async fetch(options?: PoolsRepositoryFetchOptions): Promise<Pool[]> {
console.time('fetching pools SG');
const pools = await this.poolsSubgraph.fetch(options);
console.timeEnd('fetching pools SG');
const filteredPools = this.filterPools(pools);
console.time(`fetching onchain ${filteredPools.length} pools`);

const logger = Logger.getInstance();
logger.time(`fetching onchain ${filteredPools.length} pools`);

const onchainPools = await getOnChainBalances(
filteredPools,
this.multicall,
this.vault,
this.provider
);
console.timeEnd(`fetching onchain ${filteredPools.length} pools`);

logger.timeEnd(`fetching onchain ${filteredPools.length} pools`);

return onchainPools;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,34 @@ const blockNo = TEST_BLOCK[Network.MAINNET];

describe('generalised exit execution', async function () {
this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes
context('aaveLinear V1 - bbausd', async () => {
const network = Network.MAINNET;
const pool = ADDRESSES[network].bbausd;
const slippage = '10'; // 10 bps = 0.1%
const poolAddresses = Object.values(ADDRESSES[network]).map(
(address) => address.address
);

const amountRatio = 10;
// Amount greater than the underlying main token balance, which will cause the exit to be unwrapped
const unwrapExitAmount = parseFixed('1273000', pool.decimals);
// Amount smaller than the underlying main token balance, which will cause the exit to be done directly
const mainExitAmount = unwrapExitAmount.div(amountRatio);

context('exit to main tokens directly', async () => {
it('should exit to main tokens directly', async () => {
await testFlow(
pool,
slippage,
mainExitAmount.toString(),
[],
network,
blockNo,
poolAddresses
);
});
});
});

context('ERC4626 - bbausd3', async () => {
if (!TEST_BBAUSD3) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const poolAddresses = Object.values(ADDRESSES[network]).map(
);
const addresses = ADDRESSES[network];

describe.skip('generalised exit execution', async function () {
describe('generalised exit execution', async function () {
this.timeout(120000); // Sets timeout for all tests within this scope to 2 minutes

/*
Expand Down
111 changes: 109 additions & 2 deletions balancer-js/src/modules/exits/exits.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import { AssetHelpers, subSlippage } from '@/lib/utils';
import { PoolGraph, Node } from '@/modules/graph/graph';
import { Join } from '@/modules/joins/joins.module';
import { calcPriceImpact } from '@/modules/pricing/priceImpact';
import { EncodeUnwrapInput, Relayer } from '@/modules/relayer/relayer.module';
import {
EncodeUnwrapInput,
OutputReference,
Relayer,
EncodeBatchSwapInput,
} from '@/modules/relayer/relayer.module';
import {
Simulation,
SimulationType,
Expand All @@ -20,9 +25,13 @@ import {
SingleSwap,
Swap,
SwapType,
BatchSwapStep,
} from '@/modules/swaps/types';
import { ExitPoolRequest as ExitPoolModelRequest } from '@/modules/vaultModel/poolModel/exit';
import { SwapRequest } from '@/modules/vaultModel/poolModel/swap';
import {
BatchSwapRequest,
SwapRequest,
} from '@/modules/vaultModel/poolModel/swap';
import { UnwrapRequest } from '@/modules/vaultModel/poolModel/unwrap';
import { Requests, VaultModel } from '@/modules/vaultModel/vaultModel.module';
import { ComposableStablePoolEncoder } from '@/pool-composable-stable';
Expand Down Expand Up @@ -858,6 +867,104 @@ export class Exit {
return { modelRequest, encodedCall, assets, amounts };
}

private createBatchSwap(
node: Node,
exitChildren: Node[],
exitPathIndex: number,
minAmountsOut: string[],
sender: string,
recipient: string
): {
modelRequest: BatchSwapRequest;
encodedCall: string;
assets: string[];
amounts: string[];
} {
const isRootNode = !node.parent;
const amountIn = isRootNode
? node.index
: Relayer.toChainedReference(
this.getOutputRef(exitPathIndex, node.index)
).toString();

const tokensOut = exitChildren.map((n) => n.address);
const assets = [...tokensOut, node.address];
// TODO - setting these right?
const limits = [...minAmountsOut];
limits.push(amountIn);
const batchSwapSteps: BatchSwapStep[] = [];
const outputReferences: OutputReference[] = [];
exitChildren.forEach((child, i) => {
// TODO - Is this correct?
const amount = child.proportionOfParent
.mul(amountIn)
.div(WeiPerEther)
.toString();
const swapStep: BatchSwapStep = {
poolId: node.id,
assetInIndex: assets.length - 1,
assetOutIndex: i,
amount,
userData: '0x',
};
batchSwapSteps.push(swapStep);
// TODO - Is this right?
outputReferences.push({
index: i,
key: Relayer.toChainedReference(this.getOutputRef(0, child.index)),
});
});

const total = batchSwapSteps.reduce((acc, swap) => {
return acc.add(swap.amount);
}, BigNumber.from(0));
const dust = BigNumber.from(amountIn).sub(total);
batchSwapSteps[0].amount = dust.add(batchSwapSteps[0].amount).toString();

const fromInternalBalance = this.receivesFromInternal(node);
// TODO - This is assuming that all exit to same, is this right?
const toInternalBalance = this.receivesFromInternal(exitChildren[0]);

const funds: FundManagement = {
sender,
recipient,
fromInternalBalance,
toInternalBalance,
};

const call: EncodeBatchSwapInput = {
swapType: SwapType.SwapExactIn,
swaps: batchSwapSteps,
assets,
funds,
limits,
deadline: BigNumber.from(Math.ceil(Date.now() / 1000) + 3600), // 1 hour from now
value: '0', // TODO: check if swap with ETH is possible in this case and handle it
outputReferences,
};
debugLog('\nBatchSwap:');
debugLog(JSON.stringify(call));

const encodedCall = Relayer.encodeBatchSwap(call);

const modelRequest = VaultModel.mapBatchSwapRequest(call);

// If node isn't rootNode, the swap is part of a chain and shouldn't be considered for user deltas
const bptIn = !isRootNode ? '0' : amountIn;
// If child exit action is not output, the swap is part of a chain and shouldn't be considered for user deltas
const userTokensOut = exitChildren.map((child, i) => {
const userTokenOutAmount =
child.exitAction !== 'output'
? '0'
: BigNumber.from(minAmountsOut[i]).mul(-1).toString(); // needs to be negative because it's handled by the vault model as an amount going out of the vault
return userTokenOutAmount;
});

const amounts = [...userTokensOut, bptIn];

return { modelRequest, encodedCall, assets, amounts };
}

private createExitPool(
node: Node,
exitChild: Node,
Expand Down
2 changes: 1 addition & 1 deletion balancer-js/src/modules/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ export class PoolGraph {
*/
static isProportionalPools(nodes: Node[]): boolean {
return nodes.every((node) => {
if (node.exitAction === 'exitPool') return node.isProportionalExit;
if (node.children.length > 1) return node.isProportionalExit;
else return true;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '@/lib/graphql/args-builder';

import { isSameAddress } from '@/lib/utils';
import { Logger } from '@/lib/utils/logger';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function mapPools(pools: any[]): SubgraphPoolBase[] {
Expand Down Expand Up @@ -87,14 +88,17 @@ export class SubgraphPoolDataService implements PoolDataService {
return mapped;
}

console.time(`fetching on-chain balances for ${mapped.length} pools`);
const logger = Logger.getInstance();
logger.time(`fetching on-chain balances for ${mapped.length} pools`);

const onChainBalances = await getOnChainBalances(
mapped,
this.network.addresses.contracts.multicall,
this.network.addresses.contracts.vault,
this.provider
);
console.timeEnd(`fetching on-chain balances for ${mapped.length} pools`);

logger.timeEnd(`fetching on-chain balances for ${mapped.length} pools`);

return onChainBalances;
}
Expand Down
18 changes: 18 additions & 0 deletions balancer-js/src/test/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,24 @@ export const ADDRESSES = {
symbol: 'bbausd',
slot: 0,
},
bbausdcV1: {
address: '0x9210F1204b5a24742Eba12f710636D76240dF3d0',
decimals: 18,
symbol: 'bbausdc',
slot: 0,
},
bbausdtV1: {
address: '0x2BBf681cC4eb09218BEe85EA2a5d3D13Fa40fC0C',
decimals: 18,
symbol: 'bbausdt',
slot: 0,
},
bbadaiV1: {
address: '0x804CdB9116a10bB78768D3252355a1b18067bF8f',
decimals: 18,
symbol: 'bbadai',
slot: 0,
},
bbausd2: {
id: '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d',
address: '0xa13a9247ea42d743238089903570127dda72fe44',
Expand Down
Loading