Skip to content

Commit

Permalink
feat: utility add flash loan aggregator logic
Browse files Browse the repository at this point in the history
  • Loading branch information
chouandy committed Aug 1, 2023
1 parent 874029a commit 195b3a8
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/wicked-carpets-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@protocolink/logics': patch
---

feat: utility add flash loan aggregator logic
20 changes: 16 additions & 4 deletions src/logics/aave-v2/logic.flash-loan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,23 @@ export type FlashLoanLogicFields = core.FlashLoanFields<{ referralCode?: number
export class FlashLoanLogic extends core.Logic implements core.LogicTokenListInterface, core.LogicBuilderInterface {
static readonly supportedChainIds = supportedChainIds;

get callbackAddress() {
return getContractAddress(this.chainId, 'AaveV2FlashLoanCallback');
}

async getTokenList() {
const service = new Service(this.chainId, this.provider);
const tokens: FlashLoanLogicTokenList = await service.getAssets();
const tokens = await service.getAssets();
const { assetInfos } = await service.getFlashLoanConfiguration(tokens);

const tokenList: FlashLoanLogicTokenList = [];
for (let i = 0; i < assetInfos.length; i++) {
const { isActive } = assetInfos[i];
if (!isActive) continue;
tokenList.push(tokens[i]);
}

return tokens;
return tokenList;
}

async quote(params: FlashLoanLogicParams) {
Expand Down Expand Up @@ -72,7 +84,7 @@ export class FlashLoanLogic extends core.Logic implements core.LogicTokenListInt
modes.push(InterestRateMode.none);
});
const data = LendingPool__factory.createInterface().encodeFunctionData('flashLoan', [
getContractAddress(this.chainId, 'AaveV2FlashLoanCallback'),
this.callbackAddress,
assets,
amounts,
modes,
Expand All @@ -81,7 +93,7 @@ export class FlashLoanLogic extends core.Logic implements core.LogicTokenListInt
referralCode,
]);

const callback = getContractAddress(this.chainId, 'AaveV2FlashLoanCallback');
const callback = this.callbackAddress;

return core.newLogic({ to, data, callback });
}
Expand Down
1 change: 0 additions & 1 deletion src/logics/aave-v2/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ describe('AaveV2 Service', function () {
testCases.forEach(({ assets }, i) => {
it(`case ${i + 1}`, async function () {
const flashLoanConfiguration = await service.getFlashLoanConfiguration(assets);
console.log('object :>> ', JSON.stringify(flashLoanConfiguration));
expect(flashLoanConfiguration).to.have.keys('feeBps', 'assetInfos');
expect(flashLoanConfiguration.assetInfos).to.have.lengthOf.above(0);
for (const assetInfo of flashLoanConfiguration.assetInfos) {
Expand Down
8 changes: 4 additions & 4 deletions src/logics/aave-v3/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ export const configs: Config[] = [
{
chainId: common.ChainId.polygon,
contract: {
PoolDataProvider: '0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654',
PoolDataProvider: '0x9441B65EE553F70df9C77d45d3283B6BC24F222d',
AaveV3FlashLoanCallback: '0xe1356560B683cA54e7D7e9e81b05319E9140a977',
},
},
{
chainId: common.ChainId.arbitrum,
contract: {
PoolDataProvider: '0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654',
PoolDataProvider: '0x6b4E260b765B3cA1514e618C0215A6B7839fF93e',
AaveV3FlashLoanCallback: '0xe1356560B683cA54e7D7e9e81b05319E9140a977',
},
},
{
chainId: common.ChainId.optimism,
contract: {
PoolDataProvider: '0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654',
PoolDataProvider: '0xd9Ca4878dd38B021583c1B669905592EAe76E044',
AaveV3FlashLoanCallback: '0xe1356560B683cA54e7D7e9e81b05319E9140a977',
},
},
{
chainId: common.ChainId.avalanche,
contract: {
PoolDataProvider: '0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654',
PoolDataProvider: '0x50ddd0Cd4266299527d25De9CBb55fE0EB8dAc30',
AaveV3FlashLoanCallback: '0xe1356560B683cA54e7D7e9e81b05319E9140a977',
},
},
Expand Down
20 changes: 16 additions & 4 deletions src/logics/aave-v3/logic.flash-loan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,23 @@ export class FlashLoanLogic
{
static readonly supportedChainIds = supportedChainIds;

get callbackAddress() {
return getContractAddress(this.chainId, 'AaveV3FlashLoanCallback');
}

async getTokenList() {
const service = new Service(this.chainId, this.provider);
const tokens: FlashLoanLogicTokenList = await service.getAssets();
const tokens = await service.getAssets();
const { assetInfos } = await service.getFlashLoanConfiguration(tokens);

const tokenList: FlashLoanLogicTokenList = [];
for (let i = 0; i < assetInfos.length; i++) {
const { isActive, isPaused, isFlashLoanEnabled } = assetInfos[i];
if (!isActive || isPaused || !isFlashLoanEnabled) continue;
tokenList.push(tokens[i]);
}

return tokens;
return tokenList;
}

async quote(params: FlashLoanLogicParams) {
Expand Down Expand Up @@ -77,7 +89,7 @@ export class FlashLoanLogic
modes.push(InterestRateMode.none);
});
const data = Pool__factory.createInterface().encodeFunctionData('flashLoan', [
getContractAddress(this.chainId, 'AaveV3FlashLoanCallback'),
this.callbackAddress,
assets,
amounts,
modes,
Expand All @@ -86,7 +98,7 @@ export class FlashLoanLogic
referralCode,
]);

const callback = getContractAddress(this.chainId, 'AaveV3FlashLoanCallback');
const callback = this.callbackAddress;

return core.newLogic({ to, data, callback });
}
Expand Down
8 changes: 6 additions & 2 deletions src/logics/balancer-v2/logic.flash-loan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export type FlashLoanLogicFields = core.FlashLoanFields;
export class FlashLoanLogic extends core.Logic implements core.LogicTokenListInterface, core.LogicBuilderInterface {
static readonly supportedChainIds = supportedChainIds;

get callbackAddress() {
return getContractAddress(this.chainId, 'BalancerV2FlashLoanCallback');
}

async getTokenList() {
const { data } = await axios.get<TokenList>(
'https://raw.githubusercontent.com/balancer/tokenlists/main/generated/listed-old.tokenlist.json'
Expand Down Expand Up @@ -78,13 +82,13 @@ export class FlashLoanLogic extends core.Logic implements core.LogicTokenListInt
amounts.push(output.amountWei);
}
const data = Vault__factory.createInterface().encodeFunctionData('flashLoan', [
getContractAddress(this.chainId, 'BalancerV2FlashLoanCallback'),
this.callbackAddress,
assets,
amounts,
params,
]);

const callback = getContractAddress(this.chainId, 'BalancerV2FlashLoanCallback');
const callback = this.callbackAddress;

return core.newLogic({ to, data, callback });
}
Expand Down
14 changes: 11 additions & 3 deletions src/logics/paraswap-v5/logic.swap-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ export class SwapTokenLogic

async getTokenList() {
const tokenListUrls = getTokenListUrls(this.chainId);
const tokenLists = await Promise.all(tokenListUrls.map((tokenListUrl) => axios.get<TokenList>(tokenListUrl)));
const tokenLists: TokenList[] = [];
await Promise.all(
tokenListUrls.map(async (tokenListUrl) => {
try {
const { data } = await axios.get<TokenList>(tokenListUrl);
tokenLists.push(data);
} catch {}
})
);

const tmp: Record<string, boolean> = { [this.nativeToken.address]: true };
const tokenList: SwapTokenLogicTokenList = [this.nativeToken];
for (const { data } of tokenLists) {
for (const { chainId, address, decimals, symbol, name } of data.tokens) {
for (const { tokens } of tokenLists) {
for (const { chainId, address, decimals, symbol, name } of tokens) {
if (tmp[address] || chainId !== this.chainId || !name || !symbol || !decimals) continue;
tokenList.push(new common.Token(chainId, address, decimals, symbol, name));
tmp[address] = true;
Expand Down
1 change: 1 addition & 0 deletions src/logics/utility/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './logic.custom-data';
export * from './logic.flash-loan-aggregator';
export * from './logic.multi-send';
export * from './logic.send-token';
export * from './logic.wrapped-native-token';
15 changes: 15 additions & 0 deletions src/logics/utility/logic.flash-loan-aggregator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FlashLoanAggregatorLogic } from './logic.flash-loan-aggregator';
import * as common from '@protocolink/common';
import { expect } from 'chai';

describe('Utility FlashLoanAggregatorLogic', function () {
context('Test getTokenList', async function () {
FlashLoanAggregatorLogic.supportedChainIds.forEach((chainId) => {
it(`network: ${common.toNetworkId(chainId)}`, async function () {
const logic = new FlashLoanAggregatorLogic(chainId);
const tokenList = await logic.getTokenList();
expect(tokenList).to.have.lengthOf.above(0);
});
});
});
});
98 changes: 98 additions & 0 deletions src/logics/utility/logic.flash-loan-aggregator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as aavev2 from '../aave-v2';
import * as aavev3 from '../aave-v3';
import * as balancerv2 from '../balancer-v2';
import * as common from '@protocolink/common';
import * as core from '@protocolink/core';

export const supportedFlashLoanLogics = [aavev2.FlashLoanLogic, aavev3.FlashLoanLogic, balancerv2.FlashLoanLogic];

export type FlashLoanAggregatorLogicTokenList = common.Token[];

export type FlashLoanAggregatorLogicParams = core.TokensOutFields;

export type FlashLoanAggregatorLogicQuotation = {
protocolId: string;
loans: common.TokenAmounts;
repays: common.TokenAmounts;
fees: common.TokenAmounts;
feeBps: number;
callback: string;
};

export type FlashLoanAggregatorLogicFields = core.FlashLoanFields<{ protocolId: string; referralCode?: number }>;

@core.LogicDefinitionDecorator()
export class FlashLoanAggregatorLogic
extends core.Logic
implements core.LogicTokenListInterface, core.LogicBuilderInterface
{
static readonly supportedChainIds = Array.from(
supportedFlashLoanLogics.reduce((accumulator, FlashLoanLogic) => {
for (const chainId of FlashLoanLogic.supportedChainIds) {
accumulator.add(chainId);
}
return accumulator;
}, new Set<number>())
);

async getTokenList() {
const flashLoanLogics = supportedFlashLoanLogics.filter((FlashLoanLogic) =>
FlashLoanLogic.supportedChainIds.includes(this.chainId)
);
const allTokens = await Promise.all(
flashLoanLogics.map((FlashLoanLogic) => {
const flashLoanLogic = new FlashLoanLogic(this.chainId, this.provider);
return flashLoanLogic.getTokenList();
})
);
const tmp: Record<string, boolean> = {};
const tokenList: FlashLoanAggregatorLogicTokenList = [];
for (const tokens of allTokens) {
for (const token of tokens) {
if (tmp[token.address]) continue;
tokenList.push(token);
tmp[token.address] = true;
}
}

return tokenList;
}

async quote(params: FlashLoanAggregatorLogicParams) {
const flashLoanLogics = supportedFlashLoanLogics.filter((FlashLoanLogic) =>
FlashLoanLogic.supportedChainIds.includes(this.chainId)
);

const quotations: FlashLoanAggregatorLogicQuotation[] = [];
await Promise.all(
flashLoanLogics.map(async (FlashLoanLogic) => {
const flashLoanLogic = new FlashLoanLogic(this.chainId, this.provider);
try {
const quotation = await flashLoanLogic.quote(params);
quotations.push({
protocolId: FlashLoanLogic.protocolId,
callback: flashLoanLogic.callbackAddress,
...quotation,
});
} catch {}
})
);

let quotation = quotations[0];
for (let i = 1; i < quotations.length; i++) {
if (quotations[i].feeBps < quotation.feeBps) {
quotation = quotations[i];
}
}

return quotation;
}

async build(fields: FlashLoanAggregatorLogicFields) {
const { protocolId, ...others } = fields;
const FlashLoanLogic = supportedFlashLoanLogics.find((Logic) => Logic.protocolId === protocolId)!;
const routerLogic = await new FlashLoanLogic(this.chainId, this.provider).build(others);

return routerLogic;
}
}
14 changes: 8 additions & 6 deletions test/logics/balancer-v2/flash-loan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@ describe('Test BalancerV2 FlashLoan Logic', function () {
testCases.forEach(({ outputs }, i) => {
it(`case ${i + 1}`, async function () {
// 1. get flash loan quotation
const aaveV3FlashLoanLogic = new balancerv2.FlashLoanLogic(chainId);
const { loans, repays, fees } = await aaveV3FlashLoanLogic.quote({ outputs });
const balancerV2FlashLoanLogic = new balancerv2.FlashLoanLogic(chainId);
const { loans, repays, fees } = await balancerV2FlashLoanLogic.quote({ outputs });

// 2. build funds and router logics for flash loan
const funds = new common.TokenAmounts();
const flashLoanRouterLogics: core.IParam.LogicStruct[] = [];
const utilitySendTokenLogic = new utility.SendTokenLogic(chainId);
for (let i = 0; i < fees.length; i++) {
funds.add(fees.at(i).clone());
const fee = fees.at(i).clone();
if (!fee.isZero) {
funds.add(fee);
}
flashLoanRouterLogics.push(
await utilitySendTokenLogic.build({
input: repays.at(i),
Expand All @@ -46,9 +49,8 @@ describe('Test BalancerV2 FlashLoan Logic', function () {
// 3. build router logics
const routerLogics: core.IParam.LogicStruct[] = [];

const userData = core.newCallbackParams(flashLoanRouterLogics);
const balancerV2FlashLoanLogic = new balancerv2.FlashLoanLogic(chainId);
routerLogics.push(await balancerV2FlashLoanLogic.build({ outputs: loans, params: userData }));
const params = core.newCallbackParams(flashLoanRouterLogics);
routerLogics.push(await balancerV2FlashLoanLogic.build({ outputs: loans, params: params }));

// 4. send router tx
const transactionRequest = core.newRouterExecuteTransactionRequest({ chainId, routerLogics });
Expand Down
Loading

0 comments on commit 195b3a8

Please sign in to comment.