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);