Platform: Code4rena
Start Date: 24/03/2023
Pot Size: $49,200 USDC
Total HM: 20
Participants: 246
Period: 6 days
Judge: Picodes
Total Solo HM: 1
Id: 226
League: ETH
Rank: 194/246
Findings: 1
Award: $13.13
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: brgltd
Also found by: 0x3b, 0xAgro, 0xGusMcCrae, 0xNorman, 0xRajkumar, 0xSmartContract, 0xTraub, 0xWagmi, 0xWaitress, 0xffchain, 0xhacksmithh, 0xkazim, 0xnev, 3dgeville, ArbitraryExecution, Aymen0909, BRONZEDISC, Bason, Bloqarl, BlueAlder, Brenzee, CodeFoxInc, CodingNameKiki, Cryptor, DadeKuma, DevABDee, Diana, Dug, Englave, Gde, Haipls, HollaDieWaldfee, Ignite, Infect3d, Jerry0x, Josiah, Kaysoft, Koko1912, KrisApostolov, Lavishq, LeoGold, Madalad, PNS, Rappie, RaymondFam, RedTiger, Rickard, Rolezn, Sathish9098, SunSec, T1MOH, UdarTeam, Udsen, Viktor_Cortess, Wander, adriro, ak1, alejandrocovrr, alexzoid, arialblack14, ayden, bin2chen, brevis, btk, c3phas, carlitox477, catellatech, ch0bu, chaduke, ck, climber2002, codeslide, descharre, dingo2077, ernestognw, fatherOfBlocks, favelanky, georgits, helios, hl_, inmarelibero, juancito, ks__xxxxx, lopotras, lukris02, m_Rassska, mahdirostami, maxper, nadin, navinavu, nemveer, p_crypt0, peanuts, pipoca, pixpi, qpzm, rbserver, reassor, roelio, rotcivegaf, scokaf, siddhpurakaran, slvDev, smaul, tnevler, tsvetanovv, turvy_fuzz, vagrant, wen, yac, zzzitron
13.1298 USDC - $13.13
Using import declarations of the form import {<identifier_name>} from "My/Contract.sol"
avoids polluting the symbol namespace making flattened files smaller, and speeds up compilation. Avoid import declarations of the form import "My/Contract.sol;
.
Files: All files.
Consider using named imports
Files: All files.
The NatSpec comments on the does not include the input field. Consider adding the amount argument to Natspec definition. Files:
/** @notice - Convert derivative into ETH */ function withdraw(uint256 amount) external onlyOwner { RocketTokenRETHInterface(rethAddress()).burn(amount); // solhint-disable-next-line (bool sent, ) = address(msg.sender).call{value: address(this).balance}( "" ); require(sent, "Failed to send Ether"); } /** @notice - Get price of derivative in terms of ETH */ function ethPerDerivative(uint256 _amount) public view returns (uint256) { uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets( 10 ** 18 ); return ((10 ** 18 * frxAmount) / IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle()); }
Consider using Solidity latest stable pragma version 0.8.19. Latest stable software have bug fixes and security updates. See: https://swcregistry.io/docs/SWC-102 Files: All Files
Avoid the use of floating pragma and use locked pragma See: https://swcregistry.io/docs/SWC-103 Files: All files.
Use
pragma solidity 0.8.19;
instead of
pragma solidity ^0.8.13;
The _amount
argument is not used in the ethPerDerivative
function of SafEth/derivative/SfrxEth.sol
file.
Consider removing the unused function or provide a comment why it is not used.
File: contracts/SafEth/derivatives/SfrxEth.sol#L111
/** @notice - Get price of derivative in terms of ETH */ function ethPerDerivative(uint256 _amount) public view returns (uint256) { uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets( 10 ** 18 ); return ((10 ** 18 * frxAmount) / IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle()); }
The project uses the openzeppelin/contract version 4.8.0 while the latest version is 4.8.2. Consider using the latest version of openzeppelin/contract as newer version will have security fixes and patches.
File: package.json
The setMaxSlippage
function in the Reth.sol
file does not emit an event when maximum slippage is set. Emitted events are monitored by external systems.
File: contracts/SafEth/derivatives/Reth.sol#L58 function setMaxSlippage(uint256 _slippage) external onlyOwner { maxSlippage = _slippage; }
see: https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
Files: All files Consider adding the Storage Gaps as recommended by openzeppelin.
Files:
Although 100% test coverage is not an indication of lack of bugs, it will aid in catching some easy bugs and also help in audit. Files:
Solidity docs suggests the functions should be laid out in this order :constructor(), receive(), fallback(), external, public, internal, private. However all the files do not follow this pattern.
Files: All files.
The Solidity optimizer is enabled in the project's hardhat.config.ts
file.
see: https://blog.soliditylang.org/2017/05/03/solidity-optimizer-bug/
const config: HardhatUserConfig = { solidity: { version: "0.8.13", settings: { optimizer: { enabled: true, runs: 100000, },
__Ownable_init()
function is not invoked in the initialize functions of all the files.All Smart contract files inherit from openzeppelin's OwnableUpgradable but the __Ownable_init()
function of the OwnableUpgradable contract is not called in the init functions of the smart contracts instead, the transferFrom
function is called.
See: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable
"Another difference between a constructor and a regular function is that Solidity takes care of automatically invoking the constructors of all ancestors of a contract. When writing an initializer, you need to take special care to manually call the initializers of all parent contracts. Note that the initializer modifier can only be called once even when using inheritance, so parent contracts should use the onlyInitializing modifier:"
Files: initialize functions in all files.
function initialize(string memory _tokenName, string memory _tokenSymbol) external initializer { ERC20Upgradeable.__ERC20_init(_tokenName, _tokenSymbol); _transferOwnership(msg.sender); minAmount = 5 * 10 ** 17; // initializing with .5 ETH as minimum maxAmount = 200 * 10 ** 18; // initializing with 200 ETH as maximum }
Consider calling all the parent initialize function ( __Ownable_init()
) in the smart contracts and remove the transferOwnership
function call.
Ownable2StepUpgradable
in place of the OwnableUpgradable
.The smart contracts currently lack 2 step ownership change and can lead to the irrecoverable setting of a new owner. The 2 step ownership change allow for the newOwner to confirm the change and the process can be reversed if a wrong address is set with the transferOwnership
function.
/** * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual override onlyOwner { _pendingOwner = newOwner; emit OwnershipTransferStarted(owner(), newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual override { delete _pendingOwner; super._transferOwnership(newOwner); } /** * @dev The new owner accepts the ownership transfer. */ function acceptOwnership() external { address sender = _msgSender(); require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner"); _transferOwnership(sender); }
#0 - c4-sponsor
2023-04-10T21:02:48Z
toshiSat marked the issue as sponsor acknowledged
#1 - c4-judge
2023-04-24T18:39:49Z
Picodes marked the issue as grade-b