Platform: Code4rena
Start Date: 12/12/2022
Pot Size: $36,500 USDC
Total HM: 8
Participants: 103
Period: 7 days
Judge: berndartmueller
Id: 193
League: ETH
Rank: 42/103
Findings: 2
Award: $86.20
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Jeiwan
Also found by: 0xxm, 9svR6w, BAHOZ, Bobface, CRYP70, Chom, HE1M, Junnon, RaymondFam, UNCHAIN, __141345__, bytehat, carlitox477, caventa, cccz, chaduke, hansfriese, hihen, koxuan, mauricio1802, minhquanym, minhtrng, nicobevi, obront, shung, unforgiven, wait
40.2564 USDC - $40.26
https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L421-L423 https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L77-L95
One of the assets (either baseTokens
or fractionalTokens
) will likely be overpaid when the user calls Pair.add
liquidity.
The Pair.add
function takes baseTokenAmount
, fractionalTokenAmount
and minLpTokenAmount
as inputs. The first two parameters are pulled in from the user (or in the case of Ether as baseToken
have to be sent with the transaction), while the latter is used as slippage control:
function addQuote(uint256 baseTokenAmount, uint256 fractionalTokenAmount) public view returns (uint256) { uint256 lpTokenSupply = lpToken.totalSupply(); if (lpTokenSupply > 0) { // calculate amount of lp tokens as a fraction of existing reserves uint256 baseTokenShare = (baseTokenAmount * lpTokenSupply) / baseTokenReserves(); uint256 fractionalTokenShare = (fractionalTokenAmount * lpTokenSupply) / fractionalTokenReserves(); return Math.min(baseTokenShare, fractionalTokenShare);
The addQuote
function returns the amount of lp tokens minted, as a minimum of the baseTokenShare
and fractionalTokenShare
. In the add
function both amounts are pulled in full though:
_transferFrom(msg.sender, address(this), fractionalTokenAmount); // *** Interactions *** // // mint lp tokens to sender lpToken.mint(msg.sender, lpTokenAmount); // transfer base tokens in if the base token is not ETH if (baseToken != address(0)) { // transfer base tokens in ERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseTokenAmount); }
This will likely cause overpayment for one of the assets as described under #Impact, unless the user choses 0% slippage, in which case his tx will fail, if there are any swaps or liquidity changes happening before the transaction passes.
This could also possibly used in frontrunning attacks, though this has not been checked in-depth for a viable scenario due to time constraints.
Manual Review
#0 - Shungy
2022-12-19T20:48:02Z
Seems valid.
Duplicate: https://github.com/code-423n4/2022-12-caviar-findings/issues/90
#1 - iFrostizz
2022-12-19T20:52:59Z
Seems valid.
Duplicate: #90
The slippage is checked afterwards, in the add()
function that is used to add liquidity. https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L77
Additionally, it actually "pulls the funds necessary" because the pulled funds are those which are supplied by the user.
#2 - Minh-Trng
2022-12-19T21:01:40Z
Seems valid. Duplicate: #90
The slippage is checked afterwards, in the
add()
function that is used to add liquidity. https://github.com/code-423n4/2022-12-caviar/blob/0212f9dc3b6a418803dbfacda0e340e059b8aae2/src/Pair.sol#L77 Additionally, it actually "pulls the funds necessary" because the pulled funds are those which are supplied by the user.
It will pull more than necessary if baseTokenShare
and fractionalTokenShare
have different values. The slippage check does not mitigate that
#3 - Shungy
2022-12-19T21:02:01Z
@iFrostizz , nope, this is not about slippage per se. Please read this report and report #90 carefully.
#4 - c4-judge
2022-12-28T11:04:19Z
berndartmueller marked the issue as duplicate of #376
#5 - c4-judge
2023-01-10T09:01:26Z
berndartmueller changed the severity to 3 (High Risk)
#6 - c4-judge
2023-01-10T09:01:30Z
berndartmueller marked the issue as satisfactory
🌟 Selected for report: Zarf
Also found by: 0xDave, Apocalypto, CRYP70, Franfran, Jeiwan, UNCHAIN, adriro, bytehat, chaduke, hansfriese, hihen, kiki_dev, koxuan, minhtrng, rajatbeladiya, unforgiven, wait, yixxas
45.9386 USDC - $45.94
The buyQuote
is not rounded up, which can cause a leak of value, due to the buyQuote
being underestimated.
The function Pair.buyQuote
does not round up, which can cause the issue described under #Impact:
function buyQuote(uint256 outputAmount) public view returns (uint256) { return (outputAmount * 1000 * baseTokenReserves()) / ((fractionalTokenReserves() - outputAmount) * 997); }
Compare this to the equivalent function of UniswapV2Library:
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) { require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); uint numerator = reserveIn.mul(amountOut).mul(1000); uint denominator = reserveOut.sub(amountOut).mul(997); //[warden-note]: addition of 1 here is equivalent to rounding up amountIn = (numerator / denominator).add(1); }
Manual Review
Add 1 in the Pair.buyQuote
function to round up:
return (outputAmount * 1000 * baseTokenReserves()) / ((fractionalTokenReserves() - outputAmount) * 997) + 1;
#0 - Shungy
2022-12-20T18:11:30Z
Seems technically valid.
I think the severity is Low, because the 0.3% swap fee overcompensates for the 1 wei of rounding error.
#1 - c4-judge
2022-12-28T12:24:27Z
berndartmueller marked the issue as duplicate of #243
#2 - c4-judge
2023-01-10T09:44:47Z
berndartmueller marked the issue as satisfactory