Revert Lend - JecikPo's results

A lending protocol specifically designed for liquidity providers on Uniswap v3.

General Information

Platform: Code4rena

Start Date: 04/03/2024

Pot Size: $88,500 USDC

Total HM: 31

Participants: 105

Period: 11 days

Judge: ronnyx2017

Total Solo HM: 7

Id: 342

League: ETH

Revert

Findings Distribution

Researcher Performance

Rank: 52/105

Findings: 3

Award: $133.39

🌟 Selected for report: 1

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: JecikPo

Also found by: KupiaSec, SpicyMeatball, kennedy1030, kfx, linmiaomiao, t4sk

Labels

bug
2 (Med Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
sufficient quality report
:robot:_24_group
M-07

Awards

119.7477 USDC - $119.75

External Links

Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Oracle.sol#L304

Vulnerability details

Impact

The price calculation at the V3Oracle.sol will revert upon reaching certain level when referenceToken is used with high decimal value (e. g. 18). The revert (specifically happening when calling getValue()) would make the Chainlink price feed useless. Yet the TWAP price source would still be available. The protocol team would have to disable Chainlink and rely exclusively on the TWAP source reducing security of the pricing. The issue could manifest itself after certain amount of time once the project is already live and only when price returned by the feed reaches certain point.

Proof of Concept

The following code line has an issue:

chainlinkPriceX96 = (10 ** referenceTokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96 / (10 ** feedConfig.tokenDecimals);

When referenceTokenDecimals is 18 chainlinkPriceX96 is higher than some threshold between 18 and 19 (in Q96 notation), it will cause arithmetic overflow.

Tools Used

Manual review.

Instead of calculating the price this way:

chainlinkPriceX96 = (10 ** referenceTokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96 / (10 ** feedConfig.tokenDecimals);

It could be done the following way as per Chainlink's recommendation:

if (referenceTokenDecimals > feedConfig.tokenDecimals) chainlinkPriceX96 = (10 ** referenceTokenDecimals - feedConfig.tokenDecimals) * chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96; else if (referenceTokenDecimals < feedConfig.tokenDecimals) chainlinkPriceX96 = chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96 / (10 ** feedConfig.tokenDecimals - referenceTokenDecimals); else chainlinkPriceX96 = chainlinkPriceX96 * Q96 / chainlinkReferencePriceX96;

https://docs.chain.link/data-feeds/using-data-feeds#getting-a-different-price-denomination

Assessed type

Decimal

#0 - c4-pre-sort

2024-03-22T07:24:22Z

0xEVom marked the issue as primary issue

#1 - c4-pre-sort

2024-03-22T07:24:26Z

0xEVom marked the issue as sufficient quality report

#2 - c4-sponsor

2024-03-26T17:32:32Z

kalinbas (sponsor) confirmed

#3 - c4-judge

2024-03-31T15:57:15Z

jhsagd76 marked the issue as satisfactory

#4 - c4-judge

2024-04-01T15:34:00Z

jhsagd76 marked the issue as selected for report

#5 - kalinbas

2024-04-09T22:18:46Z

Awards

3.3501 USDC - $3.35

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
:robot:_45_group
duplicate-222

External Links

Lines of code

https://github.com/code-423n4/2024-03-revert-lend/blob/435b054f9ad2404173f36f0f74a5096c894b12b7/src/V3Vault.sol#L696-L698

Vulnerability details

Impact

An attacker by front-running any liquidate() call can prevent successful liquidation by making it revert. By preventing liquidations the gains of the protocol can be diminished using relatively low cost, especially during volatile price movements.

Proof of Concept

The problem lies in the following condition inside the liquidate() function in the V3Vault.sol:

uint256 debtShares = loans[params.tokenId].debtShares;
        if (debtShares != params.debtShares) {
            revert DebtChanged();
        }

The liquidate() function requires that the caller puts the exact amount of debt that he intends to liquidate, that amount must equal the actual debt of a loan. An attacker can front-run the liquidate() transaction with a repay() and repay minimum amount of assets so that at least debtShares is reduced by 1.

A test to see the behaviour:

function testLiquidationFrontRun() external { address FRONT_RUNNER = 0xF977814e90Da44bfa03b6295a0616A897441bAcA; vm.prank(WHALE_ACCOUNT); USDC.transfer(FRONT_RUNNER, 1000); // approve front runner to vault for repay() vm.prank(FRONT_RUNNER); USDC.approve(address(vault), 100); _setupBasicLoan(true); // wait 7 days for interest to grow vm.warp(block.timestamp + 7 days); (,,, uint256 liquidationCost,) = vault.loanInfo(TEST_NFT); (uint256 debtShares) = vault.loans(TEST_NFT); vm.prank(WHALE_ACCOUNT); USDC.approve(address(vault), liquidationCost); vm.prank(FRONT_RUNNER); vault.repay(TEST_NFT, 1, true); vm.expectRevert(IErrors.DebtChanged.selector); vm.prank(WHALE_ACCOUNT); vault.liquidate(IVault.LiquidateParams(TEST_NFT, debtShares, 0, 0, WHALE_ACCOUNT, "")); }

Tools Used

Manual Review

Recommendation is to remove the above condition completely and the debtShares from LiquidateParams. liquidation should always liquidate the entire available debt.

Assessed type

DoS

#0 - c4-pre-sort

2024-03-18T18:13:51Z

0xEVom marked the issue as sufficient quality report

#1 - c4-pre-sort

2024-03-18T18:14:27Z

0xEVom marked the issue as duplicate of #231

#2 - c4-pre-sort

2024-03-22T12:02:47Z

0xEVom marked the issue as duplicate of #222

#3 - c4-judge

2024-03-31T16:05:53Z

jhsagd76 marked the issue as satisfactory

Awards

10.2896 USDC - $10.29

Labels

bug
grade-b
QA (Quality Assurance)
sufficient quality report
Q-03

External Links

[L-01] repay() function can be front-run with repaying dust

Anyone can repay any loan. So when an original loan owner wishes to repay the whole debt by specifying the attributes of the repay(), the transaction could be front-run by an attacker where he repays the exact amount so that the loan.debtShares decrements by 1. This way the debt owner's transaction gets reverted due to the below condition.

File: src/V3Vault.sol

973: if (shares > currentShares) {
            revert RepayExceedsDebt();
        }

Recommendation:

The fix should be the following: If the debt/loan owner specifies too high amount, and the above condition is hit, the shares/assets values should be recalculated, like this:

973: if (shares > currentShares) {
         shares = currentShares
         assets = _convertToAssets(shares, newDebtExchangeRateX96, Math.Rounding.Up);
     }

This way would protect the loan/debt owners from unnecessarily repeated transaction and hence save them gas.

GitHub : 973

[L-02] Uniswap positions minted to V3Vault are stuck

Using the NonfungiblePositionManager directly a user could potentially mint a position directly to V3Vault by having V3Vault as the receiver when calling NonfungiblePositionManager.mint().

Since NonfungiblePositionManager doesn’t use safeMint() this would just transfer the token and liquidity to V3Vault without calling V3Vault.onERC721Received(), thus the liquidity would be lost.

Recommendation

Consider adding a rescue function callable by owner that can transfer any accidentally minted tokens out of the contract. This could check that V3Vault is the owner of the token but has loans[tokenId].owner == address(0) to prevent misuse.

#0 - c4-pre-sort

2024-03-24T20:32:38Z

0xEVom marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-03-24T20:32:41Z

0xEVom marked the issue as grade-c

#2 - c4-pre-sort

2024-03-25T20:52:21Z

0xEVom marked the issue as sufficient quality report

#3 - c4-pre-sort

2024-03-25T20:52:24Z

0xEVom removed the grade

#4 - c4-judge

2024-04-01T15:10:18Z

jhsagd76 marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter