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: 20/58
Findings: 2
Award: $417.12
π Selected for report: 1
π Solo Findings: 0
π Selected for report: ladboy233
Also found by: 8olidity, __141345__, bin2chen, unforgiven
373.5794 USDC - $373.58
https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/src/PaprController.sol#L365 https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/src/PaprController.sol#L138
Disabled collateral can still be used to mint debt
There is a access control function in PaprController.sol
/// @inheritdoc IPaprController function setAllowedCollateral(IPaprController.CollateralAllowedConfig[] calldata collateralConfigs) external override onlyOwner {
According to IPaprController, if the collateral is disabled set to false, the user should not be allowed to mint debt using the collateral,
/// @notice sets whether a collateral is allowed to be used to mint debt /// @dev owner function /// @param collateralConfigs configuration settings indicating whether a collateral is allowed or not function setAllowedCollateral(IPaprController.CollateralAllowedConfig[] calldata collateralConfigs) external;
However, the code only checks if the collateral is allowed when adding collateral,
function _addCollateralToVault(address account, IPaprController.Collateral memory collateral) internal { if (!isAllowed[address(collateral.addr)]) { revert IPaprController.InvalidCollateral(); }
but does not have the same check when minting debt, then user can use diabled collateral to mint debt.
function _increaseDebt( address account, ERC721 asset, address mintTo, uint256 amount, ReservoirOracleUnderwriter.OracleInfo memory oracleInfo ) internal { uint256 cachedTarget = updateTarget(); uint256 newDebt = _vaultInfo[account][asset].debt + amount; uint256 oraclePrice = underwritePriceForCollateral(asset, ReservoirOracleUnderwriter.PriceKind.LOWER, oracleInfo); uint256 max = _maxDebt(_vaultInfo[account][asset].count * oraclePrice, cachedTarget); if (newDebt > max) revert IPaprController.ExceedsMaxDebt(newDebt, max); if (newDebt >= 1 << 200) revert IPaprController.DebtAmountExceedsUint200(); _vaultInfo[account][asset].debt = uint200(newDebt); PaprToken(address(papr)).mint(mintTo, amount); emit IncreaseDebt(account, asset, amount); }
As shown in the coded POC
We can add the following test to increaseDebt.t.sol
function testIncreaseDebt_POC() public { uint256 debt = 10 ether; // console.log(debt); vm.assume(debt < type(uint200).max); vm.assume(debt < type(uint256).max / controller.maxLTV() / 2); oraclePrice = debt * 2; oracleInfo = _getOracleInfoForCollateral(nft, underlying); vm.startPrank(borrower); nft.approve(address(controller), collateralId); IPaprController.Collateral[] memory c = new IPaprController.Collateral[](1); c[0] = collateral; controller.addCollateral(c); // disable the collateral but still able to mint debt IPaprController.CollateralAllowedConfig[] memory args = new IPaprController.CollateralAllowedConfig[](1); args[0] = IPaprController.CollateralAllowedConfig({ collateral: address(collateral.addr), allowed: false }); vm.stopPrank(); vm.prank(controller.owner()); controller.setAllowedCollateral(args); vm.startPrank(borrower); controller.increaseDebt(borrower, collateral.addr, debt, oracleInfo); assertEq(debtToken.balanceOf(borrower), debt); assertEq(debt, controller.vaultInfo(borrower, collateral.addr).debt); }
We disable the collateral but still able to mint debt by calling increaseDebt
We run the test
forge test -vvv --match testIncreaseDebt_POC
The test pass, but the test should revert.
Running 1 test for test/paprController/IncreaseDebt.t.sol:IncreaseDebtTest [PASS] testIncreaseDebt_POC() (gas: 239301) Test result: ok. 1 passed; 0 failed; finished in 237.42ms
Manual Review
We recommend the project add check to make sure when the collateral is disabled, the collateral should not be used to mint debt
if (!isAllowed[address(collateral.addr)]) { revert IPaprController.InvalidCollateral(); }
#0 - c4-judge
2022-12-25T12:29:02Z
trust1995 marked the issue as satisfactory
#1 - c4-judge
2022-12-25T14:55:43Z
trust1995 marked the issue as primary issue
#2 - c4-judge
2022-12-25T17:56:53Z
trust1995 marked the issue as selected for report
#3 - c4-sponsor
2023-01-03T18:39:12Z
wilsoncusack marked the issue as sponsor confirmed
#4 - wilsoncusack
2023-01-03T18:39:12Z
Hm yeah this was known but the warden is probably right that it makes sense to stop minting more debt with these.
π Selected for report: yixxas
Also found by: 0x52, 0xAgro, 0xSmartContract, 0xhacksmithh, Aymen0909, Bnke0x0, Bobface, Breeje, Diana, Franfran, HE1M, HollaDieWaldfee, IllIllI, Jeiwan, RaymondFam, Rolezn, SaharDevep, Secureverse, SmartSek, ak1, bin2chen, brgltd, chrisdior4, gz627, imare, ladboy233, lukris02, oyc_109, rvierdiiev, shark, tnevler, unforgiven, wait
43.5439 USDC - $43.54
https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/src/PaprController.sol#L444 https://github.com/with-backed/papr/blob/9528f2711ff0c1522076b9f93fba13f88d5bd5e6/src/PaprController.sol#L101
Crypto punk cannot be used a collateral.
Cryptopunks are at the core of the NFT ecosystem. As one of the first NFTs, it embodies the culture of NFT. By not supporting the cryptopunk, backed is at a severe disadvantage when compared to other lending marketplace.
In the documentation:
https://backed.mirror.xyz/8SslPvU8of0h-fxoo6AybCpm51f30nd0qxPST8ep08c
the doc use punk as an example,
Letβs go through an example. Suppose there is a papr token protocol defined by the example values in the table above.
Lending criteria: only PUNK collateral
Collateral price oracle: Trailing 30 day floor price on OpenSea
Max loan to value ratio: 50%
Underlying token: USDC
however, the protocol does not support crypto punk NFT
The crypto punk smart contract implementation comes before the ERC721 NFT standard mature
https://etherscan.io/token/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb#writeContract
The transfer method for crypto punk is
transferPunk
but in the implementation in PaprController when adding and removing collateral
all standard ERC721 transfer method and safeTransferom is used.
// allows for onReceive hook to sell and repay debt before the // debt check below collateral.addr.safeTransferFrom(address(this), sendTo, collateral.id);
and
_addCollateralToVault(msg.sender, collateralArr[i]); collateralArr[i].addr.transferFrom(msg.sender, address(this), collateralArr[i].id);
Manual Review
We recommend the project support wrapped punk or implement the logic to support crypto punk transfer.
#0 - trust1995
2022-12-25T10:09:34Z
Previously standardized at the C4 org level, QA
#1 - c4-judge
2022-12-25T10:09:42Z
trust1995 changed the severity to QA (Quality Assurance)
#2 - c4-judge
2022-12-25T17:20:17Z
trust1995 marked the issue as grade-b