Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix address filter from comments #43

Merged
merged 14 commits into from
Dec 29, 2024
2 changes: 1 addition & 1 deletion Quorum/apis/block_explorers/chains_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(self, chain: Chain) -> None:
chain_id = self.CHAIN_ID_MAP[chain]
api_key = os.getenv("ETHSCAN_API_KEY")
if not api_key:
raise ValueError(f"{chain}SCAN_API_KEY environment variable is not set.")
raise ValueError("ETHSCAN_API_KEY environment variable is not set.")

self.base_url = self.BASE_URL.format(chain_id=chain_id, api_key=api_key)

Expand Down
41 changes: 37 additions & 4 deletions Quorum/checks/price_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,31 @@
import Quorum.utils.pretty_printer as pp


def remove_solidity_comments(source_code: str) -> str:
"""
Removes single-line and multi-line comments from Solidity source code.

Args:
source_code (str): The Solidity source code as a single string.

Returns:
str: The source code with comments removed.
"""
# Regex pattern to match single-line comments (//...)
single_line_comment_pattern = r'//.*?$'

# Regex pattern to match multi-line comments (/*...*/)
multi_line_comment_pattern = r'/\*.*?\*/'

# First, remove multi-line comments
source_code = re.sub(multi_line_comment_pattern, '', source_code, flags=re.DOTALL)

# Then, remove single-line comments
source_code = re.sub(single_line_comment_pattern, '', source_code, flags=re.MULTILINE)

return source_code


class PriceFeedCheck(Check):
"""
The PriceFeedCheck class is responsible for verifying the price feed addresses in the source code
Expand Down Expand Up @@ -37,12 +62,13 @@ def __init__(
self.address_pattern = r'0x[a-fA-F0-9]{40}'
self.providers = providers

def __check_price_feed_address(self, address: str) -> dict | None:
def __check_price_feed_address(self, address: str, file_name: str) -> dict | None:
"""
Check if the given address is present in the price feed providers.

Args:
address (str): The address to be checked.
file_name (str): The name of the source code file where the address was found.

Returns:
dict | None: The price feed data if the address is found, otherwise None.
Expand All @@ -57,7 +83,7 @@ def __check_price_feed_address(self, address: str) -> dict | None:
return price_feed.model_dump()

pp.pretty_print(
f"Address {address} not found in any price feed provider: {[p.get_name() for p in self.providers]}",
f"Address {address} from {file_name} not found in any price feed provider: {[p.get_name() for p in self.providers]}",
pp.Colors.INFO
)
return None
Expand All @@ -75,10 +101,17 @@ def verify_price_feed(self) -> None:
verified_sources_path = f"{Path(source_code.file_name).stem.removesuffix('.sol')}/verified_sources.json"
verified_variables = []

# Combine all lines into a single string
contract_text = '\n'.join(source_code.file_content)
addresses = set(re.findall(self.address_pattern, contract_text))

# Remove comments from the source code
clean_text = remove_solidity_comments(contract_text)

# Extract unique addresses using regex
addresses = set(re.findall(self.address_pattern, clean_text))

for address in addresses:
if feed := self.__check_price_feed_address(address):
if feed := self.__check_price_feed_address(address, source_code.file_name):
verified_variables.append(feed)

if verified_variables:
Expand Down
5 changes: 3 additions & 2 deletions Quorum/tests/test_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ def test_global_variables(source_codes: list[SourceCode], tmp_output_path: Path)


@pytest.mark.parametrize('source_codes', ['ETH/0xAD6c03BF78A3Ee799b86De5aCE32Bb116eD24637'], indirect=True)
def test_price_feed(source_codes: list[SourceCode], tmp_output_path: Path):
price_feed_check = Checks.PriceFeedCheck('Aave', Chain.ETH, '', source_codes, [ChainLinkAPI()])
def test_price_feed_check(source_codes: list[SourceCode], tmp_output_path: Path):
price_feed_check = Checks.PriceFeedCheck('Aave', Chain.ETH, '', source_codes, [
ChainLinkAPI()])
price_feed_check.verify_price_feed()

assert sorted([p.name for p in price_feed_check.check_folder.iterdir()]) == ['AaveV2Ethereum']
Expand Down
84 changes: 84 additions & 0 deletions Quorum/tests/test_price_feed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import pytest


from Quorum.apis.block_explorers.source_code import SourceCode
import Quorum.checks as Checks
from Quorum.utils.chain_enum import Chain
from Quorum.apis.price_feeds import ChainLinkAPI

from pathlib import Path


@pytest.mark.parametrize('source_codes', ['ETH/0xAD6c03BF78A3Ee799b86De5aCE32Bb116eD24637'], indirect=True)
def test_price_feed(source_codes: list[SourceCode], tmp_output_path: Path):
price_feed_check = Checks.PriceFeedCheck('Aave', Chain.ETH, '', source_codes, [
ChainLinkAPI()])
price_feed_check.verify_price_feed()

assert sorted([p.name for p in price_feed_check.check_folder.iterdir()]) == ['AaveV2Ethereum']


def test_source_code_clean():
code = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol';
import {AaveV2Ethereum, AaveV2EthereumAssets, ILendingPoolConfigurator} from 'aave-address-book/AaveV2Ethereum.sol';

/**
* @title Reserve Factor Updates Mid July
* @author karpatkey_TokenLogic
* - Snapshot: https://snapshot.org/#/aave.eth/proposal/0x9cc7906f04f45cebeaa48a05ed281f49da00d89c4dd988a968272fa179f14d06
* - Discussion: https://governance.aave.com/t/arfc-increase-bridged-usdc-reserve-factor-across-all-deployments/17787
*/
contract AaveV2Ethereum_ReserveFactorUpdatesMidJuly_20240711 is IProposalGenericExecutor {
ILendingPoolConfigurator public constant POOL_CONFIGURATOR =
ILendingPoolConfigurator(AaveV2Ethereum.POOL_CONFIGURATOR);

uint256 public constant DAI_RF = 65_00;
uint256 public constant LINK_RF = 70_00;
uint256 public constant USDC_RF = 65_00;
uint256 public constant USDT_RF = 65_00;
uint256 public constant WBTC_RF = 70_00;
uint256 public constant WETH_RF = 65_00;

function execute() external {
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.DAI_UNDERLYING, DAI_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.LINK_UNDERLYING, LINK_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.USDC_UNDERLYING, USDC_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.USDT_UNDERLYING, USDT_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.WBTC_UNDERLYING, WBTC_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.WETH_UNDERLYING, WETH_RF);
}
}
"""
cleaned = Checks.price_feed.remove_solidity_comments(code)
expected = """pragma solidity ^0.8.0;

import {IProposalGenericExecutor} from 'aave-helpers/interfaces/IProposalGenericExecutor.sol';
import {AaveV2Ethereum, AaveV2EthereumAssets, ILendingPoolConfigurator} from 'aave-address-book/AaveV2Ethereum.sol';


contract AaveV2Ethereum_ReserveFactorUpdatesMidJuly_20240711 is IProposalGenericExecutor {
ILendingPoolConfigurator public constant POOL_CONFIGURATOR =
ILendingPoolConfigurator(AaveV2Ethereum.POOL_CONFIGURATOR);

uint256 public constant DAI_RF = 65_00;
uint256 public constant LINK_RF = 70_00;
uint256 public constant USDC_RF = 65_00;
uint256 public constant USDT_RF = 65_00;
uint256 public constant WBTC_RF = 70_00;
uint256 public constant WETH_RF = 65_00;

function execute() external {
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.DAI_UNDERLYING, DAI_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.LINK_UNDERLYING, LINK_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.USDC_UNDERLYING, USDC_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.USDT_UNDERLYING, USDT_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.WBTC_UNDERLYING, WBTC_RF);
POOL_CONFIGURATOR.setReserveFactor(AaveV2EthereumAssets.WETH_UNDERLYING, WETH_RF);
}
}"""
# Strip leading/trailing whitespace for accurate comparison
assert cleaned.strip() == expected.strip()
Loading