Platform: Code4rena
Start Date: 02/06/2023
Pot Size: $100,000 USDC
Total HM: 15
Participants: 75
Period: 7 days
Judge: Picodes
Total Solo HM: 5
Id: 249
League: ETH
Rank: 40/75
Findings: 2
Award: $40.19
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: RaymondFam
Also found by: 0xWaitress, 0xhacksmithh, ChrisTina, DadeKuma, LaScaloneta, Rolezn, SAAJ, Sathish9098, T1MOH, bin2chen, btk, catellatech, ernestognw, fatherOfBlocks, hals, hunter_w3b, jaraxxus, matrix_0wl, mgf15, naman1778, niser93, solsaver, turvy_fuzz
18.5651 USDC - $18.57
| Low-1 | abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
| 1 |
| Low-2 | Initializers could be front-run | 49 |
| Low-3 | Unsafe ERC20 operation(s) | 7 |
| Low-4 | The owner
is a single point of failure and a centralization risk | 2 |
| Low-5 | Loss of precision | 11 |
| Low-6 | Void constructor | 3 |
| Low-7 | Keccak
Constant values should used to immutable
rather than constant
| 47 |
| Low-8 | Gas griefing/theft is possible on unsafe external call | 7 |
| Low-9 | Use safetransfer
Instead Of transfer
| 4 |
| Low-10 | Use _safeMint
instead of _mint
| 1 |
| Low-11 | Division before multiplication causing significant loss of precision | 1 |
abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
Use abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456)
=> 0x123456
=> abi.encodePacked(0x1,0x23456)
, but abi.encode(0x123,0x456)
=> 0x0...1230...456
). Unless there is a compelling reason, abi.encode
should be preferred. If there is only one argument to abi.encodePacked()
it can often be cast to bytes()
or bytes32()
instead.
If all arguments are strings and or bytes, bytes.concat()
should be used instead
</details> *Instances (1)*File:2023-06-stader/contracts/SocializingPool.sol 174: bytes32 node = keccak256(abi.encodePacked(_operator, _amountSD, _amountETH));
Initializers could be front-run, allowing an attacker to either set their own values, take ownership of the contract, and in the best case forcing a re-deployment
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/Auction.sol 29: function initialize(address _admin, address _staderConfig) external initializer { 33: __AccessControl_init(); 34: __Pausable_init(); 35: __ReentrancyGuard_init();
File:2023-06-stader/contracts/ETHx.sol 29: function initialize(address _admin, address _staderConfig) public initializer { 33: __ERC20_init('ETHx', 'ETHx'); 34: __Pausable_init(); 35: __AccessControl_init();
File:2023-06-stader/contracts/OperatorRewardsCollector.sol 27: function initialize(address _admin, address _staderConfig) external initializer { 31: __AccessControl_init(); 32: __Pausable_init();
File:2023-06-stader/contracts/Penalty.sol 31: function initialize( 35: ) external initializer { 40: __ReentrancyGuard_init();
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 66: function initialize(address _admin, address _staderConfig) public initializer { 70: __Pausable_init(); 71: __ReentrancyGuard_init();
File:2023-06-stader/contracts/PermissionedPool.sol 40: function initialize(address _admin, address _staderConfig) public initializer { 44: __ReentrancyGuard_init();
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 66: function initialize(address _admin, address _staderConfig) public initializer { 70: __Pausable_init(); 71: __ReentrancyGuard_init();
File:2023-06-stader/contracts/PermissionlessPool.sol 38: function initialize(address _admin, address _staderConfig) public initializer { 42: __ReentrancyGuard_init();
File:2023-06-stader/contracts/PoolSelector.sol 36: function initialize(address _admin, address _staderConfig) external initializer {
File:2023-06-stader/contracts/PoolUtils.sol 25: function initialize(address _admin, address _staderConfig) public initializer {
File:2023-06-stader/contracts/SDCollateral.sol 26: function initialize(address _admin, address _staderConfig) public initializer { 30: __AccessControl_init(); 31: __ReentrancyGuard_init();
File:2023-06-stader/contracts/SocializingPool.sol 39: function initialize(address _admin, address _staderConfig) public initializer { 43: __AccessControl_init(); 44: __Pausable_init(); 45: __ReentrancyGuard_init();
File:2023-06-stader/contracts/StaderConfig.sol 85: function initialize(address _admin, address _ethDepositContract) external initializer { 88: __AccessControl_init();
File:2023-06-stader/contracts/StaderInsuranceFund.sol 26: function initialize(address _admin, address _staderConfig) public initializer { 30: __ReentrancyGuard_init();
File:2023-06-stader/contracts/StaderOracle.sol 62: function initialize(address _admin, address _staderConfig) public initializer { 66: __AccessControl_init(); 67: __Pausable_init(); 68: __ReentrancyGuard_init();
File:2023-06-stader/contracts/StaderStakePoolsManager.sol 50: function initialize(address _admin, address _staderConfig) public initializer { 53: __AccessControl_init(); 54: __Pausable_init(); 55: __ReentrancyGuard_init();
File:2023-06-stader/contracts/UserWithdrawalManager.sol 54: function initialize(address _admin, address _staderConfig) public initializer { 58: __Pausable_init(); 59: __ReentrancyGuard_init();
</details> *Instances (49)*File:2023-06-stader/contracts/factory/VaultFactory.sol 23: function initialize(address _admin, address _staderConfig) external initializer {
ERC20 operations can be unsafe due to different implementations and vulnerabilities in the standard. It is therefore recommended to always either use OpenZeppelin's SafeERC20 library or at least to wrap each operation in a require statement.
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/Auction.sol 55: if (!IERC20(staderConfig.getStaderToken()).transferFrom(msg.sender, address(this), _sdAmount)) { 87: if (!IERC20(staderConfig.getStaderToken()).transfer(lotItem.highestBidder, lotItem.sdAmount)) { 114: if (!IERC20(staderConfig.getStaderToken()).transfer(staderConfig.getStaderTreasury(), _sdAmount)) {
File:2023-06-stader/contracts/SDCollateral.sol 47: if (!IERC20(staderConfig.getStaderToken()).transferFrom(operator, address(this), _sdAmount)) { 68: if (!IERC20(staderConfig.getStaderToken()).transfer(payable(operator), _requestedSD)) { 106: IERC20(staderConfig.getStaderToken()).approve(auctionContract, type(uint256).max);
</details> *Instances (7)*File:2023-06-stader/contracts/SocializingPool.sol 129: if (!IERC20(staderConfig.getStaderToken()).transfer(operatorRewardsAddr, totalAmountSD)) {
owner
is a single point of failure and a centralization riskHaving a single EOA as the only owner of contracts is a large centralization risk and a single point of failure. A single private key may be taken in a hack, or the sole holder of the key may become unable to retrieve the key when necessary. Consider changing to a multi-signature setup, or having a role-based authorization model.
<details><summary><i>instances of this issue:</i></summary></details> *Instances (2)*File:2023-06-stader/contracts/VaultProxy.sol 57: function updateStaderConfig(address _staderConfig) external override onlyOwner { 68: function updateOwner(address _owner) external override onlyOwner {
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 201: uint256 validatorPerOperator = _numValidators / totalActiveOperatorCount;
File:2023-06-stader/contracts/PermissionlessPool.sol 128: uint256 requiredValidators = msg.value / (staderConfig.getFullDepositSize() - DEPOSIT_NODE_BOND);
File:2023-06-stader/contracts/PoolSelector.sol 61: uint256 poolTotalTarget = (poolWeights[_poolId] * totalValidatorsRequired) / POOL_WEIGHTS_SUM; 93: uint256 remainingValidatorsToDeposit = ethToDeposit / poolDepositSize;
File:2023-06-stader/contracts/PoolUtils.sol 263: protocolShare = (protocolFeeBps * _userShareBeforeCommision) / staderConfig.getTotalFee(); 265: operatorShare = (_totalRewards * collateralETH) / TOTAL_STAKED_ETH; 266: operatorShare += (operatorFeeBps * _userShareBeforeCommision) / staderConfig.getTotalFee();
File:2023-06-stader/contracts/StaderOracle.sol 255: if ((submissionCount == trustedNodesCount / 2 + 1)) { 290: if ((submissionCount == (2 * trustedNodesCount) / 3 + 1)) { 482: if ((submissionCount == trustedNodesCount / 2 + 1)) {
</details> *Instances (11)*File:2023-06-stader/contracts/UserWithdrawalManager.sol 136: uint256 minEThRequiredToFinalizeRequest = Math.min(requiredEth, (lockedEthX * exchangeRate) / DECIMALS);
Detect the call to a constructor that is not implemented.
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/NodeELRewardVault.sol 14: constructor() {}
File:2023-06-stader/contracts/ValidatorWithdrawalVault.sol 23: constructor() {}
</details> *Instances (3)*File:2023-06-stader/contracts/VaultProxy.sol 17: constructor() {}
Keccak
Constant values should used to immutable
rather than constant
There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts.
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/ETHx.sol 21: bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE'); 22: bytes32 public constant BURNER_ROLE = keccak256('BURNER_ROLE');
File:2023-06-stader/contracts/StaderConfig.sol 12: bytes32 public constant ETH_PER_NODE = keccak256('ETH_PER_NODE'); 14: bytes32 public constant PRE_DEPOSIT_SIZE = keccak256('PRE_DEPOSIT_SIZE'); 16: bytes32 public constant FULL_DEPOSIT_SIZE = keccak256('FULL_DEPOSIT_SIZE'); 18: bytes32 public constant DECIMALS = keccak256('DECIMALS'); 20: bytes32 public constant TOTAL_FEE = keccak256('TOTAL_FEE'); 22: bytes32 public constant OPERATOR_MAX_NAME_LENGTH = keccak256('OPERATOR_MAX_NAME_LENGTH'); 24: bytes32 public constant SOCIALIZING_POOL_CYCLE_DURATION = keccak256('SOCIALIZING_POOL_CYCLE_DURATION'); 27: bytes32 public constant REWARD_THRESHOLD = keccak256('REWARD_THRESHOLD'); 28: bytes32 public constant MIN_DEPOSIT_AMOUNT = keccak256('MIN_DEPOSIT_AMOUNT'); 29: bytes32 public constant MAX_DEPOSIT_AMOUNT = keccak256('MAX_DEPOSIT_AMOUNT'); 30: bytes32 public constant MIN_WITHDRAW_AMOUNT = keccak256('MIN_WITHDRAW_AMOUNT'); 31: bytes32 public constant MAX_WITHDRAW_AMOUNT = keccak256('MAX_WITHDRAW_AMOUNT'); 35: bytes32 public constant WITHDRAWN_KEYS_BATCH_SIZE = keccak256('WITHDRAWN_KEYS_BATCH_SIZE'); 37: bytes32 public constant ADMIN = keccak256('ADMIN'); 38: bytes32 public constant STADER_TREASURY = keccak256('STADER_TREASURY'); 40: bytes32 public constant override POOL_UTILS = keccak256('POOL_UTILS'); 41: bytes32 public constant override POOL_SELECTOR = keccak256('POOL_SELECTOR'); 42: bytes32 public constant override SD_COLLATERAL = keccak256('SD_COLLATERAL'); 43: bytes32 public constant override OPERATOR_REWARD_COLLECTOR = keccak256('OPERATOR_REWARD_COLLECTOR'); 44: bytes32 public constant override VAULT_FACTORY = keccak256('VAULT_FACTORY'); 45: bytes32 public constant override STADER_ORACLE = keccak256('STADER_ORACLE'); 46: bytes32 public constant override AUCTION_CONTRACT = keccak256('AuctionContract'); 47: bytes32 public constant override PENALTY_CONTRACT = keccak256('PENALTY_CONTRACT'); 48: bytes32 public constant override PERMISSIONED_POOL = keccak256('PERMISSIONED_POOL'); 49: bytes32 public constant override STAKE_POOL_MANAGER = keccak256('STAKE_POOL_MANAGER'); 50: bytes32 public constant override ETH_DEPOSIT_CONTRACT = keccak256('ETH_DEPOSIT_CONTRACT'); 51: bytes32 public constant override PERMISSIONLESS_POOL = keccak256('PERMISSIONLESS_POOL'); 52: bytes32 public constant override USER_WITHDRAW_MANAGER = keccak256('USER_WITHDRAW_MANAGER'); 53: bytes32 public constant override STADER_INSURANCE_FUND = keccak256('STADER_INSURANCE_FUND'); 54: bytes32 public constant override PERMISSIONED_NODE_REGISTRY = keccak256('PERMISSIONED_NODE_REGISTRY'); 55: bytes32 public constant override PERMISSIONLESS_NODE_REGISTRY = keccak256('PERMISSIONLESS_NODE_REGISTRY'); 56: bytes32 public constant override PERMISSIONED_SOCIALIZING_POOL = keccak256('PERMISSIONED_SOCIALIZING_POOL'); 57: bytes32 public constant override PERMISSIONLESS_SOCIALIZING_POOL = keccak256('PERMISSIONLESS_SOCIALIZING_POOL'); 64: bytes32 public constant override ETH_BALANCE_POR_FEED = keccak256('ETH_BALANCE_POR_FEED'); 65: bytes32 public constant override ETHX_SUPPLY_POR_FEED = keccak256('ETHX_SUPPLY_POR_FEED'); 68: bytes32 public constant override MANAGER = keccak256('MANAGER'); 69: bytes32 public constant override OPERATOR = keccak256('OPERATOR'); 71: bytes32 public constant SD = keccak256('SD'); 72: bytes32 public constant ETHx = keccak256('ETHx');
File:2023-06-stader/contracts/StaderOracle.sol 50: bytes32 public constant ETHX_ER_UF = keccak256('ETHX_ER_UF'); // ETHx Exchange Rate, Balances Update Frequency 51: bytes32 public constant SD_PRICE_UF = keccak256('SD_PRICE_UF'); // SD Price Update Frequency Key 52: bytes32 public constant VALIDATOR_STATS_UF = keccak256('VALIDATOR_STATS_UF'); // Validator Status Update Frequency Key 53: bytes32 public constant WITHDRAWN_VALIDATORS_UF = keccak256('WITHDRAWN_VALIDATORS_UF'); // Withdrawn Validator Update Frequency Key 54: bytes32 public constant MISSED_ATTESTATION_PENALTY_UF = keccak256('MISSED_ATTESTATION_PENALTY_UF'); // Missed Attestation Penalty Update Frequency Key
</details> *Instances (47)*File:2023-06-stader/contracts/factory/VaultFactory.sol 16: bytes32 public constant override NODE_REGISTRY_CONTRACT = keccak256('NODE_REGISTRY_CONTRACT');
return
data (bool success,)
has to be stored due to EVM architecture, if in a usage like below, ‘out’ and ‘outsize’ values are given (0,0) . Thus, this storage disappears and may come from external contracts a possible Gas griefing/theft problem is avoided
File:2023-06-stader/contracts/Auction.sol 131: (bool success, ) = payable(msg.sender).call{value: withdrawalAmount}('');
File:2023-06-stader/contracts/SocializingPool.sol 91: (bool success, ) = payable(staderConfig.getStaderTreasury()).call{value: _rewardsData.protocolETHRewards}(''); 121: (success, ) = payable(operatorRewardsAddr).call{value: totalAmountETH}('');
File:2023-06-stader/contracts/StaderInsuranceFund.sol 48: (bool success, ) = payable(msg.sender).call{value: _amount}('');
File:2023-06-stader/contracts/StaderStakePoolsManager.sol 102: (bool success, ) = payable(staderConfig.getUserWithdrawManager()).call{value: _amount}('');
File:2023-06-stader/contracts/UserWithdrawalManager.sol 229: (bool success, ) = _recipient.call{value: _amount}('');
</details> *Instances (7)*File:2023-06-stader/contracts/library/UtilLib.sol 168: (bool success, ) = payable(_receiver).call{value: _amount}('');
safetransfer
Instead Of transfer
File:2023-06-stader/contracts/Auction.sol 87: if (!IERC20(staderConfig.getStaderToken()).transfer(lotItem.highestBidder, lotItem.sdAmount)) { 114: if (!IERC20(staderConfig.getStaderToken()).transfer(staderConfig.getStaderTreasury(), _sdAmount)) {
File:2023-06-stader/contracts/SDCollateral.sol 68: if (!IERC20(staderConfig.getStaderToken()).transfer(payable(operator), _requestedSD)) {
File:2023-06-stader/contracts/SocializingPool.sol 129: if (!IERC20(staderConfig.getStaderToken()).transfer(operatorRewardsAddr, totalAmountSD)) {
Instances (4)
_safeMint
instead of _mint
File:2023-06-stader/contracts/ETHx.sol 48: _mint(to, amount);
Instances (1)
First divides and then multiplies again, there is a significant loss of precision
File:2023-06-stader/contracts/StaderOracle.sol 603: return (block.number / updateFrequency) * updateFrequency;
Instances (1)
#0 - c4-judge
2023-06-14T06:46:48Z
Picodes marked the issue as grade-b
🌟 Selected for report: JCN
Also found by: 0x70C9, 0xSmartContract, 0xWaitress, 0xhacksmithh, DavidGiladi, K42, LaScaloneta, Rageur, Raihan, SAAJ, SAQ, SM3_SS, Sathish9098, Tomio, bigtone, c3phas, ernestognw, etherhood, koxuan, matrix_0wl, mgf15, naman1778, niser93, petrichor, piyushshukla, sebghatullah, shamsulhaq123
21.6219 USDC - $21.62
| GAS-1 | Use assembly to check for address(0)
| 2 |
| GAS-2 | abi.encode()
is less efficient than abi.encodepacked()
| 18 |
| GAS-3 | keccak256()
EXPRESSIONS WHICH ARE FIXED, CAN BE PRECALCULATED AND ASSIGNED TO THE CONSTANT VARIABLES. | 47 |
| GAS-4 | Usage of uints/ints
smaller than 32 bytes (256 bits) incurs overhead | 89 |
| GAS-5 | Use nested if and, avoid multiple check combinations | 2 |
| GAS-6 | Using private
rather than public
for constants, saves gas | 63 |
| GAS-7 | Don't initialize variables with default value | 13 |
| GAS-8 | 10**18, can be changed to 1e18 and save some gas | 3 |
| GAS-9 | Inverting the condition of an if-else-statement wastes gas | 11 |
| GAS-10 | Gas saving is achieved by removing the delete
keyword (~60k) | 2 |
| GAS-11 | Use double if
statements instead of &&
| 2 |
| GAS-12 | With assembly, .call (bool success)
transfer can be done gas-optimized | 7 |
| GAS-13 | Use constants instead of type(uintx).max
| 1 |
| GAS-14 | Use != 0
instead of > 0
for unsigned integer comparison | 13 |
| GAS-15 | Use shift Right/Left instead of division/multiplication if possible | 49 |
| GAS-16 | Write direct outcome, instead of performing mathematical operations | 1 |
| GAS-17 | Use assembly to hash instead of Solidity | 12 |
address(0)
Saves 6 gas per instance if using assembly to check for address(0)
File:2023-06-stader/contracts/UserWithdrawalManager.sol 96: if (_owner == address(0)) revert ZeroAddressReceived();
File:2023-06-stader/contracts/library/UtilLib.sol 22: if (_address == address(0)) revert ZeroAddress();
Instances (2)
abi.encode()
is less efficient than abi.encodepacked()
See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/StaderOracle.sol 127: abi.encode( 135: abi.encode(_exchangeRate.reportingBlockNumber, _exchangeRate.totalETHBalance, _exchangeRate.totalETHXSupply) 221: abi.encode( 233: abi.encode( 282: bytes32 nodeSubmissionKey = keccak256(abi.encode(msg.sender, _sdPriceData.reportingBlockNumber)); 283: bytes32 submissionCountKey = keccak256(abi.encode(_sdPriceData.reportingBlockNumber)); 334: abi.encode( 346: abi.encode( 407: bytes memory encodedPubkeys = abi.encode(_withdrawnValidators.sortedPubkeys); 410: abi.encode( 418: abi.encode(_withdrawnValidators.reportingBlockNumber, _withdrawnValidators.nodeRegistry, encodedPubkeys) 466: bytes memory encodedPubkeys = abi.encode(_mapd.sortedPubkeys); 469: bytes32 nodeSubmissionKey = keccak256(abi.encode(msg.sender, _mapd.index, encodedPubkeys)); 470: bytes32 submissionCountKey = keccak256(abi.encode(_mapd.index, encodedPubkeys));
</details> *Instances (18)*File:2023-06-stader/contracts/factory/VaultFactory.sol 40: bytes32 salt = sha256(abi.encode(_poolId, _operatorId, _validatorCount)); 54: bytes32 salt = sha256(abi.encode(_poolId, _operatorId)); 67: bytes32 salt = sha256(abi.encode(_poolId, _operatorId, _validatorCount)); 77: bytes32 salt = sha256(abi.encode(_poolId, _operatorId));
keccak256()
EXPRESSIONS WHICH ARE FIXED, CAN BE PRECALCULATED AND ASSIGNED TO THE CONSTANT VARIABLES.Instead of calculating the keccak256() when the contract is made, pre calculate them before and only give the result to a constant
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/ETHx.sol 21: bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE'); 22: bytes32 public constant BURNER_ROLE = keccak256('BURNER_ROLE');
File:2023-06-stader/contracts/StaderConfig.sol 12: bytes32 public constant ETH_PER_NODE = keccak256('ETH_PER_NODE'); 14: bytes32 public constant PRE_DEPOSIT_SIZE = keccak256('PRE_DEPOSIT_SIZE'); 16: bytes32 public constant FULL_DEPOSIT_SIZE = keccak256('FULL_DEPOSIT_SIZE'); 18: bytes32 public constant DECIMALS = keccak256('DECIMALS'); 20: bytes32 public constant TOTAL_FEE = keccak256('TOTAL_FEE'); 22: bytes32 public constant OPERATOR_MAX_NAME_LENGTH = keccak256('OPERATOR_MAX_NAME_LENGTH'); 24: bytes32 public constant SOCIALIZING_POOL_CYCLE_DURATION = keccak256('SOCIALIZING_POOL_CYCLE_DURATION'); 27: bytes32 public constant REWARD_THRESHOLD = keccak256('REWARD_THRESHOLD'); 28: bytes32 public constant MIN_DEPOSIT_AMOUNT = keccak256('MIN_DEPOSIT_AMOUNT'); 29: bytes32 public constant MAX_DEPOSIT_AMOUNT = keccak256('MAX_DEPOSIT_AMOUNT'); 30: bytes32 public constant MIN_WITHDRAW_AMOUNT = keccak256('MIN_WITHDRAW_AMOUNT'); 31: bytes32 public constant MAX_WITHDRAW_AMOUNT = keccak256('MAX_WITHDRAW_AMOUNT'); 35: bytes32 public constant WITHDRAWN_KEYS_BATCH_SIZE = keccak256('WITHDRAWN_KEYS_BATCH_SIZE'); 37: bytes32 public constant ADMIN = keccak256('ADMIN'); 38: bytes32 public constant STADER_TREASURY = keccak256('STADER_TREASURY'); 40: bytes32 public constant override POOL_UTILS = keccak256('POOL_UTILS'); 41: bytes32 public constant override POOL_SELECTOR = keccak256('POOL_SELECTOR'); 42: bytes32 public constant override SD_COLLATERAL = keccak256('SD_COLLATERAL'); 43: bytes32 public constant override OPERATOR_REWARD_COLLECTOR = keccak256('OPERATOR_REWARD_COLLECTOR'); 44: bytes32 public constant override VAULT_FACTORY = keccak256('VAULT_FACTORY'); 45: bytes32 public constant override STADER_ORACLE = keccak256('STADER_ORACLE'); 46: bytes32 public constant override AUCTION_CONTRACT = keccak256('AuctionContract'); 47: bytes32 public constant override PENALTY_CONTRACT = keccak256('PENALTY_CONTRACT'); 48: bytes32 public constant override PERMISSIONED_POOL = keccak256('PERMISSIONED_POOL'); 49: bytes32 public constant override STAKE_POOL_MANAGER = keccak256('STAKE_POOL_MANAGER'); 50: bytes32 public constant override ETH_DEPOSIT_CONTRACT = keccak256('ETH_DEPOSIT_CONTRACT'); 51: bytes32 public constant override PERMISSIONLESS_POOL = keccak256('PERMISSIONLESS_POOL'); 52: bytes32 public constant override USER_WITHDRAW_MANAGER = keccak256('USER_WITHDRAW_MANAGER'); 53: bytes32 public constant override STADER_INSURANCE_FUND = keccak256('STADER_INSURANCE_FUND'); 54: bytes32 public constant override PERMISSIONED_NODE_REGISTRY = keccak256('PERMISSIONED_NODE_REGISTRY'); 55: bytes32 public constant override PERMISSIONLESS_NODE_REGISTRY = keccak256('PERMISSIONLESS_NODE_REGISTRY'); 56: bytes32 public constant override PERMISSIONED_SOCIALIZING_POOL = keccak256('PERMISSIONED_SOCIALIZING_POOL'); 57: bytes32 public constant override PERMISSIONLESS_SOCIALIZING_POOL = keccak256('PERMISSIONLESS_SOCIALIZING_POOL'); 64: bytes32 public constant override ETH_BALANCE_POR_FEED = keccak256('ETH_BALANCE_POR_FEED'); 65: bytes32 public constant override ETHX_SUPPLY_POR_FEED = keccak256('ETHX_SUPPLY_POR_FEED'); 68: bytes32 public constant override MANAGER = keccak256('MANAGER'); 69: bytes32 public constant override OPERATOR = keccak256('OPERATOR'); 71: bytes32 public constant SD = keccak256('SD'); 72: bytes32 public constant ETHx = keccak256('ETHx');
File:2023-06-stader/contracts/StaderOracle.sol 50: bytes32 public constant ETHX_ER_UF = keccak256('ETHX_ER_UF'); // ETHx Exchange Rate, Balances Update Frequency 51: bytes32 public constant SD_PRICE_UF = keccak256('SD_PRICE_UF'); // SD Price Update Frequency Key 52: bytes32 public constant VALIDATOR_STATS_UF = keccak256('VALIDATOR_STATS_UF'); // Validator Status Update Frequency Key 53: bytes32 public constant WITHDRAWN_VALIDATORS_UF = keccak256('WITHDRAWN_VALIDATORS_UF'); // Withdrawn Validator Update Frequency Key 54: bytes32 public constant MISSED_ATTESTATION_PENALTY_UF = keccak256('MISSED_ATTESTATION_PENALTY_UF'); // Missed Attestation Penalty Update Frequency Key
</details> *Instances (47)*File:2023-06-stader/contracts/factory/VaultFactory.sol 16: bytes32 public constant override NODE_REGISTRY_CONTRACT = keccak256('NODE_REGISTRY_CONTRACT');
uints/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contracts gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size. https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/NodeELRewardVault.sol 25: uint8 poolId = IVaultProxy(address(this)).poolId();
File:2023-06-stader/contracts/Penalty.sol 144: function markValidatorSettled(uint8 _poolId, uint256 _validatorId) external override {
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 31: uint8 public constant override POOL_ID = 2; 32: uint16 public override inputKeyCountLimit; 33: uint64 public override maxNonTerminalKeyPerOperator; 416: function updateMaxNonTerminalKeyPerOperator(uint64 _maxNonTerminalKeyPerOperator) external override { 427: function updateInputKeyCountLimit(uint16 _inputKeyCountLimit) external override { 526: ) public view override returns (uint64) { 533: uint64 totalNonWithdrawnKeyCount;
File:2023-06-stader/contracts/PermissionedPool.sol 322: uint64 value = uint64(_depositAmount / 1 gwei);
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 30: uint8 public constant override POOL_ID = 1; 31: uint16 public override inputKeyCountLimit; 32: uint64 public override maxNonTerminalKeyPerOperator; 325: function updateInputKeyCountLimit(uint16 _inputKeyCountLimit) external override { 336: function updateMaxNonTerminalKeyPerOperator(uint64 _maxNonTerminalKeyPerOperator) external override { 407: ) public view override returns (uint64) { 414: uint64 totalNonWithdrawnKeyCount;
File:2023-06-stader/contracts/PermissionlessPool.sol 274: uint64 value = uint64(_depositAmount / 1 gwei);
File:2023-06-stader/contracts/PoolSelector.sol 18: uint16 public poolAllocationMaxSize; 23: mapping(uint8 => uint256) public poolWeights; 50: function computePoolAllocationForDeposit(uint8 _poolId, uint256 _newValidatorToRegister) 79: returns (uint256[] memory selectedPoolCapacity, uint8[] memory poolIdArray) 121: uint8[] memory poolIdArray = IPoolUtils(staderConfig.getPoolUtils()).getPoolIdArray(); 140: function updatePoolAllocationMaxSize(uint16 _poolAllocationMaxSize) external {
File:2023-06-stader/contracts/PoolUtils.sol 13: uint64 private constant PUBKEY_LENGTH = 48; 14: uint64 private constant SIGNATURE_LENGTH = 96; 17: mapping(uint8 => address) public override poolAddressById; 18: uint8[] public override poolIdArray; 40: function addNewPool(uint8 _poolId, address _poolAddress) external override { 55: function updatePoolAddress(uint8 _poolId, address _newPoolAddress) 91: function getProtocolFee(uint8 _poolId) public view override onlyExistingPoolId(_poolId) returns (uint256) { 96: function getOperatorFee(uint8 _poolId) public view override onlyExistingPoolId(_poolId) returns (uint256) { 112: function getQueuedValidatorCountByPool(uint8 _poolId) 124: function getActiveValidatorCountByPool(uint8 _poolId) 136: function getSocializingPoolAddress(uint8 _poolId) 148: uint8 _poolId, 157: function getCollateralETH(uint8 _poolId) public view override onlyExistingPoolId(_poolId) returns (uint256) { 162: function getNodeRegistry(uint8 _poolId) public view override onlyExistingPoolId(_poolId) returns (address) { 188: function getOperatorPoolId(address _operAddr) external view override returns (uint8) { 199: function getValidatorPoolId(bytes calldata _pubkey) external view override returns (uint8) { 210: function getPoolIdArray() external view override returns (uint8[] memory) { 245: function calculateRewardShare(uint8 _poolId, uint256 _totalRewards) 271: function isExistingPoolId(uint8 _poolId) public view override returns (bool) { 281: function verifyNewPool(uint8 _poolId, address _poolAddress) internal view { 295: modifier onlyExistingPoolId(uint8 _poolId) {
File:2023-06-stader/contracts/SDCollateral.sol 18: mapping(uint8 => PoolThresholdInfo) public poolThresholdbyPoolId; 78: function slashValidatorSD(uint256 _validatorId, uint8 _poolId) external override nonReentrant { 120: uint8 _poolId, 145: (uint8 poolId, , uint256 validatorCount) = getOperatorInfo(_operator); 157: uint8 _poolId, 166: function getMinimumSDToBond(uint8 _poolId, uint256 _numValidator) 185: uint8 _poolId, 194: (uint8 poolId, , uint256 validatorCount) = getOperatorInfo(_operator); 221: uint8 _poolId, 238: function isPoolThresholdValid(uint8 _poolId) internal view {
File:2023-06-stader/contracts/StaderOracle.sol 46: mapping(bytes32 => uint8) private submissionCountKeys; 47: mapping(bytes32 => uint16) public override missedAttestationPenalty; 137: uint8 submissionCount = attestSubmission(nodeSubmissionKey, submissionCountKey); 253: uint8 submissionCount = attestSubmission(nodeSubmissionKey, submissionCountKey); 284: uint8 submissionCount = attestSubmission(nodeSubmissionKey, submissionCountKey); 357: uint8 submissionCount = attestSubmission(nodeSubmissionKey, submissionCountKey); 421: uint8 submissionCount = attestSubmission(nodeSubmissionKey, submissionCountKey); 471: uint8 submissionCount = attestSubmission(nodeSubmissionKey, submissionCountKey); 575: function getMerkleRootReportableBlockByPoolId(uint8 _poolId) public view override returns (uint256) { 606: function getCurrentRewardsIndexByPoolId(uint8 _poolId) public view returns (uint256) { 622: returns (uint8 _submissionCount)
File:2023-06-stader/contracts/StaderStakePoolsManager.sol 90: function receiveExcessEthFromPool(uint8 _poolId) external payable override { 183: function validatorBatchDeposit(uint8 _poolId) external override nonReentrant whenNotPaused { 227: (uint256[] memory selectedPoolCapacity, uint8[] memory poolIdArray) = IPoolSelector(
File:2023-06-stader/contracts/ValidatorWithdrawalVault.sol 31: uint8 poolId = VaultProxy(payable(address(this))).poolId(); 55: uint8 poolId = VaultProxy(payable(address(this))).poolId(); 94: uint8 poolId = VaultProxy(payable(address(this))).poolId(); 127: function getCollateralETH(uint8 _poolId, IStaderConfig _staderConfig) internal view returns (uint256) { 132: uint8 _poolId, 140: uint8 _poolId,
File:2023-06-stader/contracts/VaultProxy.sol 12: uint8 public override poolId; 22: uint8 _poolId,
File:2023-06-stader/contracts/factory/VaultFactory.sol 35: uint8 _poolId, 48: function deployNodeELRewardVault(uint8 _poolId, uint256 _operatorId) 63: uint8 _poolId, 71: function computeNodeELRewardVaultAddress(uint8 _poolId, uint256 _operatorId)
</details> *Instances (89)*File:2023-06-stader/contracts/library/UtilLib.sol 18: uint64 private constant VALIDATOR_PUBKEY_LENGTH = 48; 50: uint8 _poolId, 66: uint8 _poolId, 83: uint8 _poolId, 96: uint8 _poolId, 108: uint8 _poolId, 123: uint8 poolId = IPoolUtils(_staderConfig.getPoolUtils()).getOperatorPoolId(_operator); 148: uint8 poolId = IPoolUtils(_staderConfig.getPoolUtils()).getValidatorPoolId(_pubkey);
Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.
File:2023-06-stader/contracts/SocializingPool.sol 146: if (_amountSD[i] == 0 && _amountETH[i] == 0) {
File:2023-06-stader/contracts/ValidatorWithdrawalVault.sol 35: if (!staderConfig.onlyOperatorRole(msg.sender) && totalRewards > staderConfig.getRewardsThreshold()) {
Instances (2)
private
rather than public
for constants, saves gasIf needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/Auction.sol 22: uint256 public constant MIN_AUCTION_DURATION = 7200; // 24 hours
File:2023-06-stader/contracts/ETHx.sol 21: bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE'); 22: bytes32 public constant BURNER_ROLE = keccak256('BURNER_ROLE');
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 31: uint8 public constant override POOL_ID = 2;
File:2023-06-stader/contracts/PermissionedPool.sol 33: uint256 public constant MAX_COMMISSION_LIMIT_BIPS = 1500;
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 30: uint8 public constant override POOL_ID = 1; 42: uint256 public constant override FRONT_RUN_PENALTY = 3 ether; 43: uint256 public constant COLLATERAL_ETH = 4 ether;
File:2023-06-stader/contracts/PermissionlessPool.sol 23: uint256 public constant DEPOSIT_NODE_BOND = 3 ether; 31: uint256 public constant MAX_COMMISSION_LIMIT_BIPS = 1500;
File:2023-06-stader/contracts/PoolSelector.sol 21: uint256 public constant POOL_WEIGHTS_SUM = 10000;
File:2023-06-stader/contracts/StaderConfig.sol 12: bytes32 public constant ETH_PER_NODE = keccak256('ETH_PER_NODE'); 14: bytes32 public constant PRE_DEPOSIT_SIZE = keccak256('PRE_DEPOSIT_SIZE'); 16: bytes32 public constant FULL_DEPOSIT_SIZE = keccak256('FULL_DEPOSIT_SIZE'); 18: bytes32 public constant DECIMALS = keccak256('DECIMALS'); 20: bytes32 public constant TOTAL_FEE = keccak256('TOTAL_FEE'); 22: bytes32 public constant OPERATOR_MAX_NAME_LENGTH = keccak256('OPERATOR_MAX_NAME_LENGTH'); 24: bytes32 public constant SOCIALIZING_POOL_CYCLE_DURATION = keccak256('SOCIALIZING_POOL_CYCLE_DURATION'); 25: bytes32 public constant SOCIALIZING_POOL_OPT_IN_COOLING_PERIOD = 27: bytes32 public constant REWARD_THRESHOLD = keccak256('REWARD_THRESHOLD'); 28: bytes32 public constant MIN_DEPOSIT_AMOUNT = keccak256('MIN_DEPOSIT_AMOUNT'); 29: bytes32 public constant MAX_DEPOSIT_AMOUNT = keccak256('MAX_DEPOSIT_AMOUNT'); 30: bytes32 public constant MIN_WITHDRAW_AMOUNT = keccak256('MIN_WITHDRAW_AMOUNT'); 31: bytes32 public constant MAX_WITHDRAW_AMOUNT = keccak256('MAX_WITHDRAW_AMOUNT'); 33: bytes32 public constant MIN_BLOCK_DELAY_TO_FINALIZE_WITHDRAW_REQUEST = 35: bytes32 public constant WITHDRAWN_KEYS_BATCH_SIZE = keccak256('WITHDRAWN_KEYS_BATCH_SIZE'); 37: bytes32 public constant ADMIN = keccak256('ADMIN'); 38: bytes32 public constant STADER_TREASURY = keccak256('STADER_TREASURY'); 40: bytes32 public constant override POOL_UTILS = keccak256('POOL_UTILS'); 41: bytes32 public constant override POOL_SELECTOR = keccak256('POOL_SELECTOR'); 42: bytes32 public constant override SD_COLLATERAL = keccak256('SD_COLLATERAL'); 43: bytes32 public constant override OPERATOR_REWARD_COLLECTOR = keccak256('OPERATOR_REWARD_COLLECTOR'); 44: bytes32 public constant override VAULT_FACTORY = keccak256('VAULT_FACTORY'); 45: bytes32 public constant override STADER_ORACLE = keccak256('STADER_ORACLE'); 46: bytes32 public constant override AUCTION_CONTRACT = keccak256('AuctionContract'); 47: bytes32 public constant override PENALTY_CONTRACT = keccak256('PENALTY_CONTRACT'); 48: bytes32 public constant override PERMISSIONED_POOL = keccak256('PERMISSIONED_POOL'); 49: bytes32 public constant override STAKE_POOL_MANAGER = keccak256('STAKE_POOL_MANAGER'); 50: bytes32 public constant override ETH_DEPOSIT_CONTRACT = keccak256('ETH_DEPOSIT_CONTRACT'); 51: bytes32 public constant override PERMISSIONLESS_POOL = keccak256('PERMISSIONLESS_POOL'); 52: bytes32 public constant override USER_WITHDRAW_MANAGER = keccak256('USER_WITHDRAW_MANAGER'); 53: bytes32 public constant override STADER_INSURANCE_FUND = keccak256('STADER_INSURANCE_FUND'); 54: bytes32 public constant override PERMISSIONED_NODE_REGISTRY = keccak256('PERMISSIONED_NODE_REGISTRY'); 55: bytes32 public constant override PERMISSIONLESS_NODE_REGISTRY = keccak256('PERMISSIONLESS_NODE_REGISTRY'); 56: bytes32 public constant override PERMISSIONED_SOCIALIZING_POOL = keccak256('PERMISSIONED_SOCIALIZING_POOL'); 57: bytes32 public constant override PERMISSIONLESS_SOCIALIZING_POOL = keccak256('PERMISSIONLESS_SOCIALIZING_POOL'); 58: bytes32 public constant override NODE_EL_REWARD_VAULT_IMPLEMENTATION = 60: bytes32 public constant override VALIDATOR_WITHDRAWAL_VAULT_IMPLEMENTATION = 64: bytes32 public constant override ETH_BALANCE_POR_FEED = keccak256('ETH_BALANCE_POR_FEED'); 65: bytes32 public constant override ETHX_SUPPLY_POR_FEED = keccak256('ETHX_SUPPLY_POR_FEED'); 68: bytes32 public constant override MANAGER = keccak256('MANAGER'); 69: bytes32 public constant override OPERATOR = keccak256('OPERATOR'); 71: bytes32 public constant SD = keccak256('SD'); 72: bytes32 public constant ETHx = keccak256('ETHx');
File:2023-06-stader/contracts/StaderOracle.sol 26: uint256 public constant MAX_ER_UPDATE_FREQUENCY = 7200 * 7; // 7 days 27: uint256 public constant ER_CHANGE_MAX_BPS = 10000; 29: uint256 public constant MIN_TRUSTED_NODES = 5; 50: bytes32 public constant ETHX_ER_UF = keccak256('ETHX_ER_UF'); // ETHx Exchange Rate, Balances Update Frequency 51: bytes32 public constant SD_PRICE_UF = keccak256('SD_PRICE_UF'); // SD Price Update Frequency Key 52: bytes32 public constant VALIDATOR_STATS_UF = keccak256('VALIDATOR_STATS_UF'); // Validator Status Update Frequency Key 53: bytes32 public constant WITHDRAWN_VALIDATORS_UF = keccak256('WITHDRAWN_VALIDATORS_UF'); // Withdrawn Validator Update Frequency Key 54: bytes32 public constant MISSED_ATTESTATION_PENALTY_UF = keccak256('MISSED_ATTESTATION_PENALTY_UF'); // Missed Attestation Penalty Update Frequency Key
</details> *Instances (63)*File:2023-06-stader/contracts/factory/VaultFactory.sol 16: bytes32 public constant override NODE_REGISTRY_CONTRACT = keccak256('NODE_REGISTRY_CONTRACT');
If a variable is not initialized, it is assumed to have the default value. Explicitly initializing a variable with its default value costs unnecessary gas. For more info, see Mudit Gupta's Blog at point No need to initialize variables with default values
.
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 91: for (uint256 i = 0; i < permissionedNosLength; i++) { 597: uint256 validatorCount = 0;
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 503: uint256 validatorCount = 0; 572: uint256 optOutOperatorCount = 0;
File:2023-06-stader/contracts/PoolSelector.sol 130: for (uint256 i = 0; i < poolTargetLength; i++) {
File:2023-06-stader/contracts/PoolUtils.sol 104: for (uint256 i = 0; i < poolCount; i++) { 168: for (uint256 i = 0; i < poolCount; i++) { 179: for (uint256 i = 0; i < poolCount; i++) { 190: for (uint256 i = 0; i < poolCount; i++) { 201: for (uint256 i = 0; i < poolCount; i++) { 273: for (uint256 i = 0; i < poolCount; i++) {
File:2023-06-stader/contracts/SocializingPool.sol 145: for (uint256 i = 0; i < indexLength; i++) {
</details> *Instances (13)*File:2023-06-stader/contracts/StaderStakePoolsManager.sol 232: for (uint256 i = 0; i < poolCount; i++) {
10**18 can be changed to 1e18 to avoid unnecessary arithmetic operation and save gas.
File:2023-06-stader/contracts/StaderConfig.sol 93: setConstant(DECIMALS, 10**18); 95: setVariable(MIN_DEPOSIT_AMOUNT, 10**14); 97: setVariable(MIN_WITHDRAW_AMOUNT, 10**14);
Instances (3)
Flipping the true
and false
blocks instead saves 3 gas
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 532: _endIndex = _endIndex > validatorCount ? validatorCount : _endIndex; 595: endIndex = endIndex > nextValidatorId ? nextValidatorId : endIndex; 635: endIndex = endIndex > validatorCount ? validatorCount : endIndex; 636: Validator[] memory validators = new Validator[](endIndex > startIndex ? endIndex - startIndex : 0);
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 413: _endIndex = _endIndex > validatorCount ? validatorCount : _endIndex; 501: endIndex = endIndex > nextValidatorId ? nextValidatorId : endIndex; 541: endIndex = endIndex > validatorCount ? validatorCount : endIndex; 542: Validator[] memory validators = new Validator[](endIndex > startIndex ? endIndex - startIndex : 0); 570: endIndex = endIndex > nextOperatorId ? nextOperatorId : endIndex;
File:2023-06-stader/contracts/SDCollateral.sol 190: return (sdBalance >= minSDToBond ? 0 : minSDToBond - sdBalance);
</details> *Instances (11)*File:2023-06-stader/contracts/StaderStakePoolsManager.sol 297: (supply == 0) ? initialConvertToAssets(_shares, rounding) : _shares.mulDiv(totalAssets(), supply, rounding);
delete
keyword (~60k)30k gas savings were made by removing the delete
keyword. The reason for using the delete
keyword here is to reset the struct values (set to default value) in every operation. However, the struct values do not need to be zero each time the function is run. Therefore, the delete
key word is unnecessary. If it is removed, around 30k gas savings will be achieved.
File:2023-06-stader/contracts/StaderOracle.sol 293: delete sdPrices;
File:2023-06-stader/contracts/UserWithdrawalManager.sol 207: delete (userWithdrawRequests[_requestId]);
Instances (2)
if
statements instead of &&
If the if statement has a logical AND and is not followed by an else statement, it can be replaced with 2 if statements.
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/SocializingPool.sol 146: if (_amountSD[i] == 0 && _amountETH[i] == 0) {
</details> *Instances (2)*File:2023-06-stader/contracts/ValidatorWithdrawalVault.sol 35: if (!staderConfig.onlyOperatorRole(msg.sender) && totalRewards > staderConfig.getRewardsThreshold()) {
.call (bool success)
transfer can be done gas-optimizedreturn
data (bool success,)
has to be stored due to EVM architecture, but in a usage like below, ‘out’ and ‘outsize’ values are given (0,0), this storage disappears and gas optimization is provided.
File:2023-06-stader/contracts/Auction.sol 131: (bool success, ) = payable(msg.sender).call{value: withdrawalAmount}('');
File:2023-06-stader/contracts/SocializingPool.sol 91: (bool success, ) = payable(staderConfig.getStaderTreasury()).call{value: _rewardsData.protocolETHRewards}(''); 121: (success, ) = payable(operatorRewardsAddr).call{value: totalAmountETH}('');
File:2023-06-stader/contracts/StaderInsuranceFund.sol 48: (bool success, ) = payable(msg.sender).call{value: _amount}('');
File:2023-06-stader/contracts/StaderStakePoolsManager.sol 102: (bool success, ) = payable(staderConfig.getUserWithdrawManager()).call{value: _amount}('');
File:2023-06-stader/contracts/UserWithdrawalManager.sol 229: (bool success, ) = _recipient.call{value: _amount}('');
</details> *Instances (7)*File:2023-06-stader/contracts/library/UtilLib.sol 168: (bool success, ) = payable(_receiver).call{value: _amount}('');
type(uintx).max
it uses more gas in the distribution process and also for each transaction than constant usage.
<details><summary><i>instances of this issue:</i></summary></details> *Instances (1)*File:2023-06-stader/contracts/SDCollateral.sol 106: IERC20(staderConfig.getStaderToken()).approve(auctionContract, type(uint256).max);
!= 0
instead of > 0
for unsigned integer comparisonIt is cheaper to use != 0
than > 0
for uint256.
File:2023-06-stader/contracts/Auction.sol 109: if (lotItem.highestBidAmount > 0) revert LotWasAuctioned();
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 204: bool validatorPerOperatorGreaterThanZero = (validatorPerOperator > 0); 299: if (totalDefectedKeys > 0) {
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 209: if (frontRunValidatorsLength > 0) { 305: if (address(feeRecipientAddress).balance > 0) {
File:2023-06-stader/contracts/SocializingPool.sol 119: if (totalAmountETH > 0) { 127: if (totalAmountSD > 0) {
File:2023-06-stader/contracts/StaderOracle.sol 121: if (_exchangeRate.reportingBlockNumber % updateFrequencyMap[ETHX_ER_UF] > 0) { 274: if (_sdPriceData.reportingBlockNumber % updateFrequencyMap[SD_PRICE_UF] > 0) { 328: if (_validatorStats.reportingBlockNumber % updateFrequencyMap[VALIDATOR_STATS_UF] > 0) { 403: if (_withdrawnValidators.reportingBlockNumber % updateFrequencyMap[WITHDRAWN_VALIDATORS_UF] > 0) {
File:2023-06-stader/contracts/StaderStakePoolsManager.sol 330: (totalAssets() > 0 ||
</details> *Instances (13)*File:2023-06-stader/contracts/ValidatorWithdrawalVault.sol 115: if (totalRewards > 0) {
A division/multiplication by any number x being a power of 2 can be calculated by shifting log2(x) to the right/left. While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.
<details><summary><i>instances of this issue:</i></summary>File:2023-06-stader/contracts/Auction.sol 38: duration = 2 * MIN_AUCTION_DURATION;
File:2023-06-stader/contracts/Penalty.sol 128: return violatedEpochs.length * mevTheftPenaltyPerStrike;
File:2023-06-stader/contracts/PermissionedNodeRegistry.sol 201: uint256 validatorPerOperator = _numValidators / totalActiveOperatorCount; 593: uint256 startIndex = (_pageNumber - 1) * _pageSize + 1; 628: uint256 startIndex = (_pageNumber - 1) * _pageSize;
File:2023-06-stader/contracts/PermissionedPool.sol 73: _defectiveKeyCount * staderConfig.getPreDepositSize() 77: uint256 amountToSendToPoolManager = _defectiveKeyCount * staderConfig.getStakedEthPerNode(); 91: uint256 requiredValidators = msg.value / staderConfig.getStakedEthPerNode(); 322: uint64 value = uint64(_depositAmount / 1 gwei);
File:2023-06-stader/contracts/PermissionlessNodeRegistry.sol 170: value: staderConfig.getPreDepositSize() * keyCount 211: value: frontRunValidatorsLength * FRONT_RUN_PENALTY 499: uint256 startIndex = (_pageNumber - 1) * _pageSize + 1; 534: uint256 startIndex = (_pageNumber - 1) * _pageSize; 568: uint256 startIndex = (_pageNumber - 1) * _pageSize + 1; 664: if (msg.value != keyCount * COLLATERAL_ETH) {
File:2023-06-stader/contracts/PermissionlessPool.sol 128: uint256 requiredValidators = msg.value / (staderConfig.getFullDepositSize() - DEPOSIT_NODE_BOND); 131: requiredValidators * DEPOSIT_NODE_BOND 274: uint64 value = uint64(_depositAmount / 1 gwei);
File:2023-06-stader/contracts/PoolSelector.sol 61: uint256 poolTotalTarget = (poolWeights[_poolId] * totalValidatorsRequired) / POOL_WEIGHTS_SUM; 93: uint256 remainingValidatorsToDeposit = ethToDeposit / poolDepositSize; 99: ethToDeposit -= selectedPoolCapacity[i] * poolDepositSize;
File:2023-06-stader/contracts/PoolUtils.sol 261: uint256 _userShareBeforeCommision = (_totalRewards * usersETH) / TOTAL_STAKED_ETH; 263: protocolShare = (protocolFeeBps * _userShareBeforeCommision) / staderConfig.getTotalFee(); 265: operatorShare = (_totalRewards * collateralETH) / TOTAL_STAKED_ETH; 266: operatorShare += (operatorFeeBps * _userShareBeforeCommision) / staderConfig.getTotalFee();
File:2023-06-stader/contracts/SDCollateral.sol 148: return convertETHToSD(poolThreshold.withdrawThreshold * validatorCount); 199: uint256 totalMinThreshold = validatorCount * convertETHToSD(poolThreshold.minThreshold); 200: uint256 totalMaxThreshold = validatorCount * convertETHToSD(poolThreshold.maxThreshold); 207: return (_sdAmount * sdPriceInETH) / staderConfig.getDecimals(); 212: return (_ethAmount * staderConfig.getDecimals()) / sdPriceInETH;
File:2023-06-stader/contracts/StaderOracle.sol 26: uint256 public constant MAX_ER_UPDATE_FREQUENCY = 7200 * 7; // 7 days 148: submissionCount == trustedNodesCount / 2 + 1 && 255: if ((submissionCount == trustedNodesCount / 2 + 1)) { 290: if ((submissionCount == (2 * trustedNodesCount) / 3 + 1)) { 314: return (dataArray[(len - 1) / 2] + dataArray[len / 2]) / 2; 372: submissionCount == trustedNodesCount / 2 + 1 && 432: submissionCount == trustedNodesCount / 2 + 1 && 482: if ((submissionCount == trustedNodesCount / 2 + 1)) { 603: return (block.number / updateFrequency) * updateFrequency; 665: !(newExchangeRate >= (currentExchangeRate * (ER_CHANGE_MAX_BPS - erChangeLimit)) / ER_CHANGE_MAX_BPS && 666: newExchangeRate <= ((currentExchangeRate * (ER_CHANGE_MAX_BPS + erChangeLimit)) / ER_CHANGE_MAX_BPS))
File:2023-06-stader/contracts/StaderStakePoolsManager.sol 57: excessETHDepositCoolDown = 3 * 7200; 199: (availableETHForNewDeposit / poolDepositSize) 207: IStaderPoolBase(poolAddress).stakeUserETHToBeaconChain{value: selectedPoolCapacity * poolDepositSize}(); 208: emit ETHTransferredToPool(_poolId, poolAddress, selectedPoolCapacity * poolDepositSize); 243: IStaderPoolBase(poolAddress).stakeUserETHToBeaconChain{value: validatorToDeposit * poolDepositSize}(); 244: emit ETHTransferredToPool(i, poolAddress, validatorToDeposit * poolDepositSize);
File:2023-06-stader/contracts/UserWithdrawalManager.sol 136: uint256 minEThRequiredToFinalizeRequest = Math.min(requiredEth, (lockedEthX * exchangeRate) / DECIMALS);
</details>File:2023-06-stader/contracts/library/UtilLib.sol 163: : (totalETHBalance * DECIMALS) / totalETHXSupply;
Instances (49)
Write direct outcome instead ofperforming mathematical operations saves some gas
File:2023-06-stader/contracts/StaderOracle.sol 26: uint256 public constant MAX_ER_UPDATE_FREQUENCY = 7200 * 7; // 7 days
Instances (1)
hash can be done by use of assembly instead of keccak256(abi.encodePacked()) see
File:2023-06-stader/contracts/PermissionedPool.sol 263: bytes32 pubkey_root = sha256(abi.encodePacked(_pubkey, bytes16(0))); 266: sha256(abi.encodePacked(_signature[:64])), 267: sha256(abi.encodePacked(_signature[64:], bytes32(0))) 273: sha256(abi.encodePacked(pubkey_root, _withdrawCredential)), 274: sha256(abi.encodePacked(amount, bytes24(0), signature_root))
File:2023-06-stader/contracts/PermissionlessPool.sol 225: bytes32 pubkey_root = sha256(abi.encodePacked(_pubkey, bytes16(0))); 228: sha256(abi.encodePacked(_signature[:64])), 229: sha256(abi.encodePacked(_signature[64:], bytes32(0))) 235: sha256(abi.encodePacked(pubkey_root, _withdrawCredential)), 236: sha256(abi.encodePacked(amount, bytes24(0), signature_root))
File:2023-06-stader/contracts/SocializingPool.sol 174: bytes32 node = keccak256(abi.encodePacked(_operator, _amountSD, _amountETH));
File:2023-06-stader/contracts/library/UtilLib.sol 140: return sha256(abi.encodePacked(_pubkey, bytes16(0)));
Instances (12)
#0 - c4-judge
2023-06-14T05:43:50Z
Picodes marked the issue as grade-b