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: 55/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
The buyAndReduceDebt()
function wrongly charges swap fee from PaprController
contract itsself rather than the msg.sender
. As normally the PaprController
contract never holds any underlying asset, so the call to buyAndReduceDebt()
will always revert while params.swapFeeBips != 0
.
Code and audit comments related to the vulnerability
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); // @audit should transfer from msg.sender } _reduceDebt({account: account, asset: collateralAsset, burnFrom: msg.sender, amount: amountOut}); return amountOut; }
And the test case, put it into BuyAndReduceDebt
contract of test\paprController\BuyAndReduceDebt.t.sol
function testBuyAndReduceDebtWithFeeRevert() 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)); uint256 halfToRepay = underlyingOut / 2; uint256 fee = 500; assertGt(underlying.balanceOf(borrower), halfToRepay + halfToRepay * fee / 1e4); underlying.approve(address(controller), halfToRepay + halfToRepay * fee / 1e4); swapParams = IPaprController.SwapParams({ amount: underlyingOut, minOut: 1, sqrtPriceLimitX96: _maxSqrtPriceLimit({sellingPAPR: false}), swapFeeTo: address(5), swapFeeBips: fee }); vm.expectRevert(); controller.buyAndReduceDebt(borrower, collateral.addr, swapParams); }
Run forge test --match-test testBuyAndReduceDebtWithFeeRevert
, and the result
Running 1 test for test/paprController/BuyAndReduceDebt.t.sol:BuyAndReduceDebt [PASS] testBuyAndReduceDebtWithFeeRevert() (gas: 430158) Test result: ok. 1 passed; 0 failed; finished in 633.04ms
foundry
function buyAndReduceDebt(address account, ERC721 collateralAsset, IPaprController.SwapParams calldata params) external override returns (uint256) { // ... if (hasFee) { underlying.safeTransferFrom(msg.sender, params.swapFeeTo, amountIn * params.swapFeeBips / BIPS_ONE); } // ... }
#0 - c4-judge
2022-12-25T13:33:09Z
trust1995 marked the issue as duplicate of #20
#1 - c4-judge
2022-12-25T13:33:18Z
trust1995 marked the issue as satisfactory
#2 - C4-Staff
2023-01-10T22:32:21Z
JeeberC4 marked the issue as duplicate of #196