Skip to content

Commit 828ae7d

Browse files
authored
feat: allow aave sdk to provide the helper addresses in the construct… (#666)
* feat: allow aave sdk to provide the helper addresses in the constructor param * Add unit tests
1 parent c885c6b commit 828ae7d

File tree

2 files changed

+233
-4
lines changed

2 files changed

+233
-4
lines changed

packages/flash-loans/src/aave/AaveCollateralSwapSdk.test.ts

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import { TradingSdk, TradeParameters } from '@cowprotocol/sdk-trading'
66
import { createAdapters, TEST_ADDRESS } from '../../tests/setup'
77

88
import { AaveCollateralSwapSdk } from './AaveCollateralSwapSdk'
9-
import { AAVE_ADAPTER_FACTORY, ADAPTER_DOMAIN_NAME, HASH_ZERO } from './const'
9+
import {
10+
AAVE_ADAPTER_FACTORY,
11+
ADAPTER_DOMAIN_NAME,
12+
AAVE_HOOK_ADAPTER_PER_TYPE,
13+
AaveFlashLoanType,
14+
HASH_ZERO,
15+
} from './const'
1016

1117
const adapters = createAdapters()
1218
const adapterNames = Object.keys(adapters) as Array<keyof typeof adapters>
@@ -755,5 +761,195 @@ adapterNames.forEach((adapterName) => {
755761
expect(signature).toContain('0x')
756762
})
757763
})
764+
765+
describe('constructor configuration', () => {
766+
beforeEach(() => {
767+
// Clear any existing mocks before each test
768+
jest.clearAllMocks()
769+
})
770+
771+
const setupReadContractMock = () => {
772+
return jest.spyOn(adapter, 'readContract').mockImplementation((...args: any[]) => {
773+
const params = args[0]
774+
if (params.functionName === 'getInstanceDeterministicAddress') {
775+
return Promise.resolve('0x1234567890123456789012345678901234567890' as any)
776+
}
777+
if (params.functionName === 'allowance') {
778+
return Promise.resolve(BigInt(0) as any)
779+
}
780+
return Promise.resolve('0x0000000000000000000000000000000000000000' as any)
781+
})
782+
}
783+
784+
test('should use default hook adapter addresses when no config provided', async () => {
785+
const defaultSdk = new AaveCollateralSwapSdk()
786+
const readContractSpy = setupReadContractMock()
787+
788+
await defaultSdk.collateralSwap(
789+
{
790+
chainId: SupportedChainId.GNOSIS_CHAIN,
791+
tradeParameters: mockTradeParameters,
792+
collateralToken,
793+
},
794+
mockTradingSdk,
795+
)
796+
797+
// Verify that the default address was used
798+
const readContractCalls = readContractSpy.mock.calls
799+
const getInstanceCall = readContractCalls.find(
800+
(call) => call[0]?.functionName === 'getInstanceDeterministicAddress',
801+
)
802+
803+
expect(getInstanceCall).toBeDefined()
804+
expect(getInstanceCall?.[0]?.args?.[0]).toBe(
805+
AAVE_HOOK_ADAPTER_PER_TYPE[AaveFlashLoanType.CollateralSwap][SupportedChainId.GNOSIS_CHAIN],
806+
)
807+
})
808+
809+
test('should use custom hook adapter addresses when provided', async () => {
810+
const customHookAdapterAddress = '0x1234567890123456789012345678901234567890'
811+
const customConfig = {
812+
hookAdapterPerType: {
813+
...AAVE_HOOK_ADAPTER_PER_TYPE,
814+
[AaveFlashLoanType.CollateralSwap]: {
815+
...AAVE_HOOK_ADAPTER_PER_TYPE[AaveFlashLoanType.CollateralSwap],
816+
[SupportedChainId.GNOSIS_CHAIN]: customHookAdapterAddress,
817+
},
818+
},
819+
}
820+
821+
const customSdk = new AaveCollateralSwapSdk(customConfig)
822+
const readContractSpy = setupReadContractMock()
823+
824+
await customSdk.collateralSwap(
825+
{
826+
chainId: SupportedChainId.GNOSIS_CHAIN,
827+
tradeParameters: mockTradeParameters,
828+
collateralToken,
829+
},
830+
mockTradingSdk,
831+
)
832+
833+
// Verify that the custom address was used in getInstanceDeterministicAddress
834+
const readContractCalls = readContractSpy.mock.calls
835+
const getInstanceCall = readContractCalls.find(
836+
(call) => call[0]?.functionName === 'getInstanceDeterministicAddress',
837+
)
838+
839+
expect(getInstanceCall).toBeDefined()
840+
expect(getInstanceCall?.[0]?.args?.[0]).toBe(customHookAdapterAddress)
841+
842+
// Verify that the custom address is used in the pre-hook call data
843+
const swapSettingsCall = (mockPostSwapOrderFromQuote as jest.Mock).mock.calls[0][0]
844+
const preHookCallData = swapSettingsCall.appData.metadata.hooks.pre[0].callData
845+
846+
// The call data should contain the custom adapter address
847+
// We can verify this by checking if the encoded function includes the custom address
848+
expect(preHookCallData).toBeDefined()
849+
expect(typeof preHookCallData).toBe('string')
850+
// The call data should be a hex string that includes the custom address (without 0x prefix)
851+
const customAddressWithoutPrefix = customHookAdapterAddress.toLowerCase().slice(2)
852+
expect(preHookCallData.toLowerCase()).toContain(customAddressWithoutPrefix)
853+
})
854+
855+
test('should use custom hook adapter addresses for different flash loan types', async () => {
856+
const customCollateralSwapAddress = '0x1111111111111111111111111111111111111111'
857+
const customDebtSwapAddress = '0x2222222222222222222222222222222222222222'
858+
const customRepayAddress = '0x3333333333333333333333333333333333333333'
859+
860+
const customConfig = {
861+
hookAdapterPerType: {
862+
[AaveFlashLoanType.CollateralSwap]: {
863+
...AAVE_HOOK_ADAPTER_PER_TYPE[AaveFlashLoanType.CollateralSwap],
864+
[SupportedChainId.GNOSIS_CHAIN]: customCollateralSwapAddress,
865+
},
866+
[AaveFlashLoanType.DebtSwap]: {
867+
...AAVE_HOOK_ADAPTER_PER_TYPE[AaveFlashLoanType.DebtSwap],
868+
[SupportedChainId.GNOSIS_CHAIN]: customDebtSwapAddress,
869+
},
870+
[AaveFlashLoanType.RepayCollateral]: {
871+
...AAVE_HOOK_ADAPTER_PER_TYPE[AaveFlashLoanType.RepayCollateral],
872+
[SupportedChainId.GNOSIS_CHAIN]: customRepayAddress,
873+
},
874+
},
875+
}
876+
877+
const customSdk = new AaveCollateralSwapSdk(customConfig)
878+
const readContractSpy = setupReadContractMock()
879+
880+
await customSdk.collateralSwap(
881+
{
882+
chainId: SupportedChainId.GNOSIS_CHAIN,
883+
tradeParameters: mockTradeParameters,
884+
collateralToken,
885+
},
886+
mockTradingSdk,
887+
)
888+
889+
// Verify that the custom CollateralSwap address was used
890+
const readContractCalls = readContractSpy.mock.calls
891+
const getInstanceCall = readContractCalls.find(
892+
(call) => call[0]?.functionName === 'getInstanceDeterministicAddress',
893+
)
894+
895+
expect(getInstanceCall).toBeDefined()
896+
expect(getInstanceCall?.[0]?.args?.[0]).toBe(customCollateralSwapAddress)
897+
})
898+
899+
test('should use custom hook adapter addresses for different chains', async () => {
900+
const customGnosisAddress = '0x4444444444444444444444444444444444444444'
901+
const customMainnetAddress = '0x5555555555555555555555555555555555555555'
902+
903+
const customConfig = {
904+
hookAdapterPerType: {
905+
...AAVE_HOOK_ADAPTER_PER_TYPE,
906+
[AaveFlashLoanType.CollateralSwap]: {
907+
...AAVE_HOOK_ADAPTER_PER_TYPE[AaveFlashLoanType.CollateralSwap],
908+
[SupportedChainId.GNOSIS_CHAIN]: customGnosisAddress,
909+
[SupportedChainId.MAINNET]: customMainnetAddress,
910+
},
911+
},
912+
}
913+
914+
const customSdk = new AaveCollateralSwapSdk(customConfig)
915+
const readContractSpy = setupReadContractMock()
916+
917+
// Test with Gnosis Chain
918+
await customSdk.collateralSwap(
919+
{
920+
chainId: SupportedChainId.GNOSIS_CHAIN,
921+
tradeParameters: mockTradeParameters,
922+
collateralToken,
923+
},
924+
mockTradingSdk,
925+
)
926+
927+
let readContractCalls = readContractSpy.mock.calls
928+
let getInstanceCall = readContractCalls.find(
929+
(call) => call[0]?.functionName === 'getInstanceDeterministicAddress',
930+
)
931+
932+
expect(getInstanceCall?.[0]?.args?.[0]).toBe(customGnosisAddress)
933+
934+
// Reset mocks
935+
readContractSpy.mockClear()
936+
mockPostSwapOrderFromQuote.mockClear()
937+
938+
// Test with Mainnet
939+
await customSdk.collateralSwap(
940+
{
941+
chainId: SupportedChainId.MAINNET,
942+
tradeParameters: mockTradeParameters,
943+
collateralToken,
944+
},
945+
mockTradingSdk,
946+
)
947+
948+
readContractCalls = readContractSpy.mock.calls
949+
getInstanceCall = readContractCalls.find((call) => call[0]?.functionName === 'getInstanceDeterministicAddress')
950+
951+
expect(getInstanceCall?.[0]?.args?.[0]).toBe(customMainnetAddress)
952+
})
953+
})
758954
})
759955
})

packages/flash-loans/src/aave/AaveCollateralSwapSdk.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,32 @@ import {
4141
HASH_ZERO,
4242
PERCENT_SCALE,
4343
} from './const'
44+
import { SupportedChainId } from '@cowprotocol/sdk-config'
4445
import { aaveAdapterFactoryAbi } from './abi/AaveAdapterFactory'
4546
import { collateralSwapAdapterHookAbi } from './abi/CollateralSwapAdapterHook'
46-
import { SupportedChainId } from '@cowprotocol/sdk-config'
4747
import { debtSwapAdapterAbi } from './abi/DebtSwapAdapter'
4848
import { repayWithCollateralAdapterAbi } from './abi/RepayWithCollateralAdapter'
4949

50+
/**
51+
* Configuration options for the AaveCollateralSwapSdk.
52+
* @param {Record<AaveFlashLoanType, Record<SupportedChainId, string>>} hookAdapterPerType -
53+
* Mapping of flash loan types to chain-specific hook adapter addresses.
54+
* Defaults to the predefined addresses from the constants.
55+
* @example
56+
* ```typescript
57+
* const config: AaveCollateralSwapSdkConfig = {
58+
* hookAdapterPerType: {
59+
* [AaveFlashLoanType.CollateralSwap]: {
60+
* [SupportedChainId.GNOSIS_CHAIN]: '0x...',
61+
* },
62+
* },
63+
* }
64+
* ```
65+
*/
66+
export type AaveCollateralSwapSdkConfig = {
67+
hookAdapterPerType?: Record<AaveFlashLoanType, Record<SupportedChainId, string>>
68+
}
69+
5070
/**
5171
* SDK for executing Aave flash loan operations, particularly collateral swaps.
5272
*
@@ -58,6 +78,19 @@ import { repayWithCollateralAdapterAbi } from './abi/RepayWithCollateralAdapter'
5878
* @see https://docs.cow.fi/
5979
*/
6080
export class AaveCollateralSwapSdk {
81+
private readonly hookAdapterPerType: Record<AaveFlashLoanType, Record<SupportedChainId, string>>
82+
83+
/**
84+
* Creates an instance of AaveCollateralSwapSdk.
85+
*
86+
* @param {Object} config - Configuration options for the SDK.
87+
* @param {Record<AaveFlashLoanType, Record<SupportedChainId, string>>} config.hookAdapterPerType -
88+
* Mapping of flash loan types to chain-specific hook adapter addresses.
89+
* Defaults to the predefined addresses from the constants.
90+
*/
91+
constructor(config?: AaveCollateralSwapSdkConfig) {
92+
this.hookAdapterPerType = config?.hookAdapterPerType ?? AAVE_HOOK_ADAPTER_PER_TYPE
93+
}
6194
/**
6295
* Executes a collateral swap using Aave flash loans.
6396
*
@@ -352,7 +385,7 @@ export class AaveCollateralSwapSdk {
352385

353386
return (await getGlobalAdapter().readContract({
354387
address: AAVE_ADAPTER_FACTORY[chainId],
355-
args: [AAVE_HOOK_ADAPTER_PER_TYPE[flashLoanType][chainId], hookData],
388+
args: [this.hookAdapterPerType[flashLoanType][chainId], hookData],
356389
functionName: 'getInstanceDeterministicAddress',
357390
abi: aaveAdapterFactoryAbi,
358391
})) as AccountAddress
@@ -403,7 +436,7 @@ export class AaveCollateralSwapSdk {
403436
instanceAddress: AccountAddress,
404437
): string {
405438
const hookData = this.buildHookOrderData(trader, hookAmounts, order)
406-
const adapterImplementation = AAVE_HOOK_ADAPTER_PER_TYPE[flashLoanType][chainId]
439+
const adapterImplementation = this.hookAdapterPerType[flashLoanType][chainId]
407440

408441
return getGlobalAdapter().utils.encodeFunction(aaveAdapterFactoryAbi, 'deployAndTransferFlashLoan', [
409442
adapterImplementation,

0 commit comments

Comments
 (0)