Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Repay bundle #1773

Merged
merged 32 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
46e5eb6
feat: basic repay
JoaquinBattilana Aug 8, 2023
5a302ec
Merge branch 'main' into feat/repay-bundle
JoaquinBattilana Aug 22, 2023
448260c
feat: repay bundle
JoaquinBattilana Aug 24, 2023
8d4c5d8
Merge branch 'main' into feat/repay-bundle
grothem Sep 6, 2023
2aafd9f
fix: temp package
grothem Sep 6, 2023
94b1ebd
feat: approved amount fetching (#1777)
grothem Sep 7, 2023
97c2445
fix: query key name
grothem Sep 7, 2023
13d516c
fix: disable approval until first load
grothem Sep 8, 2023
a284a0e
fix: don't subscribe to entire store
grothem Sep 8, 2023
9b016a3
fix: bundle size
grothem Sep 8, 2023
3f6bdb0
fix: don't require approval if repaying with aTokens
grothem Sep 11, 2023
423ce22
fix: lazy load services
grothem Oct 4, 2023
a4b4a8d
fix: use latest temp utils
grothem Oct 5, 2023
83fe81c
chore: bump aave utilities
JoaquinBattilana Oct 10, 2023
48929a2
chore: merged with remote
JoaquinBattilana Oct 11, 2023
abd7af7
chore: merged with main
JoaquinBattilana Oct 11, 2023
0ec4bdf
chore: upgraded package utilities
JoaquinBattilana Oct 11, 2023
6e588fb
feat: handle -1 in approve methods
JoaquinBattilana Oct 13, 2023
664e37a
chore: merged with main
JoaquinBattilana Oct 17, 2023
bb4ff51
feat: fixed approval error
JoaquinBattilana Oct 17, 2023
30c8225
feat: added -1 to options
JoaquinBattilana Oct 17, 2023
650eda9
feat: added max uint to approve
JoaquinBattilana Oct 17, 2023
ba54f02
feat: fixed approve
JoaquinBattilana Oct 17, 2023
951f83c
feat: updated utilitiers version
JoaquinBattilana Oct 17, 2023
3f27df4
fix: symbol in success modal
grothem Oct 5, 2023
999c851
feat: use optimized path
grothem Oct 25, 2023
1c468e6
Merge branch 'main' into feat/repay-bundle
grothem Oct 25, 2023
9447207
fix: remove abpt from staking test coverage
MareskoY Oct 25, 2023
09f485c
fix: import
MareskoY Oct 25, 2023
e42c342
Merge branch 'main' into feat/repay-bundle
MareskoY Oct 25, 2023
e612ae8
Merge branch 'main' into feat/repay-bundle
grothem Nov 20, 2023
f8c9aa3
chore: update aave utilities
JoaquinBattilana Nov 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions cypress/e2e/3-stake-governance/stake.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { skipState } from '../../support/steps/common';
import { configEnvWithTenderlyMainnetFork } from '../../support/steps/configuration.steps';
import {
activateCooldown,
stake,
reCallCooldown,
claimReward,
reCallCooldown,
reStake,
stake,
} from '../../support/steps/stake.steps';

const testCases = [
Expand All @@ -18,14 +18,14 @@ const testCases = [
tabValue: 'aave',
changeApproval: true,
},
{
asset: assets.staking.ABPT,
amount: 5,
checkAmount: '5.00',
checkAmountFinal: '10.00',
tabValue: 'bpt',
changeApproval: false,
},
// {
// asset: assets.staking.ABPT,
// amount: 5,
// checkAmount: '5.00',
// checkAmountFinal: '10.00',
// tabValue: 'bpt',
// changeApproval: false,
// },
];

testCases.forEach(
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"test:coverage": "jest --coverage"
},
"dependencies": {
"@aave/contract-helpers": "1.21.0",
"@aave/math-utils": "^1.21.0",
"@aave/contract-helpers": "1.21.1-0efe71955bfd19d178a3edef77416ca0612598b9.0",
"@aave/math-utils": "1.20.1-8d191bf193b4e5f74af4086481eb6f23a17f39e5.0",
"@bgd-labs/aave-address-book": "^2.7.0",
"@emotion/cache": "11.10.3",
"@emotion/react": "11.10.4",
Expand Down
231 changes: 191 additions & 40 deletions src/components/transactions/Repay/RepayActions.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { InterestRate, ProtocolAction } from '@aave/contract-helpers';
import { gasLimitRecommendations, InterestRate, ProtocolAction } from '@aave/contract-helpers';
import { TransactionResponse } from '@ethersproject/providers';
import { Trans } from '@lingui/macro';
import { BoxProps } from '@mui/material';
import { useTransactionHandler } from 'src/helpers/useTransactionHandler';
import { parseUnits } from 'ethers/lib/utils';
import { queryClient } from 'pages/_app.page';
import { useEffect, useState } from 'react';
import { useBackgroundDataProvider } from 'src/hooks/app-data-provider/BackgroundDataProvider';
import { ComputedReserveData } from 'src/hooks/app-data-provider/useAppDataProvider';
import { SignedParams, useApprovalTx } from 'src/hooks/useApprovalTx';
import { usePoolApprovedAmount } from 'src/hooks/useApprovedAmount';
import { useModalContext } from 'src/hooks/useModal';
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
import { useRootStore } from 'src/store/root';
import { ApprovalMethod } from 'src/store/walletSlice';
import { getErrorTextFromError, TxAction } from 'src/ui-config/errorMapping';
import { QueryKeys } from 'src/ui-config/queries';

import { TxActionsWrapper } from '../TxActionsWrapper';
import { APPROVAL_GAS_LIMIT, checkRequiresApproval } from '../utils';

export interface RepayActionProps extends BoxProps {
amountToRepay: string;
Expand All @@ -17,6 +29,7 @@ export interface RepayActionProps extends BoxProps {
debtType: InterestRate;
repayWithATokens: boolean;
blocked?: boolean;
maxApproveNeeded: string;
}

export const RepayActions = ({
Expand All @@ -29,56 +42,194 @@ export const RepayActions = ({
debtType,
repayWithATokens,
blocked,
maxApproveNeeded,
...props
}: RepayActionProps) => {
const { repay, repayWithPermit, tryPermit } = useRootStore();
const [
repay,
repayWithPermit,
encodeRepayParams,
encodeRepayWithPermit,
tryPermit,
walletApprovalMethodPreference,
estimateGasLimit,
addTransaction,
optimizedPath,
] = useRootStore((store) => [
store.repay,
store.repayWithPermit,
store.encodeRepayParams,
store.encodeRepayWithPermitParams,
store.tryPermit,
store.walletApprovalMethodPreference,
store.estimateGasLimit,
store.addTransaction,
store.useOptimizedPath,
]);
const { sendTx } = useWeb3Context();
const { refetchGhoData, refetchIncentiveData, refetchPoolData } = useBackgroundDataProvider();
const [signatureParams, setSignatureParams] = useState<SignedParams | undefined>();
const {
approvalTxState,
mainTxState,
loadingTxns,
setMainTxState,
setTxError,
setGasLimit,
setLoadingTxns,
setApprovalTxState,
} = useModalContext();

const usingPermit = tryPermit({
const {
data: approvedAmount,
refetch: fetchApprovedAmount,
isFetching: fetchingApprovedAmount,
isFetchedAfterMount,
} = usePoolApprovedAmount(poolAddress);

const permitAvailable = tryPermit({
reserveAddress: poolAddress,
isWrappedBaseAsset: poolReserve.isWrappedBaseAsset,
});
const { approval, action, requiresApproval, loadingTxns, approvalTxState, mainTxState } =
useTransactionHandler({
tryPermit: usingPermit,
permitAction: ProtocolAction.repayWithPermit,
protocolAction: ProtocolAction.repay,
eventTxInfo: {
amount: amountToRepay,
assetName: poolReserve.name,
asset: poolReserve.underlyingAsset,
},
handleGetTxns: async () => {
return repay({
amountToRepay,
poolAddress,
repayWithATokens,
debtType,
poolReserve,
isWrongNetwork,
symbol,
const usePermit = permitAvailable && walletApprovalMethodPreference === ApprovalMethod.PERMIT;

setLoadingTxns(fetchingApprovedAmount);

const requiresApproval =
!repayWithATokens &&
Number(amountToRepay) !== 0 &&
checkRequiresApproval({
approvedAmount: approvedAmount?.amount || '0',
amount: Number(amountToRepay) === -1 ? maxApproveNeeded : amountToRepay,
signedAmount: signatureParams ? signatureParams.amount : '0',
});

if (requiresApproval && approvalTxState?.success) {
// There was a successful approval tx, but the approval amount is not enough.
// Clear the state to prompt for another approval.
setApprovalTxState({});
}

const { approval } = useApprovalTx({
usePermit,
approvedAmount,
requiresApproval,
assetAddress: poolAddress,
symbol,
decimals: poolReserve.decimals,
signatureAmount: amountToRepay,
onApprovalTxConfirmed: fetchApprovedAmount,
onSignTxCompleted: (signedParams) => setSignatureParams(signedParams),
});

useEffect(() => {
if (!isFetchedAfterMount && !repayWithATokens) {
fetchApprovedAmount();
}
}, [fetchApprovedAmount, isFetchedAfterMount, repayWithATokens]);

const action = async () => {
try {
setMainTxState({ ...mainTxState, loading: true });

let response: TransactionResponse;
let action = ProtocolAction.default;

if (usePermit && signatureParams) {
const repayWithPermitParams = {
amount:
amountToRepay === '-1'
? amountToRepay
: parseUnits(amountToRepay, poolReserve.decimals).toString(),
reserve: poolAddress,
interestRateMode: debtType,
signature: signatureParams.signature,
deadline: signatureParams.deadline,
};

let encodedParams: [string, string, string] | undefined;
if (optimizedPath()) {
encodedParams = await encodeRepayWithPermit(repayWithPermitParams);
}

action = ProtocolAction.repayWithPermit;
let signedRepayWithPermitTxData = repayWithPermit({
...repayWithPermitParams,
encodedTxData: encodedParams ? encodedParams[0] : undefined,
});
},
handleGetPermitTxns: async (signatures, deadline) => {
return repayWithPermit({
amountToRepay,
poolReserve,
isWrongNetwork,

signedRepayWithPermitTxData = await estimateGasLimit(signedRepayWithPermitTxData);
response = await sendTx(signedRepayWithPermitTxData);
await response.wait(1);
} else {
const repayParams = {
amountToRepay:
amountToRepay === '-1'
? amountToRepay
: parseUnits(amountToRepay, poolReserve.decimals).toString(),
poolAddress,
symbol,
debtType,
repayWithATokens,
signature: signatures[0],
deadline,
debtType,
};

let encodedParams: string | undefined;
if (optimizedPath()) {
encodedParams = await encodeRepayParams(repayParams);
}

action = ProtocolAction.repay;
let repayTxData = repay({
...repayParams,
encodedTxData: encodedParams,
});
},
skip: !amountToRepay || parseFloat(amountToRepay) === 0 || blocked,
deps: [amountToRepay, poolAddress, repayWithATokens],
});
repayTxData = await estimateGasLimit(repayTxData);
response = await sendTx(repayTxData);
await response.wait(1);
}
setMainTxState({
txHash: response.hash,
loading: false,
success: true,
});
addTransaction(response.hash, {
action,
txState: 'success',
asset: poolAddress,
amount: amountToRepay,
assetName: symbol,
});

queryClient.invalidateQueries({ queryKey: [QueryKeys.POOL_TOKENS] });
refetchPoolData && refetchPoolData();
refetchIncentiveData && refetchIncentiveData();
refetchGhoData && refetchGhoData();
} catch (error) {
const parsedError = getErrorTextFromError(error, TxAction.GAS_ESTIMATION, false);
setTxError(parsedError);
setMainTxState({
txHash: undefined,
loading: false,
});
}
};

useEffect(() => {
let supplyGasLimit = 0;
if (usePermit) {
supplyGasLimit = Number(gasLimitRecommendations[ProtocolAction.supplyWithPermit].recommended);
} else {
supplyGasLimit = Number(gasLimitRecommendations[ProtocolAction.supply].recommended);
if (requiresApproval && !approvalTxState.success) {
supplyGasLimit += Number(APPROVAL_GAS_LIMIT);
}
}
setGasLimit(supplyGasLimit.toString());
}, [requiresApproval, approvalTxState, usePermit, setGasLimit]);

return (
<TxActionsWrapper
blocked={blocked}
preparingTransactions={loadingTxns}
preparingTransactions={loadingTxns || !approvedAmount}
symbol={poolReserve.symbol}
mainTxState={mainTxState}
approvalTxState={approvalTxState}
Expand All @@ -89,10 +240,10 @@ export const RepayActions = ({
sx={sx}
{...props}
handleAction={action}
handleApproval={() => approval([{ amount: amountToRepay, underlyingAsset: poolAddress }])}
handleApproval={approval}
actionText={<Trans>Repay {symbol}</Trans>}
actionInProgressText={<Trans>Repaying {symbol}</Trans>}
tryPermit={usingPermit}
tryPermit={permitAvailable}
/>
);
};
7 changes: 5 additions & 2 deletions src/components/transactions/Repay/RepayModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export const RepayModalContent = ({
.multipliedBy(marketReferencePriceInUsd)
.shiftedBy(-USD_DECIMALS);

const safeAmountToRepayAll = valueToBigNumber(debt).multipliedBy('1.0025');
const safeAmountToRepayAll = valueToBigNumber(debt)
.multipliedBy('1.0025')
.decimalPlaces(poolReserve.decimals, BigNumber.ROUND_UP);

// calculate max amount abailable to repay
let maxAmountToRepay: BigNumber;
Expand Down Expand Up @@ -223,7 +225,7 @@ export const RepayModalContent = ({
<TxSuccessView
action={<Trans>repaid</Trans>}
amount={amountRef.current}
symbol={tokenToRepayWith.symbol}
symbol={repayWithATokens ? poolReserve.symbol : tokenToRepayWith.symbol}
/>
);

Expand Down Expand Up @@ -274,6 +276,7 @@ export const RepayModalContent = ({
{txError && <GasEstimationError txError={txError} />}

<RepayActions
maxApproveNeeded={safeAmountToRepayAll.toString()}
poolReserve={poolReserve}
amountToRepay={isMaxSelected ? repayMax : amount}
poolAddress={
Expand Down
Loading
Loading