diff --git a/packages/contract-helpers/CHANGELOG.md b/packages/contract-helpers/CHANGELOG.md index 110db8bd..20e91b4a 100644 --- a/packages/contract-helpers/CHANGELOG.md +++ b/packages/contract-helpers/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +All notable changes to this project will be documented in this file. See +[Conventional Commits](https://conventionalcommits.org) for commit guidelines. # 1.21.0 (2023-10-04) @@ -16,180 +16,117 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # 1.20.0 (2023-08-22) - ### Features -* withdraw and swap ([#555](https://github.com/aave/aave-utilities/issues/555)) ([ca6c230](https://github.com/aave/aave-utilities/commit/ca6c230f39536ba34313ba5ec8295267742216a3)) - - - - +- withdraw and swap ([#555](https://github.com/aave/aave-utilities/issues/555)) + ([ca6c230](https://github.com/aave/aave-utilities/commit/ca6c230f39536ba34313ba5ec8295267742216a3)) # 1.19.0 (2023-08-21) - ### Features -* add Scroll Sepolia and Scroll chainIds ([#554](https://github.com/aave/aave-utilities/issues/554)) ([7510d22](https://github.com/aave/aave-utilities/commit/7510d227dd358f3420d7788a9feaaa72f09e8e9e)) - - - - +- add Scroll Sepolia and Scroll chainIds + ([#554](https://github.com/aave/aave-utilities/issues/554)) + ([7510d22](https://github.com/aave/aave-utilities/commit/7510d227dd358f3420d7788a9feaaa72f09e8e9e)) ## 1.18.3 (2023-08-18) **Note:** Version bump only for package @aave/contract-helpers - - - - ## 1.18.2 (2023-07-14) - ### Bug Fixes -* remove commented imports ([#552](https://github.com/aave/aave-utilities/issues/552)) ([91a2772](https://github.com/aave/aave-utilities/commit/91a2772477ce2d9a809114dcbe2535c234603c38)) - - - - +- remove commented imports + ([#552](https://github.com/aave/aave-utilities/issues/552)) + ([91a2772](https://github.com/aave/aave-utilities/commit/91a2772477ce2d9a809114dcbe2535c234603c38)) ## 1.18.1 (2023-07-12) **Note:** Version bump only for package @aave/contract-helpers - - - - # 1.18.0 (2023-07-12) - ### Features -* debt switch ([#548](https://github.com/aave/aave-utilities/issues/548)) ([8c943d7](https://github.com/aave/aave-utilities/commit/8c943d78eda94f6e330e343d3cded9286ac2079c)) - - - - +- debt switch ([#548](https://github.com/aave/aave-utilities/issues/548)) + ([8c943d7](https://github.com/aave/aave-utilities/commit/8c943d78eda94f6e330e343d3cded9286ac2079c)) ## 1.17.8 (2023-06-22) **Note:** Version bump only for package @aave/contract-helpers - - - - ## 1.17.7 (2023-06-16) **Note:** Version bump only for package @aave/contract-helpers - - - - ## 1.17.6 (2023-06-01) - ### Bug Fixes -* gas limits ([#543](https://github.com/aave/aave-utilities/issues/543)) ([cd4e533](https://github.com/aave/aave-utilities/commit/cd4e533c8c469306abaf4a9c1b0a1066b59cf875)) - - - - +- gas limits ([#543](https://github.com/aave/aave-utilities/issues/543)) + ([cd4e533](https://github.com/aave/aave-utilities/commit/cd4e533c8c469306abaf4a9c1b0a1066b59cf875)) ## 1.17.5 (2023-05-23) **Note:** Version bump only for package @aave/contract-helpers - - - - ## 1.17.4 (2023-05-05) **Note:** Version bump only for package @aave/contract-helpers - - - - ## 1.17.3 (2023-05-02) - ### Bug Fixes -* decimal formatting in credit delegation approval checks ([#538](https://github.com/aave/aave-utilities/issues/538)) ([27eef6b](https://github.com/aave/aave-utilities/commit/27eef6b182f671fc9b4eb13cbd3712be17949c5c)) - - - - +- decimal formatting in credit delegation approval checks + ([#538](https://github.com/aave/aave-utilities/issues/538)) + ([27eef6b](https://github.com/aave/aave-utilities/commit/27eef6b182f671fc9b4eb13cbd3712be17949c5c)) ## 1.17.2 (2023-05-01) - ### Bug Fixes -* integer rounding for migration credit delegation approval buffer ([#537](https://github.com/aave/aave-utilities/issues/537)) ([3eef29e](https://github.com/aave/aave-utilities/commit/3eef29eb2e79bce2e1289c951fa00628bc5d4868)) - - - - +- integer rounding for migration credit delegation approval buffer + ([#537](https://github.com/aave/aave-utilities/issues/537)) + ([3eef29e](https://github.com/aave/aave-utilities/commit/3eef29eb2e79bce2e1289c951fa00628bc5d4868)) ## 1.17.1 (2023-05-01) **Note:** Version bump only for package @aave/contract-helpers - - - - # 1.17.0 (2023-04-27) - ### Features -* fixed available debt in isolated reserve in case that debt > ma… ([#532](https://github.com/aave/aave-utilities/issues/532)) ([7a2ea1e](https://github.com/aave/aave-utilities/commit/7a2ea1e94a26008247b4c4c045641291ff20261b)) - - - - +- fixed available debt in isolated reserve in case that debt > ma… + ([#532](https://github.com/aave/aave-utilities/issues/532)) + ([7a2ea1e](https://github.com/aave/aave-utilities/commit/7a2ea1e94a26008247b4c4c045641291ff20261b)) # 1.16.0 (2023-04-17) - ### Features -* update borrow methods SDK ([#527](https://github.com/aave/aave-utilities/issues/527)) ([da31b83](https://github.com/aave/aave-utilities/commit/da31b836dd7a85da0dbfe664b67612b1f261c7a7)) - - - - +- update borrow methods SDK + ([#527](https://github.com/aave/aave-utilities/issues/527)) + ([da31b83](https://github.com/aave/aave-utilities/commit/da31b836dd7a85da0dbfe664b67612b1f261c7a7)) # 1.15.0 (2023-04-12) - ### Features -* add Scroll Alpha chainId ([#528](https://github.com/aave/aave-utilities/issues/528)) ([3418c70](https://github.com/aave/aave-utilities/commit/3418c7087796397ed535fc7a3522445fb44f94c5)) - - - - +- add Scroll Alpha chainId + ([#528](https://github.com/aave/aave-utilities/issues/528)) + ([3418c70](https://github.com/aave/aave-utilities/commit/3418c7087796397ed535fc7a3522445fb44f94c5)) # 1.14.0 (2023-04-05) - ### Features -* tx bundle refactor ([#514](https://github.com/aave/aave-utilities/issues/514)) ([fc5c937](https://github.com/aave/aave-utilities/commit/fc5c9378b48a14a31df9fdbe435095a3e81390a2)), closes [#524](https://github.com/aave/aave-utilities/issues/524) [#504](https://github.com/aave/aave-utilities/issues/504) - - - - +- tx bundle refactor ([#514](https://github.com/aave/aave-utilities/issues/514)) + ([fc5c937](https://github.com/aave/aave-utilities/commit/fc5c9378b48a14a31df9fdbe435095a3e81390a2)), + closes [#524](https://github.com/aave/aave-utilities/issues/524) + [#504](https://github.com/aave/aave-utilities/issues/504) # Change Log @@ -200,10 +137,6 @@ All notable changes to this project will be documented in this file. See **Note:** Version bump only for package @aave/contract-helpers - - - - ## 1.13.6 (2023-03-03) **Note:** Version bump only for package @aave/contract-helpers diff --git a/packages/contract-helpers/src/commons/types.ts b/packages/contract-helpers/src/commons/types.ts index 141ba33d..0c12eff7 100644 --- a/packages/contract-helpers/src/commons/types.ts +++ b/packages/contract-helpers/src/commons/types.ts @@ -1,5 +1,10 @@ import { BigNumber, BytesLike, PopulatedTransaction } from 'ethers'; -import { LPBorrowParamsType } from '../v3-pool-contract/lendingPoolTypes'; +import { + LPBorrowParamsType, + LPRepayParamsType, + LPRepayWithATokensType, + LPSignedRepayParamsType, +} from '../v3-pool-contract/lendingPoolTypes'; export type tEthereumAddress = string; export type ENS = string; // something.eth @@ -115,6 +120,8 @@ export enum ProtocolAction { liquidationCall = 'liquidationCall', liquidationFlash = 'liquidationFlash', repay = 'repay', + repayETH = 'repayETH', + repayWithATokens = 'repayWithATokens', swapCollateral = 'swapCollateral', repayCollateral = 'repayCollateral', withdrawETH = 'withdrawETH', @@ -344,4 +351,41 @@ export type BorrowTxBuilder = { useOptimizedPath, encodedTxData, }: LPBorrowParamsType) => PopulatedTransaction; + encodeBorrowParams: ({ + reserve, + amount, + interestRateMode, + referralCode, + }: Omit) => Promise; +}; + +export type RepayTxBuilder = { + generateTxData: (params: LPRepayParamsType) => PopulatedTransaction; + generateSignedTxData: ( + params: LPSignedRepayParamsType, + ) => PopulatedTransaction; + encodeRepayParams: ({ + reserve, + amount, + interestRateMode, + }: Omit) => Promise; + encodeRepayWithPermitParams: ({ + reserve, + amount, + interestRateMode, + deadline, + signature, + }: Pick< + LPSignedRepayParamsType, + 'reserve' | 'amount' | 'interestRateMode' | 'signature' | 'deadline' + >) => Promise<[string, string, string]>; +}; + +export type RepayWithATokensTxBuilder = { + generateTxData: (params: LPRepayWithATokensType) => PopulatedTransaction; + encodeRepayWithATokensParams: ({ + reserve, + amount, + rateMode, + }: Omit) => Promise; }; diff --git a/packages/contract-helpers/src/commons/utils.ts b/packages/contract-helpers/src/commons/utils.ts index 352e59e9..e711ac9b 100644 --- a/packages/contract-helpers/src/commons/utils.ts +++ b/packages/contract-helpers/src/commons/utils.ts @@ -78,6 +78,10 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '300000', recommended: '300000', }, + [ProtocolAction.repayETH]: { + limit: '350000', + recommended: '300000', + }, [ProtocolAction.borrowETH]: { limit: '450000', recommended: '450000', @@ -106,6 +110,10 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '350000', recommended: '350000', }, + [ProtocolAction.stake]: { + limit: '350000', + recommended: '350000', + }, [ProtocolAction.stakeWithPermit]: { limit: '400000', recommended: '400000', @@ -114,10 +122,6 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '125000', recommended: '125000', }, - [ProtocolAction.stake]: { - limit: '395000', - recommended: '395000', - }, [ProtocolAction.claimRewards]: { limit: '275000', recommended: '275000', @@ -126,6 +130,10 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '138000', recommended: '138000', }, + [ProtocolAction.repayWithATokens]: { + limit: '300000', + recommended: '300000', + }, [ProtocolAction.withdrawAndSwitch]: { limit: '1000000', recommended: '1000000', diff --git a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts index 6ae6a64d..e3a19f88 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts @@ -1,10 +1,11 @@ -import { providers, PopulatedTransaction, BigNumber } from 'ethers'; +import { providers, PopulatedTransaction, BigNumber, constants } from 'ethers'; import BaseService from '../commons/BaseService'; import { BorrowTxBuilder, InterestRate, LendingPoolMarketConfig, ProtocolAction, + RepayTxBuilder, tEthereumAddress, } from '../commons/types'; import { @@ -45,6 +46,8 @@ export type DepositTxBuilder = { export interface LendingPoolBundleInterface { depositTxBuilder: DepositTxBuilder; + borrowTxBuilder: Pick; + repayTxBuilder: Pick; } export class LendingPoolBundle @@ -64,7 +67,8 @@ export class LendingPoolBundle readonly wethGatewayAddress: tEthereumAddress; depositTxBuilder: DepositTxBuilder; - borrowTxBuilder: Omit; + borrowTxBuilder: Pick; + repayTxBuilder: Pick; constructor( provider: providers.Provider, @@ -191,5 +195,41 @@ export class LendingPoolBundle return actionTx; }, }; + this.repayTxBuilder = { + generateTxData: ({ + user, + reserve, + onBehalfOf, + interestRateMode, + amount, + }) => { + const actionTx: PopulatedTransaction = {}; + if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { + return this.wethGatewayService.generateRepayEthTxData({ + lendingPool: this.lendingPoolAddress, + user, + amount, + interestRateMode, + onBehalfOf, + }); + } + + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const txData = this.contractInterface.encodeFunctionData('repay', [ + reserve, + amount === '-1' ? constants.MaxUint256.toString() : amount, + numericRateMode, + onBehalfOf ?? user, + ]); + actionTx.to = this.lendingPoolAddress; + actionTx.from = user; + actionTx.data = txData; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repay].recommended, + ); + return actionTx; + }, + }; } } diff --git a/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts b/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts index 3c7e4466..104a41ef 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts @@ -237,4 +237,87 @@ describe('LendingPoolBundle', () => { ); }); }); + + describe('RepayTxBuilder', () => { + const config = { + LENDING_POOL, + WETH_GATEWAY, + }; + + const instance = new LendingPoolBundle(provider, config); + + it('generates repay tx data with generateTxData with variable debt', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + }); + + const differentParamsSameResult = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + + expect(result.to).toEqual(LENDING_POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + expect(differentParamsSameResult.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx data with generateTxData with stable debt', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Stable, + }); + + expect(result.to).toEqual(LENDING_POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx for WETHGateway data with generateTxData with variable debt', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: API_ETH_MOCK_ADDRESS.toLowerCase(), + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + }); + expect(result.to).toEqual(WETH_GATEWAY); + expect(result.from).toEqual(USER); + expect(result.value).toEqual(BigNumber.from('1')); + expect(result.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx data with generateTxData with variable debt and max repay', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '-1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + }); + + expect(result.to).toEqual(LENDING_POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + }); }); diff --git a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts index 171c353c..767957aa 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts @@ -1,10 +1,12 @@ import { Signature, splitSignature } from '@ethersproject/bytes'; -import { BigNumber, PopulatedTransaction, providers } from 'ethers'; +import { BigNumber, PopulatedTransaction, constants, providers } from 'ethers'; import BaseService from '../commons/BaseService'; import { BorrowTxBuilder, InterestRate, ProtocolAction, + RepayTxBuilder, + RepayWithATokensTxBuilder, tEthereumAddress, } from '../commons/types'; import { @@ -32,10 +34,6 @@ import { import { IPool, IPoolInterface } from '../v3-pool-contract/typechain/IPool'; import { IPool__factory } from '../v3-pool-contract/typechain/IPool__factory'; import { L2Pool, L2PoolInterface } from '../v3-pool-rollups'; -import { - LPSupplyWithPermitType as LPSupplyWithPermitTypeL2, - LPBorrowParamsType as LPBorrowParamsTypeL2, -} from '../v3-pool-rollups/poolTypes'; import { WETHGatewayInterface, WETHGatewayService, @@ -62,6 +60,23 @@ export type SupplyTxBuilder = { encodedTxData, }: LPSignedSupplyParamsType) => PopulatedTransaction; getApprovedAmount: ({ user, token }: TokenOwner) => Promise; + encodeSupplyParams: ({ + reserve, + amount, + referralCode, + }: Pick< + LPSupplyParamsType, + 'reserve' | 'amount' | 'referralCode' + >) => Promise; + encodeSupplyWithPermitParams: ({ + reserve, + amount, + referralCode, + signature, + }: Pick< + LPSignedSupplyParamsType, + 'reserve' | 'amount' | 'referralCode' | 'signature' | 'deadline' + >) => Promise<[string, string, string]>; }; export interface PoolBundleInterface { @@ -96,6 +111,8 @@ export class PoolBundle supplyTxBuilder: SupplyTxBuilder; borrowTxBuilder: BorrowTxBuilder; + repayTxBuilder: RepayTxBuilder; + repayWithATokensTxBuilder: RepayWithATokensTxBuilder; constructor( provider: providers.Provider, @@ -152,6 +169,13 @@ export class PoolBundle useOptimizedPath, encodedTxData, }: LPSupplyParamsType): PopulatedTransaction => { + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedSupplyTxData({ + encodedTxData, + user, + }); + } + let actionTx: PopulatedTransaction = {}; const onBehalfOfParam = onBehalfOf ?? user; const referralCodeParam = referralCode ?? '0'; @@ -163,22 +187,6 @@ export class PoolBundle onBehalfOf: onBehalfOfParam, referralCode: referralCodeParam, }); - } else if (useOptimizedPath) { - if (encodedTxData) { - actionTx = this.l2PoolService.generateEncodedSupplyTxData({ - encodedTxData, - user, - }); - } else { - const args: LPSupplyParamsType = { - user, - reserve, - amount, - onBehalfOf: onBehalfOfParam, - referralCode: referralCodeParam, - }; - actionTx = this.l2PoolService.generateSupplyTxData(args); - } } else { const txData = this.contractInterface.encodeFunctionData('supply', [ reserve, @@ -207,58 +215,69 @@ export class PoolBundle deadline, encodedTxData, }: LPSignedSupplyParamsType): PopulatedTransaction => { + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedSupplyWithPermitTxData({ + encodedTxData, + user, + signature, + }); + } + const decomposedSignature: Signature = splitSignature(signature); - let populatedTx: PopulatedTransaction = {}; + const populatedTx: PopulatedTransaction = {}; const onBehalfOfParam = onBehalfOf ?? user; const referralCodeParam = referralCode ?? '0'; - if (useOptimizedPath) { - if (encodedTxData) { - populatedTx = - this.l2PoolService.generateEncodedSupplyWithPermitTxData({ - encodedTxData, - user, - signature, - }); - } else { - const args: LPSupplyWithPermitTypeL2 = { - user, - reserve, - amount, - referralCode: referralCodeParam, - onBehalfOf: onBehalfOfParam, - permitR: decomposedSignature.r, - permitS: decomposedSignature.s, - permitV: decomposedSignature.v, - deadline: Number(deadline), - }; - populatedTx = - this.l2PoolService.generateSupplyWithPermitTxData(args); - } - } else { - const txData = this.contractInterface.encodeFunctionData( - 'supplyWithPermit', - [ - reserve, - amount, - onBehalfOfParam, - referralCodeParam, - deadline, - decomposedSignature.v, - decomposedSignature.r, - decomposedSignature.s, - ], - ); - populatedTx.to = this.poolAddress; - populatedTx.from = user; - populatedTx.data = txData; - populatedTx.gasLimit = BigNumber.from( - gasLimitRecommendations[ProtocolAction.supplyWithPermit] - .recommended, - ); - } + const txData = this.contractInterface.encodeFunctionData( + 'supplyWithPermit', + [ + reserve, + amount, + onBehalfOfParam, + referralCodeParam, + deadline, + decomposedSignature.v, + decomposedSignature.r, + decomposedSignature.s, + ], + ); + populatedTx.to = this.poolAddress; + populatedTx.from = user; + populatedTx.data = txData; + populatedTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.supplyWithPermit].recommended, + ); return populatedTx; }, + encodeSupplyParams: async ({ + reserve, + amount, + referralCode, + }): Promise => { + return this.l2PoolService + .getEncoder() + .encodeSupplyParams(reserve, amount, referralCode ?? '0'); + }, + encodeSupplyWithPermitParams: async ({ + reserve, + amount, + signature, + deadline, + referralCode, + }): Promise<[string, string, string]> => { + const decomposedSignature: Signature = splitSignature(signature); + return this.l2PoolService + .getEncoder() + .encodeSupplyWithPermitParams( + reserve, + amount, + referralCode ?? '0', + deadline, + decomposedSignature.v, + decomposedSignature.r, + decomposedSignature.s, + ); + }, }; this.borrowTxBuilder = { @@ -273,6 +292,13 @@ export class PoolBundle useOptimizedPath, encodedTxData, }: LPBorrowParamsType): PopulatedTransaction => { + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedBorrowTxData({ + encodedTxData, + user, + }); + } + let actionTx: PopulatedTransaction = {}; const referralCodeParam = referralCode ?? '0'; const onBehalfOfParam = onBehalfOf ?? user; @@ -293,23 +319,6 @@ export class PoolBundle interestRateMode, referralCode: referralCodeParam, }); - } else if (useOptimizedPath) { - if (encodedTxData) { - actionTx = this.l2PoolService.generateEncodedBorrowTxData({ - encodedTxData, - user, - }); - } else { - const args: LPBorrowParamsTypeL2 = { - user, - reserve, - amount, - onBehalfOf: onBehalfOfParam, - referralCode: referralCodeParam, - numericRateMode, - }; - actionTx = this.l2PoolService.generateBorrowTxData(args); - } } else { const txData = this.contractInterface.encodeFunctionData('borrow', [ reserve, @@ -328,6 +337,204 @@ export class PoolBundle return actionTx; }, + encodeBorrowParams: async ({ + reserve, + amount, + interestRateMode, + referralCode, + }): Promise => { + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + + return this.l2PoolService + .getEncoder() + .encodeBorrowParams( + reserve, + amount, + numericRateMode, + referralCode ?? '0', + ); + }, + }; + + this.repayTxBuilder = { + generateTxData: ({ + user, + reserve, + amount, + interestRateMode, + onBehalfOf, + useOptimizedPath, + encodedTxData, + }) => { + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const onBehalfOfParam = onBehalfOf ?? user; + if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { + return this.wethGatewayService.generateRepayEthTxData({ + lendingPool: this.poolAddress, + user, + amount, + interestRateMode, + onBehalfOf: onBehalfOfParam, + }); + } + + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedRepayTxData({ + encodedTxData, + user, + }); + } + + const actionTx: PopulatedTransaction = {}; + const txData = this.contractInterface.encodeFunctionData('repay', [ + reserve, + amount === '-1' ? constants.MaxUint256.toString() : amount, + numericRateMode, + onBehalfOfParam, + ]); + actionTx.to = this.poolAddress; + actionTx.from = user; + actionTx.data = txData; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repay].recommended, + ); + return actionTx; + }, + generateSignedTxData: ({ + onBehalfOf, + signature, + deadline, + user, + reserve, + amount, + interestRateMode, + useOptimizedPath, + encodedTxData, + }) => { + const decomposedSignature: Signature = splitSignature(signature); + const populatedTx: PopulatedTransaction = {}; + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const onBehalfOfParam = onBehalfOf ?? user; + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedRepayWithPermitTxData({ + encodedTxData, + user, + signature, + }); + } + + const txData = this.contractInterface.encodeFunctionData( + 'repayWithPermit', + [ + reserve, + amount === '-1' ? constants.MaxUint256.toString() : amount, + numericRateMode, + onBehalfOfParam, + deadline, + decomposedSignature.v, + decomposedSignature.r, + decomposedSignature.s, + ], + ); + populatedTx.to = this.poolAddress; + populatedTx.from = user; + populatedTx.data = txData; + populatedTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayWithPermit].recommended, + ); + return populatedTx; + }, + encodeRepayParams: async ({ reserve, amount, interestRateMode }) => { + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + + const repayAmount = + amount === '-1' ? constants.MaxUint256.toString() : amount; + + return this.l2PoolService + .getEncoder() + .encodeRepayParams(reserve, repayAmount, numericRateMode); + }, + encodeRepayWithPermitParams: async ({ + reserve, + amount, + interestRateMode, + signature, + deadline, + }) => { + const decomposedSignature: Signature = splitSignature(signature); + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const repayAmount = + amount === '-1' ? constants.MaxUint256.toString() : amount; + + return this.l2PoolService + .getEncoder() + .encodeRepayWithPermitParams( + reserve, + repayAmount, + numericRateMode, + deadline, + decomposedSignature.v, + decomposedSignature.r, + decomposedSignature.s, + ); + }, + }; + + this.repayWithATokensTxBuilder = { + generateTxData: ({ + rateMode, + user, + amount, + reserve, + useOptimizedPath, + encodedTxData, + }) => { + const actionTx: PopulatedTransaction = {}; + const numericRateMode = rateMode === InterestRate.Variable ? 2 : 1; + if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { + throw new Error( + 'Can not repay with aTokens with eth. Should be WETH instead', + ); + } + + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedRepayWithATokensTxData({ + encodedTxData, + user, + }); + } + + const txData = this.contractInterface.encodeFunctionData( + 'repayWithATokens', + [ + reserve, + amount === '-1' ? constants.MaxUint256.toString() : amount, + numericRateMode, + ], + ); + actionTx.to = this.poolAddress; + actionTx.from = user; + actionTx.data = txData; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayWithATokens].recommended, + ); + + return actionTx; + }, + encodeRepayWithATokensParams: async ({ reserve, amount, rateMode }) => { + const numericRateMode = rateMode === InterestRate.Variable ? 2 : 1; + const repayAmount = + amount === '-1' ? constants.MaxUint256.toString() : amount; + + return this.l2PoolService + .getEncoder() + .encodeRepayWithATokensParams(reserve, repayAmount, numericRateMode); + }, }; } } diff --git a/packages/contract-helpers/src/v3-pool-contract-bundle/pool-bundle.test.ts b/packages/contract-helpers/src/v3-pool-contract-bundle/pool-bundle.test.ts index 5473e791..2224aae3 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/pool-bundle.test.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/pool-bundle.test.ts @@ -1,6 +1,8 @@ import { BigNumber, providers } from 'ethers'; import { InterestRate } from '../commons/types'; import { API_ETH_MOCK_ADDRESS } from '../commons/utils'; +import { L2Encoder } from '../v3-pool-rollups/typechain/L2Encoder'; +import { L2Encoder__factory } from '../v3-pool-rollups/typechain/L2Encoder__factory'; import { PoolBundle } from './index'; jest.mock('../commons/gasStation', () => { @@ -28,6 +30,31 @@ describe('PoolBundle', () => { '0x0000000000000000000000000000000000000004'; const SWAP_COLLATERAL_ADAPTER = '0x0000000000000000000000000000000000000005'; const L2_ENCODER = '0x0000000000000000000000000000000000000020'; + + const encodedArg = + '0x0000000000000000000000000000000000000000000000000000006d6168616d'; + const permitR = + '0x0000000000000000000000000000000000000000000000000000000000000000'; + const permitS = + '0x0000000000000000000000000000000000000000000000000000000000000000'; + + const encoderSpy = jest.spyOn(L2Encoder__factory, 'connect').mockReturnValue({ + encodeSupplyParams: async () => Promise.resolve(encodedArg), + encodeSupplyWithPermitParams: async () => + Promise.resolve([encodedArg, permitR, permitS]), + encodeWithdrawParams: async () => Promise.resolve(encodedArg), + encodeBorrowParams: async () => Promise.resolve(encodedArg), + encodeRepayParams: async () => Promise.resolve(encodedArg), + encodeRepayWithPermitParams: async () => + Promise.resolve([encodedArg, permitR, permitS]), + encodeRepayWithATokensParams: async () => Promise.resolve(encodedArg), + encodeSwapBorrowRateMode: async () => Promise.resolve(encodedArg), + encodeSetUserUseReserveAsCollateral: async () => + Promise.resolve(encodedArg), + encodeLiquidationCall: async () => + Promise.resolve([encodedArg, encodedArg]), + } as unknown as L2Encoder); + describe('Initialization', () => { const config = { POOL, @@ -326,6 +353,46 @@ describe('PoolBundle', () => { '0x680dd47c0000000000000000000000000000000000000000000000000000006d6168616d532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', ); }); + + it('encodes supply params for L2', async () => { + await instance.supplyTxBuilder.encodeSupplyParams({ + reserve: TOKEN, + amount: '1', + referralCode: '1', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); + + it('encodes supply params for L2 without referral code', async () => { + await instance.supplyTxBuilder.encodeSupplyParams({ + reserve: TOKEN, + amount: '1', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); + + it('encodes supply with permit parmas for L2', async () => { + await instance.supplyTxBuilder.encodeSupplyWithPermitParams({ + reserve: TOKEN, + amount: '1', + referralCode: '1', + deadline: '10000', + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); + + it('encodes supply with permit parmas for L2 without referral code', async () => { + await instance.supplyTxBuilder.encodeSupplyWithPermitParams({ + reserve: TOKEN, + amount: '1', + deadline: '10000', + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); }); describe('BorrowTxBuilder', () => { @@ -523,5 +590,387 @@ describe('PoolBundle', () => { // Will be identical to variable, since tx data is pre-encoded, rate mode has no effect expect(resultStable.data).toEqual(txData); }); + + it('encodes variable borrow params for L2', async () => { + await instance.borrowTxBuilder.encodeBorrowParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + referralCode: '1', + }); + + expect(encoderSpy).toHaveBeenCalled(); + + await instance.borrowTxBuilder.encodeBorrowParams({ + reserve: TOKEN, + amount: '-1', + interestRateMode: InterestRate.Stable, + referralCode: '1', + }); + + expect(encoderSpy).toHaveBeenCalled(); + + await instance.borrowTxBuilder.encodeBorrowParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + + expect(encoderSpy).toHaveBeenCalled(); + }); + }); + + describe('RepayTxBuilder', () => { + const config = { + POOL, + FLASH_LIQUIDATION_ADAPTER, + REPAY_WITH_COLLATERAL_ADAPTER, + SWAP_COLLATERAL_ADAPTER, + WETH_GATEWAY, + L2_ENCODER, + }; + + const instance = new PoolBundle(provider, config); + + it('generates repay tx data with generateTxData with variable debt', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + onBehalfOf: USER, + }); + + const differentParamsSameResult = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + expect(differentParamsSameResult.to).toEqual(POOL); + expect(differentParamsSameResult.from).toEqual(USER); + expect(differentParamsSameResult.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx data with generateTxData with variable debt and max input', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '-1', + interestRateMode: InterestRate.Variable, + onBehalfOf: USER, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx data with generateTxData with stable debt', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Stable, + onBehalfOf: USER, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x573ade810000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx for WETHGateway data with generateTxData', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: API_ETH_MOCK_ADDRESS.toLowerCase(), + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + }); + + const differentParamsSameResult = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: API_ETH_MOCK_ADDRESS.toLowerCase(), + amount: '1', + interestRateMode: InterestRate.Variable, + }); + expect(result.to).toEqual(WETH_GATEWAY); + expect(result.from).toEqual(USER); + expect(result.value).toEqual(BigNumber.from('1')); + expect(result.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + expect(differentParamsSameResult.to).toEqual(WETH_GATEWAY); + expect(differentParamsSameResult.from).toEqual(USER); + expect(differentParamsSameResult.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + + it('generates repay tx data with generateTxData and L2POOL with encoded txData', () => { + const result = instance.repayTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + useOptimizedPath: true, + encodedTxData: + '0x0000000000000000000000000000000000000000000000000000006d6168616d', + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x563dd6130000000000000000000000000000000000000000000000000000006d6168616d', + ); + }); + + it('generates signed repay tx data with generateSignedTxData and L2POOL with encoded txData', () => { + const result = instance.repayTxBuilder.generateSignedTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + deadline: '10000', + useOptimizedPath: true, + encodedTxData: + '0x0000000000000000000000000000000000000000000000000000006d6168616d', + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x94b576de0000000000000000000000000000000000000000000000000000006d6168616d532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + ); + }); + + it('generates signed tx with generateSignedTxData with variable debt', () => { + const result = instance.repayTxBuilder.generateSignedTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + deadline: '10000', + }); + + const differentParamsSameResult = + instance.repayTxBuilder.generateSignedTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + deadline: '10000', + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + ); + expect(differentParamsSameResult.to).toEqual(POOL); + expect(differentParamsSameResult.from).toEqual(USER); + expect(differentParamsSameResult.data).toEqual( + '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + ); + }); + + it('generates signed tx with generateSignedTxData with variable debt and max input', () => { + const result = instance.repayTxBuilder.generateSignedTxData({ + user: USER, + reserve: TOKEN, + amount: '-1', + onBehalfOf: USER, + interestRateMode: InterestRate.Variable, + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + deadline: '10000', + }); + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0xee3e210b0000000000000000000000000000000000000000000000000000000000000004ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + ); + }); + + it('generates signed tx with generateSignedTxData with stable debt', () => { + const result = instance.repayTxBuilder.generateSignedTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + onBehalfOf: USER, + interestRateMode: InterestRate.Stable, + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + deadline: '10000', + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + ); + }); + + it('encodes repay params for L2', async () => { + await instance.repayTxBuilder.encodeRepayParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + expect(encoderSpy).toHaveBeenCalled(); + + await instance.repayTxBuilder.encodeRepayParams({ + reserve: TOKEN, + amount: '-1', + interestRateMode: InterestRate.Stable, + }); + expect(encoderSpy).toHaveBeenCalled(); + + await instance.repayTxBuilder.encodeRepayWithPermitParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + deadline: '10000', + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + }); + expect(encoderSpy).toHaveBeenCalled(); + + await instance.repayTxBuilder.encodeRepayWithPermitParams({ + reserve: TOKEN, + amount: '-1', + interestRateMode: InterestRate.Stable, + deadline: '10000', + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); + }); + + describe('RepayWithATokenTxBuilder', () => { + const config = { + POOL, + FLASH_LIQUIDATION_ADAPTER, + REPAY_WITH_COLLATERAL_ADAPTER, + SWAP_COLLATERAL_ADAPTER, + WETH_GATEWAY, + L2_ENCODER, + }; + + const instance = new PoolBundle(provider, config); + + it('generates repayWithAToken tx data with generateTxData with variable rate', () => { + const result = instance.repayWithATokensTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + rateMode: InterestRate.Variable, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x2dad97d4000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002', + ); + }); + + it('generates repayWithAToken tx data with generateTxData with variable rate and max input', () => { + const result = instance.repayWithATokensTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '-1', + rateMode: InterestRate.Variable, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x2dad97d40000000000000000000000000000000000000000000000000000000000000004ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000002', + ); + }); + + it('generates repayWithAToken tx data with generateTxData with stable debt', () => { + const result = instance.repayWithATokensTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + rateMode: InterestRate.Stable, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0x2dad97d4000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001', + ); + }); + + it('should throw error if ETH mock address is passed', () => { + const generateData = () => + instance.repayWithATokensTxBuilder.generateTxData({ + user: USER, + reserve: API_ETH_MOCK_ADDRESS, + amount: '1', + rateMode: InterestRate.Variable, + }); + + expect(generateData).toThrow(); + }); + + it('generates repay tx data with generateTxData and L2POOL with encoded txData', () => { + const result = instance.repayWithATokensTxBuilder.generateTxData({ + user: USER, + reserve: TOKEN, + amount: '1', + rateMode: InterestRate.Variable, + useOptimizedPath: true, + encodedTxData: + '0x0000000000000000000000000000000000000000000000000000006d6168616d', + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0xdc7c0bff0000000000000000000000000000000000000000000000000000006d6168616d', + ); + }); + + it('encodes repay with aToken params for L2', async () => { + await instance.repayWithATokensTxBuilder.encodeRepayWithATokensParams({ + reserve: TOKEN, + amount: '-1', + rateMode: InterestRate.Variable, + }); + expect(encoderSpy).toHaveBeenCalled(); + + await instance.repayWithATokensTxBuilder.encodeRepayWithATokensParams({ + reserve: TOKEN, + amount: '1', + rateMode: InterestRate.Stable, + }); + expect(encoderSpy).toHaveBeenCalled(); + }); }); }); diff --git a/packages/contract-helpers/src/v3-pool-contract/lendingPoolTypes.ts b/packages/contract-helpers/src/v3-pool-contract/lendingPoolTypes.ts index 1058f7e4..42b948f5 100644 --- a/packages/contract-helpers/src/v3-pool-contract/lendingPoolTypes.ts +++ b/packages/contract-helpers/src/v3-pool-contract/lendingPoolTypes.ts @@ -50,6 +50,12 @@ export type LPRepayParamsType = { interestRateMode: InterestRate; onBehalfOf?: tEthereumAddress; useOptimizedPath?: boolean; + encodedTxData?: string; +}; + +export type LPSignedRepayParamsType = LPRepayParamsType & { + signature: string; + deadline: string; }; export type LPSwapBorrowRateMode = { @@ -147,6 +153,7 @@ export type LPRepayWithPermitParamsType = { signature: SignatureLike; useOptimizedPath?: boolean; deadline: string; + encodedTxData?: string; }; export type LPSignERC20ApprovalType = { @@ -167,6 +174,7 @@ export type LPRepayWithATokensType = { amount: string; rateMode: InterestRate; useOptimizedPath?: boolean; + encodedTxData?: string; }; export type LPReserveData = { diff --git a/packages/contract-helpers/src/v3-pool-rollups/index.ts b/packages/contract-helpers/src/v3-pool-rollups/index.ts index 95428671..95e6d1f1 100644 --- a/packages/contract-helpers/src/v3-pool-rollups/index.ts +++ b/packages/contract-helpers/src/v3-pool-rollups/index.ts @@ -53,6 +53,19 @@ export interface L2PoolInterface { user: string; signature: string; }) => PopulatedTransaction; + generateEncodedRepayTxData: (args: { + encodedTxData: string; + user: string; + }) => PopulatedTransaction; + generateEncodedRepayWithPermitTxData: (args: { + encodedTxData: string; + user: string; + signature: string; + }) => PopulatedTransaction; + generateEncodedRepayWithATokensTxData: (args: { + encodedTxData: string; + user: string; + }) => PopulatedTransaction; supplyWithPermit: ( args: LPSupplyWithPermitType, txs: EthereumTransactionTypeExtended[], @@ -126,6 +139,22 @@ export class L2Pool extends BaseService implements L2PoolInterface { signature: string; }) => PopulatedTransaction; + generateEncodedRepayTxData: (args: { + encodedTxData: string; + user: string; + }) => PopulatedTransaction; + + generateEncodedRepayWithPermitTxData: (args: { + encodedTxData: string; + user: string; + signature: string; + }) => PopulatedTransaction; + + generateEncodedRepayWithATokensTxData: (args: { + encodedTxData: string; + user: string; + }) => PopulatedTransaction; + constructor(provider: providers.Provider, l2PoolConfig?: L2PoolConfigType) { super(provider, IL2Pool__factory); @@ -283,6 +312,58 @@ export class L2Pool extends BaseService implements L2PoolInterface { ); return actionTx; }; + + this.generateEncodedRepayTxData = ({ encodedTxData, user }) => { + const actionTx: PopulatedTransaction = {}; + const txData = this.l2PoolContractInstance.encodeFunctionData('repay', [ + encodedTxData, + ]); + + actionTx.to = this.l2PoolAddress; + actionTx.data = txData; + actionTx.from = user; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repay].limit, + ); + return actionTx; + }; + + this.generateEncodedRepayWithPermitTxData = ({ + encodedTxData, + user, + signature, + }) => { + const actionTx: PopulatedTransaction = {}; + const decomposedSignature: Signature = splitSignature(signature); + const txData = this.l2PoolContractInstance.encodeFunctionData( + 'repayWithPermit', + [encodedTxData, decomposedSignature.r, decomposedSignature.s], + ); + + actionTx.to = this.l2PoolAddress; + actionTx.data = txData; + actionTx.from = user; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayWithPermit].limit, + ); + return actionTx; + }; + + this.generateEncodedRepayWithATokensTxData = ({ encodedTxData, user }) => { + const actionTx: PopulatedTransaction = {}; + const txData = this.l2PoolContractInstance.encodeFunctionData( + 'repayWithATokens', + [encodedTxData], + ); + + actionTx.to = this.l2PoolAddress; + actionTx.data = txData; + actionTx.from = user; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayWithATokens].limit, + ); + return actionTx; + }; } @L2PValidator diff --git a/packages/contract-helpers/src/v3-pool-rollups/pool-rollups.test.ts b/packages/contract-helpers/src/v3-pool-rollups/pool-rollups.test.ts index 8896694c..5ef173d7 100644 --- a/packages/contract-helpers/src/v3-pool-rollups/pool-rollups.test.ts +++ b/packages/contract-helpers/src/v3-pool-rollups/pool-rollups.test.ts @@ -287,6 +287,84 @@ describe('L2Pool', () => { expect(txData.from).toEqual(user); expect(txData.data).toEqual(encodedBorrowTxData); }); + + it('Generate repay tx data with encoded parameter', async () => { + const instance: L2PoolInterface = new L2Pool(provider, config); + + const encoder = instance.getEncoder(); + + const encodedTxData = await encoder.encodeRepayParams(reserve, 1, '0'); + + const txData = instance.generateEncodedSupplyTxData({ + user, + encodedTxData, + }); + + const encodedRepayTxData = + '0xf7a738400000000000000000000000000000000000000000000000000000006d6168616d'; + expect(txData.to).toEqual(l2PoolAddress); + expect(txData.from).toEqual(user); + expect(txData.data).toEqual(encodedRepayTxData); + }); + + it('Generate repayWithATokens tx data with encoded parameter', async () => { + const instance: L2PoolInterface = new L2Pool(provider, config); + + const encoder = instance.getEncoder(); + + const encodedTxData = await encoder.encodeRepayWithATokensParams( + reserve, + 1, + '0', + ); + + const txData = instance.generateEncodedRepayWithATokensTxData({ + user, + encodedTxData, + }); + + const encodedRepayTxData = + '0xdc7c0bff0000000000000000000000000000000000000000000000000000006d6168616d'; + expect(txData.to).toEqual(l2PoolAddress); + expect(txData.from).toEqual(user); + expect(txData.data).toEqual(encodedRepayTxData); + }); + + it('Generate repayWithPermit tx data with encoded parameter', async () => { + const instance: L2PoolInterface = new L2Pool(provider, config); + + const encoder = instance.getEncoder(); + + const signature = + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c'; + const decomposedSignature = splitSignature(signature); + expect(instance).toBeDefined(); + expect(encoder).toBeDefined(); + + const encodedTxData = await encoder.encodeRepayWithPermitParams( + reserve, + 1, + '0', + '10000', + decomposedSignature.v, + decomposedSignature.r, + decomposedSignature.s, + ); + + expect(encodedTxData).toBeDefined(); + + const txData = instance.generateEncodedRepayWithPermitTxData({ + user, + encodedTxData: encodedTxData[0], + signature, + }); + + const encodedRepayWithPermitTxData = + '0x94b576de0000000000000000000000000000000000000000000000000000006d6168616d532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4'; + expect(txData.to).toEqual(l2PoolAddress); + expect(txData.from).toEqual(user); + expect(txData.data).toEqual(encodedRepayWithPermitTxData); + }); }); describe('getEncoder', () => { const instance: L2PoolInterface = new L2Pool(provider, config); diff --git a/packages/contract-helpers/src/v3-pool-rollups/poolTypes.ts b/packages/contract-helpers/src/v3-pool-rollups/poolTypes.ts index 7f93ea16..034eba55 100644 --- a/packages/contract-helpers/src/v3-pool-rollups/poolTypes.ts +++ b/packages/contract-helpers/src/v3-pool-rollups/poolTypes.ts @@ -28,6 +28,7 @@ export type LPRepayParamsType = { reserve: tEthereumAddress; amount: string; numericRateMode: number; + onBehalfOf?: string; }; export type LPSupplyWithPermitType = { @@ -51,6 +52,7 @@ export type LPRepayWithPermitParamsType = { permitV: BigNumberish; permitR: BytesLike; permitS: BytesLike; + onBehalfOf?: string; }; export type LPRepayWithATokensType = { diff --git a/packages/contract-helpers/src/wethgateway-contract/index.ts b/packages/contract-helpers/src/wethgateway-contract/index.ts index 72f7fcb6..d3c262c9 100644 --- a/packages/contract-helpers/src/wethgateway-contract/index.ts +++ b/packages/contract-helpers/src/wethgateway-contract/index.ts @@ -62,6 +62,7 @@ export interface WETHGatewayInterface { args: WETHDepositParamsType, ) => PopulatedTransaction; generateBorrowEthTxData: (args: WETHBorrowParamsType) => PopulatedTransaction; + generateRepayEthTxData: (args: WETHRepayParamsType) => PopulatedTransaction; depositETH: ( args: WETHDepositParamsType, ) => EthereumTransactionTypeExtended[]; @@ -92,6 +93,8 @@ export class WETHGatewayService generateBorrowEthTxData: (args: WETHBorrowParamsType) => PopulatedTransaction; + generateRepayEthTxData: (args: WETHRepayParamsType) => PopulatedTransaction; + constructor( provider: providers.Provider, erc20Service: IERC20ServiceInterface, @@ -153,6 +156,33 @@ export class WETHGatewayService }; return actionTx; }; + + this.generateRepayEthTxData = ({ + interestRateMode, + lendingPool, + amount, + user, + onBehalfOf, + }) => { + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const txData = this.wethGatewayInstance.encodeFunctionData('repayETH', [ + lendingPool, + amount, + numericRateMode, + onBehalfOf ?? user, + ]); + const actionTx: PopulatedTransaction = { + data: txData, + to: this.wethGatewayAddress, + from: user, + value: BigNumber.from(amount), + gasLimit: BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayETH].limit, + ), + }; + return actionTx; + }; } @WETHValidator diff --git a/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts b/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts index 9bf43fef..5b6ef6db 100644 --- a/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts +++ b/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts @@ -113,6 +113,69 @@ describe('WethGatewayService', () => { ); }); }); + describe('generateRepayEthTxData', () => { + it('generates repayETH tx data with variable debt', () => { + const provider: providers.Provider = new providers.JsonRpcProvider(); + const erc20Service = new ERC20Service(provider); + const weth = new WETHGatewayService( + provider, + erc20Service, + wethGatewayAddress, + ); + const user = '0x0000000000000000000000000000000000000003'; + const txData = weth.generateRepayEthTxData({ + lendingPool, + user, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + + expect(txData.to).toEqual(wethGatewayAddress); + expect(txData.from).toEqual(user); + expect(txData.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + + const onBehalfOf = '0x0000000000000000000000000000000000000004'; + + const txDataUpdatedParams = weth.generateRepayEthTxData({ + lendingPool, + user, + amount: '1', + onBehalfOf, + interestRateMode: InterestRate.Variable, + }); + + expect(txDataUpdatedParams.to).toEqual(wethGatewayAddress); + expect(txDataUpdatedParams.from).toEqual(user); + expect(txDataUpdatedParams.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004', + ); + }); + + it('generates repayETH tx data with stable debt', () => { + const provider: providers.Provider = new providers.JsonRpcProvider(); + const erc20Service = new ERC20Service(provider); + const weth = new WETHGatewayService( + provider, + erc20Service, + wethGatewayAddress, + ); + const user = '0x0000000000000000000000000000000000000003'; + const txData = weth.generateRepayEthTxData({ + lendingPool, + user, + amount: '1', + interestRateMode: InterestRate.Stable, + }); + + expect(txData.to).toEqual(wethGatewayAddress); + expect(txData.from).toEqual(user); + expect(txData.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003', + ); + }); + }); describe('depositETH', () => { const user = '0x0000000000000000000000000000000000000003'; const onBehalfOf = '0x0000000000000000000000000000000000000004'; diff --git a/packages/math-utils/CHANGELOG.md b/packages/math-utils/CHANGELOG.md index b98edf1a..420d600b 100644 --- a/packages/math-utils/CHANGELOG.md +++ b/packages/math-utils/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +All notable changes to this project will be documented in this file. See +[Conventional Commits](https://conventionalcommits.org) for commit guidelines. # 1.21.0 (2023-10-04) @@ -16,180 +16,117 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline # 1.20.0 (2023-08-22) - ### Features -* withdraw and swap ([#555](https://github.com/aave/aave-utilities/issues/555)) ([ca6c230](https://github.com/aave/aave-utilities/commit/ca6c230f39536ba34313ba5ec8295267742216a3)) - - - - +- withdraw and swap ([#555](https://github.com/aave/aave-utilities/issues/555)) + ([ca6c230](https://github.com/aave/aave-utilities/commit/ca6c230f39536ba34313ba5ec8295267742216a3)) # 1.19.0 (2023-08-21) - ### Features -* add Scroll Sepolia and Scroll chainIds ([#554](https://github.com/aave/aave-utilities/issues/554)) ([7510d22](https://github.com/aave/aave-utilities/commit/7510d227dd358f3420d7788a9feaaa72f09e8e9e)) - - - - +- add Scroll Sepolia and Scroll chainIds + ([#554](https://github.com/aave/aave-utilities/issues/554)) + ([7510d22](https://github.com/aave/aave-utilities/commit/7510d227dd358f3420d7788a9feaaa72f09e8e9e)) ## 1.18.3 (2023-08-18) **Note:** Version bump only for package @aave/math-utils - - - - ## 1.18.2 (2023-07-14) - ### Bug Fixes -* remove commented imports ([#552](https://github.com/aave/aave-utilities/issues/552)) ([91a2772](https://github.com/aave/aave-utilities/commit/91a2772477ce2d9a809114dcbe2535c234603c38)) - - - - +- remove commented imports + ([#552](https://github.com/aave/aave-utilities/issues/552)) + ([91a2772](https://github.com/aave/aave-utilities/commit/91a2772477ce2d9a809114dcbe2535c234603c38)) ## 1.18.1 (2023-07-12) **Note:** Version bump only for package @aave/math-utils - - - - # 1.18.0 (2023-07-12) - ### Features -* debt switch ([#548](https://github.com/aave/aave-utilities/issues/548)) ([8c943d7](https://github.com/aave/aave-utilities/commit/8c943d78eda94f6e330e343d3cded9286ac2079c)) - - - - +- debt switch ([#548](https://github.com/aave/aave-utilities/issues/548)) + ([8c943d7](https://github.com/aave/aave-utilities/commit/8c943d78eda94f6e330e343d3cded9286ac2079c)) ## 1.17.8 (2023-06-22) **Note:** Version bump only for package @aave/math-utils - - - - ## 1.17.7 (2023-06-16) **Note:** Version bump only for package @aave/math-utils - - - - ## 1.17.6 (2023-06-01) - ### Bug Fixes -* gas limits ([#543](https://github.com/aave/aave-utilities/issues/543)) ([cd4e533](https://github.com/aave/aave-utilities/commit/cd4e533c8c469306abaf4a9c1b0a1066b59cf875)) - - - - +- gas limits ([#543](https://github.com/aave/aave-utilities/issues/543)) + ([cd4e533](https://github.com/aave/aave-utilities/commit/cd4e533c8c469306abaf4a9c1b0a1066b59cf875)) ## 1.17.5 (2023-05-23) **Note:** Version bump only for package @aave/math-utils - - - - ## 1.17.4 (2023-05-05) **Note:** Version bump only for package @aave/math-utils - - - - ## 1.17.3 (2023-05-02) - ### Bug Fixes -* decimal formatting in credit delegation approval checks ([#538](https://github.com/aave/aave-utilities/issues/538)) ([27eef6b](https://github.com/aave/aave-utilities/commit/27eef6b182f671fc9b4eb13cbd3712be17949c5c)) - - - - +- decimal formatting in credit delegation approval checks + ([#538](https://github.com/aave/aave-utilities/issues/538)) + ([27eef6b](https://github.com/aave/aave-utilities/commit/27eef6b182f671fc9b4eb13cbd3712be17949c5c)) ## 1.17.2 (2023-05-01) - ### Bug Fixes -* integer rounding for migration credit delegation approval buffer ([#537](https://github.com/aave/aave-utilities/issues/537)) ([3eef29e](https://github.com/aave/aave-utilities/commit/3eef29eb2e79bce2e1289c951fa00628bc5d4868)) - - - - +- integer rounding for migration credit delegation approval buffer + ([#537](https://github.com/aave/aave-utilities/issues/537)) + ([3eef29e](https://github.com/aave/aave-utilities/commit/3eef29eb2e79bce2e1289c951fa00628bc5d4868)) ## 1.17.1 (2023-05-01) **Note:** Version bump only for package @aave/math-utils - - - - # 1.17.0 (2023-04-27) - ### Features -* fixed available debt in isolated reserve in case that debt > ma… ([#532](https://github.com/aave/aave-utilities/issues/532)) ([7a2ea1e](https://github.com/aave/aave-utilities/commit/7a2ea1e94a26008247b4c4c045641291ff20261b)) - - - - +- fixed available debt in isolated reserve in case that debt > ma… + ([#532](https://github.com/aave/aave-utilities/issues/532)) + ([7a2ea1e](https://github.com/aave/aave-utilities/commit/7a2ea1e94a26008247b4c4c045641291ff20261b)) # 1.16.0 (2023-04-17) - ### Features -* update borrow methods SDK ([#527](https://github.com/aave/aave-utilities/issues/527)) ([da31b83](https://github.com/aave/aave-utilities/commit/da31b836dd7a85da0dbfe664b67612b1f261c7a7)) - - - - +- update borrow methods SDK + ([#527](https://github.com/aave/aave-utilities/issues/527)) + ([da31b83](https://github.com/aave/aave-utilities/commit/da31b836dd7a85da0dbfe664b67612b1f261c7a7)) # 1.15.0 (2023-04-12) - ### Features -* add Scroll Alpha chainId ([#528](https://github.com/aave/aave-utilities/issues/528)) ([3418c70](https://github.com/aave/aave-utilities/commit/3418c7087796397ed535fc7a3522445fb44f94c5)) - - - - +- add Scroll Alpha chainId + ([#528](https://github.com/aave/aave-utilities/issues/528)) + ([3418c70](https://github.com/aave/aave-utilities/commit/3418c7087796397ed535fc7a3522445fb44f94c5)) # 1.14.0 (2023-04-05) - ### Features -* tx bundle refactor ([#514](https://github.com/aave/aave-utilities/issues/514)) ([fc5c937](https://github.com/aave/aave-utilities/commit/fc5c9378b48a14a31df9fdbe435095a3e81390a2)), closes [#524](https://github.com/aave/aave-utilities/issues/524) [#504](https://github.com/aave/aave-utilities/issues/504) - - - - +- tx bundle refactor ([#514](https://github.com/aave/aave-utilities/issues/514)) + ([fc5c937](https://github.com/aave/aave-utilities/commit/fc5c9378b48a14a31df9fdbe435095a3e81390a2)), + closes [#524](https://github.com/aave/aave-utilities/issues/524) + [#504](https://github.com/aave/aave-utilities/issues/504) # Change Log @@ -200,10 +137,6 @@ All notable changes to this project will be documented in this file. See **Note:** Version bump only for package @aave/math-utils - - - - ## 1.13.6 (2023-03-03) **Note:** Version bump only for package @aave/math-utils