From 18987ebb76510d1ecc0cb3f4396e5fee28b4478e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Brand=C3=A3o?= Date: Tue, 31 Oct 2023 10:44:42 +0000 Subject: [PATCH] Aave V3 supply and withdraw --- core/src/abis/index.ts | 2 + .../asset-strategies/AaveV3DepositStrategy.ts | 238 ++++++++++++++++++ .../asset-strategies/BeefyDepositStrategy.ts | 4 +- .../asset-strategies/GammaDepositStrategy.ts | 10 +- .../src/asset-strategies/InterfaceStrategy.ts | 12 +- .../asset-strategies/asset-type-strategies.ts | 2 + core/src/config/config.default.ts | 4 +- core/src/path/exchanges.ts | 4 +- core/src/transaction/types.ts | 5 +- core/tests/protocols/aave.spec.ts | 88 +++++++ data/assets.json | 45 +++- router/contracts/Router.sol | 4 +- router/test/Router.ts | 4 +- 13 files changed, 393 insertions(+), 29 deletions(-) create mode 100644 core/src/asset-strategies/AaveV3DepositStrategy.ts create mode 100644 core/tests/protocols/aave.spec.ts diff --git a/core/src/abis/index.ts b/core/src/abis/index.ts index 7cb1752..faa240e 100644 --- a/core/src/abis/index.ts +++ b/core/src/abis/index.ts @@ -1,6 +1,7 @@ import { Interface } from "ethers"; import IERC20ABI from "./IERC20.json"; +import IPoolABI from "./Aave/IPool.json"; import IBeefyVaultV6ABI from "./Beefy/IBeefyVaultV6.json"; import IHypervisorABI from "./Gamma/IHypervisor.json"; import IHypervisorRouterABI from "./Gamma/IHypervisorRouter.json"; @@ -11,6 +12,7 @@ import RouterABI from "./Router.json"; import RouterSimulatorABI from "./RouterSimulator.json"; export const IERC20 = new Interface(IERC20ABI.abi); +export const IPool = new Interface(IPoolABI); export const IBeefyVaultV6 = new Interface(IBeefyVaultV6ABI); export const IHypervisor = new Interface(IHypervisorABI); export const IHypervisorRouter = new Interface(IHypervisorRouterABI.abi); diff --git a/core/src/asset-strategies/AaveV3DepositStrategy.ts b/core/src/asset-strategies/AaveV3DepositStrategy.ts new file mode 100644 index 0000000..26306af --- /dev/null +++ b/core/src/asset-strategies/AaveV3DepositStrategy.ts @@ -0,0 +1,238 @@ +import { RequestTree } from "../transaction/get-prices-and-linked-assets"; +import { StoreOpType } from "../transaction/types"; +import { + fetchPriceData, + getPrice, +} from "../transaction/asset-type-strategies-helpers"; +import { getMagicOffsets } from "core/src/utils/get-magic-offset"; +import { IERC20, IPool } from "core/src/abis"; +import { + FRACTION_MULTIPLIER, + MAGIC_REPLACER_0, +} from "core/src/utils/get-magic-offset"; +import { + FetchPriceDataParams, + GetPriceParams, + GenerateStepParams, +} from "./InterfaceStrategy"; +import { InterfaceStrategy } from "./InterfaceStrategy"; + +const poolAddress = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"; + +export class AaveV3DepositStrategy extends InterfaceStrategy { + fetchPriceData({ provider, assetStore, asset }: FetchPriceDataParams) { + const linkedAsset = assetStore.getAssetById(asset.linkedAssets[0].assetId); + + let requestTree: RequestTree = {}; + + requestTree[asset.address] = {}; + + const fetchedData = fetchPriceData({ + provider, + assetStore, + asset: linkedAsset, + }); + requestTree = { + ...requestTree, + ...fetchedData, + }; + return requestTree; + } + + getPrice({ assetStore, asset, requestTree }: GetPriceParams) { + const linkedAsset = assetStore.getAssetById(asset.linkedAssets[0].assetId); + + return getPrice({ assetStore, asset: linkedAsset, requestTree }); + } + + async generateStep({ + assetAllocation, + assetStore, + walletAddress, + value, + currentAllocation, + routerOperation, + }: GenerateStepParams) { + const asset = assetStore.getAssetById(assetAllocation.assetId); + if (asset.linkedAssets.length != 1) { + throw new Error( + `AaveV3DepositStrategy: asset ${asset.id} should have exactly one linked asset` + ); + } + const linkedAsset = assetStore.getAssetById(asset.linkedAssets[0].assetId); + + if (assetAllocation.fraction > 0) { + const storeNumberFrom = routerOperation.stores.findOrInitializeStoreIdx({ + assetId: linkedAsset.id, + }); + const storeNumberTo = routerOperation.stores.findOrInitializeStoreIdx({ + assetId: asset.id, + }); + const storeNumberTmp = routerOperation.stores.findOrInitializeStoreIdx({ + tmpStoreName: `${asset.id} tmp store 0`, + }); + + const currentFraction = currentAllocation.getAssetById({ + assetId: linkedAsset.id, + }).fraction; + const newFraction = asset.linkedAssets[0].fraction / currentFraction; + const variation = currentFraction * newFraction; + + currentAllocation.updateFraction({ + assetId: linkedAsset.id, + delta: -variation, + }); + currentAllocation.updateFraction({ + assetId: asset.id, + delta: variation, + }); + + const { data: approveEncodedCall, offsets: approveFromOffsets } = + getMagicOffsets({ + data: IERC20.encodeFunctionData("approve", [ + poolAddress, + MAGIC_REPLACER_0, + ]), + magicReplacers: [MAGIC_REPLACER_0], + }); + + routerOperation.steps.push({ + stepAddress: linkedAsset.address, + stepEncodedCall: approveEncodedCall, + storeOperations: [ + { + storeOpType: StoreOpType.RetrieveStoreAssignCall, + storeNumber: storeNumberFrom, + offset: approveFromOffsets[0], + fraction: Math.round(FRACTION_MULTIPLIER * newFraction), + }, + ], + }); + + const { offsets: balanceOfToOffsets } = getMagicOffsets({ + data: IERC20.encodeFunctionResult("balanceOf", [MAGIC_REPLACER_0]), + magicReplacers: [MAGIC_REPLACER_0], + }); + + routerOperation.steps.push({ + stepAddress: asset.address, + stepEncodedCall: IERC20.encodeFunctionData("balanceOf", [ + asset.address, + ]), + storeOperations: [ + { + storeOpType: StoreOpType.RetrieveResultAddStore, + storeNumber: storeNumberTmp, + offset: balanceOfToOffsets[0], + fraction: FRACTION_MULTIPLIER, + }, + ], + }); + + const { data: supplyEncodedCall, offsets: supplyFromOffsets } = + getMagicOffsets({ + data: IPool.encodeFunctionData("supply", [ + linkedAsset.address, // asset + MAGIC_REPLACER_0, // amount + walletAddress, // onBehalfOf + 0, // referralCode + ]), + magicReplacers: [MAGIC_REPLACER_0], + }); + + routerOperation.steps.push({ + stepAddress: poolAddress, + stepEncodedCall: supplyEncodedCall, + storeOperations: [ + { + storeOpType: StoreOpType.RetrieveStoreAssignCallSubtract, + storeNumber: storeNumberFrom, + offset: supplyFromOffsets[0], + fraction: newFraction * FRACTION_MULTIPLIER, + }, + ], + }); + + routerOperation.steps.push({ + stepAddress: asset.address, + stepEncodedCall: IERC20.encodeFunctionData("balanceOf", [ + asset.address, + ]), + storeOperations: [ + { + storeOpType: StoreOpType.RetrieveResultAddStore, + storeNumber: storeNumberTo, + offset: balanceOfToOffsets[0], + fraction: newFraction * FRACTION_MULTIPLIER, + }, + { + storeOpType: StoreOpType.SubtractStoreFromStore, + storeNumber: storeNumberTmp, + offset: storeNumberTo, + fraction: FRACTION_MULTIPLIER, + }, + ], + }); + } else if (assetAllocation.fraction < 0) { + const storeNumberFrom = routerOperation.stores.findOrInitializeStoreIdx({ + assetId: asset.id, + }); + const storeNumberTo = routerOperation.stores.findOrInitializeStoreIdx({ + assetId: linkedAsset.id, + }); + + const currentFraction = currentAllocation.getAssetById({ + assetId: asset.id, + }).fraction; + const newFraction = -assetAllocation.fraction / currentFraction; + const variation = newFraction * currentFraction; + + asset.linkedAssets.map((la, i) => { + currentAllocation.updateFraction({ + assetId: la.assetId, + delta: variation * la.fraction, + }); + currentAllocation.updateFraction({ + assetId: asset.id, + delta: -variation * la.fraction, + }); + }); + + const { data: withdrawEncodedCall, offsets: withdrawFromOffsets } = + getMagicOffsets({ + data: IPool.encodeFunctionData("withdraw", [ + linkedAsset.address, // asset + MAGIC_REPLACER_0, // _shares + walletAddress, // to + ]), + magicReplacers: [MAGIC_REPLACER_0], + }); + + const { offsets: withdrawToOffsets } = getMagicOffsets({ + data: IPool.encodeFunctionResult("withdraw", [MAGIC_REPLACER_0]), + magicReplacers: [MAGIC_REPLACER_0], + }); + + routerOperation.steps.push({ + stepAddress: poolAddress, + stepEncodedCall: withdrawEncodedCall, + storeOperations: [ + { + storeOpType: StoreOpType.RetrieveStoreAssignCallSubtract, + storeNumber: storeNumberFrom, + offset: withdrawFromOffsets[0], + fraction: newFraction * FRACTION_MULTIPLIER, + }, + { + storeOpType: StoreOpType.RetrieveResultAddStore, + storeNumber: storeNumberTo, + offset: withdrawToOffsets[0], + fraction: FRACTION_MULTIPLIER, + }, + ], + }); + } + + return routerOperation; + } +} diff --git a/core/src/asset-strategies/BeefyDepositStrategy.ts b/core/src/asset-strategies/BeefyDepositStrategy.ts index 21bdf10..215e336 100644 --- a/core/src/asset-strategies/BeefyDepositStrategy.ts +++ b/core/src/asset-strategies/BeefyDepositStrategy.ts @@ -138,7 +138,7 @@ export class BeefyDepositStrategy extends InterfaceStrategy { ]), storeOperations: [ { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTmp, offset: balanceOfToOffsets[0], fraction: FRACTION_MULTIPLIER, @@ -227,7 +227,7 @@ export class BeefyDepositStrategy extends InterfaceStrategy { ]), storeOperations: [ { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTmp, offset: balanceOfToOffsets[0], fraction: FRACTION_MULTIPLIER, diff --git a/core/src/asset-strategies/GammaDepositStrategy.ts b/core/src/asset-strategies/GammaDepositStrategy.ts index 5c38cc2..cc3af67 100644 --- a/core/src/asset-strategies/GammaDepositStrategy.ts +++ b/core/src/asset-strategies/GammaDepositStrategy.ts @@ -217,13 +217,13 @@ export class GammaDepositStrategy extends InterfaceStrategy { fraction: Math.round(linkedAssetFractions[1] * FRACTION_MULTIPLIER), }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTmp[0], offset: calculateRatiosToOffsets[0], fraction: FRACTION_MULTIPLIER, }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTmp[1], offset: calculateRatiosToOffsets[1], fraction: FRACTION_MULTIPLIER, @@ -305,7 +305,7 @@ export class GammaDepositStrategy extends InterfaceStrategy { fraction: FRACTION_MULTIPLIER, }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTo, offset: depositToOffsets[0], fraction: FRACTION_MULTIPLIER, @@ -372,13 +372,13 @@ export class GammaDepositStrategy extends InterfaceStrategy { fraction: newFraction * FRACTION_MULTIPLIER, }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTo0, offset: withdrawToOffsets[0], fraction: FRACTION_MULTIPLIER, }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTo1, offset: withdrawToOffsets[1], fraction: FRACTION_MULTIPLIER, diff --git a/core/src/asset-strategies/InterfaceStrategy.ts b/core/src/asset-strategies/InterfaceStrategy.ts index 9c4d020..99f3c26 100644 --- a/core/src/asset-strategies/InterfaceStrategy.ts +++ b/core/src/asset-strategies/InterfaceStrategy.ts @@ -1,8 +1,5 @@ import { Provider } from "ethers"; -import { - RequestTree, - RequestTree, -} from "../transaction/get-prices-and-linked-assets"; +import { RequestTree } from "../transaction/get-prices-and-linked-assets"; import { RouterOperation, LinkedAsset, @@ -10,14 +7,7 @@ import { AssetStore, CurrentAllocation, FractionAllocationItem, - RouterOperation, } from "../transaction/types"; -import { - GenerateStepParams, - FetchPriceDataParams, - GetPriceParams, - GetLinkedAssetsParams, -} from "./InterfaceStrategy"; export abstract class InterfaceStrategy { abstract generateStep({ diff --git a/core/src/asset-strategies/asset-type-strategies.ts b/core/src/asset-strategies/asset-type-strategies.ts index b2fad91..51e8145 100644 --- a/core/src/asset-strategies/asset-type-strategies.ts +++ b/core/src/asset-strategies/asset-type-strategies.ts @@ -3,6 +3,7 @@ import { NetworkTokenStrategy } from "./NetworkTokenStrategy"; import { TokenStrategy } from "./TokenStrategy"; import { GammaDepositStrategy } from "./GammaDepositStrategy"; import { BeefyDepositStrategy } from "./BeefyDepositStrategy"; +import { AaveV3DepositStrategy } from "./AaveV3DepositStrategy"; import { InterfaceStrategy } from "./InterfaceStrategy"; export const assetTypeStrategies: { @@ -15,5 +16,6 @@ export const assetTypeStrategies: { networkToken: new NetworkTokenStrategy(), beefyDeposit: new BeefyDepositStrategy(), gammaDeposit: new GammaDepositStrategy(), + aaveV3Deposit: new AaveV3DepositStrategy(), }, }; diff --git a/core/src/config/config.default.ts b/core/src/config/config.default.ts index bcf561f..57c4e88 100644 --- a/core/src/config/config.default.ts +++ b/core/src/config/config.default.ts @@ -1,8 +1,8 @@ export default { networks: { 137: { - routerAddress: "0x9cD5c60284654b99540Bc5839Ab0edBFDaa6cA20", - routerSimulatorAddress: "0xa33C0B0Be63BaDf55054Feaa1f3B5B382E19A52D", + routerAddress: "0x5F5Ef893d65b91c5A060119fcBe8F0EB964dB5a8", + routerSimulatorAddress: "0xf12093D00E70D0AF5a3d16950d515cbC8232d46F", gammaRatiosCalculator: "0x0F306e004258dc4c9Ecc0373b6E3dC59A3f0dB58", }, }, diff --git a/core/src/path/exchanges.ts b/core/src/path/exchanges.ts index 7305f93..640cf02 100644 --- a/core/src/path/exchanges.ts +++ b/core/src/path/exchanges.ts @@ -176,7 +176,7 @@ export class Paraswap extends Exchange { fraction: Math.round(path.fraction * FRACTION_MULTIPLIER), }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTo, offset: swapToOffset, fraction: 0, @@ -292,7 +292,7 @@ export class ZeroX extends Exchange { fraction: Math.round(path.fraction * FRACTION_MULTIPLIER), }, { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: storeNumberTo, offset: swapToOffsets[0], fraction: 0, diff --git a/core/src/transaction/types.ts b/core/src/transaction/types.ts index 199ebcc..6db5263 100644 --- a/core/src/transaction/types.ts +++ b/core/src/transaction/types.ts @@ -269,12 +269,13 @@ export type AssetType = | "token" | "networkToken" | "gammaDeposit" - | "beefyDeposit"; + | "beefyDeposit" + | "aaveV3Deposit"; export enum StoreOpType { RetrieveStoreAssignValue, // 0 RetrieveStoreAssignCall, // 1 - RetrieveResultAssignStore, // 2 + RetrieveResultAddStore, // 2 RetrieveResultSubtractStore, // 3 RetrieveStoreAssignValueSubtract, // 4 RetrieveStoreAssignCallSubtract, // 5 diff --git a/core/tests/protocols/aave.spec.ts b/core/tests/protocols/aave.spec.ts new file mode 100644 index 0000000..bedd1de --- /dev/null +++ b/core/tests/protocols/aave.spec.ts @@ -0,0 +1,88 @@ +import { test } from "vitest"; +import { generateTransaction } from "core/src/transaction/generate-transaction"; +import { AssetStore } from "core/src/transaction/types"; +import { loadConfig } from "core/src/config/load-config"; +import { simulateRouterOperation } from "core/src/path/tx-simulator"; +import { getProvider } from "core/src/utils/get-provider"; + +test("generateTransaction: USDC to aPolUSDC (aaveV3Deposit)", async () => { + const assetStore = new AssetStore(); + const config = await loadConfig(); + const provider = await getProvider({ chainId: 137 }); + + const routerOperation = await generateTransaction({ + inputAllocation: [ + { + assetId: "e251ecf6-48c2-4538-afcd-fbb92424054d", + amountStr: "1000000000", + }, + ], + outputAllocation: [ + { + assetId: "371b83f1-3301-4c69-b3ad-8d199c6d1774", + fraction: 1.0, + }, + ], + assetStore, + chainId: 137, + walletAddress: config.networks[137].routerSimulatorAddress, + }); + + const result = await simulateRouterOperation({ + chainId: 137, + routerOperation, + provider, + sellAsset: assetStore.getAssetById("e251ecf6-48c2-4538-afcd-fbb92424054d"), + amountIn: "1000000000", + buyAsset: assetStore.getAssetById("371b83f1-3301-4c69-b3ad-8d199c6d1774"), + }); + + console.dir( + { + encodedTransactionData: routerOperation.getEncodedTransactionData(), + result, + }, + { depth: null, maxStringLength: null } + ); +}); + +test.skip("generateTransaction: aPolUSDC (aaveV3Deposit) to USDC", async () => { + const assetStore = new AssetStore(); + const config = await loadConfig(); + const provider = await getProvider({ chainId: 137 }); + + const routerOperation = await generateTransaction({ + inputAllocation: [ + { + assetId: "371b83f1-3301-4c69-b3ad-8d199c6d1774", + amountStr: "1000000000", + }, + ], + outputAllocation: [ + { + assetId: "e251ecf6-48c2-4538-afcd-fbb92424054d", + fraction: 1.0, + }, + ], + assetStore, + chainId: 137, + walletAddress: config.networks[137].routerSimulatorAddress, + }); + + const result = await simulateRouterOperation({ + chainId: 137, + routerOperation, + provider, + sellAsset: assetStore.getAssetById("371b83f1-3301-4c69-b3ad-8d199c6d1774"), + amountIn: "1000000000", + buyAsset: assetStore.getAssetById("e251ecf6-48c2-4538-afcd-fbb92424054d"), + }); + + console.dir( + { + encodedTransactionData: routerOperation.getEncodedTransactionData(), + result, + }, + { depth: null, maxStringLength: null } + ); +}); diff --git a/data/assets.json b/data/assets.json index 1f2bde3..615e76d 100644 --- a/data/assets.json +++ b/data/assets.json @@ -1448,6 +1448,49 @@ "allowSlot": 1, "balanceSlot": 0 }, + { + "id": "46062b41-6661-4eae-bc11-4d4ec07b062f", + "active": true, + "address": "0x6d80113e533a2C0fe82EaBD35f1875DcEA89Ea97", + "color": "#468af0", + "decimals": 18, + "linkedAssets": [ + { + "assetId": "d604439e-d464-4df5-bed1-66815b348cab", + "fraction": 1 + } + ], + "name": "Aave Polygon WMATIC", + "chainId": 137, + "symbol": "aPolWMATIC", + "type": "aaveV3Deposit", + "visible": true, + "category": ["Lending", "Yield", "Blue chip"], + "allowSlot": 53, + "balanceSlot": 52, + "maxSize": 64 + }, + { + "id": "371b83f1-3301-4c69-b3ad-8d199c6d1774", + "active": true, + "address": "0x625e7708f30ca75bfd92586e17077590c60eb4cd", + "color": "#2775ca", + "decimals": 6, + "linkedAssets": [ + { + "assetId": "e251ecf6-48c2-4538-afcd-fbb92424054d", + "fraction": 1 + } + ], + "name": "Aave Polygon USDC", + "chainId": 137, + "symbol": "aPolUSDC", + "type": "aaveV3Deposit", + "visible": true, + "allowSlot": 53, + "balanceSlot": 52, + "maxSize": 64 + }, { "id": "fecfd33d-e6a7-476b-89cb-910a0058fa48", "name": "beefy.finance", @@ -3926,4 +3969,4 @@ } ] } -] \ No newline at end of file +] diff --git a/router/contracts/Router.sol b/router/contracts/Router.sol index 3948579..e8289be 100644 --- a/router/contracts/Router.sol +++ b/router/contracts/Router.sol @@ -17,7 +17,7 @@ contract Router { RetrieveStoreAssignCall, // 2: Retrieve result and adds to store // - retrieve result value at "offset" of "result" and add to "storeNumber" - RetrieveResultAssignStore, + RetrieveResultAddStore, // 3: Retrieve result and subtracts from store // - retrieve result value at "offset" of "result" and subtracts from "storeNumber" RetrieveResultSubtractStore, @@ -161,7 +161,7 @@ contract Router { for (uint16 j = 0; j < steps[i].storeOperations.length; j++) { if ( steps[i].storeOperations[j].storeOpType == - StoreOpType.RetrieveResultAssignStore + StoreOpType.RetrieveResultAddStore ) { stores[ steps[i].storeOperations[j].storeNumber diff --git a/router/test/Router.ts b/router/test/Router.ts index 7909e6f..830e4bc 100644 --- a/router/test/Router.ts +++ b/router/test/Router.ts @@ -9,7 +9,7 @@ import { BigNumberish } from "ethers"; enum StoreOpType { RetrieveStoreAssignValue, // 0 RetrieveStoreAssignCall, // 1 - RetrieveResultAssignStore, // 2 + RetrieveResultAddStore, // 2 RetrieveStoreAssignValueSubtract, // 3 RetrieveStoreAssignCallSubtract, // 4 SubtractStoreFromStore, // 5 @@ -234,7 +234,7 @@ describe("Router", function () { ), storeOperations: [ { - storeOpType: StoreOpType.RetrieveResultAssignStore, + storeOpType: StoreOpType.RetrieveResultAddStore, storeNumber: 1, offset: 32 + 32 + 32 * (2 - 1), fraction: 1000000,