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

Sync Master <> Dev #583

Merged
merged 25 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
37dcd2a
add solc env field for solc standard json
garythung Nov 1, 2023
5d35878
add solc working dir
garythung Nov 1, 2023
c526de8
add optimizer settings to solc standard json
garythung Dec 19, 2023
59b2b75
Merge pull request #514 from garythung/master
montyly Apr 13, 2024
62354dd
Fix some comments
pullmerge Apr 14, 2024
93c106e
Merge pull request #559 from pullmerge/master
0xalpharush Apr 14, 2024
3e2d198
---
dependabot[bot] May 20, 2024
87c6f6b
---
dependabot[bot] May 20, 2024
9105e83
Merge pull request #561 from crytic/dependabot/github_actions/dev/cac…
0xalpharush Jun 4, 2024
4291072
Merge pull request #560 from crytic/dependabot/github_actions/dev/cac…
0xalpharush Jun 4, 2024
0d121d1
platform: etherscan: add support for v2 API
elopez Oct 26, 2024
a5cddc5
platform: etherscan: support ETHERSCAN_API_KEY environment variable
elopez Oct 26, 2024
61ce91b
tests: vyper: fix compilation failure
elopez Oct 26, 2024
ae5d6f8
Update etherscan.yml
montyly Dec 6, 2024
f0ad96f
Fix CI handling of API key
elopez Dec 30, 2024
f799182
Silence mypy error on test
elopez Dec 30, 2024
be77462
Merge pull request #574 from crytic/dev-etherscan-v2
montyly Dec 30, 2024
042aa15
chore(deps): bump pypa/gh-action-pypi-publish from 1.8.14 to 1.12.3
dependabot[bot] Dec 9, 2024
e4829d1
chore(deps): bump cachix/install-nix-action from V27 to 30
dependabot[bot] Oct 28, 2024
8d562e4
chore(deps): bump sigstore/gh-action-sigstore-python from 2.1.1 to 3.0.0
dependabot[bot] Jul 15, 2024
9801d0b
Merge pull request #580 from crytic/dependabot/github_actions/dev/pyp…
elopez Dec 30, 2024
2900f3d
Merge pull request #576 from crytic/dependabot/github_actions/dev/cac…
elopez Dec 30, 2024
b569435
Merge pull request #565 from crytic/dependabot/github_actions/dev/sig…
elopez Dec 30, 2024
fa12661
Fix foundry optimizer key
smonicas Jan 15, 2025
2d02b9d
Merge pull request #582 from crytic/dev-fix-foundry
smonicas Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ jobs:
solc-select use 0.5.7 --always-install
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v30
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v14
uses: cachix/cachix-action@v15
with:
name: dapp
- name: Install Foundry
Expand All @@ -75,7 +75,6 @@ jobs:
- name: Run Tests
env:
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/etherscan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
- name: Run Tests
env:
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
shell: bash
run: |
bash "scripts/ci_test_${TEST_TYPE}.sh"
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ jobs:
path: dist/

- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.14
uses: pypa/gh-action-pypi-publish@v1.12.3

- name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.1
uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ jobs:
solc-select use latest --always-install
- name: Run Tests
env:
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
run: |
pytest tests
80 changes: 0 additions & 80 deletions crytic_compile/cryticparser/cryticparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,30 +329,6 @@ def _init_etherscan(parser: ArgumentParser) -> None:
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--arbiscan-apikey",
help="Etherscan API key.",
action="store",
dest="arbiscan_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--polygonscan-apikey",
help="Etherscan API key.",
action="store",
dest="polygonscan_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--test-polygonscan-apikey",
help="Etherscan API key.",
action="store",
dest="test_polygonscan_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--avax-apikey",
help="Etherscan API key.",
Expand All @@ -361,62 +337,6 @@ def _init_etherscan(parser: ArgumentParser) -> None:
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--ftmscan-apikey",
help="Etherscan API key.",
action="store",
dest="ftmscan_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--bscan-apikey",
help="Etherscan API key.",
action="store",
dest="bscan_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--optim-apikey",
help="Optimistic API key.",
action="store",
dest="optim_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--base-apikey",
help="Basescan API key.",
action="store",
dest="base_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--gno-apikey",
help="Gnosisscan API key.",
action="store",
dest="gno_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--polyzk-apikey",
help="zkEVM Polygonscan API key.",
action="store",
dest="polyzk_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--blast-apikey",
help="Blastscan API key.",
action="store",
dest="blast_api_key",
default=DEFAULTS_FLAG_IN_CONFIG["etherscan_api_key"],
)

group_etherscan.add_argument(
"--etherscan-export-directory",
help="Directory in which to save the analyzed contracts.",
Expand Down
205 changes: 122 additions & 83 deletions crytic_compile/platform/etherscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,102 @@
LOGGER = logging.getLogger("CryticCompile")


ETHERSCAN_BASE = "https://api%s/api?module=contract&action=getsourcecode&address=%s"
# Etherscan v1 API style (per-scanner URL)
ETHERSCAN_BASE_V1 = "https://api%s/api?module=contract&action=getsourcecode&address=%s"

# Etherscan v2 API style (unified)
ETHERSCAN_BASE_V2 = (
"https://api.etherscan.io/v2/api?chainid=%s&module=contract&action=getsourcecode&address=%s"
)

# Bytecode URL style (for scraping)
ETHERSCAN_BASE_BYTECODE = "https://%s/address/%s#code"

SUPPORTED_NETWORK = {
# Key, (prefix_base, perfix_bytecode)
"mainet:": (".etherscan.io", "etherscan.io"),
"optim:": ("-optimistic.etherscan.io", "optimistic.etherscan.io"),
"goerli:": ("-goerli.etherscan.io", "goerli.etherscan.io"),
"sepolia:": ("-sepolia.etherscan.io", "sepolia.etherscan.io"),
"tobalaba:": ("-tobalaba.etherscan.io", "tobalaba.etherscan.io"),
"bsc:": (".bscscan.com", "bscscan.com"),
"testnet.bsc:": ("-testnet.bscscan.com", "testnet.bscscan.com"),
"arbi:": (".arbiscan.io", "arbiscan.io"),
"testnet.arbi:": ("-testnet.arbiscan.io", "testnet.arbiscan.io"),
"poly:": (".polygonscan.com", "polygonscan.com"),
"mumbai:": ("-testnet.polygonscan.com", "testnet.polygonscan.com"),
"avax:": (".snowtrace.io", "snowtrace.io"),
"testnet.avax:": ("-testnet.snowtrace.io", "testnet.snowtrace.io"),
"ftm:": (".ftmscan.com", "ftmscan.com"),
"goerli.base:": ("-goerli.basescan.org", "goerli.basescan.org"),
"base:": (".basescan.org", "basescan.org"),
"gno:": (".gnosisscan.io", "gnosisscan.io"),
"polyzk:": ("-zkevm.polygonscan.com", "zkevm.polygonscan.com"),
"blast:": (".blastscan.io", "blastscan.io"),
# v1 style scanners
SUPPORTED_NETWORK_V1: Dict[str, Tuple[str, str]] = {
# None at this time. External tracer instances not operated by Etherscan would be here
}

# v2 style scanners
SUPPORTED_NETWORK_V2: Dict[str, Tuple[str, str]] = {
# Key, (chainid, perfix_bytecode)
"mainnet": ("1", "etherscan.io"),
"sepolia": ("11155111", "sepolia.etherscan.io"),
"holesky": ("17000", "holesky.etherscan.io"),
"bsc": ("56", "bscscan.com"),
"testnet.bsc": ("97", "testnet.bscscan.com"),
"poly": ("137", "polygonscan.com"),
"amoy.poly": ("80002", "amoy.polygonscan.com"),
"polyzk": ("1101", "zkevm.polygonscan.com"),
"cardona.polyzk": ("2442", "cardona-zkevm.polygonscan.com"),
"base": ("8453", "basescan.org"),
"sepolia.base": ("84532", "sepolia.basescan.org"),
"arbi": ("42161", "arbiscan.io"),
"nova.arbi": ("42170", "nova.arbiscan.io"),
"sepolia.arbi": ("421614", "sepolia.arbiscan.io"),
"linea": ("59144", "lineascan.build"),
"sepolia.linea": ("59141", "sepolia.lineascan.build"),
"ftm": ("250", "ftmscan.com"),
"testnet.ftm": ("4002", "testnet.ftmscan.com"),
"blast": ("81457", "blastscan.io"),
"sepolia.blast": ("168587773", "sepolia.blastscan.io"),
"optim": ("10", "optimistic.etherscan.io"),
"sepolia.optim": ("11155420", "sepolia-optimism.etherscan.io"),
"avax": ("43114", "snowscan.xyz"),
"testnet.avax": ("43113", "testnet.snowscan.xyz"),
"bttc": ("199", "bttcscan.com"),
"testnet.bttc": ("1028", "testnet.bttcscan.com"),
"celo": ("42220", "celoscan.io"),
"alfajores.celo": ("44787", "alfajores.celoscan.io"),
"cronos": ("25", "cronoscan.com"),
"frax": ("252", "fraxscan.com"),
"holesky.frax": ("2522", "holesky.fraxscan.com"),
"gno": ("100", "gnosisscan.io"),
"kroma": ("255", "kromascan.com"),
"sepolia.kroma": ("2358", "sepolia.kromascan.com"),
"mantle": ("5000", "mantlescan.xyz"),
"sepolia.mantle": ("5003", "sepolia.mantlescan.xyz"),
"moonbeam": ("1284", "moonbeam.moonscan.io"),
"moonriver": ("1285", "moonriver.moonscan.io"),
"moonbase": ("1287", "moonbase.moonscan.io"),
"opbnb": ("204", "opbnb.bscscan.com"),
"testnet.opbnb": ("5611", "opbnb-testnet.bscscan.com"),
"scroll": ("534352", "scrollscan.com"),
"sepolia.scroll": ("534351", "sepolia.scrollscan.com"),
"taiko": ("167000", "taikoscan.io"),
"hekla.taiko": ("167009", "hekla.taikoscan.io"),
"wemix": ("1111", "wemixscan.com"),
"testnet.wemix": ("1112", "testnet.wemixscan.com"),
"era.zksync": ("324", "era.zksync.network"),
"sepoliaera.zksync": ("300", "sepolia-era.zksync.network"),
"xai": ("660279", "xaiscan.io"),
"sepolia.xai": ("37714555429", "sepolia.xaiscan.io"),
}

SUPPORTED_NETWORK = {**SUPPORTED_NETWORK_V1, **SUPPORTED_NETWORK_V2}


def generate_supported_network_v2_list() -> None:
"""Manual function to generate a dictionary for updating the SUPPORTED_NETWORK_V2 array"""

with urllib.request.urlopen("https://api.etherscan.io/v2/chainlist") as response:
items = response.read()
networks = json.loads(items)

id2name = {}
for name, (chainid, _) in SUPPORTED_NETWORK_V2.items():
id2name[chainid] = name

results = {}
for network in networks["result"]:
name = id2name.get(network["chainid"], f"{network['chainid']}")
results[name] = (
network["chainid"],
network["blockexplorer"].replace("https://", "").strip("/"),
)

print(results)


def _handle_bytecode(crytic_compile: "CryticCompile", target: str, result_b: bytes) -> None:
"""Parse the bytecode and populate CryticCompile info
Expand Down Expand Up @@ -215,15 +284,24 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:

target = self._target

if target.startswith(tuple(SUPPORTED_NETWORK)):
prefix: Union[None, str] = SUPPORTED_NETWORK[target[: target.find(":") + 1]][0]
prefix_bytecode = SUPPORTED_NETWORK[target[: target.find(":") + 1]][1]
api_key_required = None

if target.startswith(tuple(SUPPORTED_NETWORK_V2)):
api_key_required = 2
prefix, addr = target.split(":", 2)
chainid, prefix_bytecode = SUPPORTED_NETWORK_V2[prefix]
etherscan_url = ETHERSCAN_BASE_V2 % (chainid, addr)
etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode, addr)
elif target.startswith(tuple(SUPPORTED_NETWORK_V1)):
api_key_required = 1
prefix = SUPPORTED_NETWORK_V1[target[: target.find(":") + 1]][0]
prefix_bytecode = SUPPORTED_NETWORK_V1[target[: target.find(":") + 1]][1]
addr = target[target.find(":") + 1 :]
etherscan_url = ETHERSCAN_BASE % (prefix, addr)
etherscan_url = ETHERSCAN_BASE_V1 % (prefix, addr)
etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % (prefix_bytecode, addr)

else:
etherscan_url = ETHERSCAN_BASE % (".etherscan.io", target)
api_key_required = 2
etherscan_url = ETHERSCAN_BASE_V2 % ("1", target)
etherscan_bytecode_url = ETHERSCAN_BASE_BYTECODE % ("etherscan.io", target)
addr = target
prefix = None
Expand All @@ -232,75 +310,36 @@ def compile(self, crytic_compile: "CryticCompile", **kwargs: str) -> None:
only_bytecode = kwargs.get("etherscan_only_bytecode", False)

etherscan_api_key = kwargs.get("etherscan_api_key", None)
arbiscan_api_key = kwargs.get("arbiscan_api_key", None)
polygonscan_api_key = kwargs.get("polygonscan_api_key", None)
test_polygonscan_api_key = kwargs.get("test_polygonscan_api_key", None)
avax_api_key = kwargs.get("avax_api_key", None)
ftmscan_api_key = kwargs.get("ftmscan_api_key", None)
bscan_api_key = kwargs.get("bscan_api_key", None)
optim_api_key = kwargs.get("optim_api_key", None)
base_api_key = kwargs.get("base_api_key", None)
gno_api_key = kwargs.get("gno_api_key", None)
polyzk_api_key = kwargs.get("polyzk_api_key", None)
blast_api_key = kwargs.get("blast_api_key", None)
if etherscan_api_key is None:
etherscan_api_key = os.getenv("ETHERSCAN_API_KEY")

export_dir = kwargs.get("export_dir", "crytic-export")
export_dir = os.path.join(
export_dir, kwargs.get("etherscan_export_dir", "etherscan-contracts")
)

if etherscan_api_key and "etherscan" in etherscan_url:
if api_key_required == 2 and etherscan_api_key:
etherscan_url += f"&apikey={etherscan_api_key}"
etherscan_bytecode_url += f"&apikey={etherscan_api_key}"
if arbiscan_api_key and "arbiscan" in etherscan_url:
etherscan_url += f"&apikey={arbiscan_api_key}"
etherscan_bytecode_url += f"&apikey={arbiscan_api_key}"
if polygonscan_api_key and "polygonscan" in etherscan_url:
etherscan_url += f"&apikey={polygonscan_api_key}"
etherscan_bytecode_url += f"&apikey={polygonscan_api_key}"
if test_polygonscan_api_key and "polygonscan" in etherscan_url:
etherscan_url += f"&apikey={test_polygonscan_api_key}"
etherscan_bytecode_url += f"&apikey={test_polygonscan_api_key}"
if avax_api_key and "snowtrace" in etherscan_url:
etherscan_url += f"&apikey={avax_api_key}"
etherscan_bytecode_url += f"&apikey={avax_api_key}"
if ftmscan_api_key and "ftmscan" in etherscan_url:
etherscan_url += f"&apikey={ftmscan_api_key}"
etherscan_bytecode_url += f"&apikey={ftmscan_api_key}"
if bscan_api_key and "bscscan" in etherscan_url:
etherscan_url += f"&apikey={bscan_api_key}"
etherscan_bytecode_url += f"&apikey={bscan_api_key}"
if optim_api_key and "optim" in etherscan_url:
etherscan_url += f"&apikey={optim_api_key}"
etherscan_bytecode_url += f"&apikey={optim_api_key}"
if base_api_key and "base" in etherscan_url:
etherscan_url += f"&apikey={base_api_key}"
etherscan_bytecode_url += f"&apikey={base_api_key}"
if gno_api_key and "gno" in etherscan_url:
etherscan_url += f"&apikey={gno_api_key}"
etherscan_bytecode_url += f"&apikey={gno_api_key}"
if polyzk_api_key and "zkevm" in etherscan_url:
etherscan_url += f"&apikey={polyzk_api_key}"
etherscan_bytecode_url += f"&apikey={polyzk_api_key}"
if blast_api_key and "blast" in etherscan_url:
etherscan_url += f"&apikey={blast_api_key}"
etherscan_bytecode_url += f"&apikey={blast_api_key}"
# API key handling for external tracers would be here e.g.
# elif api_key_required == 1 and avax_api_key and "snowtrace" in etherscan_url:
# etherscan_url += f"&apikey={avax_api_key}"
# etherscan_bytecode_url += f"&apikey={avax_api_key}"

source_code: str = ""
result: Dict[str, Union[bool, str, int]] = {}
contract_name: str = ""

if not only_bytecode:
if "polygon" in etherscan_url or "basescan" in etherscan_url:
# build object with headers, then send request
new_etherscan_url = urllib.request.Request(
etherscan_url, headers={"User-Agent": "Mozilla/5.0"}
)
with urllib.request.urlopen(new_etherscan_url) as response:
html = response.read()
else:
with urllib.request.urlopen(etherscan_url) as response:
html = response.read()
# build object with headers, then send request
new_etherscan_url = urllib.request.Request(
etherscan_url,
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 crytic-compile/0"
},
)
with urllib.request.urlopen(new_etherscan_url) as response:
html = response.read()

info = json.loads(html)

Expand Down
Loading
Loading