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: 53/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
https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/src/PaprController.sol#L225-L227 https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/test/paprController/BuyAndReduceDebt.t.sol#L12-L43
When the User specifies a swap fee swapFeeBips
different from 0, the function buyAndReduceDebt
always reverts leaving the swap fee feature useless [DOS].
Furthermore, the current implementation gives the user the ability to transfer any amount of the underlying balance of PaprCopntroller
to the swapFeeTo
specified, even though the controller is not supposed to hold any underlying balance is important to fix this issue for future features where it could hold or hidden possible states.
I ranked this vulnerability as medium risk because of the DOS impact but, the user being able to transfer any amount of the underlying balance of PaprCopntroller
if it somehow would hold any underlying, even though is not very probable, in my opinion makes it eligible for high risk.
Reusing testBuyAndReduceDebtReducesDebtWithFee()
test:
https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/test/paprController/BuyAndReduceDebt.t.sol#L12-L43
I have written the following PoC test initializing the fee variable to a value different from 0, to prove that the execution will always revert:
function testBuyAndReduceDebtReducesDebtWithFee() 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, underlying.balanceOf(borrower)); //FEE VALUE INITIALIZED TO AN INTEGER DIFFERENT TO 0 uint256 fee = 2; underlying.approve(address(controller), underlyingOut + underlyingOut * fee / 1e4); swapParams = IPaprController.SwapParams({ amount: underlyingOut, minOut: 1, sqrtPriceLimitX96: _maxSqrtPriceLimit({sellingPAPR: false}), swapFeeTo: address(5), 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); }
This PoC test always reverts.
Based on the tests on the BuyAndReduceDebt.t.sol
file:
https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/test/paprController/BuyAndReduceDebt.t.sol
My interpretation of the desired functionality of the swap fee is to be transferred directly from the user to the fee receiver. Because on both tests, before initializing the SwapParams
object to call buyAndReduce()
, the controller
is approved the (SwapAmount plus the FeeAmount): underlying.approve(address(controller), underlyingOut + underlyingOut * fee / 1e4);
.
As the fee
variable was not initialized on the tests, this scenario was not being tested.
Therefore the transfer()
function on line 226 of PaprController.sol
.
https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/src/PaprController.sol#L226
underlying.transfer(params.swapFeeTo, amountIn * params.swapFeeBips / BIPS_ONE);
Should be changed to a transferFrom
function with msg.sender
as the from
parameter in order to make this functionality work and prevent future exploits if any time the controller holds underlying balance:
underlying.transferFrom(msg.sender, params.swapFeeTo, amountIn * params.swapFeeBips / BIPS_ONE);
Foundry, Visual Studio
As said earlier the transfer
function should be changed to a transferFrom
function with msg.sender as first parameter.
#0 - c4-judge
2022-12-25T09:54:43Z
trust1995 marked the issue as duplicate of #20
#1 - c4-judge
2022-12-25T09:54:52Z
trust1995 marked the issue as satisfactory
#2 - C4-Staff
2023-01-10T22:32:21Z
JeeberC4 marked the issue as duplicate of #196