Mochi contest - leastwood's results

Next-Gen Decentralized Digital Currency Backed By Long-Tail Cryptoassets.

General Information

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

Mochi

Findings Distribution

Researcher Performance

Rank: 3/15

Findings: 9

Award: $13,459.47

🌟 Selected for report: 6

🚀 Solo Findings: 5

Findings Information

🌟 Selected for report: gpersoon

Also found by: jonah1005, leastwood

Labels

bug
duplicate
3 (High Risk)

Awards

0.2624 ETH - $1,092.19

External Links

Handle

leastwood

Vulnerability details

Impact

registerAsset() is used to add assets to the Mochi Protocol. These assets have an associated asset class which represents an asset's risk factor. registerAsset() can be called by any user and abused in such a way that existing assets can have their respective risk factors reset to Sigma (the riskiest class). This ultimately affects liquidations, fees and the overall health of the system. A user could abuse this to earn fees by liquidating positions that were considered less risky but due to the abuse of this function, have now become risky.

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/profile/MochiProfileV0.sol#L58-L62

Tools Used

Manual code review. Discussions with the Mochi team.

Consider adding the onlyGov modifier to the registerAsset() function and checking if an asset has already been registered before making changes.

#0 - r2moon

2021-10-27T15:50:44Z

Findings Information

🌟 Selected for report: leastwood

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

0.9718 ETH - $4,045.16

External Links

Handle

leastwood

Vulnerability details

Impact

The FeePoolV0.sol contract accrues fees upon the liquidation of undercollaterised positions. These fees are split between treasury and vMochi contracts. However, when distributeMochi() is called to distribute mochi tokens to veCRV holders, both mochiShare and treasuryShare is flushed from the contract when there are still usdm tokens in the contract.

Proof of Concept

Consider the following scenario:

  • The FeePoolV0.sol contract contains 100 usdm tokens at an exchange rate of 1:1 with mochi tokens.
  • updateReserve() is called to set the split of usdm tokens such that treasuryShare has claim on 20 usdm tokens and mochiShare has claim on the other 80 tokens.
  • A veCRV holder seeks to increase their earnings by calling distributeMochi() before sendToTreasury() has been called.
  • As a result, 80 usdm tokens are converted to mochi tokens and locked in a curve rewards pool.
  • Consequently, mochiShare and treasuryShare is set to 0 (aka flushed).
  • The same user calls updateReserve() to split the leftover 20 usdm tokens between treasuryShare and mochiShare.
  • mochiShare is now set to 16 usdm tokens.
  • The above process is repeated to distribute mochi tokens to veCRV holders again and again.
  • The end result is that veCRV holders have been able to receive all tokens that were intended to be distributed to the treasury.

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/feePool/FeePoolV0.sol#L94

Tools Used

Manual code review Discussions with the Mochi team.

Consider removing the line in FeePoolV0.sol (mentioned above), where treasuryShare is flushed.

Findings Information

🌟 Selected for report: leastwood

Labels

bug
3 (High Risk)
sponsor confirmed

Awards

0.9718 ETH - $4,045.16

External Links

Handle

leastwood

Vulnerability details

Impact

The VestedRewardPool.sol contract is a public facing contract aimed at vesting tokens for a minimum of 90 days before allowing the recipient to withdraw their mochi. The vest() function does not utilise safeTransferFrom() to ensure that vested tokens are correctly allocated to the recipient. As a result, it is possible to frontrun a call to vest() and effectively steal a recipient's vested tokens. The same issue applies to the lock() function.

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/emission/VestedRewardPool.sol#L36-L46 https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/emission/VestedRewardPool.sol#L54-L64

Tools Used

Manual code review Discussions with the Mochi team

Ensure that users understand that this function should not be interacted directly as this could result in lost mochi tokens. Additionally, it might be worthwhile creating a single externally facing function which calls safeTransferFrom(), vest() and lock() in a single transaction.

Findings Information

🌟 Selected for report: loop

Also found by: WatchPug, cmichel, defsec, gzeon, leastwood, nikitastupin, pants

Labels

bug
duplicate
2 (Med Risk)

Awards

0.0174 ETH - $72.55

External Links

Handle

leastwood

Vulnerability details

Impact

settleLiquidation() does not validate the return value of an ERC20 transfer() call. As a result, if the call to transfer does not revert but instead returns a boolean or void value, it may be possible that a user liquidates a user's position but does not receive the associated collateral. Therefore, all interactions with asset throughout the Mochi protocol should be replaced with OpenZeppelin's SafeERC20 equivalent.

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/liquidator/DutchAuctionLiquidator.sol#L107

Tools Used

Manual code review Discussions with the Mochi team

Consider utilising OpenZeppelin's SafeERC20 library in all interactions with the asset token. In this case asset.transfer(msg.sender, _collateral) in DutchAuctionLiquidator.sol should be replaced with asset.safeTransfer(msg.sender, _collateral) at the very least.

#0 - r2moon

2021-10-29T15:23:15Z

Findings Information

🌟 Selected for report: nikitastupin

Also found by: WatchPug, cmichel, defsec, leastwood

Labels

bug
duplicate
2 (Med Risk)

Awards

0.0383 ETH - $159.24

External Links

Handle

leastwood

Vulnerability details

Impact

ChainlinkAdapter.getPrice() queries a Chainlink oracle to retrieve the latest price for a given asset. However, this external call does not validate the data retrieved is fresh.

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-cssr/contracts/adapter/ChainlinkAdapter.sol#L49 https://docs.chain.link/docs/faq/#how-can-i-check-if-the-answer-to-a-round-is-being-carried-over-from-a-previous-round

Tools Used

Manual code review

Consider adding the following validations to ChainlinkAdapter.getPrice().

( uint80 roundId, int256 price, , uint256 updateTime, uint80 answeredInRound ) = feed[_asset].latestRoundData(); require(price > 0, "Chainlink price <= 0"); require(updateTime != 0, "Incomplete round"); require(answeredInRound >= roundId, "Stale price");

This ensures that stale prices are not used when querying ChainlinkAdapter.sol.

#0 - r2moon

2021-10-29T12:58:00Z

Findings Information

🌟 Selected for report: leastwood

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

0.2915 ETH - $1,213.55

External Links

Handle

leastwood

Vulnerability details

Impact

The BeaconProxyDeployer.deploy() function is used to deploy lightweight proxy contracts that act as each asset's vault. The function does not revert properly if there is a failed contract deployment or revert from the create2 opcode as it does not properly check the returned address for bytecode. The create2 opcode returns the expected address which will never be the zero address (as is what is currently checked).

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-library/contracts/BeaconProxyDeployer.sol#L31

Tools Used

Manual code review Discussions with the Mochi team Discussions with library dev

The recommended mitigation was to update iszero(result) to iszero(extcodesize(result)) in the line mentioned above. This change has already been made in the corresponding library which can be found here, however, this needs to also be reflected in Mochi's contracts.

Findings Information

🌟 Selected for report: leastwood

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

0.2915 ETH - $1,213.55

External Links

Handle

leastwood

Vulnerability details

Impact

withdrawLock() does not prevent users from calling this function when locking has been toggled. As a result, withdraws may be made unexpectedly.

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/treasury/MochiTreasuryV0.sol#L40-L42

Tools Used

Manual code review

Consider adding require(lockCrv, "!lock"); to withdrawLock() to ensure this function is not called unexpectedly. Alternatively if this is intended behaviour, it should be rather checked that the lock has not been toggled, otherwise users could maliciously relock tokens.

Findings Information

🌟 Selected for report: leastwood

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

0.2915 ETH - $1,213.55

External Links

Handle

leastwood

Vulnerability details

Impact

MochiTreasuryV0.sol interacts with Curve's voting escrow contract to lock tokens for 90 days, where it can be later withdrawn by the governance role. However, VotingEscrow.vy does not allow contracts to call the following functions; create_lock(), increase_amount() and increase_unlock_time(). For these functions, msg.sender must be an EOA account or an approved smart wallet. As a result, any attempt to lock tokens will fail in MochiTreasuryV0.sol.

Proof of Concept

https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L418 https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L438 https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/VotingEscrow.vy#L455

Tools Used

Manual code review Discussions with the Mochi team

Consider updating this contract to potentially use another escrow service that enables msg.sender to be a contract. Alternatively, this escrow functionality can be replaced with an internal contract which holds usdm tokens instead, removing the need to convert half of the tokens to Curve tokens. Holding Curve tokens for a minimum of 90 days may overly expose the Mochi treasury to Curve token price fluctuations.

Findings Information

🌟 Selected for report: leastwood

Labels

bug
1 (Low Risk)

Awards

0.0972 ETH - $404.52

External Links

Handle

leastwood

Vulnerability details

Impact

The flashFee() function differs to the implementation found in usdm.flashFee() due to a missing SCALE variable which ensures the result is not truncated. The end result for MochiVault.flashFee() is that users could end up paying a slightly lower fee than intended if _amount is somewhat small.

This could abused by calling flashLoan() with a small _amount input and continually reentering the contract before using the actual funds. This enables a user to pay marginally less in fees as compared to a typical user.

If the asset being held in the vault is configured with a low decimals value, the degree of truncation could be significant, potentially resulting in little to no fees paid back into MochiVault.sol.

Proof of Concept

https://github.com/code-423n4/2021-10-mochi/blob/main/projects/mochi-core/contracts/vault/MochiVault.sol#L345-L354

Tools Used

Manual code review

Consider updating return (_amount * 1337) / 1000000; in MochiVault.flashLoan() to return (_amount * ((1337 * SCALE) / 1000000)) / SCALE; where SCALE = 1e18.

#0 - r2moon

2021-10-29T10:13:13Z

return (_amount * 1337) / 1000000; and return (_amount * ((1337 * SCALE) / 1000000)) / SCALE; could return same amount i think.

#1 - ghoul-sol

2021-11-02T16:44:05Z

I see a risk of rounding errors but I think this is low risk since small amount flash loans are inpractical

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter