Skip to content

Commit b4e9364

Browse files
sammdecclaude
andcommitted
fix: address review feedback on repay approval amount
- Round the approval amount to the token's decimals on both branches of getRepayAmountToApprove. The repay input has no decimalScale, so a typed 7dp amount on a 6dp token made parseUnits throw during render and took the modal down. Rounding up keeps the approval at or above the gate's target. - Key the USDT-on-Ethereum reset check off the amount actually being approved rather than signatureAmount, which is the '-1' sentinel on a max repay and skipped the check entirely. An exact approval on a max repay leaves a residual allowance, and USDT reverts approve() over a non-zero allowance, so the next full repay failed at gas estimation. - Report the approval amount to Amplitude in token units instead of base units, matching every other GENERAL.TRANSACTION caller. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 61848e6 commit b4e9364

3 files changed

Lines changed: 50 additions & 14 deletions

File tree

src/components/transactions/__tests__/utils.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BigNumber } from 'bignumber.js';
2+
import { parseUnits } from 'ethers/lib/utils';
23

34
import { checkRequiresApproval, getRepayAmountToApprove, getSafeAmountToRepayAll } from '../utils';
45

@@ -45,6 +46,32 @@ describe('getRepayAmountToApprove', () => {
4546
).toBe('10.5');
4647
});
4748

49+
it('trims a typed amount to the token decimals so parseUnits cannot throw', () => {
50+
// The repay input has no decimalScale, so 7dp on a 6dp token is reachable by typing.
51+
const approved = getRepayAmountToApprove({
52+
amountRequiringApproval: '1.1234567',
53+
isMaxRepay: false,
54+
decimals: 6,
55+
});
56+
57+
expect(approved).toBe('1.123457');
58+
expect(() => parseUnits(approved, 6)).not.toThrow();
59+
});
60+
61+
it('rounds a typed amount up, so the trimmed approval still clears the gate', () => {
62+
const typed = '1.1234567';
63+
const approved = getRepayAmountToApprove({
64+
amountRequiringApproval: typed,
65+
isMaxRepay: false,
66+
decimals: 6,
67+
});
68+
69+
expect(new BigNumber(approved).isGreaterThanOrEqualTo(typed)).toBe(true);
70+
expect(
71+
checkRequiresApproval({ approvedAmount: approved, amount: typed, signedAmount: '0' })
72+
).toBe(false);
73+
});
74+
4875
it('reproduces the reported bug: a hand-set approval above the debt still fails the gate', () => {
4976
expect(new BigNumber(HAND_SET_APPROVAL).isGreaterThan(DEBT)).toBe(true);
5077
expect(passesGate(HAND_SET_APPROVAL, DEBT)).toBe(false);

src/components/transactions/utils.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,18 @@ export const getRepayAmountToApprove = ({
5555
amountRequiringApproval: string;
5656
isMaxRepay: boolean;
5757
decimals: number;
58-
}): string =>
58+
}): string => {
5959
// A typed amount is fixed, so the gate's target cannot drift away from it. A full repay
6060
// is derived from live debt and does drift, hence the margin.
61-
isMaxRepay
62-
? valueToBigNumber(amountRequiringApproval)
63-
.multipliedBy(REPAY_ALL_APPROVAL_MARGIN)
64-
.decimalPlaces(decimals, BigNumber.ROUND_UP)
65-
.toString(10)
66-
: amountRequiringApproval;
61+
const amount = isMaxRepay
62+
? valueToBigNumber(amountRequiringApproval).multipliedBy(REPAY_ALL_APPROVAL_MARGIN)
63+
: valueToBigNumber(amountRequiringApproval);
64+
65+
// The repay input accepts more decimals than the token has, and this result is handed to
66+
// `parseUnits` during render, which throws on the excess. Rounding up rather than down
67+
// keeps the approval at or above what the gate asked for.
68+
return amount.decimalPlaces(decimals, BigNumber.ROUND_UP).toString(10);
69+
};
6770

6871
export const checkRequiresApproval = ({
6972
approvedAmount,

src/hooks/useApprovalTx.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ApproveType, MAX_UINT_AMOUNT, ProtocolAction } from '@aave/contract-helpers';
22
import { SignatureLike } from '@ethersproject/bytes';
33
import { constants, ethers } from 'ethers';
4-
import { parseUnits } from 'ethers/lib/utils';
4+
import { formatUnits, parseUnits } from 'ethers/lib/utils';
55
import { useEffect, useState } from 'react';
66
import { MOCK_SIGNED_HASH } from 'src/helpers/useTransactionHandler';
77
import { useWeb3Context } from 'src/libs/hooks/useWeb3Context';
@@ -68,23 +68,29 @@ export const useApprovalTx = ({
6868

6969
const [requiresApprovalReset, setRequiresApprovalReset] = useState(false);
7070

71+
// What the next approval will actually be for, in token units. `signatureAmount` is only
72+
// a stand-in for it: on a full repay it is the '-1' sentinel, which would skip the check
73+
// below even though `amountToApprove` holds a real, finite allowance to compare against.
74+
const newApprovalAmount = amountToApprove
75+
? formatUnits(amountToApprove, decimals)
76+
: signatureAmount;
77+
7178
// Warning for USDT on Ethereum approval reset
7279
useEffect(() => {
7380
if (
7481
!chainId ||
7582
!isUSDTOnEthereum(symbol, chainId, underlyingChainId) ||
7683
!setShowUSDTResetWarning ||
77-
!signatureAmount ||
78-
signatureAmount === '0' ||
79-
signatureAmount === '-1'
84+
!newApprovalAmount ||
85+
Number(newApprovalAmount) <= 0
8086
) {
8187
return;
8288
}
8389

8490
const currentApproved = approvedAmount?.amount ?? '0';
8591

8692
if (
87-
needsUSDTApprovalReset(symbol, chainId, currentApproved, signatureAmount, underlyingChainId)
93+
needsUSDTApprovalReset(symbol, chainId, currentApproved, newApprovalAmount, underlyingChainId)
8894
) {
8995
setShowUSDTResetWarning(true);
9096
setRequiresApprovalReset(true);
@@ -97,7 +103,7 @@ export const useApprovalTx = ({
97103
chainId,
98104
underlyingChainId,
99105
approvedAmount?.amount,
100-
signatureAmount,
106+
newApprovalAmount,
101107
setShowUSDTResetWarning,
102108
]);
103109

@@ -207,7 +213,7 @@ export const useApprovalTx = ({
207213
action: ProtocolAction.approval,
208214
txState: 'success',
209215
asset: assetAddress,
210-
amount: amountToApprove ?? MAX_UINT_AMOUNT,
216+
amount: amountToApprove ? formatUnits(amountToApprove, decimals) : MAX_UINT_AMOUNT,
211217
assetName: symbol,
212218
});
213219
if (onApprovalTxConfirmed) {

0 commit comments

Comments
 (0)