From 89b8eca7faf9e11372b9a6485713bcc805af6d09 Mon Sep 17 00:00:00 2001 From: Joaquin Battilana Date: Tue, 22 Aug 2023 03:29:30 +0100 Subject: [PATCH 01/10] feat: added tests --- .../contract-helpers/src/commons/types.ts | 20 ++- .../contract-helpers/src/commons/utils.ts | 12 +- .../src/lendingPool-contract-bundle/index.ts | 42 +++++ .../lendingPoolBundle.test.ts | 51 +++++++ .../src/v3-pool-contract-bundle/index.ts | 104 +++++++++++++ .../pool-bundle.test.ts | 144 ++++++++++++++++++ .../src/v3-pool-rollups/poolTypes.ts | 2 + .../src/wethgateway-contract/index.ts | 30 ++++ .../wethgateway-contract/wethGateway.test.ts | 40 +++++ 9 files changed, 440 insertions(+), 5 deletions(-) diff --git a/packages/contract-helpers/src/commons/types.ts b/packages/contract-helpers/src/commons/types.ts index b868eaca..298f0d0d 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, + LPRepayWithPermitParamsType, +} from '../v3-pool-contract/lendingPoolTypes'; export type tEthereumAddress = string; export type ENS = string; // something.eth @@ -109,6 +114,8 @@ export enum ProtocolAction { liquidationCall = 'liquidationCall', liquidationFlash = 'liquidationFlash', repay = 'repay', + repayETH = 'repayETH', + repayWithATokens = 'repayWithATokens', swapCollateral = 'swapCollateral', repayCollateral = 'repayCollateral', withdrawETH = 'withdrawETH', @@ -338,3 +345,14 @@ export type BorrowTxBuilder = { encodedTxData, }: LPBorrowParamsType) => PopulatedTransaction; }; + +export type RepayTxBuilder = { + generateTxData: (params: LPRepayParamsType) => PopulatedTransaction; + generateSignedTxData: ( + params: LPRepayWithPermitParamsType, + ) => PopulatedTransaction; +}; + +export type RepayWithATokensTxBuilder = { + generateTxData: (params: LPRepayWithATokensType) => PopulatedTransaction; +}; diff --git a/packages/contract-helpers/src/commons/utils.ts b/packages/contract-helpers/src/commons/utils.ts index f563c105..bf887eb1 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', @@ -118,10 +122,6 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '125000', recommended: '125000', }, - [ProtocolAction.stake]: { - limit: '395000', - recommended: '395000', - }, [ProtocolAction.claimRewards]: { limit: '275000', recommended: '275000', @@ -130,6 +130,10 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '138000', recommended: '138000', }, + [ProtocolAction.repayWithATokens]: { + limit: '300000', + recommended: '300000', + }, }; export const mintAmountsPerToken: Record = { diff --git a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts index 6ae6a64d..741b544a 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts @@ -20,6 +20,7 @@ import { import { LPBorrowParamsType, LPDepositParamsType, + LPRepayParamsType, } from '../lendingPool-contract/lendingPoolTypes'; import { ILendingPool, @@ -43,6 +44,10 @@ export type DepositTxBuilder = { getApprovedAmount: ({ user, token }: TokenOwner) => Promise; }; +export type LPRepayTxBuilder = { + generateTxData: (args: LPRepayParamsType) => PopulatedTransaction; +}; + export interface LendingPoolBundleInterface { depositTxBuilder: DepositTxBuilder; } @@ -65,6 +70,7 @@ export class LendingPoolBundle depositTxBuilder: DepositTxBuilder; borrowTxBuilder: Omit; + repayTxBuilder: LPRepayTxBuilder; constructor( provider: providers.Provider, @@ -191,5 +197,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, + 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..7221b700 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,55 @@ describe('LendingPoolBundle', () => { ); }); }); + + describe('RepayTxBuilder', () => { + const config = { + LENDING_POOL, + WETH_GATEWAY, + }; + + const instance = new LendingPoolBundle(provider, config); + + it('generates repay tx data with generateTxData', () => { + 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 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, + }); + expect(result.to).toEqual(WETH_GATEWAY); + expect(result.from).toEqual(USER); + expect(result.value).toEqual(BigNumber.from('1')); + expect(result.data).toEqual( + '0x02c5fcf80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003', + ); + }); + }); }); 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..fc1f4b3a 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts @@ -5,6 +5,8 @@ import { BorrowTxBuilder, InterestRate, ProtocolAction, + RepayTxBuilder, + RepayWithATokensTxBuilder, tEthereumAddress, } from '../commons/types'; import { @@ -96,6 +98,8 @@ export class PoolBundle supplyTxBuilder: SupplyTxBuilder; borrowTxBuilder: BorrowTxBuilder; + repayTxBuilder: RepayTxBuilder; + repayWithATokensTxBuilder: RepayWithATokensTxBuilder; constructor( provider: providers.Provider, @@ -329,5 +333,105 @@ export class PoolBundle return actionTx; }, }; + + this.repayTxBuilder = { + generateTxData: ({ + user, + reserve, + amount, + interestRateMode, + onBehalfOf, + }) => { + let actionTx: PopulatedTransaction = {}; + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const onBehalfOfParam = onBehalfOf ?? user; + if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { + actionTx = this.wethGatewayService.generateRepayEthTxData({ + lendingPool: this.poolAddress, + user, + amount, + interestRateMode, + onBehalfOf: onBehalfOfParam, + }); + } else { + const txData = this.contractInterface.encodeFunctionData('repay', [ + reserve, + 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, + }) => { + const decomposedSignature: Signature = splitSignature(signature); + const populatedTx: PopulatedTransaction = {}; + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + const onBehalfOfParam = onBehalfOf ?? user; + const txData = this.contractInterface.encodeFunctionData( + 'repayWithPermit', + [ + reserve, + 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; + }, + }; + + this.repayWithATokensTxBuilder = { + generateTxData: ({ rateMode, user, amount, reserve }) => { + 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', + ); + } else { + const txData = this.contractInterface.encodeFunctionData( + 'repayWithATokens', + [reserve, amount, numericRateMode], + ); + actionTx.to = this.poolAddress; + actionTx.from = user; + actionTx.data = txData; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayWithATokens] + .recommended, + ); + } + + return actionTx; + }, + }; } } 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..d8407455 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 @@ -524,4 +524,148 @@ describe('PoolBundle', () => { expect(resultStable.data).toEqual(txData); }); }); + + 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', () => { + 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 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 signed tx with generateSignedTxData', () => { + 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', + ); + }); + }); + + 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', () => { + 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('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(); + }); + }); }); 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..87a1a7d2 100644 --- a/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts +++ b/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts @@ -113,6 +113,46 @@ describe('WethGatewayService', () => { ); }); }); + describe('generateRepayEthTxData', () => { + it('generates repayETH tx data', () => { + 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', + ); + }); + }); describe('depositETH', () => { const user = '0x0000000000000000000000000000000000000003'; const onBehalfOf = '0x0000000000000000000000000000000000000004'; From 45458494b8e59c90f88fae315d34749099213262 Mon Sep 17 00:00:00 2001 From: Joaquin Battilana Date: Tue, 22 Aug 2023 03:36:45 +0100 Subject: [PATCH 02/10] feat: added test for stable debt --- .../lendingPoolBundle.test.ts | 20 ++++++- .../pool-bundle.test.ts | 56 ++++++++++++++++++- .../wethgateway-contract/wethGateway.test.ts | 25 ++++++++- 3 files changed, 95 insertions(+), 6 deletions(-) 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 7221b700..cfc4051a 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts @@ -246,7 +246,7 @@ describe('LendingPoolBundle', () => { const instance = new LendingPoolBundle(provider, config); - it('generates repay tx data with generateTxData', () => { + it('generates repay tx data with generateTxData with variable debt', () => { const result = instance.repayTxBuilder.generateTxData({ user: USER, reserve: TOKEN, @@ -272,7 +272,23 @@ describe('LendingPoolBundle', () => { ); }); - it('generates repay tx for WETHGateway data with generateTxData', () => { + 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(), 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 d8407455..39db5697 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 @@ -537,7 +537,7 @@ describe('PoolBundle', () => { const instance = new PoolBundle(provider, config); - it('generates repay tx data with generateTxData', () => { + it('generates repay tx data with generateTxData with variable debt', () => { const result = instance.repayTxBuilder.generateTxData({ user: USER, reserve: TOKEN, @@ -565,6 +565,22 @@ describe('PoolBundle', () => { ); }); + 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, @@ -593,7 +609,7 @@ describe('PoolBundle', () => { ); }); - it('generates signed tx with generateSignedTxData', () => { + it('generates signed tx with generateSignedTxData with variable debt', () => { const result = instance.repayTxBuilder.generateSignedTxData({ user: USER, reserve: TOKEN, @@ -627,6 +643,25 @@ describe('PoolBundle', () => { '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', ); }); + + 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', + ); + }); }); describe('RepayWithATokenTxBuilder', () => { @@ -641,7 +676,7 @@ describe('PoolBundle', () => { const instance = new PoolBundle(provider, config); - it('generates repayWithAToken tx data with generateTxData', () => { + it('generates repayWithAToken tx data with generateTxData with variable rate', () => { const result = instance.repayWithATokensTxBuilder.generateTxData({ user: USER, reserve: TOKEN, @@ -656,6 +691,21 @@ describe('PoolBundle', () => { ); }); + 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({ diff --git a/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts b/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts index 87a1a7d2..5b6ef6db 100644 --- a/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts +++ b/packages/contract-helpers/src/wethgateway-contract/wethGateway.test.ts @@ -114,7 +114,7 @@ describe('WethGatewayService', () => { }); }); describe('generateRepayEthTxData', () => { - it('generates repayETH tx data', () => { + it('generates repayETH tx data with variable debt', () => { const provider: providers.Provider = new providers.JsonRpcProvider(); const erc20Service = new ERC20Service(provider); const weth = new WETHGatewayService( @@ -152,6 +152,29 @@ describe('WethGatewayService', () => { '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'; From 9a9b6935eb38067043d6773ef5b53be01fc9b667 Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Wed, 6 Sep 2023 16:08:16 -0500 Subject: [PATCH 03/10] fix: bad merge --- packages/contract-helpers/src/commons/utils.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/contract-helpers/src/commons/utils.ts b/packages/contract-helpers/src/commons/utils.ts index 39cf3aa9..e711ac9b 100644 --- a/packages/contract-helpers/src/commons/utils.ts +++ b/packages/contract-helpers/src/commons/utils.ts @@ -110,6 +110,10 @@ export const gasLimitRecommendations: GasRecommendationType = { limit: '350000', recommended: '350000', }, + [ProtocolAction.stake]: { + limit: '350000', + recommended: '350000', + }, [ProtocolAction.stakeWithPermit]: { limit: '400000', recommended: '400000', From 30c68cc735a2c682c7aae2862921cb484aa81adf Mon Sep 17 00:00:00 2001 From: Joaquin Battilana Date: Fri, 15 Sep 2023 17:51:45 +0100 Subject: [PATCH 04/10] feat: added encoded tx data --- .../contract-helpers/src/commons/types.ts | 4 +- .../src/lendingPool-contract-bundle/index.ts | 4 +- .../lendingPoolBundle.test.ts | 16 ++++ .../src/v3-pool-contract-bundle/index.ts | 87 +++++++++++++------ .../pool-bundle.test.ts | 57 ++++++++++++ .../src/v3-pool-contract/lendingPoolTypes.ts | 8 ++ .../src/v3-pool-rollups/index.ts | 81 +++++++++++++++++ .../src/v3-pool-rollups/pool-rollups.test.ts | 78 +++++++++++++++++ 8 files changed, 303 insertions(+), 32 deletions(-) diff --git a/packages/contract-helpers/src/commons/types.ts b/packages/contract-helpers/src/commons/types.ts index 1ab680f7..5ce69546 100644 --- a/packages/contract-helpers/src/commons/types.ts +++ b/packages/contract-helpers/src/commons/types.ts @@ -3,7 +3,7 @@ import { LPBorrowParamsType, LPRepayParamsType, LPRepayWithATokensType, - LPRepayWithPermitParamsType, + LPSignedRepayParamsType, } from '../v3-pool-contract/lendingPoolTypes'; export type tEthereumAddress = string; @@ -356,7 +356,7 @@ export type BorrowTxBuilder = { export type RepayTxBuilder = { generateTxData: (params: LPRepayParamsType) => PopulatedTransaction; generateSignedTxData: ( - params: LPRepayWithPermitParamsType, + params: LPSignedRepayParamsType, ) => PopulatedTransaction; }; diff --git a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts index 741b544a..5ce2be98 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts @@ -1,4 +1,4 @@ -import { providers, PopulatedTransaction, BigNumber } from 'ethers'; +import { providers, PopulatedTransaction, BigNumber, constants } from 'ethers'; import BaseService from '../commons/BaseService'; import { BorrowTxBuilder, @@ -220,7 +220,7 @@ export class LendingPoolBundle interestRateMode === InterestRate.Variable ? 2 : 1; const txData = this.contractInterface.encodeFunctionData('repay', [ reserve, - amount, + amount === '-1' ? constants.MaxUint256.toString() : amount, numericRateMode, onBehalfOf ?? user, ]); 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 cfc4051a..104a41ef 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/lendingPoolBundle.test.ts @@ -303,5 +303,21 @@ describe('LendingPoolBundle', () => { '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 fc1f4b3a..c2f6b674 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts @@ -341,34 +341,42 @@ export class PoolBundle amount, interestRateMode, onBehalfOf, + useOptimizedPath, + encodedTxData, }) => { - let actionTx: PopulatedTransaction = {}; const numericRateMode = interestRateMode === InterestRate.Variable ? 2 : 1; const onBehalfOfParam = onBehalfOf ?? user; if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { - actionTx = this.wethGatewayService.generateRepayEthTxData({ + return this.wethGatewayService.generateRepayEthTxData({ lendingPool: this.poolAddress, user, amount, interestRateMode, onBehalfOf: onBehalfOfParam, }); - } else { - const txData = this.contractInterface.encodeFunctionData('repay', [ - reserve, - amount, - numericRateMode, - onBehalfOfParam, - ]); - actionTx.to = this.poolAddress; - actionTx.from = user; - actionTx.data = txData; - actionTx.gasLimit = BigNumber.from( - gasLimitRecommendations[ProtocolAction.repay].recommended, - ); } + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedRepayTxData({ + encodedTxData, + user, + }); + } + + const actionTx: PopulatedTransaction = {}; + const txData = this.contractInterface.encodeFunctionData('repay', [ + reserve, + amount, + numericRateMode, + onBehalfOfParam, + ]); + actionTx.to = this.poolAddress; + actionTx.from = user; + actionTx.data = txData; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repay].recommended, + ); return actionTx; }, generateSignedTxData: ({ @@ -379,12 +387,22 @@ export class PoolBundle 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', [ @@ -409,27 +427,40 @@ export class PoolBundle }; this.repayWithATokensTxBuilder = { - generateTxData: ({ rateMode, user, amount, reserve }) => { + 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', ); - } else { - const txData = this.contractInterface.encodeFunctionData( - 'repayWithATokens', - [reserve, amount, numericRateMode], - ); - actionTx.to = this.poolAddress; - actionTx.from = user; - actionTx.data = txData; - actionTx.gasLimit = BigNumber.from( - gasLimitRecommendations[ProtocolAction.repayWithATokens] - .recommended, - ); } + if (useOptimizedPath && encodedTxData) { + return this.l2PoolService.generateEncodedRepayWithATokensTxData({ + encodedTxData, + user, + }); + } + + const txData = this.contractInterface.encodeFunctionData( + 'repayWithATokens', + [reserve, amount, numericRateMode], + ); + actionTx.to = this.poolAddress; + actionTx.from = user; + actionTx.data = txData; + actionTx.gasLimit = BigNumber.from( + gasLimitRecommendations[ProtocolAction.repayWithATokens].recommended, + ); + return actionTx; }, }; 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 39db5697..790c4994 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 @@ -609,6 +609,45 @@ describe('PoolBundle', () => { ); }); + 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, + }); + + expect(result.to).toEqual(POOL); + expect(result.from).toEqual(USER); + expect(result.data).toEqual( + '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + ); + }); + it('generates signed tx with generateSignedTxData with variable debt', () => { const result = instance.repayTxBuilder.generateSignedTxData({ user: USER, @@ -717,5 +756,23 @@ describe('PoolBundle', () => { 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', + ); + }); }); }); 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); From 718b1e357d29b466a55feee9e3587908a543de37 Mon Sep 17 00:00:00 2001 From: Joaquin Battilana Date: Fri, 15 Sep 2023 17:57:03 +0100 Subject: [PATCH 05/10] feat: added test for signed repay with l2 pool --- .../src/v3-pool-contract-bundle/pool-bundle.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 790c4994..41360448 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 @@ -639,12 +639,14 @@ describe('PoolBundle', () => { '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', deadline: '10000', useOptimizedPath: true, + encodedTxData: + '0x0000000000000000000000000000000000000000000000000000006d6168616d', }); expect(result.to).toEqual(POOL); expect(result.from).toEqual(USER); expect(result.data).toEqual( - '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', + '0x94b576de0000000000000000000000000000000000000000000000000000006d6168616d532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', ); }); From 529ca8b427ce366f5f5b03df8d3088c7c522e8b3 Mon Sep 17 00:00:00 2001 From: Joaquin Battilana Date: Fri, 15 Sep 2023 18:08:13 +0100 Subject: [PATCH 06/10] feat: -1 handle --- .../src/v3-pool-contract-bundle/index.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 c2f6b674..891246d3 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts @@ -1,5 +1,5 @@ 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, @@ -367,7 +367,7 @@ export class PoolBundle const actionTx: PopulatedTransaction = {}; const txData = this.contractInterface.encodeFunctionData('repay', [ reserve, - amount, + amount === '-1' ? constants.MaxUint256.toString() : amount, numericRateMode, onBehalfOfParam, ]); @@ -407,7 +407,7 @@ export class PoolBundle 'repayWithPermit', [ reserve, - amount, + amount === '-1' ? constants.MaxUint256.toString() : amount, numericRateMode, onBehalfOfParam, deadline, @@ -452,7 +452,11 @@ export class PoolBundle const txData = this.contractInterface.encodeFunctionData( 'repayWithATokens', - [reserve, amount, numericRateMode], + [ + reserve, + amount === '-1' ? constants.MaxUint256.toString() : amount, + numericRateMode, + ], ); actionTx.to = this.poolAddress; actionTx.from = user; From 8d191bf193b4e5f74af4086481eb6f23a17f39e5 Mon Sep 17 00:00:00 2001 From: Joaquin Battilana Date: Fri, 15 Sep 2023 18:15:03 +0100 Subject: [PATCH 07/10] feat: added test for repay with max input (-1) --- .../pool-bundle.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) 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 41360448..302491a5 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 @@ -565,6 +565,22 @@ describe('PoolBundle', () => { ); }); + 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, @@ -685,6 +701,24 @@ describe('PoolBundle', () => { ); }); + 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, @@ -732,6 +766,21 @@ describe('PoolBundle', () => { ); }); + 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, From f5b8cfb0abc843ccf7510df0fb747b8138430dfe Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Tue, 24 Oct 2023 10:44:24 -0500 Subject: [PATCH 08/10] feat: added encode functions for using optimized path --- .../contract-helpers/src/commons/types.ts | 26 +++ .../src/lendingPool-contract-bundle/index.ts | 12 +- .../src/v3-pool-contract-bundle/index.ts | 216 +++++++++++------- .../pool-bundle.test.ts | 116 ++++++++++ 4 files changed, 280 insertions(+), 90 deletions(-) diff --git a/packages/contract-helpers/src/commons/types.ts b/packages/contract-helpers/src/commons/types.ts index 5ce69546..0c12eff7 100644 --- a/packages/contract-helpers/src/commons/types.ts +++ b/packages/contract-helpers/src/commons/types.ts @@ -351,6 +351,12 @@ export type BorrowTxBuilder = { useOptimizedPath, encodedTxData, }: LPBorrowParamsType) => PopulatedTransaction; + encodeBorrowParams: ({ + reserve, + amount, + interestRateMode, + referralCode, + }: Omit) => Promise; }; export type RepayTxBuilder = { @@ -358,8 +364,28 @@ export type RepayTxBuilder = { 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/lendingPool-contract-bundle/index.ts b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts index 5ce2be98..e3a19f88 100644 --- a/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/lendingPool-contract-bundle/index.ts @@ -5,6 +5,7 @@ import { InterestRate, LendingPoolMarketConfig, ProtocolAction, + RepayTxBuilder, tEthereumAddress, } from '../commons/types'; import { @@ -20,7 +21,6 @@ import { import { LPBorrowParamsType, LPDepositParamsType, - LPRepayParamsType, } from '../lendingPool-contract/lendingPoolTypes'; import { ILendingPool, @@ -44,12 +44,10 @@ export type DepositTxBuilder = { getApprovedAmount: ({ user, token }: TokenOwner) => Promise; }; -export type LPRepayTxBuilder = { - generateTxData: (args: LPRepayParamsType) => PopulatedTransaction; -}; - export interface LendingPoolBundleInterface { depositTxBuilder: DepositTxBuilder; + borrowTxBuilder: Pick; + repayTxBuilder: Pick; } export class LendingPoolBundle @@ -69,8 +67,8 @@ export class LendingPoolBundle readonly wethGatewayAddress: tEthereumAddress; depositTxBuilder: DepositTxBuilder; - borrowTxBuilder: Omit; - repayTxBuilder: LPRepayTxBuilder; + borrowTxBuilder: Pick; + repayTxBuilder: Pick; constructor( provider: providers.Provider, 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 891246d3..2dfd3c82 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts @@ -34,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, @@ -64,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 { @@ -156,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'; @@ -167,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, @@ -211,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 = { @@ -277,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; @@ -297,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, @@ -332,6 +337,21 @@ export class PoolBundle return actionTx; }, + encodeBorrowParams: async ({ + reserve, + amount, + interestRateMode, + referralCode, + }): Promise => { + return this.l2PoolService + .getEncoder() + .encodeBorrowParams( + reserve, + amount, + interestRateMode, + referralCode ?? '0', + ); + }, }; this.repayTxBuilder = { @@ -424,6 +444,31 @@ export class PoolBundle ); return populatedTx; }, + encodeRepayParams: async ({ reserve, amount, interestRateMode }) => { + return this.l2PoolService + .getEncoder() + .encodeRepayParams(reserve, amount, interestRateMode); + }, + encodeRepayWithPermitParams: async ({ + reserve, + amount, + interestRateMode, + signature, + deadline, + }) => { + const decomposedSignature: Signature = splitSignature(signature); + return this.l2PoolService + .getEncoder() + .encodeRepayWithPermitParams( + reserve, + amount, + interestRateMode, + deadline, + decomposedSignature.v, + decomposedSignature.r, + decomposedSignature.s, + ); + }, }; this.repayWithATokensTxBuilder = { @@ -467,6 +512,11 @@ export class PoolBundle return actionTx; }, + encodeRepayWithATokensParams: async ({ reserve, amount, rateMode }) => { + return this.l2PoolService + .getEncoder() + .encodeRepayWithATokensParams(reserve, amount, rateMode); + }, }; } } 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 302491a5..62182946 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,6 +590,25 @@ 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 borrow params for L2', async () => { + await instance.borrowTxBuilder.encodeBorrowParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + referralCode: '1', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); + + it('encodes borrow params for L2 without referral code', async () => { + await instance.borrowTxBuilder.encodeBorrowParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + expect(encoderSpy).toHaveBeenCalled(); + }); }); describe('RepayTxBuilder', () => { @@ -737,6 +823,27 @@ describe('PoolBundle', () => { '0xee3e210b00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4', ); }); + + it('encodes repay params for L2', async () => { + await instance.repayTxBuilder.encodeRepayParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + }); + expect(encoderSpy).toHaveBeenCalled(); + }); + + it('encodes repay with permit params for L2', async () => { + await instance.repayTxBuilder.encodeRepayWithPermitParams({ + reserve: TOKEN, + amount: '1', + interestRateMode: InterestRate.Variable, + deadline: '10000', + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + }); + expect(encoderSpy).toHaveBeenCalled(); + }); }); describe('RepayWithATokenTxBuilder', () => { @@ -825,5 +932,14 @@ describe('PoolBundle', () => { '0xdc7c0bff0000000000000000000000000000000000000000000000000000006d6168616d', ); }); + + it('encodes repay with aToken params for L2', async () => { + await instance.repayWithATokensTxBuilder.encodeRepayWithATokensParams({ + reserve: TOKEN, + amount: '1', + rateMode: InterestRate.Variable, + }); + expect(encoderSpy).toHaveBeenCalled(); + }); }); }); From 46fe68f5b8a10b1f0b37da82a16a1a49e203076a Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Tue, 24 Oct 2023 14:21:06 -0500 Subject: [PATCH 09/10] fix: rate mode and max repay --- .../src/v3-pool-contract-bundle/index.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) 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 2dfd3c82..767957aa 100644 --- a/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts +++ b/packages/contract-helpers/src/v3-pool-contract-bundle/index.ts @@ -343,12 +343,15 @@ export class PoolBundle interestRateMode, referralCode, }): Promise => { + const numericRateMode = + interestRateMode === InterestRate.Variable ? 2 : 1; + return this.l2PoolService .getEncoder() .encodeBorrowParams( reserve, amount, - interestRateMode, + numericRateMode, referralCode ?? '0', ); }, @@ -445,9 +448,15 @@ export class PoolBundle 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, amount, interestRateMode); + .encodeRepayParams(reserve, repayAmount, numericRateMode); }, encodeRepayWithPermitParams: async ({ reserve, @@ -457,12 +466,17 @@ export class PoolBundle 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, - amount, - interestRateMode, + repayAmount, + numericRateMode, deadline, decomposedSignature.v, decomposedSignature.r, @@ -513,9 +527,13 @@ export class PoolBundle 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, amount, rateMode); + .encodeRepayWithATokensParams(reserve, repayAmount, numericRateMode); }, }; } From 0efe71955bfd19d178a3edef77416ca0612598b9 Mon Sep 17 00:00:00 2001 From: Mark Grothe Date: Tue, 24 Oct 2023 14:47:44 -0500 Subject: [PATCH 10/10] test: repay amount and interest rate mode --- .../pool-bundle.test.ts | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) 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 62182946..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 @@ -591,22 +591,31 @@ describe('PoolBundle', () => { expect(resultStable.data).toEqual(txData); }); - it('encodes borrow params for L2', async () => { + 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(); - }); - it('encodes borrow params for L2 without referral code', async () => { await instance.borrowTxBuilder.encodeBorrowParams({ reserve: TOKEN, amount: '1', interestRateMode: InterestRate.Variable, }); + expect(encoderSpy).toHaveBeenCalled(); }); }); @@ -831,9 +840,14 @@ describe('PoolBundle', () => { interestRateMode: InterestRate.Variable, }); expect(encoderSpy).toHaveBeenCalled(); - }); - it('encodes repay with permit params for L2', async () => { + await instance.repayTxBuilder.encodeRepayParams({ + reserve: TOKEN, + amount: '-1', + interestRateMode: InterestRate.Stable, + }); + expect(encoderSpy).toHaveBeenCalled(); + await instance.repayTxBuilder.encodeRepayWithPermitParams({ reserve: TOKEN, amount: '1', @@ -843,6 +857,16 @@ describe('PoolBundle', () => { '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', }); expect(encoderSpy).toHaveBeenCalled(); + + await instance.repayTxBuilder.encodeRepayWithPermitParams({ + reserve: TOKEN, + amount: '-1', + interestRateMode: InterestRate.Stable, + deadline: '10000', + signature: + '0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c', + }); + expect(encoderSpy).toHaveBeenCalled(); }); }); @@ -936,10 +960,17 @@ describe('PoolBundle', () => { it('encodes repay with aToken params for L2', async () => { await instance.repayWithATokensTxBuilder.encodeRepayWithATokensParams({ reserve: TOKEN, - amount: '1', + amount: '-1', rateMode: InterestRate.Variable, }); expect(encoderSpy).toHaveBeenCalled(); + + await instance.repayWithATokensTxBuilder.encodeRepayWithATokensParams({ + reserve: TOKEN, + amount: '1', + rateMode: InterestRate.Stable, + }); + expect(encoderSpy).toHaveBeenCalled(); }); }); });