Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $90,500 USDC
Total HM: 47
Participants: 169
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 211
League: ETH
Rank: 133/169
Findings: 1
Award: $35.48
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x3b, 0xAgro, 0xBeirao, 0xMirce, 0xNineDec, 0xRobocop, 0xSmartContract, 0xTraub, 0xWeiss, 2997ms, 41i3xn, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Cryptor, DadeKuma, Deathstore, Deekshith99, DevABDee, DevTimSch, Dewaxindo, Diana, Ermaniwe, Guild_3, H0, IceBear, Inspectah, JDeryl, Kaiziron, Kaysoft, Kenshin, Mukund, Praise, RaymondFam, Rickard, Rolezn, Ruhum, Sathish9098, SkyWalkerMan, SleepingBugs, UdarTeam, Udsen, Walter, aashar, adeolu, apvlki, arialblack14, ast3ros, btk, chaduke, chandkommanaboyina, chrisdior4, climber2002, codetilda, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, descharre, dharma09, doublesharp, eccentricexit, ethernomad, fs0c, georgits, halden, hansfriese, hashminer0725, immeas, lukris02, luxartvinsec, matrix_0wl, merlin, mookimgo, mrpathfindr, nadin, olegthegoat, pavankv, rbserver, rebase, savi0ur, sayan, scokaf, seeu, shark, simon135, tnevler, tsvetanovv, ulqiorra, ustas, waldenyan20, y1cunhui, yongskiws, yosuke
35.4779 USDC - $35.48
This is only a partial report - it is not an exhaustive list of issues with the codebase.
Some files, such as VaultController.sol use 2 spaces, whereas other files such as Vault.sol use 4.
In general there are very few comments throughout the codebase to explain how it works. When writing smart contracts it is essential that every single detail is explained explicitly.
Compared to the whitepaper, the codebase uses incorrect terms to refer to base concepts. This makes it hard to understand the codebase.
The whitepaper refers to Wrapper
, but the codebase uses the term Adapter
.
The whitepaper refers to Modules
, but the codebase uses the term Strategy
.
bytes32 public immutable VAULT = "Vault"; bytes32 public immutable ADAPTER = "Adapter"; bytes32 public immutable STRATEGY = "Strategy"; bytes32 public immutable STAKING = "Staking";
It needs to be explained in a comment that these are contract template categories.
For example, YearnAdapter.maxDeposit() is implementing IERC4626Upgradeable.maxDeposit()
This needs to be put in the comment above the function.
VaultController.deploymentController
being passed redundantlyVaultController.deployVault()
passes VaultController.deploymentController
to VaultController._deployVault()
This is not necessary because VaultController.deploymentController
is available to every method in the contract.
VaultController.deployAdapter
and VaultController.deployStaking
do the same thing.
If it is implemented this way to save gas, this need to be documented in a code comment.
In Vault.deposit()
, feeShares
is calculated from assets
and depositFee
as follows:
uint256 feeShares = convertToShares( assets.mulDiv(uint256(fees.deposit), 1e18, Math.Rounding.Down) );
This is not explained, but it implies that a fee of 1e18 is equivalent to 100%.
In Vault.mint()
, feeShares
is calculated from shares
and depositFee
as follows:
uint256 depositFee = uint256(fees.deposit); uint256 feeShares = shares.mulDiv( depositFee, 1e18 - depositFee, Math.Rounding.Down );
This is the inverse of the math in deposit(), but it needs to be explained why this is correct.
Similar issues exists in calculations involving withdrawalFee
, managementFee
and performanceFee
.
Vault.deposit()
and Vault.mint()
These two functions end up doing the same thing.
Logic at the end of these functions could be replaced by the following internal function:
function _deposit(uint256 assets, uint256 shares, uint256 feeShares, address receiver) internal { if (feeShares > 0) _mint(feeRecipient, feeShares); // Mint vault tokens for the receiver. _mint(receiver, shares); // Attempt to transfer the underlying token from sender to the vault. asset.safeTransferFrom(msg.sender, address(this), assets); // Send the assets from the vault to the adapter (also an ERC4626). adapter.deposit(assets, address(this)); // Log event. emit Deposit(msg.sender, receiver, assets, shares); }
PermissionRegistry
utilizes a strange hackPermissionRegistry
enables addresses to be endorsed, rejected.
VaultController._verifyToken()
can operate in one of two modes.
address(0)
has been endorsed, only token addresses that have been endorsed are acceptable.address(0)
has not been endorsed, token addresses must not have been rejectedVaultController.canCreate()
operates in a similar manner, except it checks address(1)
to determine the operating mode.
This looks like it might have been code not intended for production. If it is intended that the permission operating mode can be switched over, this needs to be implemented as a proper API in PermissionRegistry instead of this strange hack.
Changing from blacklisting to whitelisting could totally screw up the address permissions. This should probably be set at deploy time only.
CloneFactory.deploy()
is badly writtenIt checks the success
variable even if template.requiresInitData
is false.
It could be written more robustly like this:
if (template.requiresInitData) { bool success; (success, ) = clone.call(data); if (!success) revert DeploymentInitFailed(); }
Vault
does not specify that it implements IERC4626Vault
correctly specifies that it is ERC20Upgradeable
, which is IERC20Upgradeable
.
Vault
does not specify that it implements IERC4626
, even though that is its core purpose.
#0 - c4-judge
2023-02-28T15:06:31Z
dmvt marked the issue as grade-b