Implementing a two-step procedure for updating protocol addresses adds an extra layer of security. In such a system, the first step initiates the change, and the second step, after a predefined delay, confirms and finalizes it. This delay allows stakeholders or monitoring tools to observe and react to unintended or malicious changes. If an unauthorized change is detected, corrective actions can be taken before the change is finalized. To achieve this, introduce a "proposed address" state variable and a "delay period". Upon an update request, set the "proposed address". After the delay, if not contested, the main protocol address can be updated.
Path: ./code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol
114: pendingAdmin = _newPendingAdmin; // @audit-issue
133: validatorTimelock = _validatorTimelock; // @audit-issue
234: stateTransition[_chainId] = _stateTransitionContract; // @audit-issue
Path: ./code/contracts/ethereum/contracts/state-transition/ValidatorTimelock.sol
74: stateTransitionManager = _stateTransitionManager; // @audit-issue
74,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
27: s.pendingAdmin = _newPendingAdmin; // @audit-issue
27,
Path: ./code/contracts/ethereum/contracts/upgrades/BaseZkSyncUpgrade.sol
136: s.verifier = _newVerifier; // @audit-issue
136,
Path: ./code/contracts/ethereum/contracts/bridgehub/Bridgehub.sol
55: pendingAdmin = _newPendingAdmin; // @audit-issue
109: sharedBridge = IL1SharedBridge(_sharedBridge); // @audit-issue
134: stateTransitionManager[_chainId] = _stateTransitionManager; // @audit-issue
135: baseToken[_chainId] = _baseToken; // @audit-issue
Path: ./code/contracts/ethereum/contracts/governance/Governance.sol
258: securityCouncil = _newSecurityCouncil; // @audit-issue
258,
Path: ./code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol
133: chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + amount; // @audit-issue
143: l2BridgeAddress[_chainId] = _l2BridgeAddress; // @audit-issue
Path: ./code/contracts/zksync/contracts/bridge/L2WrappedBaseToken.sol
52: l2Bridge = _l2Bridge; // @audit-issue
53: l1Address = _l1Address; // @audit-issue
Path: ./code/contracts/zksync/contracts/bridge/L2StandardERC20.sol
52: l1Address = _l1Address; // @audit-issue
52,
Path: ./code/contracts/zksync/contracts/bridge/L2SharedBridge.sol
99: l1TokenAddress[expectedL2Token] = _l1Token; // @audit-issue
99,
Path: ./code/system-contracts/contracts/SystemContext.sol
100: origin = _newOrigin; // @audit-issue
100,
Introduce two state variables in your contract: one to hold the proposed new address (address public proposedAddress
) and another to timestamp the proposal (uint public addressChangeInitiated
). Implement two functions: one to propose a new address, recording the current timestamp, and another to finalize the address change after the delay period has elapsed.
In Solidity, the Ethereum address 0x0000000000000000000000000000000000000000
is known as the "zero address". This address has significance because it's the default value for uninitialized address variables and is often used to represent an invalid or non-existent address. The "
Missing zero address control" issue arises when a Solidity smart contract does not properly check or prevent interactions with the zero address, leading to unintended behavior.
For instance, a contract might allow tokens to be sent to the zero address without any checks, which essentially burns those tokens as they become irretrievable. While sometimes this is intentional, without proper control or checks, accidental transfers could occur.
Path: ./code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol
59: bridgehub = _bridgehub; // @audit-issue
59,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/DiamondInit.sol
30: s.chainId = _initializeData.chainId; // @audit-issue
31: s.bridgehub = _initializeData.bridgehub; // @audit-issue
32: s.stateTransitionManager = _initializeData.stateTransitionManager; // @audit-issue
33: s.baseToken = _initializeData.baseToken; // @audit-issue
34: s.baseTokenBridge = _initializeData.baseTokenBridge; // @audit-issue
35: s.protocolVersion = _initializeData.protocolVersion; // @audit-issue
37: s.verifier = _initializeData.verifier; // @audit-issue
38: s.admin = _initializeData.admin; // @audit-issue
41: s.storedBatchHashes[0] = _initializeData.storedBatchZero; // @audit-issue
42: s.verifierParams = _initializeData.verifierParams; // @audit-issue
43: s.l2BootloaderBytecodeHash = _initializeData.l2BootloaderBytecodeHash; // @audit-issue
44: s.l2DefaultAccountBytecodeHash = _initializeData.l2DefaultAccountBytecodeHash; // @audit-issue
45: s.priorityTxMaxGasLimit = _initializeData.priorityTxMaxGasLimit; // @audit-issue
46: s.feeParams = _initializeData.feeParams; // @audit-issue
47: s.blobVersionedHashRetriever = _initializeData.blobVersionedHashRetriever; // @audit-issue
30, 31, 32, 33, 34, 35, 37, 38, 41, 42, 43, 44, 45, 46, 47,
Path: ./code/contracts/ethereum/contracts/governance/Governance.sol
46: securityCouncil = _securityCouncil; // @audit-issue
46,
Path: ./code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol
56: sharedBridge = _sharedBridge; // @audit-issue
56,
Path: ./code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol
96: l1WethAddress = _l1WethAddress; // @audit-issue
97: bridgehub = _bridgehub; // @audit-issue
98: legacyBridge = _legacyBridge; // @audit-issue
Path: ./code/system-contracts/contracts/test-contracts/SystemCaller.sol
16: to = _to; // @audit-issue
16,
Path: ./code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol
114: pendingAdmin = _newPendingAdmin; // @audit-issue
133: validatorTimelock = _validatorTimelock; // @audit-issue
234: stateTransition[_chainId] = _stateTransitionContract; // @audit-issue
Path: ./code/contracts/ethereum/contracts/state-transition/ValidatorTimelock.sol
74: stateTransitionManager = _stateTransitionManager; // @audit-issue
74,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
27: s.pendingAdmin = _newPendingAdmin; // @audit-issue
27,
Path: ./code/contracts/ethereum/contracts/bridgehub/Bridgehub.sol
55: pendingAdmin = _newPendingAdmin; // @audit-issue
109: sharedBridge = IL1SharedBridge(_sharedBridge); // @audit-issue
134: stateTransitionManager[_chainId] = _stateTransitionManager; // @audit-issue
135: baseToken[_chainId] = _baseToken; // @audit-issue
Path: ./code/contracts/ethereum/contracts/governance/Governance.sol
258: securityCouncil = _newSecurityCouncil; // @audit-issue
258,
Path: ./code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol
143: l2BridgeAddress[_chainId] = _l2BridgeAddress; // @audit-issue
143,
Path: ./code/system-contracts/contracts/SystemContext.sol
100: origin = _newOrigin; // @audit-issue
100,
It is strongly recommended to implement checks to prevent the zero address from being set during the initialization of contracts. This can be achieved by adding require statements that ensure address parameters are not the zero address.
It is recommended from a risk perspective to disallow setting 100% fees at all. A reasonable fee maximum should be checked for instead.
Path: ./code/system-contracts/contracts/SystemContext.sol
463: baseFee = _baseFee; // @audit-issue
479: baseFee = _baseFee; // @audit-issue
Implement strict validation checks in your smart contract's fee-setting functions to ensure that fees cannot be configured to 100%. Define a maximum allowable fee percentage that preserves the contract's functionality and user experience, such as 10% or 20%, depending on your project's specific requirements and economic model. For example:
function setTransactionFee(uint256 newFeePercentage) public onlyOwner {
require(newFeePercentage <= 20, "Fee cannot exceed 20%");
transactionFee = newFeePercentage;
}
A low-level call will copy any amount of bytes to local memory. When bytes are copied from returndata to memory, the memory expansion cost is paid.
This means that when using a standard solidity call, the callee can 'returnbomb' the caller, imposing an arbitrary gas cost.
Because this gas is paid by the caller and in the caller's context, it can cause the caller to run out of gas and halt execution.
Consider replacing all unsafe call
with excessivelySafeCall
from this repository.
Path: ./code/contracts/ethereum/contracts/dev-contracts/ConstructorForwarder.sol
10: (bool success, ) = payable(to).call{value: msg.value}(data); // @audit-issue
10,
Path: ./code/contracts/ethereum/contracts/dev-contracts/Forwarder.sol
11: (success, returnValue) = payable(to).call{value: msg.value}(data); // @audit-issue
11,
Path: ./code/contracts/zksync/contracts/TestnetPaymaster.sol
63: (bool success, ) = payable(BOOTLOADER_ADDRESS).call{value: requiredETH}(""); // @audit-issue
63,
Path: ./code/contracts/zksync/contracts/bridge/L2WrappedBaseToken.sol
108: (bool success, ) = _to.call{value: _amount}(""); // @audit-issue
108,
Path: ./code/system-contracts/contracts/MsgValueSimulator.sol
63: (bool success, ) = address(REAL_BASE_TOKEN_SYSTEM_CONTRACT).call( // @audit-issue
63,
Path: ./code/system-contracts/contracts/GasBoundCaller.sol
57: bytes memory returnData = EfficientCall.call(gasleft(), _to, msg.value, _data, false); // @audit-issue
57,
Path: ./code/system-contracts/contracts/openzeppelin/utils/Address.sol
66: (bool success, ) = recipient.call{value: amount}(""); // @audit-issue
159: (bool success, bytes memory returndata) = target.call{value: value}( // @audit-issue
Mitigate the risk of gas griefing by replacing direct low-level calls (call
, delegatecall
, etc.) with excessivelySafeCall
, a utility that limits the amount of gas used for copying return data, thereby preventing return bombing. The excessivelySafeCall
function, available in certain security-focused repositories such as nomad-xyz/ExcessivelySafeCall, implements additional checks to ensure that the callee cannot impose arbitrary gas costs on the caller. To incorporate this into your contract:
- Review your contract for external calls to untrusted contracts.
- Import or implement an
excessivelySafeCall
utility in your contract. - Replace unsafe external calls with
excessivelySafeCall
, specifying limits on return data size as appropriate for your application.
Example usage:
// Assuming excessivelySafeCall is available in your contract
(bool success, ) = target.excessivelySafeCall(gasLimit, callData, maxReturnSize);
require(success, "External call failed");
This approach not only protects against gas griefing attacks but also enhances the overall security posture of your contract by ensuring that external interactions do not lead to unexpected execution halts or excessive gas consumption. Always perform thorough testing after integrating `excessivelySafeCall` to ensure compatibility and intended functionality.
### Use Of `transfer` or `send` Instead Of `call` To Send Native Assets
The use of `transfer()` in the contracts may have unintended outcomes on the native asset being sent to the receiver. The transaction will fail when:
- The receiver address is a smart contract that does not implement a payable function.
- The receiver address is a smart contract that implements a payable fallback function which uses more than 2300 gas units.
- The receiver address is a smart contract that implements a payable fallback function that needs less than 2300 gas units but is called through proxy, raising the call's gas usage above 2300.
- Additionally, using a gas value higher than 2300 might be mandatory for some multi-signature wallets.
```solidity
Path: ./code/contracts/ethereum/contracts/dev-contracts/WETH9.sol
33: payable(msg.sender).transfer(wad); // @audit-issue
33,
Path: ./code/system-contracts/contracts/test-contracts/TransferTest.sol
12: to.transfer(amount); // @audit-issue
21: bool success = to.send(amount); // @audit-issue
To transfer, a native asset is recommended to either use:
<receiver>.call.value(amount)
- OpenZeppelin Address.sendValue()
In both cases, care must be taken not to create reentrancy vulnerabilities. Consider using ReentrancyGuard or the Check-Effects-Interactions pattern.
The built-in EVM precompile ecrecover
is susceptible to signature malleability, which could lead to replay attacks.
Consider using OpenZeppelin’s ECDSA library instead of the built-in function.
Path: ./code/system-contracts/contracts/DefaultAccount.sol
186: address recoveredAddress = ecrecover(_hash, v, r, s); // @audit-issue
186,
To enhance the security of your Solidity smart contracts and mitigate the risk of signature malleability attacks, it is advisable to use OpenZeppelin's ECDSA library instead of the built-in ecrecover
function. The ECDSA library provides robust and reliable signature verification, reducing the vulnerability to replay attacks and ensuring the integrity of your contract interactions.
To ensure accuracy in comparisons within programming, especially when dealing with integers, it's often more efficient to use multiplication rather than division. This approach stems from the fact that division operations are generally slower and more complex than multiplication. And in the context of solidity they can cause precision errors.
Suppose you want to compare if a/b is greater than c/d (where a, b, c, and d are integers). Instead of performing division, which is prone to precision errors, you can cross-multiply to avoid division. The comparison a/b > c/d is equivalent to ad > bc. This way, you only use multiplication, which is faster and avoids potential inaccuracies or complexities associated with division.
Path: ./code/contracts/ethereum/contracts/state-transition/libraries/TransactionValidator.sol
30: require(l2GasForTxBody / _transaction.gasPerPubdataByteLimit <= _priorityTxMaxPubdata, "uk"); // @audit-issue
30,
When comparing ratios or fractions in Solidity, avoid direct division operations within the comparison. Instead, cross-multiply to maintain precision and reduce computational complexity. For example, to compare if a/b
is greater than c/d
, where a
, b
, c
, and d
are integers, reframe the comparison as a * d > b * c
. This approach avoids the division entirely, eliminating the risk of precision loss due to integer division truncation. Additionally, this method is more gas-efficient, as multiplication operations are cheaper than division. Apply this strategy in all relevant comparisons to enhance the accuracy and performance of your smart contracts. Always test thoroughly to ensure that the logic remains correct after the transformation.
The smart contract under inspection inherits from the Ownable
library, which provides basic authorization control functions, simplifying the implementation of user permissions. However, the contract does not provide a mechanism to transfer ownership to another address or account, and it retains the default renounceOwnership
function from Ownable
.
Given this, once the owner renounces ownership using the renounceOwnership
function, the contract becomes ownerless. As evidenced in the provided transaction logs, after the renounceOwnership
function is called, attempts to call functions that require owner permissions fail with the error message: "Ownable: caller is not the owner."
This state renders the contract's adjustable parameters immutable and potentially makes the contract useless for any future administrative changes that might be necessary.
Path: ./code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol
25:contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Ownable2Step { // @audit-issue
25,
Path: ./code/contracts/ethereum/contracts/state-transition/ValidatorTimelock.sol
22:contract ValidatorTimelock is IExecutor, Ownable2Step { // @audit-issue
22,
Path: ./code/contracts/ethereum/contracts/bridgehub/Bridgehub.sol
16:contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2Step { // @audit-issue
16,
Path: ./code/contracts/ethereum/contracts/governance/Governance.sol
20:contract Governance is IGovernance, Ownable2Step { // @audit-issue
20,
Path: ./code/contracts/ethereum/contracts/bridge/L1SharedBridge.sol
30:contract L1SharedBridge is IL1SharedBridge, ReentrancyGuard, Initializable, Ownable2Step { // @audit-issue
30,
To mitigate this vulnerability:
- Override the renounceOwnership function to revert transactions: By overriding this function to simply revert any transaction, it will become impossible for the contract owner to unintentionally (or intentionally) render the contract ownerless and thus immutable.
- Implement an ownership transfer function: While the Ownable library does provide a transferOwnership function, if this is not present or has been removed from the current contract, it should be re-implemented to ensure there is a way to transfer ownership in future scenarios.
The identifier is imported but never used within the file.
Path: ./code/contracts/ethereum/contracts/state-transition/StateTransitionManager.sol
19:import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, SYSTEM_UPGRADE_L2_TX_TYPE, ERA_DIAMOND_PROXY, ERA_CHAIN_ID} from "../common/Config.sol"; // @audit-issue: ERA_DIAMOND_PROXY not used in the contract.
19:import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, SYSTEM_UPGRADE_L2_TX_TYPE, ERA_DIAMOND_PROXY, ERA_CHAIN_ID} from "../common/Config.sol"; // @audit-issue: ERA_CHAIN_ID not used in the contract.
Path: ./code/contracts/ethereum/contracts/state-transition/chain-interfaces/IDiamondInit.sol
5:import {L2CanonicalTransaction} from "../../common/Messaging.sol"; // @audit-issue: L2CanonicalTransaction not used in the contract.
5,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-interfaces/IZkSyncStateTransition.sol
9:import {Verifier} from "../Verifier.sol"; // @audit-issue: Verifier not used in the contract.
10:import {VerifierParams} from "./IVerifier.sol"; // @audit-issue: VerifierParams not used in the contract.
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/DiamondInit.sol
7:import {FeeParams} from "./ZkSyncStateTransitionStorage.sol"; // @audit-issue: FeeParams not used in the contract.
10:import {VerifierParams} from "../chain-interfaces/IVerifier.sol"; // @audit-issue: VerifierParams not used in the contract.
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Admin.sol
13:import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; // @audit-issue: IZkSyncStateTransitionBase not used in the contract.
13,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Executor.sol
17:import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; // @audit-issue: IZkSyncStateTransitionBase not used in the contract.
17,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Getters.sol
15:import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; // @audit-issue: IZkSyncStateTransitionBase not used in the contract.
15,
Path: ./code/contracts/ethereum/contracts/state-transition/chain-deps/facets/Mailbox.sol
14:import {UnsafeBytes} from "../../../common/libraries/UnsafeBytes.sol"; // @audit-issue: UnsafeBytes not used in the contract.
19:import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "../../../common/L2ContractAddresses.sol"; // @audit-issue: L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR not used in the contract.
21:import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; // @audit-issue: IBridgehub not used in the contract.
25:import {IZkSyncStateTransitionBase} from "../../chain-interfaces/IZkSyncStateTransitionBase.sol"; // @audit-issue: IZkSyncStateTransitionBase not used in the contract.
Path: ./code/contracts/ethereum/contracts/upgrades/BaseZkSyncUpgrade.sol
6:import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; // @audit-issue: IMailbox not used in the contract.
6,
Path: ./code/contracts/ethereum/contracts/upgrades/BaseZkSyncUpgradeGenesis.sol
11:import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../common/Config.sol"; // @audit-issue: MAX_NEW_FACTORY_DEPS not used in the contract.
11:import {MAX_NEW_FACTORY_DEPS, SYSTEM_UPGRADE_L2_TX_TYPE, MAX_ALLOWED_PROTOCOL_VERSION_DELTA} from "../common/Config.sol"; // @audit-issue: SYSTEM_UPGRADE_L2_TX_TYPE not used in the contract.
Path: ./code/contracts/zksync/contracts/bridge/L2SharedBridge.sol
15:import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_BASE_TOKEN_ADDRESS, IContractDeployer} from "../L2ContractHelper.sol"; // @audit-issue: L2_BASE_TOKEN_ADDRESS not used in the contract.
17:import {ERA_CHAIN_ID, ERA_WETH_ADDRESS} from "../Config.sol"; // @audit-issue: ERA_WETH_ADDRESS not used in the contract.
Path: ./code/system-contracts/contracts/Compressor.sol
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: INITIAL_WRITE_STARTING_POSITION not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: COMPRESSED_INITIAL_WRITE_SIZE not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: STATE_DIFF_ENUM_INDEX_OFFSET not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: STATE_DIFF_FINAL_VALUE_OFFSET not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: STATE_DIFF_DERIVED_KEY_OFFSET not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: DERIVED_KEY_LENGTH not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: VALUE_LENGTH not used in the contract.
10:import {L1_MESSENGER_CONTRACT, INITIAL_WRITE_STARTING_POSITION, COMPRESSED_INITIAL_WRITE_SIZE, STATE_DIFF_ENTRY_SIZE, STATE_DIFF_ENUM_INDEX_OFFSET, STATE_DIFF_FINAL_VALUE_OFFSET, STATE_DIFF_DERIVED_KEY_OFFSET, DERIVED_KEY_LENGTH, VALUE_LENGTH, ENUM_INDEX_LENGTH, KNOWN_CODE_STORAGE_CONTRACT} from "./Constants.sol"; // @audit-issue: ENUM_INDEX_LENGTH not used in the contract.
10, 10, 10, 10, 10, 10, 10, 10,
Path: ./code/system-contracts/contracts/PubdataChunkPublisher.sol
6:import {L1_MESSENGER_CONTRACT, BLOB_SIZE_BYTES, MAX_NUMBER_OF_BLOBS} from "./Constants.sol"; // @audit-issue: L1_MESSENGER_CONTRACT not used in the contract.
7:import {EfficientCall} from "./libraries/EfficientCall.sol"; // @audit-issue: EfficientCall not used in the contract.
Path: ./code/system-contracts/contracts/Constants.sol
5:import {IAccountCodeStorage} from "./interfaces/IAccountCodeStorage.sol"; // @audit-issue: IAccountCodeStorage not used in the contract.
6:import {INonceHolder} from "./interfaces/INonceHolder.sol"; // @audit-issue: INonceHolder not used in the contract.
7:import {IContractDeployer} from "./interfaces/IContractDeployer.sol"; // @audit-issue: IContractDeployer not used in the contract.
8:import {IKnownCodesStorage} from "./interfaces/IKnownCodesStorage.sol"; // @audit-issue: IKnownCodesStorage not used in the contract.
9:import {IImmutableSimulator} from "./interfaces/IImmutableSimulator.sol"; // @audit-issue: IImmutableSimulator not used in the contract.
10:import {IBaseToken} from "./interfaces/IBaseToken.sol"; // @audit-issue: IBaseToken not used in the contract.
11:import {IL1Messenger} from "./interfaces/IL1Messenger.sol"; // @audit-issue: IL1Messenger not used in the contract.
12:import {ISystemContext} from "./interfaces/ISystemContext.sol"; // @audit-issue: ISystemContext not used in the contract.
13:import {ICompressor} from "./interfaces/ICompressor.sol"; // @audit-issue: ICompressor not used in the contract.
14:import {IComplexUpgrader} from "./interfaces/IComplexUpgrader.sol"; // @audit-issue: IComplexUpgrader not used in the contract.
15:import {IBootloaderUtilities} from "./interfaces/IBootloaderUtilities.sol"; // @audit-issue: IBootloaderUtilities not used in the contract.
16:import {IPubdataChunkPublisher} from "./interfaces/IPubdataChunkPublisher.sol"; // @audit-issue: IPubdataChunkPublisher not used in the contract.
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
Path: ./code/system-contracts/contracts/L1Messenger.sol
10:import {SystemLogKey, SYSTEM_CONTEXT_CONTRACT, KNOWN_CODE_STORAGE_CONTRACT, COMPRESSOR_CONTRACT, STATE_DIFF_ENTRY_SIZE, MAX_ALLOWED_PUBDATA_PER_BATCH, L2_TO_L1_LOGS_MERKLE_TREE_LEAVES, PUBDATA_CHUNK_PUBLISHER, COMPUTATIONAL_PRICE_FOR_PUBDATA} from "./Constants.sol"; // @audit-issue: KNOWN_CODE_STORAGE_CONTRACT not used in the contract.
10,
Path: ./code/system-contracts/contracts/ContractDeployer.sol
7:import {CREATE2_PREFIX, CREATE_PREFIX, NONCE_HOLDER_SYSTEM_CONTRACT, ACCOUNT_CODE_STORAGE_SYSTEM_CONTRACT, FORCE_DEPLOYER, MAX_SYSTEM_CONTRACT_ADDRESS, KNOWN_CODE_STORAGE_CONTRACT, BASE_TOKEN_SYSTEM_CONTRACT, IMMUTABLE_SIMULATOR_SYSTEM_CONTRACT, COMPLEX_UPGRADER_CONTRACT, KECCAK256_SYSTEM_CONTRACT} from "./Constants.sol"; // @audit-issue: KECCAK256_SYSTEM_CONTRACT not used in the contract.
7,
Path: ./code/system-contracts/contracts/libraries/SystemContractHelper.sol
7:import {SystemContractsCaller, CalldataForwardingMode, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_GAS_PER_PUBDATA_BYTE_OFFSET, MIMIC_CALL_BY_REF_CALL_ADDRESS, META_CALL_ADDRESS, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, RAW_FAR_CALL_BY_REF_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, SYSTEM_CALL_BY_REF_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; // @audit-issue: SystemContractsCaller not used in the contract.
7:import {SystemContractsCaller, CalldataForwardingMode, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_GAS_PER_PUBDATA_BYTE_OFFSET, MIMIC_CALL_BY_REF_CALL_ADDRESS, META_CALL_ADDRESS, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, RAW_FAR_CALL_BY_REF_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, SYSTEM_CALL_BY_REF_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; // @audit-issue: CalldataForwardingMode not used in the contract.
7:import {SystemContractsCaller, CalldataForwardingMode, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_GAS_PER_PUBDATA_BYTE_OFFSET, MIMIC_CALL_BY_REF_CALL_ADDRESS, META_CALL_ADDRESS, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, RAW_FAR_CALL_BY_REF_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, SYSTEM_CALL_BY_REF_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; // @audit-issue: MIMIC_CALL_BY_REF_CALL_ADDRESS not used in the contract.
7:import {SystemContractsCaller, CalldataForwardingMode, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_GAS_PER_PUBDATA_BYTE_OFFSET, MIMIC_CALL_BY_REF_CALL_ADDRESS, META_CALL_ADDRESS, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, RAW_FAR_CALL_BY_REF_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, SYSTEM_CALL_BY_REF_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; // @audit-issue: MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT not used in the contract.
7:import {SystemContractsCaller, CalldataForwardingMode, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_GAS_PER_PUBDATA_BYTE_OFFSET, MIMIC_CALL_BY_REF_CALL_ADDRESS, META_CALL_ADDRESS, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, RAW_FAR_CALL_BY_REF_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, SYSTEM_CALL_BY_REF_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; // @audit-issue: RAW_FAR_CALL_BY_REF_CALL_ADDRESS not used in the contract.
7:import {SystemContractsCaller, CalldataForwardingMode, CALLFLAGS_CALL_ADDRESS, CODE_ADDRESS_CALL_ADDRESS, EVENT_WRITE_ADDRESS, EVENT_INITIALIZE_ADDRESS, GET_EXTRA_ABI_DATA_ADDRESS, LOAD_CALLDATA_INTO_ACTIVE_PTR_CALL_ADDRESS, META_CODE_SHARD_ID_OFFSET, META_CALLER_SHARD_ID_OFFSET, META_SHARD_ID_OFFSET, META_AUX_HEAP_SIZE_OFFSET, META_HEAP_SIZE_OFFSET, META_GAS_PER_PUBDATA_BYTE_OFFSET, MIMIC_CALL_BY_REF_CALL_ADDRESS, META_CALL_ADDRESS, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT, PTR_CALLDATA_CALL_ADDRESS, PTR_ADD_INTO_ACTIVE_CALL_ADDRESS, PTR_SHRINK_INTO_ACTIVE_CALL_ADDRESS, PTR_PACK_INTO_ACTIVE_CALL_ADDRESS, RAW_FAR_CALL_BY_REF_CALL_ADDRESS, PRECOMPILE_CALL_ADDRESS, SET_CONTEXT_VALUE_CALL_ADDRESS, SYSTEM_CALL_BY_REF_CALL_ADDRESS, TO_L1_CALL_ADDRESS} from "./SystemContractsCaller.sol"; // @audit-issue: SYSTEM_CALL_BY_REF_CALL_ADDRESS not used in the contract.
Regularly review your Solidity code to remove unused imports. This practice declutters the codebase, making it easier to understand and maintain. In addition, consider using tools or IDE features that can automatically detect and highlight unused imports for cleanup. Keeping imports limited to only what is necessary helps maintain a clear understanding of the contract's dependencies and reduces potential confusion for developers working on or reviewing the code.
The Anyswap hack occurred because the permit()
function didn't really exist, but the fallback function that took its place did not complain. Consider using safePermit()
which ensures that the permit actually went through.
Path: ./code/system-contracts/contracts/openzeppelin/token/ERC20/utils/SafeERC20.sol
120: token.permit(owner, spender, value, deadline, v, r, s); // @audit-issue
120,
Consider replacing the use of the permit()
function with safePermit()
from OpenZeppelin's SafeERC20 library or a similar safe implementation. safePermit()
provides additional safety checks to ensure that the permit transaction is executed securely, reducing the risk of potential vulnerabilities.
Index event fields make the field more quickly accessible to off-chain tools that parse events. This is especially useful when it comes to filtering based on an address. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Where applicable, each event
should use three indexed
fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three applicable fields, all of the applicable fields should be indexed
.
Path: ./code/contracts/ethereum/contracts/state-transition/ValidatorTimelock.sol
32: event ValidatorAdded(uint256 _chainId, address _addedValidator); // @audit-issue
35: event ValidatorRemoved(uint256 _chainId, address _removedValidator); // @audit-issue
Path: ./code/contracts/ethereum/contracts/state-transition/libraries/Diamond.sol
24: event DiamondCut(FacetCut[] facetCuts, address initAddress, bytes initCalldata); // @audit-issue
24,
Path: ./code/contracts/ethereum/contracts/bridgehub/IBridgehub.sol
133: event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); // @audit-issue
133,
Path: ./code/contracts/ethereum/contracts/governance/IGovernance.sol
77: event ChangeSecurityCouncil(address _securityCouncilBefore, address _securityCouncilAfter); // @audit-issue
77,
Path: ./code/contracts/ethereum/contracts/dev-contracts/EventOnFallback.sol
9: event Called(address msgSender, uint256 value, bytes data); // @audit-issue
9,
Path: ./code/contracts/ethereum/contracts/bridge/interfaces/IL1SharedBridge.sol
31: event BridgehubDepositBaseTokenInitiated( // @audit-issue
32: uint256 indexed chainId,
33: address indexed from,
34: address l1Token,
35: uint256 amount
36: );
31,
Enhance smart contract efficiency post-deployment by utilizing indexed events. This approach aids in efficiently tracking contract activities, significantly contributing to the reduction of gas costs.
Changing an allowance with approve()
brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. Refer to ERC20 API: An Attack Vector on the Approve/TransferFrom Methods. It is recommended to use the increaseAllowance()
/decreaseAllowance()
to avoid ths problem.
Path: ./code/system-contracts/contracts/libraries/TransactionHelper.sol
382: IERC20(token).safeApprove(paymaster, 0); // @audit-issue
383: IERC20(token).safeApprove(paymaster, minAllowance); // @audit-issue
To enhance the security of your Solidity smart contracts and avoid potential issues related to transaction ordering, it is advisable to use the increaseAllowance()
and decreaseAllowance()
functions instead of approve()
or safeApprove()
. These functions provide a safer and more atomic way to modify allowances and mitigate the risk associated with potential attack vectors like those described in the ERC20 API: An Attack Vector on the Approve/TransferFrom Methods document.