Platform: Code4rena
Start Date: 21/10/2021
Pot Size: $80,000 ETH
Total HM: 28
Participants: 15
Period: 7 days
Judge: ghoulsol
Total Solo HM: 19
Id: 42
League: ETH
Rank: 5/15
Findings: 4
Award: $6,834.13
🌟 Selected for report: 5
🚀 Solo Findings: 1
🌟 Selected for report: harleythedog
0.9718 ETH - $4,045.16
harleythedog
Right now, there is only one discount profile in the github repo: the "NoDiscountProfile" which does not discount the debt at all. This specific discount profile works correctly, but I claim that any other discount profile will result in liquidation never working.
Suppose that we instead have a discount profile where discount() returns any value strictly larger than 0. Now, suppose someone wants to trigger a liquidation on a position. First, triggerLiquidation will be called (within DutchAuctionLiquidator.sol). The variable "debt" is initialized as equal to vault.currentDebt(_nftId). Notice that currentDebt(_ndfId) (within MochiVault.sol) simply scales the current debt of the position using the liveDebtIndex() function, but there is no discounting being done within the function - this will be important. Back within the triggerLiquidation function, the variable "collateral" is simply calculated as the total collateral of the position. Then, the function calls vault.liquidate(_nftId, collateral, debt), and I claim that this will never work due to underflow. Indeed, the liquidate function will first update the debt of the position (due to the updateDebt(_id) modifier). The debt of the position is thus updated using lines 99-107 in MochiVault.sol. We can see that the details[_id].debt is updated in the exact same way as the calculations for currentDebt(_nftId), however, there is the extra subtraction of the discountedDebt on line 107.
Eventually we will reach line 293 in MochiVault.sol. However, since we discounted the debt in the calculation of details[_id].debt, but we did not discount the debt for the passed in parameter _usdm (and thus is strictly larger in value), line 293 will always error due to an underflow. In summary, any discount profile that actually discounts the debt of the position will result in all liquidations erroring out due to this underflow. Since no positions will be liquidatable, this represents a major flaw in the contract as then no collateral can be liquidated so the entire functionality of the contract is compromised.
Liquidate function in MochiVault.sol: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/vault/MochiVault.sol#:~:text=function-,liquidate,-(
triggerLiquidation function in DutchAuctionLiquidator.sol: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/liquidator/DutchAuctionLiquidator.sol#:~:text=function-,triggerLiquidation,-(address%20_asset%2C%20uint256
Retracing the steps as I have described above, we can see that any call to triggerLiquidation will result in:
details[_id].debt -= _usdm;
throwing an error since _usdm will be larger than details[_id].debt.
Manual inspection.
An easy fix is to simply change:
details[_id].debt -= _usdm;
to be:
details[_id].debt = 0;
as liquidating a position should probably just be equivalent to repaying all of the debt in the position.
Side Note: If there are no other discount profiles planned to be added other than "NoDiscountProfile", then I would recommend deleting all of the discount logic entirely, since NoDiscountProfile doesn't actually do anything
🌟 Selected for report: harleythedog
Also found by: WatchPug
0.4373 ETH - $1,820.32
harleythedog
In MochiVault.sol, the deposit function allows anyone to deposit collateral into any position. A malicious user can call this function with amount = 0, which would reset the amount of time the owner has to wait before they can withdraw their collateral from their position. This is especially troublesome with longer delays, as a malicious user would only have to spend a little gas to lock out all other users from being able to withdraw from their positions, compromising the functionality of the contract altogether.
deposit function here: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/vault/MochiVault.sol#:~:text=function-,deposit,-(uint256%20_id%2C%20uint256
Notice that calling this function with amount = 0 is not disallowed. This overwrites lastDeposit[_id], extending the wait period before a withdraw is allowed.
Manual inspection.
I would recommend adding:
require(amount > 0, "zero")
at the start of the function, as depositing zero collateral does not seem to be a necessary use case to support.
It may also be worthwhile to consider only allowing the owner of a position to deposit collateral.
🌟 Selected for report: loop
Also found by: WatchPug, defsec, gzeon, harleythedog
harleythedog
In MochiVault.sol, there is a storage variable "liquidated" that is never used in the contract. I recommend removing this variable to clean things up and optimize gas.
Link to variable declaration here: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/vault/MochiVault.sol#:~:text=uint256%20public%20liquidated%3B
Manual inspection.
Remove unused variable described above.
#0 - r2moon
2021-10-27T14:37:44Z
duplicated with https://github.com/code-423n4/2021-10-mochi-findings/issues/88
🌟 Selected for report: gzeon
Also found by: harleythedog
harleythedog
In MochiVault.sol within the borrow function, there are 2 require statements on lines 226 and 227 that can be moved to the very top of the function. Moving these two lines to the top of the function would save gas (since you don't have to do any of the calculations on function calls that will ultimately be reverted), and it makes sense in general.
Link to the two require statements here: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/vault/MochiVault.sol#:~:text=)%3B-,require(engine.nft().ownerOf(_id)%20%3D%3D%20msg.sender%2C%20%22!approved%22)%3B,require(engine.nft().asset(_id)%20%3D%3D%20address(asset)%2C%20%22!asset%22)%3B,-if(details%5B_id
I recommend moving these two lines to the very start of the function, similar to in the withdraw function.
Manual inspection.
Move two lines in borrow function to save gas as described above.
#0 - r2moon
2021-10-27T14:16:41Z
duplicated with https://github.com/code-423n4/2021-10-mochi-findings/issues/27
🌟 Selected for report: harleythedog
0.0768 ETH - $319.56
harleythedog
In UsdmMinter.sol, there is a public array called "factories", but this array is never added to or altered anywhere within the contract. So, this array will always be empty and doesn't seem like it would be of any use to anyone, so I recommend removing it to save gas.
Variable declaration here: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/minter/UsdmMinter.sol#:~:text=address%5B%5D%20public%20factories%3B
Manual inspection.
Remove unused variable to optimize gas.
🌟 Selected for report: harleythedog
Also found by: WatchPug
0.0345 ETH - $143.80
harleythedog
In MochiVault.sol within the withdraw function, it is required that engine.nft().ownerOf(_id) == msg.sender at the start of the function. At the end of the function there is a transfer to this address, but the more expensive calculation of engine.nft().ownerOf(_id) is used. It would save gas (and be simpler overall) to just use msg.sender, since we know this is the same value.
See this line here: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/vault/MochiVault.sol#:~:text=asset.cheapTransfer(engine.nft().ownerOf(_id)%2C%20_amount)%3B
I recommend replacing:
asset.cheapTransfer(engine.nft().ownerOf(_id), _amount);
with
asset.cheapTransfer(msg.sender, _amount);
Manual inspection.
Change code as described above to save gas and make things simpler.
🌟 Selected for report: harleythedog
0.0768 ETH - $319.56
harleythedog
On line 100 of DutchAuctionLiquidator.sol (within settleLiquidation), there is a require statement for auction.boughtAt == 0. This is already checked on line 121 within the "buy" function, and this is the only function that can possibly call settleLiquidation, so this require statement always passes. Removing it would save gas.
Link to require statement here: https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/liquidator/DutchAuctionLiquidator.sol#:~:text=require(auction.boughtAt%20%3D%3D%200%2C%20%22liquidated%22)%3B
Manual inspection.
Remove unnecessary require statement described above to save gas.