Platform: Code4rena
Start Date: 05/01/2023
Pot Size: $90,500 USDC
Total HM: 55
Participants: 103
Period: 14 days
Judge: Picodes
Total Solo HM: 18
Id: 202
League: ETH
Rank: 78/103
Findings: 1
Award: $51.32
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ladboy233
Also found by: 0x1f8b, 0xAgro, 0xSmartContract, 0xbepresent, 0xkato, Aymen0909, CodingNameKiki, Cryptor, Deekshith99, Deivitto, HE1M, IllIllI, Kaysoft, Koolex, PaludoX0, Qeew, RaymondFam, Rolezn, Sathish9098, Tointer, a12jmx, arialblack14, ast3ros, ayeslick, bin2chen, btk, caventa, ch0bu, chaduke, chrisdior4, delfin454000, descharre, evan, fatherOfBlocks, georgits, gz627, jasonxiale, joestakey, kaden, lukris02, nicobevi, nogo, oberon, oyc_109, pfapostol, rbserver, sakshamguruji, seeu, shark, simon135, slvDev, synackrst, tnevler, whilom, zaskoh
51.3151 USDC - $51.32
Number | Issues Details | Context |
---|---|---|
[L-01] | Use safeMint instead of mint for ERC721 | 2 |
[L-02] | ERC4626 does not work with fee-on-transfer tokens | 1 |
[L-03] | ERC4626Cloned 's implmentation is not fully up to EIP-4626's specification | 1 |
[L-04] | Lack of nonReentrant modifier | 2 |
[L-05] | No storage gap for upgradeable contracts | 3 |
[L-06] | Take warnings seriously | 5 |
[L-07] | Solmate's SafeTransferLib doesn't check whether the ERC20 contract exists | 5 |
Number | Issues Details | Context |
---|---|---|
[NC-01] | Lack of event emit | 7 |
[NC-02] | Require messages are too short and unclear | 24 |
[NC-03] | Use bytes.concat() and string.concat() | 11 |
[NC-04] | Lines are too long | 6 |
[NC-05] | Empty blocks should be removed or Emit something | 2 |
[NC-06] | Reusable require statements should be changed to a modifier | 6 |
[NC-07] | Use a more recent version of OpenZeppelin dependencies | 1 |
[NC-08] | Critical changes should use-two step procedure | 1 |
[NC-09] | Include @return parameters in NatSpec comments | All Contracts |
[NC-10] | Function writing does not comply with the Solidity Style Guide | All Contracts |
[NC-11] | The protocol should include NatSpec | 1 |
[NC-12] | Avoid shadowing inherited state variables | 3 |
safeMint
instead of mint for ERC721Users could lost their NFTs if msg.sender
is a contract address that does not support ERC721
, the NFT can be frozen in the contract forever.
As per the documentation of EIP-721:
A wallet/broker/auction application MUST implement the wallet interface if it will accept safe transfers.
As per the documentation of ERC721.sol by Openzeppelin:
Use _safeMint
instead of mint
to check received address support for ERC721 implementation.
function _safeMint( address to, uint256 tokenId, bytes memory data ) internal virtual { _mint(to, tokenId); require( _checkOnERC721Received(address(0), to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer" ); }
The ERC4626-Cloned.deposit/mint functions do not work well with fee-on-transfer tokens as the assets
variable is the pre-fee amount, including the fee, whereas the totalAssets
do not include the fee anymore.
This can be abused to mint more shares than desired.
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) { // Check for rounding error since we round down in previewDeposit. require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES"); require(shares > minDepositAmount(), "VALUE_TOO_SMALL"); // Need to transfer before minting or ERC777s could reenter. ERC20(asset()).safeTransferFrom(msg.sender, address(this), assets); _mint(receiver, shares); emit Deposit(msg.sender, receiver, assets, shares); afterDeposit(assets, shares); }
assets
should be the amount excluding the fee (i.e the amount the contract actually received), therefore it's recommended to use the balance change before and after the transfer instead of the amount.
Must return the maximum amount of shares mint would allow to be deposited to receiver and not cause a revert, which must not be higher than the actual maximum that would be accepted (it should underestimate if necessary).
This assumes that the user has infinite assets, i.e. must not rely on balanceOf of asset.
function maxDeposit(address) public view virtual returns (uint256) { return type(uint256).max; } function maxMint(address) public view virtual returns (uint256) { return type(uint256).max; }
Could cause unexpected behavior in the future due to non-compliance with EIP-4626 standard.
maxMint()
and maxDeposit()
should reflect the limitation of maxSupply.
nonReentrant
modifierIt is a best practice to use the nonReentrant
modifier when you make external calls to untrustable entities because even if an auditor did not think of a way to exploit it, an attacker just might.
For upgradeable contracts, inheriting contracts may introduce new variables. In order to be able to add new variables to the upgradeable contract without causing storage collisions, a storage gap should be added to the upgradeable contract.
In order to be able to add new variables to the upgradeable contract without causing storage collisions, a storage gap should be added to the upgradeable contract.
See this for a description of this storage variable.
Consider adding a storage gap at the end of the upgradeable contract:
/** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap;
If the compiler warns you about something, you should change it. Even if you do not think that this particular warning has security implications, there might be another issue buried beneath it. Any compiler warning we issue can be silenced by slight changes to the code.
Ref: https://docs.soliditylang.org/en/v0.8.17/security-considerations.html#take-warnings-seriously
Solmate's SafeTransferLib.sol
, which is often used to interact with non-compliant/unsafe ERC20 tokens, does not check whether the ERC20 contract exists. The following code will not revert in case the token doesn't exist.
Ref: https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol#L9
Add a contract exist check in functions or use Openzeppelin safeERC20
instead.
The below methods do not emit an event when the state changes, something that it's very important for dApps and users.
The correct and clear error description explains to the user why the function reverts, but the error descriptions below in the project are not self-explanatory. These error descriptions are very important in the debug features of DApps like Tenderly. Error definitions should be added to the require block, not exceeding 32 bytes.
bytes.concat()
and string.concat()
Solidity version 0.8.4 introduces:
bytes.concat()
vs abi.encodePacked(<bytes>,<bytes>)
string.concat()
vs abi.encodePacked(<string>,<string>)
Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length.
Reference: https://docs.soliditylang.org/en/v0.8.10/style-guide.html#maximum-line-length
The empty blocks should do something useful, such as emitting an event or reverting.
modifier onlyOwner() { require(msg.sender == owner()); _; }
For security, it is best practice to use the latest OpenZeppelin version.
"name": "openzeppelin-solidity", "description": "Secure Smart Contract library for Solidity", "version": "4.6.0",
Old version of OpenZeppelin is used (4.6.0)
, newer version can be used (4.7.3)
.
The CollateralToken.sol
inherits the Solmate Auth.sol
contract, which does not have a two-step procedure for critical changes.
function transferOwnership(address newOwner) public virtual requiresAuth { owner = newOwner; emit OwnershipTransferred(msg.sender, newOwner); }
Consider adding two step procedure on the critical functions where the first is announcing a pending new owner and the new address should then claim its ownership.
If Return parameters are declared, you must prefix them with /// @return
. Some code analysis programs do analysis by reading NatSpec details, if they can't see the @return
tag, they do incomplete analysis.
Include the @return
argument in the NatSpec comments.
Solidity Style Guide
Ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
Functions should be grouped according to their visibility and ordered:
constructor()
receive()
fallback()
external / public / internal / private
view / pure
Follow Solidity Style Guide.
It is recommended that Solidity contracts are fully annotated using NatSpec, it is clearly stated in the Solidity official documentation.
In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
Some code analysis programs do analysis by reading NatSpec details, if they can't see the tags (@param, @dev, @return)
, they do incomplete analysis.
Include NatSpec
comments in the codebase.
In PublicVault.sol
there is a local variable named owner
, but there is a function named owner()
in the inherited AstariaVaultBase.sol
with the same name. This use causes compilers to issue warnings, negatively affecting checking and code readability.
function owner() public pure returns (address) { return _getArgAddress(21); //ends at 44 }
Avoid using variables with the same name.
#0 - c4-judge
2023-01-26T15:01:11Z
Picodes marked the issue as grade-b