Skip to content

Feat/enable hooks sor #896

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

Merged
merged 83 commits into from
Jan 15, 2025
Merged
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
a5d46aa
docs: update test instruction
mkflow27 Aug 21, 2024
3ea3bd0
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Aug 26, 2024
6955e5e
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Aug 29, 2024
be1cf51
feat: add hooks to sor (tests only)
mkflow27 Sep 4, 2024
9111dfe
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Sep 13, 2024
3258ae9
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Sep 18, 2024
9eb1a3a
feat: sor to consider pools with hooks
mkflow27 Oct 2, 2024
4b2b30a
refactor: updates to classes & data
mkflow27 Oct 2, 2024
1ef97b4
feat: disallow pools with hooks for rout consideration
mkflow27 Oct 2, 2024
017a008
chore: add lock file
mkflow27 Oct 2, 2024
7cb5ba2
test: updates to deploy-8
mkflow27 Oct 2, 2024
36b2d07
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Oct 31, 2024
169519b
chore: enable sor to consider pools with hooks again
mkflow27 Nov 1, 2024
9984542
test: update tests
mkflow27 Nov 1, 2024
258fca9
chore: update sdk version
mkflow27 Nov 1, 2024
842ad9f
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Nov 26, 2024
fd86b37
test: only hook test
mkflow27 Nov 26, 2024
b533604
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Nov 26, 2024
393023e
Merge branch 'feat/enable-hooks-sor' of https://github.com/balancer/b…
mkflow27 Nov 26, 2024
0e60a9c
fix: capitalize to align with Vault requirement
mkflow27 Nov 28, 2024
9de4330
fix: typo
mkflow27 Nov 28, 2024
7584fa1
chore: bump version
mkflow27 Nov 29, 2024
2481e65
feat: support for hooks in pools
mkflow27 Dec 2, 2024
6f6ea7b
feat: consider liquidityManagement in route building
mkflow27 Dec 2, 2024
4d7971f
chore: remove hook from type
mkflow27 Dec 2, 2024
b18a52e
feat: add liquidity management
mkflow27 Dec 2, 2024
890e837
test: 11th testnet & hook tests
mkflow27 Dec 2, 2024
d7189b5
chore: update lockfile
mkflow27 Dec 2, 2024
b852a07
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Dec 2, 2024
9655d66
refactor: remove getHookState & typeguard
mkflow27 Dec 2, 2024
36027fd
refactor: update factories
mkflow27 Dec 2, 2024
3f224d5
chore: cleanup
mkflow27 Dec 2, 2024
e29bf4d
test: cleanup
mkflow27 Dec 2, 2024
cb53e9c
fix: build issues
mkflow27 Dec 3, 2024
da0e9d0
refactor: undo changes
mkflow27 Dec 3, 2024
c74f7d4
chore: typing changes
mkflow27 Dec 3, 2024
fc32ce1
test: update liquidity management
mkflow27 Dec 3, 2024
d6b35f0
docs: remove comments
mkflow27 Dec 3, 2024
e92195a
chore: typing
mkflow27 Dec 3, 2024
acaf6d3
refactor: include null hook
mkflow27 Dec 3, 2024
1c53b58
test: update tests to reflect api/onchain state at given block
mkflow27 Dec 3, 2024
bd6e2b2
test: undo changes
mkflow27 Dec 3, 2024
524ec30
Update balancer-sor hardcoded numbers to match updated blockNumber
brunoguerios Dec 3, 2024
ff90a05
refactor: rename fn
mkflow27 Dec 10, 2024
a689a51
style: remove redundancy
mkflow27 Dec 10, 2024
f000336
fix: do math with ethers library
mkflow27 Dec 10, 2024
563313e
fix: process api results based on factory & api
mkflow27 Dec 10, 2024
897ff9e
test: update to deploy 12
mkflow27 Dec 17, 2024
8de4a1d
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Dec 17, 2024
c1645b1
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Dec 18, 2024
f390d65
test: update tests
mkflow27 Dec 18, 2024
fdc9ea0
test: update tests
mkflow27 Dec 18, 2024
36262c0
add lockfile
mkflow27 Dec 18, 2024
9ba2908
chore: add changeset
mkflow27 Dec 18, 2024
bc9b8aa
chore: remove yarn lockfile
mkflow27 Dec 19, 2024
935b43e
docs: update test docs
mkflow27 Dec 20, 2024
6ff7577
refactor: renaming hook to hookState
mkflow27 Dec 20, 2024
e7ed805
fix: build
mkflow27 Dec 20, 2024
12d461a
fix: add hookState
mkflow27 Jan 9, 2025
23f1834
test: add givenOut test
mkflow27 Jan 9, 2025
43dd7dd
Merge branch 'v3-canary' into feat/enable-hooks-sor
mkflow27 Jan 9, 2025
3c7d451
lockfile
mkflow27 Jan 9, 2025
b298aeb
test: test all
mkflow27 Jan 9, 2025
61a15fa
fix: access surge threshold from hook dynamic data
mkflow27 Jan 10, 2025
3a2c9e6
test: check difference given hookState is passed
mkflow27 Jan 10, 2025
05ce65f
bun install
mkflow27 Jan 10, 2025
229f42e
fix: Stable surge data fix
mkflow27 Jan 13, 2025
1fd68f3
fix: add hookName to poolState
mkflow27 Jan 13, 2025
08ff575
test: add stable surge test
mkflow27 Jan 13, 2025
904fe50
test: add poolState tests for routing inside vault
mkflow27 Jan 13, 2025
85bf5dd
fix: build ignore name property access
mkflow27 Jan 13, 2025
6a1fe48
test: change poolType
mkflow27 Jan 13, 2025
1535368
fix: add percentage scaling
mkflow27 Jan 13, 2025
3916eb6
test: liquidity management handling
mkflow27 Jan 13, 2025
3e01976
fix: 18 decimal scaling
mkflow27 Jan 13, 2025
17a507c
chore: version bump
mkflow27 Jan 15, 2025
f2eba4c
refactor: better hookType handling
mkflow27 Jan 15, 2025
5de8b2d
refactor: add hookType to return
mkflow27 Jan 15, 2025
39fbafa
test: update due to maths version bump
mkflow27 Jan 15, 2025
cdba3a8
chore: lockfile
mkflow27 Jan 15, 2025
d443c40
style: format
mkflow27 Jan 15, 2025
f3572b9
refactor: rename vars
mkflow27 Jan 15, 2025
f73eaa9
Merge branch 'v3-canary' into feat/enable-hooks-sor
franzns Jan 15, 2025
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
5 changes: 5 additions & 0 deletions .changeset/funny-hounds-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'backend': minor
---

This pr adds hooks to the SOR (DirectionalFee, StableSurge, ExitFee)
Binary file modified bun.lockb
Binary file not shown.
587 changes: 508 additions & 79 deletions modules/sor/balancer-sor.integration.test.ts

Large diffs are not rendered by default.

107 changes: 101 additions & 6 deletions modules/sor/sorV2/lib/poolsV3/stable/stablePool.test.ts
Original file line number Diff line number Diff line change
@@ -6,34 +6,44 @@ import { PrismaPoolAndHookWithDynamic } from '../../../../../../prisma/prisma-ty
import { WAD } from '../../utils/math';
import { StablePool } from './stablePool';

import { Token, TokenAmount } from '@balancer/sdk';

// keep factories imports at the end - moving up will break the test
import {
poolTokenFactory,
prismaPoolDynamicDataFactory,
prismaPoolFactory,
prismaPoolTokenFactory,
hookFactory,
} from '../../../../../../test/factories';
import { createRandomAddress } from '../../../../../../test/utils';

describe('SOR V3 Stable Pool Tests', () => {
let amp: string;
let scalingFactors: bigint[];
let aggregateSwapFee: bigint;
let stablePool: StablePool;
let stablePoolWithHook: StablePool;
let stablePrismaPool: PrismaPoolAndHookWithDynamic;
let stablePrismaPoolWithHook: PrismaPoolAndHookWithDynamic;
let swapFee: string;
let tokenAddresses: string[];
let tokenBalances: string[];
let tokenDecimals: number[];
let tokenRates: string[];
let totalShares: string;
let poolAddress: string;

beforeAll(() => {
swapFee = '0.01';
tokenBalances = ['169', '144'];
swapFee = '0.001';
tokenBalances = ['52110', '51290'];
tokenDecimals = [6, 18];
tokenRates = ['1', '1'];
totalShares = '156';
amp = '10';
scalingFactors = [WAD * 10n ** 12n, WAD];
totalShares = '100000';
amp = '1000';
scalingFactors = [10n ** 12n, 10n ** 0n];
aggregateSwapFee = 0n;
poolAddress = createRandomAddress();

const poolToken1 = prismaPoolTokenFactory.build({
token: poolTokenFactory.build({ decimals: tokenDecimals[0] }),
@@ -48,29 +58,114 @@ describe('SOR V3 Stable Pool Tests', () => {

tokenAddresses = [poolToken1.address, poolToken2.address];

const hookDynamicData = {
surgeThresholdPercentage: '0.3',
};

const stableSurgeHook = hookFactory.build({
name: 'StableSurge',
dynamicData: hookDynamicData,
enableHookAdjustedAmounts: true,
shouldCallAfterAddLiquidity: true,
shouldCallAfterInitialize: true,
shouldCallAfterRemoveLiquidity: true,
shouldCallAfterSwap: true,
shouldCallBeforeAddLiquidity: true,
shouldCallBeforeInitialize: true,
shouldCallBeforeRemoveLiquidity: true,
shouldCallBeforeSwap: true,
shouldCallComputeDynamicSwapFee: true,
});

stablePrismaPool = prismaPoolFactory.build({
address: poolAddress,
type: 'STABLE',
protocolVersion: 3,
typeData: {
amp,
},
tokens: [poolToken1, poolToken2],
dynamicData: prismaPoolDynamicDataFactory.build({ swapFee, totalShares }),
liquidityManagement: {
disableUnbalancedLiquidity: true,
enableAddLiquidityCustom: false,
enableDonation: false,
enableRemoveLiquidityCustom: false,
},
});
stablePool = StablePool.fromPrismaPool(stablePrismaPool);

stablePrismaPoolWithHook = prismaPoolFactory.build({
address: poolAddress,
hook: stableSurgeHook,
type: 'STABLE',
protocolVersion: 3,
typeData: {
amp,
},
tokens: [poolToken1, poolToken2],
dynamicData: prismaPoolDynamicDataFactory.build({ swapFee, totalShares }),
liquidityManagement: {
disableUnbalancedLiquidity: true,
enableAddLiquidityCustom: false,
enableDonation: false,
enableRemoveLiquidityCustom: false,
},
});
stablePoolWithHook = StablePool.fromPrismaPool(stablePrismaPoolWithHook);
});

test('Get Pool State', () => {
const poolState = {
poolType: 'Stable',
poolType: 'STABLE',
poolAddress: poolAddress,
swapFee: parseEther(swapFee),
balancesLiveScaled18: tokenBalances.map((b) => parseEther(b)),
tokenRates: tokenRates.map((r) => parseEther(r)),
totalSupply: parseEther(totalShares),
amp: parseUnits(amp, 3),
tokens: tokenAddresses,
scalingFactors,
aggregateSwapFee,
supportsUnbalancedLiquidity: false,
};
expect(poolState).toEqual(stablePool.getPoolState());
});
test('Get Pool State with hook', () => {
const poolState = {
poolType: 'STABLE',
hookType: 'StableSurge',
poolAddress: poolAddress,
swapFee: parseEther(swapFee),
balancesLiveScaled18: tokenBalances.map((b) => parseEther(b)),
tokenRates: tokenRates.map((r) => parseEther(r)),
totalSupply: parseEther(totalShares),
amp: parseUnits(amp, 3),
tokens: tokenAddresses,
scalingFactors,
aggregateSwapFee,
supportsUnbalancedLiquidity: false,
};
expect(poolState).toEqual(stablePoolWithHook.getPoolState('StableSurge'));
});
test('results differ when hookState is passed', () => {
const poolToken1 = new Token(1, stablePool.tokens[0].token.address, 18, 'pt1', 'poolToken2');

const poolToken2 = new Token(1, stablePool.tokens[1].token.address, 18, 'pt2', 'poolToken2');

// If given a high enough swap Amount, the pool with hookState should return a lower amount Out
// as it charges the surge Fee.
const tokenAmountOut = stablePool.swapGivenIn(
poolToken1,
poolToken2,
TokenAmount.fromRawAmount(poolToken1, '777700000000'),
);

const tokenAmountOutWithHook = stablePoolWithHook.swapGivenIn(
poolToken1,
poolToken2,
TokenAmount.fromRawAmount(poolToken1, '777700000000'),
);
expect(tokenAmountOut.amount > tokenAmountOutWithHook.amount).toBe(true);
});
});
67 changes: 63 additions & 4 deletions modules/sor/sorV2/lib/poolsV3/stable/stablePool.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Address, Hex, parseEther, parseUnits } from 'viem';

import { MAX_UINT256, PoolType, SwapKind, Token, TokenAmount } from '@balancer/sdk';
import { AddKind, RemoveKind, StableState, Vault } from '@balancer-labs/balancer-maths';
import { AddKind, RemoveKind, StableState, Vault, HookState } from '@balancer-labs/balancer-maths';
import { Chain } from '@prisma/client';

import { PrismaPoolAndHookWithDynamic } from '../../../../../../prisma/prisma-types';
@@ -14,6 +14,10 @@ import { BasePoolV3 } from '../../poolsV2/basePool';
import { StableBasePoolToken } from './stableBasePoolToken';
import { Erc4626PoolToken } from '../../poolsV2/erc4626PoolToken';

import { getHookState, isLiquidityManagement } from '../../utils/helpers';

import { LiquidityManagement } from '../../../../../sor/types';

type StablePoolToken = StableBasePoolToken | Erc4626PoolToken;

export class StablePool implements BasePoolV3 {
@@ -27,6 +31,8 @@ export class StablePool implements BasePoolV3 {

public totalShares: bigint;
public tokens: StablePoolToken[];
public readonly hookState: HookState | undefined;
public readonly liquidityManagement: LiquidityManagement;

private readonly tokenMap: Map<string, StablePoolToken>;

@@ -76,6 +82,14 @@ export class StablePool implements BasePoolV3 {
const totalShares = parseEther(pool.dynamicData.totalShares);
const amp = parseUnits((pool.typeData as StableData).amp, 3);

//transform
const hookState = getHookState(pool);

// typeguard
if (!isLiquidityManagement(pool.liquidityManagement)) {
throw new Error('LiquidityManagement must be of type LiquidityManagement and cannot be null');
}

return new StablePool(
pool.id as Hex,
pool.address,
@@ -85,6 +99,8 @@ export class StablePool implements BasePoolV3 {
poolTokens,
totalShares,
pool.dynamicData.tokenPairsData as TokenPairData[],
pool.liquidityManagement,
hookState,
);
}

@@ -97,6 +113,8 @@ export class StablePool implements BasePoolV3 {
tokens: StablePoolToken[],
totalShares: bigint,
tokenPairs: TokenPairData[],
liquidityManagement: LiquidityManagement,
hookState: HookState | undefined = undefined,
) {
this.chain = chain;
this.id = id;
@@ -108,13 +126,15 @@ export class StablePool implements BasePoolV3 {
this.tokens = tokens.sort((a, b) => a.index - b.index);
this.tokenMap = new Map(this.tokens.map((token) => [token.token.address, token]));
this.tokenPairs = tokenPairs;
this.hookState = hookState;
this.liquidityManagement = liquidityManagement;

// add BPT to tokenMap, so we can handle add/remove liquidity operations
const bpt = new Token(tokens[0].token.chainId, this.id, 18, 'BPT', 'BPT');
this.tokenMap.set(bpt.address, new StableBasePoolToken(bpt, totalShares, -1, WAD));

this.vault = new Vault();
this.poolState = this.getPoolState();
this.poolState = this.getPoolState(hookState?.hookType);
}

public getLimitAmountSwap(tokenIn: Token, tokenOut: Token, swapKind: SwapKind): bigint {
@@ -157,6 +177,13 @@ export class StablePool implements BasePoolV3 {
let calculatedAmount: bigint;

if (tIn.token.isSameAddress(this.id)) {
// if liquidityManagement.disableUnbalancedLiquidity is true return 0
// as the pool does not allow unbalanced operations. 0 return marks the
// route as truly unfeasible route.
if (this.liquidityManagement.disableUnbalancedLiquidity) {
return TokenAmount.fromRawAmount(tOut.token, 0n);
}

// remove liquidity
const { amountsOutRaw } = this.vault.removeLiquidity(
{
@@ -166,9 +193,17 @@ export class StablePool implements BasePoolV3 {
kind: RemoveKind.SINGLE_TOKEN_EXACT_IN,
},
this.poolState,
this.hookState,
);
calculatedAmount = amountsOutRaw[tOut.index];
} else if (tOut.token.isSameAddress(this.id)) {
// if liquidityManagement.disableUnbalancedLiquidity is true return 0
// as the pool does not allow unbalanced operations. 0 return marks the
// route as truly unfeasible route.
if (this.liquidityManagement.disableUnbalancedLiquidity) {
return TokenAmount.fromRawAmount(tOut.token, 0n);
}

// add liquidity
const { bptAmountOutRaw } = this.vault.addLiquidity(
{
@@ -178,6 +213,7 @@ export class StablePool implements BasePoolV3 {
kind: AddKind.UNBALANCED,
},
this.poolState,
this.hookState,
);
calculatedAmount = bptAmountOutRaw;
} else {
@@ -190,6 +226,7 @@ export class StablePool implements BasePoolV3 {
swapKind: SwapKind.GivenIn,
},
this.poolState,
this.hookState,
);
}
return TokenAmount.fromRawAmount(tOut.token, calculatedAmount);
@@ -201,6 +238,13 @@ export class StablePool implements BasePoolV3 {
let calculatedAmount: bigint;

if (tIn.token.isSameAddress(this.id)) {
// if liquidityManagement.disableUnbalancedLiquidity is true return 0
// as the pool does not allow unbalanced operations. 0 return marks the
// route as truly unfeasible route.
if (this.liquidityManagement.disableUnbalancedLiquidity) {
return TokenAmount.fromRawAmount(tOut.token, 0n);
}

// remove liquidity
const { bptAmountInRaw } = this.vault.removeLiquidity(
{
@@ -210,9 +254,17 @@ export class StablePool implements BasePoolV3 {
kind: RemoveKind.SINGLE_TOKEN_EXACT_OUT,
},
this.poolState,
this.hookState,
);
calculatedAmount = bptAmountInRaw;
} else if (tOut.token.isSameAddress(this.id)) {
// if liquidityManagement.disableUnbalancedLiquidity is true return 0
// as the pool does not allow unbalanced operations. 0 return marks the
// route as truly unfeasible route.
if (this.liquidityManagement.disableUnbalancedLiquidity) {
return TokenAmount.fromRawAmount(tOut.token, 0n);
}

// add liquidity
const { amountsInRaw } = this.vault.addLiquidity(
{
@@ -222,6 +274,7 @@ export class StablePool implements BasePoolV3 {
kind: AddKind.SINGLE_TOKEN_EXACT_OUT,
},
this.poolState,
this.hookState,
);
calculatedAmount = amountsInRaw[tIn.index];
} else {
@@ -234,6 +287,7 @@ export class StablePool implements BasePoolV3 {
swapKind: SwapKind.GivenOut,
},
this.poolState,
this.hookState,
);
}
return TokenAmount.fromRawAmount(tIn.token, calculatedAmount);
@@ -254,8 +308,8 @@ export class StablePool implements BasePoolV3 {
return 0n;
}

public getPoolState(): StableState {
return {
public getPoolState(hookName?: string): StableState {
const poolState: StableState = {
poolType: 'STABLE',
poolAddress: this.address,
swapFee: this.swapFee,
@@ -266,7 +320,12 @@ export class StablePool implements BasePoolV3 {
tokens: this.tokens.map((t) => t.token.address),
scalingFactors: this.tokens.map((t) => t.scalar),
aggregateSwapFee: 0n,
supportsUnbalancedLiquidity: !this.liquidityManagement.disableUnbalancedLiquidity,
};

poolState.hookType = hookName;

return poolState;
}

public getPoolTokens(tokenIn: Token, tokenOut: Token): { tIn: StablePoolToken; tOut: StablePoolToken } {
Loading