Platform: Code4rena
Start Date: 07/07/2023
Pot Size: $121,650 USDC
Total HM: 36
Participants: 111
Period: 7 days
Judge: Picodes
Total Solo HM: 13
Id: 258
League: ETH
Rank: 61/111
Findings: 2
Award: $114.54
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Udsen
Also found by: 0xMirce, 0xPsuedoPandit, 0xStalin, 0xbepresent, Aymen0909, Bobface, Co0nan, GREY-HAWK-REACH, Jeiwan, John, KupiaSec, LuchoLeonel1, Nyx, Praise, RedTiger, alexweb3, bin2chen, btk, dacian, dirk_y, josephdara, keccak123, ktg, mahdirostami, markus_ether, minhtrng, ni8mare, peanuts, ptsanev, ravikiranweb3, rvierdiiev, seeques, serial-coder, shaka, teawaterwire, wangxx2026, zzzitron
2.2492 USDC - $2.25
Anyone can steal the yield fees.
The mintYieldFee()
function in Vault.sol has no modifier and thus anyone can call this function to mint yield fees to themselves.
function mintYieldFee(uint256 _shares, address _recipient) external { _requireVaultCollateralized(); if (_shares > _yieldFeeTotalSupply) revert YieldFeeGTAvailable(_shares, _yieldFeeTotalSupply); _yieldFeeTotalSupply -= _shares; _mint(_recipient, _shares); emit MintYieldFee(msg.sender, _recipient, _shares); }
There is an assigned yieldFeeRecipient_
done in the constructor and the owner can change the yield fee recipient by calling setYieldFeeRecipient()
. The fees should only go to the yield fee recipient.
constructor( _setYieldFeeRecipient(yieldFeeRecipient_);
function setYieldFeeRecipient(address yieldFeeRecipient_) external onlyOwner returns (address) { address _previousYieldFeeRecipient = _yieldFeeRecipient; _setYieldFeeRecipient(yieldFeeRecipient_); emit YieldFeeRecipientSet(_previousYieldFeeRecipient, yieldFeeRecipient_); return yieldFeeRecipient_; }
Manual Review
Make sure mintYieldFee()
has an OnlyOwner modifier or lock the recipient to the yieldFeeRecipient_
set in _setYieldFeeRecipient()
function _setYieldFeeRecipient(address yieldFeeRecipient_) internal { _yieldFeeRecipient = yieldFeeRecipient_; }
Context
#0 - c4-judge
2023-07-16T22:11:08Z
Picodes marked the issue as duplicate of #396
#1 - c4-judge
2023-08-05T22:03:52Z
Picodes changed the severity to 3 (High Risk)
#2 - c4-judge
2023-08-05T22:04:32Z
Picodes marked the issue as satisfactory
🌟 Selected for report: 0xStalin
Also found by: 0xSmartContract, 0xWaitress, 3docSec, ABAIKUNANBAEV, DadeKuma, Jeiwan, K42, catellatech, dirk_y, keccak123, peanuts, squeaky_cactus
112.2875 USDC - $112.29
Focused solely on the Vault, TWAB controller and TWAB library.
Check the resultant amount of shares/assets when depositing / withdrawing from the vault. Make sure that the shares and assets received after converting is not zero. If it is zero, make sure to revert. For example:
function deposit(uint256 _assets, address _receiver) public virtual override returns (uint256) { if (_assets > maxDeposit(_receiver)) revert DepositMoreThanMax(_receiver, _assets, maxDeposit(_receiver)); uint256 _shares = _convertToShares(_assets, Math.Rounding.Down); + require(_shares != 0, "Zero Shares"); _deposit(msg.sender, _receiver, _assets, _shares); Fee-on-transfer tokens, rebasing tokens and low-decimal tokens may affect the vault and accounting so maybe can check the decimal places of the asset during constructions, and set a whitelist of the assets used. Also, safeApprove is used, and I believe USDT doesn't work through safeApprove but have to set the value to zero first.
Codebase is pretty complicated, so it'll be good if there is a step-by-step walkthrough for all the points and examples for the Math used.
Good amount of NATSPECs, the external calls is a little hard to grasp, especially jumping from Vault to TwAB controller to TWAB library and back. I see that alot of thought and effort is put into this new version, with the extensive test coverage as well.
The two-vault scenario is pretty novel and interesting, but extremely confusing to me. I believe that the Vault doesn't actually mint shares, but call the TWAB controller to store the amount of shares in the vault. The owner still receives shares from the yield vault and virtual shares from the TWAB controller. I would like to see how it plays out, especially since I am also a user of PoolTogether.
The Vault is permissionless and the Yield Vault is also not checked. This invites alot of complication and trust issues with the user and the protocol because users has to trust that the Vault is not malicious in order to deposit and interact with the protocol. Would be good if there are some Protocol-initiated Vaults to increase user's trust.
I don't see a failsafe measure to restore funds in case funds get stuck in the protocol. Maybe can introduce the emergency withdraw function through a multisig in case any part of the protocol gets compromised, especially so when the protocol is going permissionless
10 hours
#0 - c4-judge
2023-07-18T18:44:23Z
Picodes marked the issue as grade-b
#1 - c4-sponsor
2023-07-20T23:40:24Z
asselstine marked the issue as sponsor confirmed
#2 - PierrickGT
2023-09-11T23:16:40Z