Platform: Code4rena
Start Date: 16/12/2022
Pot Size: $60,500 USDC
Total HM: 12
Participants: 58
Period: 5 days
Judge: Trust
Total Solo HM: 4
Id: 196
League: ETH
Rank: 57/58
Findings: 1
Award: $33.40
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Jeiwan
Also found by: 0x52, Franfran, HollaDieWaldfee, KingNFT, Saintcode_, bin2chen, evan, fs0c, noot, poirots, rvierdiiev, stealthyz, teawaterwire, unforgiven
33.3998 USDC - $33.40
In PaprController#buyAndReduceDebt, the user has the ability to pay debt using the underlying token, delegating to the contract swapping the tokens for Papr tokens.
function buyAndReduceDebt(address account, ERC721 collateralAsset, IPaprController.SwapParams calldata params) external override returns (uint256) { bool hasFee = params.swapFeeBips != 0; (uint256 amountOut, uint256 amountIn) = UniswapHelpers.swap( pool, account, token0IsUnderlying, params.amount, params.minOut, params.sqrtPriceLimitX96, abi.encode(msg.sender) ); if (hasFee) { underlying.transfer(params.swapFeeTo, amountIn * params.swapFeeBips / BIPS_ONE); } _reduceDebt({account: account, asset: collateralAsset, burnFrom: msg.sender, amount: amountOut}); return amountOut; }
Alternatively to increaseDebtAndSell, during the transaction the controller never has any underlying tokens, so in any instance where the fee is set up to be paid, it will fail.
Confirm the behavior with the following test, adapted from BuyAndReduceDebt
.
function testFee() public { vm.startPrank(borrower); nft.approve(address(controller), collateralId); IPaprController.Collateral[] memory c = new IPaprController.Collateral[](1); c[0] = collateral; controller.addCollateral(c); IPaprController.SwapParams memory swapParams = IPaprController.SwapParams({ amount: debt,//*2, minOut: 982507,//*2, sqrtPriceLimitX96: _maxSqrtPriceLimit({sellingPAPR: true}), swapFeeTo: address(0), swapFeeBips: 0 }); uint256 underlyingOut = controller.increaseDebtAndSell(borrower, collateral.addr, swapParams, oracleInfo); IPaprController.VaultInfo memory vaultInfo = controller.vaultInfo(borrower, collateral.addr); uint256 fee = 100; //@audit setting up a fee. When fee is zero, the test doesn't fail. underlying.approve(address(controller), underlyingOut + underlyingOut * fee / 1e4); uint160 priceLimit = _maxSqrtPriceLimit({sellingPAPR: false}); uint256 out = quoter.quoteExactInputSingle({ tokenIn: address(underlying), tokenOut: address(controller.papr()), fee: 10000, amountIn: underlyingOut * 75 / 100, //@audit paying 75% of debt sqrtPriceLimitX96: priceLimit }); swapParams = IPaprController.SwapParams({ amount: underlyingOut * 75 / 100, minOut: out, sqrtPriceLimitX96: _maxSqrtPriceLimit({sellingPAPR: false}), swapFeeTo: address(5), swapFeeBips: fee }); controller.buyAndReduceDebt(borrower, collateral.addr, swapParams); //@audit when fee is set up results in "Arithmetic over/underflow" vaultInfo = controller.vaultInfo(borrower, collateral.addr); }
Manual, Foundry
Use transferFrom
if it is expected to pay using the users permission or ensure that the controller has enough underlying tokens to pay the expected amount.
#0 - c4-judge
2022-12-25T15:23:44Z
trust1995 marked the issue as duplicate of #20
#1 - c4-judge
2022-12-25T15:23:51Z
trust1995 marked the issue as satisfactory
#2 - C4-Staff
2023-01-10T22:32:18Z
JeeberC4 marked the issue as duplicate of #196