From 842943d4d1452e4994cc7033251e33d05195184d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 25 Jul 2024 14:43:50 +0100 Subject: [PATCH] feat: Add before support for add/remove/swap. --- typescript/src/hooks/types.ts | 15 +- typescript/src/vault/vault.ts | 78 +++++++--- .../test/hooks/afterAddLiquidity.test.ts | 6 +- .../test/hooks/afterRemoveLiquidity.test.ts | 6 +- typescript/test/hooks/afterSwap.test.ts | 6 +- .../test/hooks/beforeAddLiquidity.test.ts | 122 +++++++++++++++ .../test/hooks/beforeRemoveLiquidity.test.ts | 142 ++++++++++++++++++ typescript/test/hooks/beforeSwap.test.ts | 106 +++++++++++++ typescript/test/hooks/hook.test.ts | 16 +- 9 files changed, 455 insertions(+), 42 deletions(-) create mode 100644 typescript/test/hooks/beforeAddLiquidity.test.ts create mode 100644 typescript/test/hooks/beforeRemoveLiquidity.test.ts create mode 100644 typescript/test/hooks/beforeSwap.test.ts diff --git a/typescript/src/hooks/types.ts b/typescript/src/hooks/types.ts index 6275e20..9e24e6c 100644 --- a/typescript/src/hooks/types.ts +++ b/typescript/src/hooks/types.ts @@ -27,13 +27,12 @@ export interface HookBase { enableHookAdjustedAmounts: boolean; onBeforeAddLiquidity( - router: string, - pool: string, kind: AddKind, maxAmountsInScaled18: bigint[], minBptAmountOut: bigint, balancesScaled18: bigint[], - ): boolean; + hookState: HookState | unknown, + ): { success: boolean; hookAdjustedBalancesScaled18: bigint[] }; onAfterAddLiquidity( kind: AddKind, amountsInScaled18: bigint[], @@ -43,13 +42,12 @@ export interface HookBase { hookState: HookState | unknown, ): { success: boolean; hookAdjustedAmountsInRaw: bigint[] }; onBeforeRemoveLiquidity( - router: string, - pool: string, kind: RemoveKind, maxBptAmountIn: bigint, minAmountsOutScaled18: bigint[], balancesScaled18: bigint[], - ): boolean; + hookState: HookState | unknown, + ): { success: boolean; hookAdjustedBalancesScaled18: bigint[] }; onAfterRemoveLiquidity( kind: RemoveKind, bptAmountIn: bigint, @@ -58,7 +56,10 @@ export interface HookBase { balancesScaled18: bigint[], hookState: HookState | unknown, ): { success: boolean; hookAdjustedAmountsOutRaw: bigint[] }; - onBeforeSwap(params: SwapInput, poolAddress: string): boolean; + onBeforeSwap(params: SwapInput & { hookState: HookState | unknown }): { + success: boolean; + hookAdjustedBalancesScaled18: bigint[]; + }; onAfterSwap(params: AfterSwapParams): { success: boolean; hookAdjustedAmountCalculatedRaw: bigint; diff --git a/typescript/src/vault/vault.ts b/typescript/src/vault/vault.ts index b2b12c3..f03bada 100644 --- a/typescript/src/vault/vault.ts +++ b/typescript/src/vault/vault.ts @@ -101,9 +101,23 @@ export class Vault { poolState.tokenRates, ); - // hook: shouldCallBeforeSwap (TODO - need to handle balance changes, etc see code) + const updatedBalancesLiveScaled18 = [...poolState.balancesLiveScaled18]; if (hook.shouldCallBeforeSwap) { - throw new Error('Hook Unsupported: shouldCallBeforeSwap'); + /* + Note - in SC balances and amounts are updated to reflect any rate change. + Daniel said we should not worry about this as any large rate changes will mean something has gone wrong. + We do take into account and balance changes due to hook using hookAdjustedBalancesScaled18. + */ + const { success, hookAdjustedBalancesScaled18 } = hook.onBeforeSwap( + { + ...input, + hookState, + }, + ); + if (!success) throw new Error('BeforeSwapHookFailed'); + hookAdjustedBalancesScaled18.forEach( + (a, i) => (updatedBalancesLiveScaled18[i] = a), + ); } // hook: dynamicSwapFee @@ -117,7 +131,7 @@ export class Vault { const swapParams: SwapParams = { swapKind: input.swapKind, amountGivenScaled18, - balancesLiveScaled18: poolState.balancesLiveScaled18, + balancesLiveScaled18: updatedBalancesLiveScaled18, indexIn: inputIndex, indexOut: outputIndex, }; @@ -184,7 +198,6 @@ export class Vault { amountGivenScaled18, ]; - const updatedBalancesLiveScaled18 = [...poolState.balancesLiveScaled18]; updatedBalancesLiveScaled18[inputIndex] += locals.balanceInIncrement; updatedBalancesLiveScaled18[outputIndex] -= locals.balanceOutDecrement; @@ -245,9 +258,26 @@ export class Vault { poolState.tokenRates, ); - // hook: shouldCallBeforeAddLiquidity (TODO - need to handle balance changes, etc see code) + const updatedBalancesLiveScaled18 = [...poolState.balancesLiveScaled18]; + if (hook.shouldCallBeforeAddLiquidity) { - throw new Error('Hook Unsupported: shouldCallBeforeAddLiquidity'); + /* + Note - in SC balances and amounts are updated to reflect any rate change. + Daniel said we should not worry about this as any large rate changes will mean something has gone wrong. + We do take into account and balance changes due to hook using hookAdjustedBalancesScaled18. + */ + const { success, hookAdjustedBalancesScaled18 } = + hook.onBeforeAddLiquidity( + input.kind, + input.maxAmountsIn, + input.minBptAmountOut, + updatedBalancesLiveScaled18, + hookState, + ); + if (!success) throw new Error('BeforeAddLiquidityHookFailed'); + hookAdjustedBalancesScaled18.forEach( + (a, i) => (updatedBalancesLiveScaled18[i] = a), + ); } let amountsInScaled18: bigint[]; @@ -257,7 +287,7 @@ export class Vault { if (input.kind === AddKind.UNBALANCED) { amountsInScaled18 = maxAmountsInScaled18; const computed = computeAddLiquidityUnbalanced( - poolState.balancesLiveScaled18, + updatedBalancesLiveScaled18, maxAmountsInScaled18, poolState.totalSupply, poolState.swapFee, @@ -271,7 +301,7 @@ export class Vault { amountsInScaled18 = maxAmountsInScaled18; bptAmountOut = input.minBptAmountOut; const computed = computeAddLiquiditySingleTokenExactOut( - poolState.balancesLiveScaled18, + updatedBalancesLiveScaled18, tokenIndex, bptAmountOut, poolState.totalSupply, @@ -288,7 +318,6 @@ export class Vault { } else throw new Error('Unsupported AddLiquidity Kind'); const amountsInRaw: bigint[] = new Array(poolState.tokens.length); - const updatedBalancesLiveScaled18 = [...poolState.balancesLiveScaled18]; for (let i = 0; i < poolState.tokens.length; i++) { // amountsInRaw are amounts actually entering the Pool, so we round up. amountsInRaw[i] = this._toRawUndoRateRoundUp( @@ -306,7 +335,7 @@ export class Vault { ); updatedBalancesLiveScaled18[i] = - poolState.balancesLiveScaled18[i] + + updatedBalancesLiveScaled18[i] + amountsInScaled18[i] - aggregateSwapFeeAmountScaled18; } @@ -369,10 +398,24 @@ export class Vault { poolState.tokenRates, ); - // hook: shouldCallBeforeRemoveLiquidity (TODO - need to handle balance changes, etc see code) + const updatedBalancesLiveScaled18 = [...poolState.balancesLiveScaled18]; if (hook.shouldCallBeforeRemoveLiquidity) { - throw new Error( - 'Hook Unsupported: shouldCallBeforeRemoveLiquidity', + /* + Note - in SC balances and amounts are updated to reflect any rate change. + Daniel said we should not worry about this as any large rate changes will mean something has gone wrong. + We do take into account and balance changes due to hook using hookAdjustedBalancesScaled18. + */ + const { success, hookAdjustedBalancesScaled18 } = + hook.onBeforeRemoveLiquidity( + input.kind, + input.maxBptAmountIn, + input.minAmountsOut, + updatedBalancesLiveScaled18, + hookState, + ); + if (!success) throw new Error('BeforeRemoveLiquidityHookFailed'); + hookAdjustedBalancesScaled18.forEach( + (a, i) => (updatedBalancesLiveScaled18[i] = a), ); } @@ -387,7 +430,7 @@ export class Vault { 0n, ); amountsOutScaled18 = computeProportionalAmountsOut( - poolState.balancesLiveScaled18, + updatedBalancesLiveScaled18, poolState.totalSupply, input.maxBptAmountIn, ); @@ -396,7 +439,7 @@ export class Vault { amountsOutScaled18 = minAmountsOutScaled18; tokenOutIndex = this._getSingleInputIndex(input.minAmountsOut); const computed = computeRemoveLiquiditySingleTokenExactIn( - poolState.balancesLiveScaled18, + updatedBalancesLiveScaled18, tokenOutIndex, input.maxBptAmountIn, poolState.totalSupply, @@ -414,7 +457,7 @@ export class Vault { amountsOutScaled18 = minAmountsOutScaled18; tokenOutIndex = this._getSingleInputIndex(input.minAmountsOut); const computed = computeRemoveLiquiditySingleTokenExactOut( - poolState.balancesLiveScaled18, + updatedBalancesLiveScaled18, tokenOutIndex, amountsOutScaled18[tokenOutIndex], poolState.totalSupply, @@ -427,7 +470,6 @@ export class Vault { } else throw new Error('Unsupported RemoveLiquidity Kind'); const amountsOutRaw = new Array(poolState.tokens.length); - const updatedBalancesLiveScaled18 = [...poolState.balancesLiveScaled18]; for (let i = 0; i < poolState.tokens.length; ++i) { // amountsOut are amounts exiting the Pool, so we round down. @@ -446,7 +488,7 @@ export class Vault { ); updatedBalancesLiveScaled18[i] = - poolState.balancesLiveScaled18[i] - + updatedBalancesLiveScaled18[i] - (amountsOutScaled18[i] + aggregateSwapFeeAmountScaled18); } diff --git a/typescript/test/hooks/afterAddLiquidity.test.ts b/typescript/test/hooks/afterAddLiquidity.test.ts index ca7b879..65c36ac 100644 --- a/typescript/test/hooks/afterAddLiquidity.test.ts +++ b/typescript/test/hooks/afterAddLiquidity.test.ts @@ -100,7 +100,7 @@ class CustomHook implements HookBase { public enableHookAdjustedAmounts = true; onBeforeAddLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterAddLiquidity( kind: AddKind, @@ -134,13 +134,13 @@ class CustomHook implements HookBase { }; } onBeforeRemoveLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterRemoveLiquidity() { return { success: false, hookAdjustedAmountsOutRaw: [] }; } onBeforeSwap() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterSwap() { return { success: false, hookAdjustedAmountCalculatedRaw: 0n }; diff --git a/typescript/test/hooks/afterRemoveLiquidity.test.ts b/typescript/test/hooks/afterRemoveLiquidity.test.ts index 668873a..609fdef 100644 --- a/typescript/test/hooks/afterRemoveLiquidity.test.ts +++ b/typescript/test/hooks/afterRemoveLiquidity.test.ts @@ -128,13 +128,13 @@ class CustomHook implements HookBase { public enableHookAdjustedAmounts = true; onBeforeAddLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterAddLiquidity() { return { success: false, hookAdjustedAmountsInRaw: [] }; } onBeforeRemoveLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterRemoveLiquidity( kind: RemoveKind, @@ -167,7 +167,7 @@ class CustomHook implements HookBase { }; } onBeforeSwap() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterSwap() { return { success: false, hookAdjustedAmountCalculatedRaw: 0n }; diff --git a/typescript/test/hooks/afterSwap.test.ts b/typescript/test/hooks/afterSwap.test.ts index eb7f695..974be10 100644 --- a/typescript/test/hooks/afterSwap.test.ts +++ b/typescript/test/hooks/afterSwap.test.ts @@ -106,13 +106,13 @@ class CustomHook implements HookBase { public enableHookAdjustedAmounts = true; onBeforeAddLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterAddLiquidity() { return { success: false, hookAdjustedAmountsInRaw: [] }; } onBeforeRemoveLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterRemoveLiquidity() { return { @@ -121,7 +121,7 @@ class CustomHook implements HookBase { }; } onBeforeSwap() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterSwap(params: AfterSwapParams) { const { diff --git a/typescript/test/hooks/beforeAddLiquidity.test.ts b/typescript/test/hooks/beforeAddLiquidity.test.ts new file mode 100644 index 0000000..035cb76 --- /dev/null +++ b/typescript/test/hooks/beforeAddLiquidity.test.ts @@ -0,0 +1,122 @@ +// pnpm test -- beforeAddLiquidity.test.ts +import { describe, expect, test } from 'vitest'; +import { AddKind, Vault, Weighted } from '../../src'; +import { HookBase, HookState } from '@/hooks/types'; + +const addLiquidityInput = { + pool: '0xb2456a6f51530053bc41b0ee700fe6a2c37282e8', + maxAmountsIn: [200000000000000000n, 100000000000000000n], + minBptAmountOut: 0n, + kind: AddKind.UNBALANCED, +}; + +class CustomPool extends Weighted {} + +const pool = { + poolType: 'CustomPool', + hookType: 'CustomHook', + chainId: '11155111', + blockNumber: '5955145', + poolAddress: '0xb2456a6f51530053bc41b0ee700fe6a2c37282e8', + tokens: [ + '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', + '0xb19382073c7A0aDdbb56Ac6AF1808Fa49e377B75', + ], + scalingFactors: [1000000000000000000n, 1000000000000000000n], + weights: [500000000000000000n, 500000000000000000n], + swapFee: 100000000000000000n, + aggregateSwapFee: 500000000000000000n, + balancesLiveScaled18: [2000000000000000000n, 2000000000000000000n], + tokenRates: [1000000000000000000n, 1000000000000000000n], + totalSupply: 1000000000000000000n, +}; + +describe('hook - beforeAddLiquidity', () => { + const vault = new Vault({ + customPoolClasses: { + CustomPool: CustomPool, + }, + customHookClasses: { + CustomHook: CustomHook, + }, + }); + + test('should alter pool balances', () => { + /* + hook state is used to pass new balances which give expected bptAmount out + */ + const inputHookState = { + balanceChange: [1000000000000000000n, 1000000000000000000n], + }; + const test = vault.addLiquidity( + addLiquidityInput, + pool, + inputHookState, + ); + expect(test.amountsIn).to.deep.eq([ + 200000000000000000n, + 100000000000000000n, + ]); + expect(test.bptAmountOut).to.deep.eq(146464294351915965n); + }); +}); + +class CustomHook implements HookBase { + public shouldCallComputeDynamicSwapFee = false; + public shouldCallBeforeSwap = false; + public shouldCallAfterSwap = false; + public shouldCallBeforeAddLiquidity = true; + public shouldCallAfterAddLiquidity = false; + public shouldCallBeforeRemoveLiquidity = false; + public shouldCallAfterRemoveLiquidity = false; + public enableHookAdjustedAmounts = false; + + onBeforeAddLiquidity( + kind: AddKind, + maxAmountsInScaled18: bigint[], + minBptAmountOut: bigint, + balancesScaled18: bigint[], + hookState: HookState | unknown, + ) { + if ( + !( + typeof hookState === 'object' && + hookState !== null && + 'balanceChange' in hookState + ) + ) + throw new Error('Unexpected hookState'); + expect(kind).to.eq(addLiquidityInput.kind); + expect(maxAmountsInScaled18).to.deep.eq(addLiquidityInput.maxAmountsIn); + expect(minBptAmountOut).to.deep.eq(addLiquidityInput.minBptAmountOut); + expect(balancesScaled18).to.deep.eq(pool.balancesLiveScaled18); + return { + success: true, + hookAdjustedBalancesScaled18: hookState.balanceChange as bigint[], + }; + } + onAfterAddLiquidity() { + return { + success: false, + hookAdjustedAmountsInRaw: [], + }; + } + onBeforeRemoveLiquidity() { + return { + success: true, + hookAdjustedBalancesScaled18: [], + }; + } + onAfterRemoveLiquidity() { + return { success: false, hookAdjustedAmountsOutRaw: [] }; + } + onBeforeSwap() { + return { success: false, hookAdjustedBalancesScaled18: [] }; + } + onAfterSwap() { + return { success: false, hookAdjustedAmountCalculatedRaw: 0n }; + } + onComputeDynamicSwapFee() { + return { success: false, dynamicSwapFee: 0n }; + } +} diff --git a/typescript/test/hooks/beforeRemoveLiquidity.test.ts b/typescript/test/hooks/beforeRemoveLiquidity.test.ts new file mode 100644 index 0000000..16ac87a --- /dev/null +++ b/typescript/test/hooks/beforeRemoveLiquidity.test.ts @@ -0,0 +1,142 @@ +// pnpm test -- beforeRemoveLiquidity.test.ts +import { describe, expect, test } from 'vitest'; +import { RemoveKind, Vault, type PoolBase } from '../../src'; +import { HookBase, HookState } from '@/hooks/types'; + +const removeLiquidityInput = { + pool: '0xb2456a6f51530053bc41b0ee700fe6a2c37282e8', + minAmountsOut: [0n, 1n], + maxBptAmountIn: 100000000000000000n, + kind: RemoveKind.SINGLE_TOKEN_EXACT_IN, +}; + +/* +remove: + SINGLE_TOKEN_EXACT_IN: + bptAmountIn: 100000000000000000n + returns: + amountsOutScaled18: [ 0n, 909999999999999999n ] + amountsOutRaw: [ 0n, 909999999999999999n ] +*/ +const pool = { + poolType: 'CustomPool', + hookType: 'CustomHook', + chainId: '11155111', + blockNumber: '5955145', + poolAddress: '0xb2456a6f51530053bc41b0ee700fe6a2c37282e8', + tokens: [ + '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9', + '0xb19382073c7A0aDdbb56Ac6AF1808Fa49e377B75', + ], + scalingFactors: [1000000000000000000n, 1000000000000000000n], + weights: [500000000000000000n, 500000000000000000n], + swapFee: 100000000000000000n, + aggregateSwapFee: 500000000000000000n, + balancesLiveScaled18: [2000000000000000000n, 2000000000000000000n], + tokenRates: [1000000000000000000n, 1000000000000000000n], + totalSupply: 1000000000000000000n, +}; + +describe('hook - afterRemoveLiquidity', () => { + const vault = new Vault({ + customPoolClasses: { + CustomPool: CustomPool, + }, + customHookClasses: { + CustomHook: CustomHook, + }, + }); + + test('aggregateSwapFee of 50% should take half of remaining', () => { + /* + hook state is used to pass new balances which give expected result + */ + const inputHookState = { + balanceChange: [1000000000000000000n, 1000000000000000000n], + }; + const test = vault.removeLiquidity( + removeLiquidityInput, + pool, + inputHookState, + ); + expect(test.bptAmountIn).to.eq(removeLiquidityInput.maxBptAmountIn); + expect(test.amountsOut).to.deep.eq([0n, 909999999999999999n]); + }); +}); + +class CustomPool implements PoolBase { + constructor() {} + + getMaxSwapAmount(): bigint { + return 1n; + } + + onSwap(): bigint { + return 1n; + } + computeInvariant(): bigint { + return 1n; + } + computeBalance(): bigint { + return 1n; + } +} + +class CustomHook implements HookBase { + public shouldCallComputeDynamicSwapFee = false; + public shouldCallBeforeSwap = false; + public shouldCallAfterSwap = false; + public shouldCallBeforeAddLiquidity = false; + public shouldCallAfterAddLiquidity = false; + public shouldCallBeforeRemoveLiquidity = true; + public shouldCallAfterRemoveLiquidity = false; + public enableHookAdjustedAmounts = false; + + onBeforeAddLiquidity() { + return { success: false, hookAdjustedBalancesScaled18: [] }; + } + onAfterAddLiquidity() { + return { success: false, hookAdjustedAmountsInRaw: [] }; + } + onBeforeRemoveLiquidity( + kind: RemoveKind, + maxBptAmountIn: bigint, + minAmountsOutScaled18: bigint[], + balancesScaled18: bigint[], + hookState: HookState | unknown, + ) { + if ( + !( + typeof hookState === 'object' && + hookState !== null && + 'balanceChange' in hookState + ) + ) + throw new Error('Unexpected hookState'); + expect(kind).to.eq(removeLiquidityInput.kind); + expect(maxBptAmountIn).to.deep.eq(removeLiquidityInput.maxBptAmountIn); + expect(minAmountsOutScaled18).to.deep.eq( + removeLiquidityInput.minAmountsOut, + ); + expect(balancesScaled18).to.deep.eq(pool.balancesLiveScaled18); + return { + success: true, + hookAdjustedBalancesScaled18: hookState.balanceChange as bigint[], + }; + } + onAfterRemoveLiquidity() { + return { + success: true, + hookAdjustedAmountsOutRaw: [], + }; + } + onBeforeSwap() { + return { success: false, hookAdjustedBalancesScaled18: [] }; + } + onAfterSwap() { + return { success: false, hookAdjustedAmountCalculatedRaw: 0n }; + } + onComputeDynamicSwapFee() { + return { success: false, dynamicSwapFee: 0n }; + } +} diff --git a/typescript/test/hooks/beforeSwap.test.ts b/typescript/test/hooks/beforeSwap.test.ts new file mode 100644 index 0000000..a1bb9cd --- /dev/null +++ b/typescript/test/hooks/beforeSwap.test.ts @@ -0,0 +1,106 @@ +// pnpm test -- beforeSwap.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: 100000000000000000n, + aggregateSwapFee: 500000000000000000n, + balancesLiveScaled18: [2000000000000000000n, 2000000000000000000n], + tokenRates: [1000000000000000000n, 1000000000000000000n], + totalSupply: 1000000000000000000n, +}; + +const swapInput = { + swapKind: SwapKind.GivenIn, + amountRaw: 100000000n, + tokenIn: pool.tokens[0], + tokenOut: pool.tokens[1], +}; + +describe('hook - beforeSwap', () => { + const vault = new Vault({ + customPoolClasses: { + CustomPool: CustomPool, + }, + customHookClasses: { + CustomHook: CustomHook, + }, + }); + + test('should alter pool balances', () => { + /* + hook state is used to pass new balances which give expected swap result + */ + const inputHookState = { + balanceChange: [1000000000000000000n, 1000000000000000000n], + }; + const test = vault.swap(swapInput, pool, inputHookState); + expect(test).to.eq(89999999n); + }); +}); + +class CustomPool extends Weighted {} + +class CustomHook implements HookBase { + public shouldCallComputeDynamicSwapFee = false; + public shouldCallBeforeSwap = true; + 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(params: SwapInput & { hookState: HookState | unknown }) { + const { hookState, swapKind, tokenIn, tokenOut, amountRaw } = params; + if ( + !( + typeof hookState === 'object' && + hookState !== null && + 'balanceChange' in hookState + ) + ) + throw new Error('Unexpected hookState'); + expect(swapKind).to.eq(swapInput.swapKind); + expect(tokenIn).to.eq(swapInput.tokenIn); + expect(tokenOut).to.eq(swapInput.tokenOut); + expect(amountRaw).to.eq(swapInput.amountRaw); + return { + success: true, + hookAdjustedBalancesScaled18: hookState.balanceChange as bigint[], + }; + } + onAfterSwap() { + return { success: false, hookAdjustedAmountCalculatedRaw: 0n }; + } + onComputeDynamicSwapFee() { + return { success: false, dynamicSwapFee: 0n }; + } +} diff --git a/typescript/test/hooks/hook.test.ts b/typescript/test/hooks/hook.test.ts index 2125053..ad96170 100644 --- a/typescript/test/hooks/hook.test.ts +++ b/typescript/test/hooks/hook.test.ts @@ -1,6 +1,6 @@ // pnpm test -- hook.test.ts import { describe, expect, test } from 'vitest'; -import { MaxSwapParams, Vault, type PoolBase } from '../../src'; +import { Vault, type PoolBase } from '../../src'; import { HookBase } from '@/hooks/types'; describe('hook tests', () => { @@ -30,6 +30,7 @@ describe('hook tests', () => { tokenRates: [1000000000000000000n, 1000000000000000000n], totalSupply: 1736721048412749353n, randoms: [77n, 88n], + aggregateSwapFee: 0n, }; test('should throw when no hook state passed', () => { expect(() => { @@ -62,13 +63,11 @@ describe('hook tests', () => { class CustomPool implements PoolBase { public randoms: bigint[]; - constructor(poolState: { - randoms: bigint[]; - }) { + constructor(poolState: { randoms: bigint[] }) { this.randoms = poolState.randoms; } - getMaxSwapAmount(_maxSwapParams: MaxSwapParams): bigint { + getMaxSwapAmount(): bigint { return 1n; } @@ -91,21 +90,22 @@ class CustomHook implements HookBase { public shouldCallAfterAddLiquidity = false; public shouldCallBeforeRemoveLiquidity = false; public shouldCallAfterRemoveLiquidity = false; + public enableHookAdjustedAmounts = false; onBeforeAddLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterAddLiquidity() { return { success: false, hookAdjustedAmountsInRaw: [] }; } onBeforeRemoveLiquidity() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterRemoveLiquidity() { return { success: false, hookAdjustedAmountsOutRaw: [] }; } onBeforeSwap() { - return false; + return { success: false, hookAdjustedBalancesScaled18: [] }; } onAfterSwap() { return { success: false, hookAdjustedAmountCalculatedRaw: 0n };