Skip to content

Commit

Permalink
Merge pull request #25 from balancer/hook-support-dynamicFee
Browse files Browse the repository at this point in the history
feat: Add dynamicFee support.
  • Loading branch information
johngrantuk authored Jul 25, 2024
2 parents cc0887e + a2f14a6 commit baa4dfc
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 6 deletions.
2 changes: 1 addition & 1 deletion typescript/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export interface HookBase {
};
onComputeDynamicSwapFee(
params: SwapInput,
poolAddress: string,
staticSwapFeePercentage: bigint,
hookState: HookState | unknown,
): { success: boolean; dynamicSwapFee: bigint };
}

Expand Down
13 changes: 8 additions & 5 deletions typescript/src/vault/vault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,14 @@ export class Vault {
);
}

// hook: dynamicSwapFee
let swapFee = poolState.swapFee;
if (hook.shouldCallComputeDynamicSwapFee) {
throw new Error(
'Hook Unsupported: shouldCallComputeDynamicSwapFee',
const { success, dynamicSwapFee } = hook.onComputeDynamicSwapFee(
input,
poolState.swapFee,
hookState,
);
if (success) swapFee = dynamicSwapFee;
}

// _swap()
Expand All @@ -140,13 +143,13 @@ export class Vault {

// Set swapFeeAmountScaled18 based on the amountCalculated.
let swapFeeAmountScaled18 = 0n;
if (poolState.swapFee > 0) {
if (swapFee > 0) {
// Swap fee is always a percentage of the amountCalculated. On ExactIn, subtract it from the calculated
// amountOut. On ExactOut, add it to the calculated amountIn.
// Round up to avoid losses during precision loss.
swapFeeAmountScaled18 = MathSol.mulUpFixed(
amountCalculatedScaled18,
poolState.swapFee,
swapFee,
);
}

Expand Down
111 changes: 111 additions & 0 deletions typescript/test/hooks/dynamicFee.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// pnpm test -- dynamicFee.test.ts
import { describe, expect, test } from 'vitest';
import { SwapInput, SwapKind, Vault, Weighted } from '../../src';
import { HookBase, HookState } from '@/hooks/types';

const pool = {
poolType: 'CustomPool',
hookType: 'CustomHook',
chainId: '11155111',
blockNumber: '5955145',
poolAddress: '0xb2456a6f51530053bc41b0ee700fe6a2c37282e8',
tokens: [
'0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9',
'0xb19382073c7A0aDdbb56Ac6AF1808Fa49e377B75',
],
scalingFactors: [1000000000000000000n, 1000000000000000000n],
weights: [500000000000000000n, 500000000000000000n],
swapFee: 0n,

aggregateSwapFee: 500000000000000000n,
balancesLiveScaled18: [2000000000000000000n, 2000000000000000000n],
tokenRates: [1000000000000000000n, 1000000000000000000n],
totalSupply: 1000000000000000000n,
};

const swapInput = {
swapKind: SwapKind.GivenIn,
amountRaw: 100000000000000n,
tokenIn: pool.tokens[0],
tokenOut: pool.tokens[1],
};

describe('hook - dynamicFee', () => {
const vault = new Vault({
customPoolClasses: {
CustomPool: CustomPool,
},
customHookClasses: {
CustomHook: CustomHook,
},
});

test('should use dynamicFee set by hook', () => {
/*
hook state is used to pass new swapFee which gives expected swap result
*/
const inputHookState = {
newSwapFee: 100000000000000000n,
};
const test = vault.swap(swapInput, pool, inputHookState);
expect(test).to.eq(89995500224987n);
});
});

class CustomPool extends Weighted {}

class CustomHook implements HookBase {
public shouldCallComputeDynamicSwapFee = true;
public shouldCallBeforeSwap = false;
public shouldCallAfterSwap = false;
public shouldCallBeforeAddLiquidity = false;
public shouldCallAfterAddLiquidity = false;
public shouldCallBeforeRemoveLiquidity = false;
public shouldCallAfterRemoveLiquidity = false;
public enableHookAdjustedAmounts = false;

onBeforeAddLiquidity() {
return { success: false, hookAdjustedBalancesScaled18: [] };
}
onAfterAddLiquidity() {
return { success: false, hookAdjustedAmountsInRaw: [] };
}
onBeforeRemoveLiquidity() {
return { success: false, hookAdjustedBalancesScaled18: [] };
}
onAfterRemoveLiquidity() {
return {
success: false,
hookAdjustedAmountsOutRaw: [],
};
}
onBeforeSwap() {
return { success: false, hookAdjustedBalancesScaled18: [] };
}
onAfterSwap() {
return { success: false, hookAdjustedAmountCalculatedRaw: 0n };
}
onComputeDynamicSwapFee(
params: SwapInput,
staticSwapFeePercentage: bigint,
hookState: HookState | unknown,
) {
if (
!(
typeof hookState === 'object' &&
hookState !== null &&
'newSwapFee' in hookState
)
)
throw new Error('Unexpected hookState');
expect(params.swapKind).to.eq(swapInput.swapKind);
expect(params.tokenIn).to.eq(swapInput.tokenIn);
expect(params.tokenOut).to.eq(swapInput.tokenOut);
expect(params.amountRaw).to.eq(swapInput.amountRaw);
expect(staticSwapFeePercentage).to.eq(pool.swapFee);
return {
success: true,
dynamicSwapFee: hookState.newSwapFee as bigint,
};
}
}

0 comments on commit baa4dfc

Please sign in to comment.