diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index ee2a98eb3a..2fe40bdabb 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -29,7 +29,7 @@ class FileScope: def __init__(self, filename: Filename) -> None: self.filename = filename self.accessible_scopes: List[FileScope] = [] - self.exported_symbols: Set[int] = set() + self.exported_symbols: List[int] = [] self.contracts: Dict[str, Contract] = {} # Custom error are a list instead of a dict @@ -82,8 +82,16 @@ def add_accessible_scopes(self) -> bool: # pylint: disable=too-many-branches # To get around this bug for aliases https://github.com/ethereum/solidity/pull/11881, # we propagate the exported_symbols from the imported file to the importing file # See tests/e2e/solc_parsing/test_data/top-level-nested-import-0.7.1.sol - if not new_scope.exported_symbols.issubset(self.exported_symbols): - self.exported_symbols |= new_scope.exported_symbols + if not set(new_scope.exported_symbols).issubset(self.exported_symbols): + # We are using lists and specifically extending them to keep the order in which + # elements are added. This will come handy when we have name collisions. + # See issue : https://github.com/crytic/slither/issues/2477 + new_symbols = [ + symbol + for symbol in new_scope.exported_symbols + if symbol not in self.exported_symbols + ] + self.exported_symbols.extend(new_symbols) learn_something = True # This is need to support aliasing when we do a late lookup using SolidityImportPlaceholder diff --git a/slither/detectors/statements/unused_import.py b/slither/detectors/statements/unused_import.py index d3447dcd81..e0ab236616 100644 --- a/slither/detectors/statements/unused_import.py +++ b/slither/detectors/statements/unused_import.py @@ -92,9 +92,9 @@ def _detect(self) -> List[Output]: # pylint: disable=too-many-branches use_found = False # Search through all references to the imported file - for _, refs_to_imported_path in self.slither._offset_to_references[ + for refs_to_imported_path in self.slither._offset_to_references[ imported_path - ].items(): + ].values(): for ref in refs_to_imported_path: # If there is a reference in this file to the imported file, it is used. if ref.filename == filename: diff --git a/slither/slither.py b/slither/slither.py index 7adc0694ca..4b30048dc1 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -56,7 +56,13 @@ def _update_file_scopes( for refId in scope.exported_symbols: if refId in sol_parser.contracts_by_id: contract = sol_parser.contracts_by_id[refId] - scope.contracts[contract.name] = contract + + # Add elements only if they are not already present. By keeping the exported symbols + # in the order they were encountered, we ensure that the most local imports are + # resolved first. + if contract.name not in scope.contracts: + scope.contracts[contract.name] = contract + elif refId in sol_parser.functions_by_id: functions = sol_parser.functions_by_id[refId] assert len(functions) == 1 diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index 36efeef33a..848e2965b8 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -3,6 +3,7 @@ import logging import os import re +from itertools import chain from pathlib import Path from typing import List, Dict @@ -256,8 +257,13 @@ def parse_top_level_items(self, data_loaded: Dict, filename: str) -> None: scope = self.compilation_unit.get_scope(filename) # Exported symbols includes a reference ID to all top-level definitions the file exports, # including def's brought in by imports (even transitively) and def's local to the file. - for refId in exported_symbols.values(): - scope.exported_symbols |= set(refId) + + new_symbols = [ + symbol + for symbol in chain.from_iterable(exported_symbols.values()) + if symbol not in scope.exported_symbols + ] + scope.exported_symbols.extend(new_symbols) for top_level_data in data_loaded[self.get_children()]: if top_level_data[self.get_key()] == "ContractDefinition": diff --git a/tests/e2e/detectors/snapshots/detectors__detector_UnusedImport_0_8_16_CrossDomainMessenger_sol__0.txt b/tests/e2e/detectors/snapshots/detectors__detector_UnusedImport_0_8_16_CrossDomainMessenger_sol__0.txt new file mode 100644 index 0000000000..15d6b29ef1 --- /dev/null +++ b/tests/e2e/detectors/snapshots/detectors__detector_UnusedImport_0_8_16_CrossDomainMessenger_sol__0.txt @@ -0,0 +1,3 @@ +The following unused import(s) in tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol should be removed: + -import "./utils/console.sol"; (tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol#6) + diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/Constants.sol b/tests/e2e/detectors/test_data/unused-import/0.8.16/Constants.sol new file mode 100644 index 0000000000..106151daee --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-import/0.8.16/Constants.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; + +import { ResourceMetering } from "./lib/ResourceMetering.sol"; + +/// @title Constants +/// @notice Constants is a library for storing constants. Simple! Don't put everything in here, just +/// the stuff used in multiple contracts. Constants that only apply to a single contract +/// should be defined in that contract instead. +library Constants { + /// @notice Special address to be used as the tx origin for gas estimation calls in the + /// OptimismPortal and CrossDomainMessenger calls. You only need to use this address if + /// the minimum gas limit specified by the user is not actually enough to execute the + /// given message and you're attempting to estimate the actual necessary gas limit. We + /// use address(1) because it's the ecrecover precompile and therefore guaranteed to + /// never have any code on any EVM chain. + address internal constant ESTIMATION_ADDRESS = address(1); + + /// @notice Value used for the L2 sender storage slot in both the OptimismPortal and the + /// CrossDomainMessenger contracts before an actual sender is set. This value is + /// non-zero to reduce the gas cost of message passing transactions. + address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD; + + /// @notice The storage slot that holds the address of a proxy implementation. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)` + bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /// @notice The storage slot that holds the address of the owner. + /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` + bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /// @notice The address that represents ether when dealing with ERC20 token addresses. + address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + /// @notice The address that represents the system caller responsible for L1 attributes + /// transactions. + address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice Returns the default values for the ResourceConfig. These are the recommended values + /// for a production network. + function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) { + ResourceMetering.ResourceConfig memory config = ResourceMetering.ResourceConfig({ + maxResourceLimit: 20_000_000, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1 gwei, + systemTxMaxGas: 1_000_000, + maximumBaseFee: type(uint128).max + }); + return config; + } +} diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol b/tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol new file mode 100644 index 0000000000..425421140d --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +import { Initializable } from "./utils/upgradeable/Initializable.sol"; +import { Constants } from "./Constants.sol"; +import "./utils/console.sol"; + + +/// @custom:upgradeable +/// @title CrossDomainMessenger +/// @notice CrossDomainMessenger is a base contract that provides the core logic for the L1 and L2 +/// cross-chain messenger contracts. It's designed to be a universal interface that only +/// needs to be extended slightly to provide low-level message passing functionality on each +/// chain it's deployed on. Currently only designed for message passing between two paired +/// chains and does not support one-to-many interactions. +/// Any changes to this contract MUST result in a semver bump for contracts that inherit it. +abstract contract CrossDomainMessenger is + Initializable +{ + /// @notice Current message version identifier. + uint16 public constant MESSAGE_VERSION = 1; + + /// @notice Constant overhead added to the base gas for a message. + uint64 public constant RELAY_CONSTANT_OVERHEAD = 200_000; + + /// @notice Numerator for dynamic overhead added to the base gas for a message. + uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR = 64; + + /// @notice Denominator for dynamic overhead added to the base gas for a message. + uint64 public constant MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = 63; + + /// @notice Extra gas added to base gas for each byte of calldata in a message. + uint64 public constant MIN_GAS_CALLDATA_OVERHEAD = 16; + + /// @notice Gas reserved for performing the external call in `relayMessage`. + uint64 public constant RELAY_CALL_OVERHEAD = 40_000; + + /// @notice Gas reserved for finalizing the execution of `relayMessage` after the safe call. + uint64 public constant RELAY_RESERVED_GAS = 40_000; + + /// @notice Gas reserved for the execution between the `hasMinGas` check and the external + /// call in `relayMessage`. + uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000; + + /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only + /// be present in this mapping if it has successfully been relayed on this chain, and + /// can therefore not be relayed again. + mapping(bytes32 => bool) public successfulMessages; + + /// @notice Address of the sender of the currently executing message on the other chain. If the + /// value of this variable is the default value (0x00000000...dead) then no message is + /// currently being executed. Use the xDomainMessageSender getter which will throw an + /// error if this is the case. + address internal xDomainMsgSender; + + /// @notice Nonce for the next message to be sent, without the message version applied. Use the + /// messageNonce getter which will insert the message version into the nonce to give you + /// the actual nonce to be used for the message. + uint240 internal msgNonce; + + /// @notice Mapping of message hashes to a boolean if and only if the message has failed to be + /// executed at least once. A message will not be present in this mapping if it + /// successfully executed on the first attempt. + mapping(bytes32 => bool) public failedMessages; + + /// @notice CrossDomainMessenger contract on the other chain. + /// @custom:network-specific + CrossDomainMessenger public otherMessenger; + + /// @notice Reserve extra slots in the storage layout for future upgrades. + /// A gap size of 43 was chosen here, so that the first slot used in a child contract + /// would be 1 plus a multiple of 50. + uint256[43] private __gap; + + /// @notice Emitted whenever a message is sent to the other chain. + /// @param target Address of the recipient of the message. + /// @param sender Address of the sender of the message. + /// @param message Message to trigger the recipient address with. + /// @param messageNonce Unique nonce attached to the message. + /// @param gasLimit Minimum gas limit that the message can be executed with. + event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit); + + /// @notice Additional event data to emit, required as of Bedrock. Cannot be merged with the + /// SentMessage event without breaking the ABI of this contract, this is good enough. + /// @param sender Address of the sender of the message. + /// @param value ETH value sent along with the message to the recipient. + event SentMessageExtension1(address indexed sender, uint256 value); + + /// @notice Emitted whenever a message is successfully relayed on this chain. + /// @param msgHash Hash of the message that was relayed. + event RelayedMessage(bytes32 indexed msgHash); + + /// @notice Emitted whenever a message fails to be relayed on this chain. + /// @param msgHash Hash of the message that failed to be relayed. + event FailedRelayedMessage(bytes32 indexed msgHash); + + /// @notice Sends a message to some target address on the other chain. Note that if the call + /// always reverts, then the message will be unrelayable, and any ETH sent will be + /// permanently locked. The same will occur if the target on the other chain is + /// considered unsafe (see the _isUnsafeTarget() function). + /// @param _target Target contract or wallet address. + /// @param _message Message to trigger the target address with. + /// @param _minGasLimit Minimum gas limit that the message can be executed with. + function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable { + if (isCustomGasToken()) { + require(msg.value == 0, "CrossDomainMessenger: cannot send value with custom gas token"); + } + + // Triggers a message to the other messenger. Note that the amount of gas provided to the + // message is the amount of gas requested by the user PLUS the base gas value. We want to + // guarantee the property that the call to the target contract will always have at least + // the minimum gas limit specified by the user. + _sendMessage({ + _to: address(otherMessenger), + _gasLimit: baseGas(_message, _minGasLimit), + _value: msg.value, + _data: abi.encodeWithSelector( + this.relayMessage.selector, messageNonce(), msg.sender, _target, msg.value, _minGasLimit, _message + ) + }); + + emit SentMessage(_target, msg.sender, _message, messageNonce(), _minGasLimit); + emit SentMessageExtension1(msg.sender, msg.value); + + unchecked { + ++msgNonce; + } + } + + /// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only + /// be executed via cross-chain call from the other messenger OR if the message was + /// already received once and is currently being replayed. + /// @param _nonce Nonce of the message being relayed. + /// @param _sender Address of the user who sent the message. + /// @param _target Address that the message is targeted at. + /// @param _value ETH value to send with the message. + /// @param _minGasLimit Minimum amount of gas that the message can be executed with. + /// @param _message Message to send to the target. + function relayMessage( + uint256 _nonce, + address _sender, + address _target, + uint256 _value, + uint256 _minGasLimit, + bytes calldata _message + ) + external + payable + { + return; + } + + /// @notice Retrieves the address of the contract or wallet that initiated the currently + /// executing message on the other chain. Will throw an error if there is no message + /// currently being executed. Allows the recipient of a call to see who triggered it. + /// @return Address of the sender of the currently executing message on the other chain. + function xDomainMessageSender() external view returns (address) { + require( + xDomainMsgSender != Constants.DEFAULT_L2_SENDER, "CrossDomainMessenger: xDomainMessageSender is not set" + ); + + return xDomainMsgSender; + } + + /// @notice Retrieves the address of the paired CrossDomainMessenger contract on the other chain + /// Public getter is legacy and will be removed in the future. Use `otherMessenger()` instead. + /// @return CrossDomainMessenger contract on the other chain. + /// @custom:legacy + function OTHER_MESSENGER() public view returns (CrossDomainMessenger) { + return otherMessenger; + } + + /// @notice Retrieves the next message nonce. Message version will be added to the upper two + /// bytes of the message nonce. Message version allows us to treat messages as having + /// different structures. + /// @return Nonce of the next message to be sent, with added message version. + function messageNonce() public view returns (uint256) { + return 1; + } + + /// @notice Computes the amount of gas required to guarantee that a given message will be + /// received on the other chain without running out of gas. Guaranteeing that a message + /// will not run out of gas is important because this ensures that a message can always + /// be replayed on the other chain if it fails to execute completely. + /// @param _message Message to compute the amount of required gas for. + /// @param _minGasLimit Minimum desired gas limit when message goes to target. + /// @return Amount of gas required to guarantee message receipt. + function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) { + return + // Constant overhead + RELAY_CONSTANT_OVERHEAD + // Calldata overhead + + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) + // Dynamic overhead (EIP-150) + + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) + // Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas + // factors. (Conservative) + + RELAY_CALL_OVERHEAD + // Relay reserved gas (to ensure execution of `relayMessage` completes after the + // subcontext finishes executing) (Conservative) + + RELAY_RESERVED_GAS + // Gas reserved for the execution between the `hasMinGas` check and the `CALL` + // opcode. (Conservative) + + RELAY_GAS_CHECK_BUFFER; + } + + /// @notice Returns the address of the gas token and the token's decimals. + function gasPayingToken() internal view virtual returns (address, uint8); + + /// @notice Returns whether the chain uses a custom gas token or not. + function isCustomGasToken() internal view returns (bool) { + (address token,) = gasPayingToken(); + return token != Constants.ETHER; + } + + /// @notice Initializer. + /// @param _otherMessenger CrossDomainMessenger contract on the other chain. + function __CrossDomainMessenger_init(CrossDomainMessenger _otherMessenger) internal onlyInitializing { + // We only want to set the xDomainMsgSender to the default value if it hasn't been initialized yet, + // meaning that this is a fresh contract deployment. + // This prevents resetting the xDomainMsgSender to the default value during an upgrade, which would enable + // a reentrant withdrawal to sandwhich the upgrade replay a withdrawal twice. + if (xDomainMsgSender == address(0)) { + xDomainMsgSender = Constants.DEFAULT_L2_SENDER; + } + otherMessenger = _otherMessenger; + } + + /// @notice Sends a low-level message to the other messenger. Needs to be implemented by child + /// contracts because the logic for this depends on the network where the messenger is + /// being deployed. + /// @param _to Recipient of the message on the other chain. + /// @param _gasLimit Minimum gas limit the message can be executed with. + /// @param _value Amount of ETH to send with the message. + /// @param _data Message data. + function _sendMessage(address _to, uint64 _gasLimit, uint256 _value, bytes memory _data) internal virtual; + + /// @notice Checks whether the message is coming from the other messenger. Implemented by child + /// contracts because the logic for this depends on the network where the messenger is + /// being deployed. + /// @return Whether the message is coming from the other messenger. + function _isOtherMessenger() internal view virtual returns (bool); + + /// @notice Checks whether a given call target is a system address that could cause the + /// messenger to peform an unsafe action. This is NOT a mechanism for blocking user + /// addresses. This is ONLY used to prevent the execution of messages to specific + /// system addresses that could cause security issues, e.g., having the + /// CrossDomainMessenger send messages to itself. + /// @param _target Address of the contract to check. + /// @return Whether or not the address is an unsafe system address. + function _isUnsafeTarget(address _target) internal view virtual returns (bool); + + /// @notice This function should return true if the contract is paused. + /// On L1 this function will check the SuperchainConfig for its paused status. + /// On L2 this function should be a no-op. + /// @return Whether or not the contract is paused. + function paused() public view virtual returns (bool) { + return false; + } +} diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol-0.8.16.zip b/tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol-0.8.16.zip new file mode 100644 index 0000000000..6cb35358a7 Binary files /dev/null and b/tests/e2e/detectors/test_data/unused-import/0.8.16/CrossDomainMessenger.sol-0.8.16.zip differ diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/lib/ResourceMetering.sol b/tests/e2e/detectors/test_data/unused-import/0.8.16/lib/ResourceMetering.sol new file mode 100644 index 0000000000..024d22e8bb --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-import/0.8.16/lib/ResourceMetering.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.16; + +import { Initializable } from "../utils/original/Initializable.sol"; + + +/// @custom:upgradeable +/// @title ResourceMetering +/// @notice ResourceMetering implements an EIP-1559 style resource metering system where pricing +/// updates automatically based on current demand. +abstract contract ResourceMetering is Initializable { + /// @notice Error returned when too much gas resource is consumed. + error OutOfGas(); + + /// @notice Represents the various parameters that control the way in which resources are + /// metered. Corresponds to the EIP-1559 resource metering system. + /// @custom:field prevBaseFee Base fee from the previous block(s). + /// @custom:field prevBoughtGas Amount of gas bought so far in the current block. + /// @custom:field prevBlockNum Last block number that the base fee was updated. + struct ResourceParams { + uint128 prevBaseFee; + uint64 prevBoughtGas; + uint64 prevBlockNum; + } + + /// @notice Represents the configuration for the EIP-1559 based curve for the deposit gas + /// market. These values should be set with care as it is possible to set them in + /// a way that breaks the deposit gas market. The target resource limit is defined as + /// maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a + /// single word. There is additional space for additions in the future. + /// @custom:field maxResourceLimit Represents the maximum amount of deposit gas that + /// can be purchased per block. + /// @custom:field elasticityMultiplier Determines the target resource limit along with + /// the resource limit. + /// @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block. + /// @custom:field minimumBaseFee The min deposit base fee, it is clamped to this + /// value. + /// @custom:field systemTxMaxGas The amount of gas supplied to the system + /// transaction. This should be set to the same + /// number that the op-node sets as the gas limit + /// for the system transaction. + /// @custom:field maximumBaseFee The max deposit base fee, it is clamped to this + /// value. + struct ResourceConfig { + uint32 maxResourceLimit; + uint8 elasticityMultiplier; + uint8 baseFeeMaxChangeDenominator; + uint32 minimumBaseFee; + uint32 systemTxMaxGas; + uint128 maximumBaseFee; + } + + /// @notice EIP-1559 style gas parameters. + ResourceParams public params; + + /// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. + uint256[48] private __gap; + + /// @notice Meters access to a function based an amount of a requested resource. + /// @param _amount Amount of the resource requested. + modifier metered(uint64 _amount) { + // Record initial gas amount so we can refund for it later. + uint256 initialGas = gasleft(); + + // Run the underlying function. + _; + + // Run the metering function. + _metered(_amount, initialGas); + } + + /// @notice An internal function that holds all of the logic for metering a resource. + /// @param _amount Amount of the resource requested. + /// @param _initialGas The amount of gas before any modifier execution. + function _metered(uint64 _amount, uint256 _initialGas) internal { + // Update block number and base fee if necessary. + uint256 blockDiff = block.number - params.prevBlockNum; + + ResourceConfig memory config = _resourceConfig(); + int256 targetResourceLimit = + int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier)); + + if (blockDiff > 0) { + // Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate + // at which deposits can be created and therefore limit the potential for deposits to + // spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes. + int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit; + int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta) + / (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator))); + + // Update base fee by adding the base fee delta and clamp the resulting value between + // min and max. + int256 newBaseFee = 0; + + // If we skipped more than one block, we also need to account for every empty block. + // Empty block means there was no demand for deposits in that block, so we should + // reflect this lack of demand in the fee. + if (blockDiff > 1) { + // Update the base fee by repeatedly applying the exponent 1-(1/change_denominator) + // blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value + // between min and max. + newBaseFee = 0; + } + + // Update new base fee, reset bought gas, and update block number. + params.prevBaseFee = uint128(uint256(newBaseFee)); + params.prevBoughtGas = 0; + params.prevBlockNum = uint64(block.number); + } + + // Make sure we can actually buy the resource amount requested by the user. + params.prevBoughtGas += _amount; + if (int256(uint256(params.prevBoughtGas)) > int256(uint256(config.maxResourceLimit))) { + revert OutOfGas(); + } + + // Determine the amount of ETH to be paid. + uint256 resourceCost = uint256(_amount) * uint256(params.prevBaseFee); + + // We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount + // into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid + // division by zero for L1s that don't support 1559 or to avoid excessive gas burns during + // periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei + // during any 1 day period in the last 5 years, so should be fine. + uint256 gasCost = resourceCost / 1; + + // Give the user a refund based on the amount of gas they used to do all of the work up to + // this point. Since we're at the end of the modifier, this should be pretty accurate. Acts + // effectively like a dynamic stipend (with a minimum value). + uint256 usedGas = _initialGas - gasleft(); + } + + /// @notice Adds an amount of L2 gas consumed to the prev bought gas params. This is meant to be used + /// when L2 system transactions are generated from L1. + /// @param _amount Amount of the L2 gas resource requested. + function useGas(uint32 _amount) internal { + params.prevBoughtGas += uint64(_amount); + } + + /// @notice Virtual function that returns the resource config. + /// Contracts that inherit this contract must implement this function. + /// @return ResourceConfig + function _resourceConfig() internal virtual returns (ResourceConfig memory); + + /// @notice Sets initial resource parameter values. + /// This function must either be called by the initializer function of an upgradeable + /// child contract. + function __ResourceMetering_init() internal onlyInitializing { + if (params.prevBlockNum == 0) { + params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) }); + } + } +} diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/console.sol b/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/console.sol new file mode 100644 index 0000000000..966aaafd77 --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/console.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.16; + +contract Console { + constructor(){ + + } +} diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/original/Initializable.sol b/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/original/Initializable.sol new file mode 100644 index 0000000000..a613b63164 --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/original/Initializable.sol @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol) + +pragma solidity ^0.8.16; + +abstract contract Initializable { + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:openzeppelin.storage.Initializable + */ + struct InitializableStorage { + /** + * @dev Indicates that the contract has been initialized. + */ + uint64 _initialized; + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool _initializing; + } + + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; + + /** + * @dev The contract is already initialized. + */ + error InvalidInitialization(); + + /** + * @dev The contract is not initializing. + */ + error NotInitializing(); + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint64 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any + * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in + * production. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + // Cache values to avoid duplicated sloads + bool isTopLevelCall = !$._initializing; + uint64 initialized = $._initialized; + + // Allowed calls: + // - initialSetup: the contract is not in the initializing state and no previous version was + // initialized + // - construction: the contract is initialized at version 1 (no reininitialization) and the + // current contract is just being deployed + bool initialSetup = initialized == 0 && isTopLevelCall; + bool construction = initialized == 1 && address(this).code.length == 0; + + if (!initialSetup && !construction) { + revert InvalidInitialization(); + } + $._initialized = 1; + if (isTopLevelCall) { + $._initializing = true; + } + _; + if (isTopLevelCall) { + $._initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(uint64 version) { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing || $._initialized >= version) { + revert InvalidInitialization(); + } + $._initialized = version; + $._initializing = true; + _; + $._initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + _checkInitializing(); + _; + } + + /** + * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. + */ + function _checkInitializing() internal view virtual { + if (!_isInitializing()) { + revert NotInitializing(); + } + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing) { + revert InvalidInitialization(); + } + if ($._initialized != type(uint64).max) { + $._initialized = type(uint64).max; + emit Initialized(type(uint64).max); + } + } + + /** + * @dev Returns the highest version that has been initialized. See {reinitializer}. + */ + function _getInitializedVersion() internal view returns (uint64) { + return _getInitializableStorage()._initialized; + } + + /** + * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. + */ + function _isInitializing() internal view returns (bool) { + return _getInitializableStorage()._initializing; + } + + /** + * @dev Returns a pointer to the storage namespace. + */ + // solhint-disable-next-line var-name-mixedcase + function _getInitializableStorage() private pure returns (InitializableStorage storage $) { + assembly { + $.slot := INITIALIZABLE_STORAGE + } + } +} diff --git a/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/upgradeable/Initializable.sol b/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/upgradeable/Initializable.sol new file mode 100644 index 0000000000..404e2bc9ef --- /dev/null +++ b/tests/e2e/detectors/test_data/unused-import/0.8.16/utils/upgradeable/Initializable.sol @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol) + +pragma solidity ^0.8.16; + +/** + * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed + * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an + * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer + * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. + * + * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be + * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in + * case an upgrade adds a module that needs to be initialized. + * + * For example: + * + * [.hljs-theme-light.nopadding] + * ```solidity + * contract MyToken is ERC20Upgradeable { + * function initialize() initializer public { + * __ERC20_init("MyToken", "MTK"); + * } + * } + * + * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + * function initializeV2() reinitializer(2) public { + * __ERC20Permit_init("MyToken"); + * } + * } + * ``` + * + * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as + * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. + * + * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure + * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + * + * [CAUTION] + * ==== + * Avoid leaving a contract uninitialized. + * + * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation + * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke + * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed: + * + * [.hljs-theme-light.nopadding] + * ``` + * /// @custom:oz-upgrades-unsafe-allow constructor + * constructor() { + * _disableInitializers(); + * } + * ``` + * ==== + */ +abstract contract Initializable { + /** + * @dev Storage of the initializable contract. + * + * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions + * when using with upgradeable contracts. + * + * @custom:storage-location erc7201:openzeppelin.storage.Initializable + */ + struct InitializableStorage { + /** + * @dev Indicates that the contract has been initialized. + */ + uint64 _initialized; + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool _initializing; + } + + // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; + + /** + * @dev The contract is already initialized. + */ + error InvalidInitialization(); + + /** + * @dev The contract is not initializing. + */ + error NotInitializing(); + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint64 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any + * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in + * production. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + // Cache values to avoid duplicated sloads + bool isTopLevelCall = !$._initializing; + uint64 initialized = $._initialized; + + // Allowed calls: + // - initialSetup: the contract is not in the initializing state and no previous version was + // initialized + // - construction: the contract is initialized at version 1 (no reininitialization) and the + // current contract is just being deployed + bool initialSetup = initialized == 0 && isTopLevelCall; + bool construction = initialized == 1 && address(this).code.length == 0; + + if (!initialSetup && !construction) { + revert InvalidInitialization(); + } + $._initialized = 1; + if (isTopLevelCall) { + $._initializing = true; + } + _; + if (isTopLevelCall) { + $._initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer(uint64 version) { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing || $._initialized >= version) { + revert InvalidInitialization(); + } + $._initialized = version; + $._initializing = true; + _; + $._initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + _checkInitializing(); + _; + } + + /** + * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}. + */ + function _checkInitializing() internal view virtual { + if (!_isInitializing()) { + revert NotInitializing(); + } + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + // solhint-disable-next-line var-name-mixedcase + InitializableStorage storage $ = _getInitializableStorage(); + + if ($._initializing) { + revert InvalidInitialization(); + } + if ($._initialized != type(uint64).max) { + $._initialized = type(uint64).max; + emit Initialized(type(uint64).max); + } + } + + /** + * @dev Returns the highest version that has been initialized. See {reinitializer}. + */ + function _getInitializedVersion() internal view returns (uint64) { + return _getInitializableStorage()._initialized; + } + + /** + * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. + */ + function _isInitializing() internal view returns (bool) { + return _getInitializableStorage()._initializing; + } + + /** + * @dev Returns a pointer to the storage namespace. + */ + // solhint-disable-next-line var-name-mixedcase + function _getInitializableStorage() private pure returns (InitializableStorage storage $) { + assembly { + $.slot := INITIALIZABLE_STORAGE + } + } +} diff --git a/tests/e2e/detectors/test_detectors.py b/tests/e2e/detectors/test_detectors.py index 2c6a5f55a3..c4fe2b14e5 100644 --- a/tests/e2e/detectors/test_detectors.py +++ b/tests/e2e/detectors/test_detectors.py @@ -4,6 +4,7 @@ from typing import Type, Optional, List import pytest + from crytic_compile import CryticCompile, save_to_zip from crytic_compile.utils.zip import load_from_zip @@ -1714,191 +1715,196 @@ def id_test(test_item: Test): "out_of_order_retryable.sol", "0.8.20", ), - # Test( - # all_detectors.UnusedImport, - # "ConstantContractLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "ConstantContractLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "ConstantTopLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "ConstantTopLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "ContractUsedInContractTest1.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "ContractUsedInContractTest2.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "ContractUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomErrorTopLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomEventContractLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomEventContractLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeContractLevelUsedInContractTest1.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeContractLevelUsedInContractTest2.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeContractLevelUsedInContractTest3.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeContractLevelUsedInContractTest4.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeContractLevelUsedTopLevelTest1.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeContractLevelUsedTopLevelTest2.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeTopLevelUsedInContractTest1.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeTopLevelUsedInContractTest2.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeTopLevelUsedInContractTest3.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeTopLevelUsedInContractTest4.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeTopLevelUsedTopLevelTest1.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "CustomTypeTopLevelUsedTopLevelTest2.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "EnumContractLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "EnumContractLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "EnumTopLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "EnumTopLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "FunctionContractLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "FunctionContractLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "FunctionTopLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "FunctionTopLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "LibraryUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "LibraryUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "StructContractLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "StructContractLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "StructTopLevelUsedInContractTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "StructTopLevelUsedTopLevelTest.sol", - # "0.8.16", - # ), - # Test( - # all_detectors.UnusedImport, - # "C.sol", - # "0.8.16", - # ), + Test( + all_detectors.UnusedImport, + "ConstantContractLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "ConstantContractLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "ConstantTopLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "ConstantTopLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "ContractUsedInContractTest1.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "ContractUsedInContractTest2.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "ContractUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomErrorTopLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomEventContractLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomEventContractLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeContractLevelUsedInContractTest1.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeContractLevelUsedInContractTest2.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeContractLevelUsedInContractTest3.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeContractLevelUsedInContractTest4.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeContractLevelUsedTopLevelTest1.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeContractLevelUsedTopLevelTest2.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeTopLevelUsedInContractTest1.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeTopLevelUsedInContractTest2.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeTopLevelUsedInContractTest3.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeTopLevelUsedInContractTest4.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeTopLevelUsedTopLevelTest1.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CustomTypeTopLevelUsedTopLevelTest2.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "EnumContractLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "EnumContractLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "EnumTopLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "EnumTopLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "FunctionContractLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "FunctionContractLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "FunctionTopLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "FunctionTopLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "LibraryUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "LibraryUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "StructContractLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "StructContractLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "StructTopLevelUsedInContractTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "StructTopLevelUsedTopLevelTest.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "C.sol", + "0.8.16", + ), + Test( + all_detectors.UnusedImport, + "CrossDomainMessenger.sol", + "0.8.16", + ), ] GENERIC_PATH = "/GENERIC_PATH"