Skip to content

Commit 99c0288

Browse files
authored
Merge pull request #304 from crytic/fix-metadata-issue
add metadata parsing + removal from bytecode
2 parents 567ffb0 + 56fd5f5 commit 99c0288

File tree

4 files changed

+129
-12
lines changed

4 files changed

+129
-12
lines changed

.github/workflows/pytest.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Pytest
2+
3+
defaults:
4+
run:
5+
# To load bashrc
6+
shell: bash -ieo pipefail {0}
7+
8+
on:
9+
push:
10+
branches:
11+
- main
12+
- dev
13+
pull_request:
14+
branches: [main, dev]
15+
schedule:
16+
# run CI every day even if no PRs/merges occur
17+
- cron: '0 12 * * *'
18+
19+
jobs:
20+
tests:
21+
runs-on: ubuntu-latest
22+
23+
steps:
24+
- uses: actions/checkout@v1
25+
- name: Set up Python 3.8
26+
uses: actions/setup-python@v1
27+
with:
28+
python-version: 3.8
29+
30+
# Used by ci_test.sh
31+
- name: Install dependencies
32+
run: |
33+
python setup.py install
34+
pip install pytest
35+
pip install solc-select
36+
- name: Run Tests
37+
run: |
38+
pytest tests/test_metadata.py

crytic_compile/source_unit.py

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
import re
55
from typing import Dict, List, Optional, Union, Tuple, Set, TYPE_CHECKING
6+
import cbor2
67

78
from Crypto.Hash import keccak
89

@@ -546,22 +547,64 @@ def _compute_topics_events(self, name: str) -> None:
546547
###################################################################################
547548
###################################################################################
548549

550+
def metadata_of(self, name: str) -> Dict[str, Union[str, bool]]:
551+
"""Return the parsed metadata of a contract by name
552+
553+
Args:
554+
name (str): contract name
555+
556+
Raises:
557+
ValueError: If no contract/library with that name exists
558+
559+
Returns:
560+
Dict[str, Union[str, bool]]: fielname => value
561+
"""
562+
# the metadata is at the end of the runtime(!) bytecode
563+
try:
564+
bytecode = self._runtime_bytecodes[name]
565+
print("runtime bytecode", bytecode)
566+
except:
567+
raise ValueError( # pylint: disable=raise-missing-from
568+
f"contract {name} does not exist"
569+
)
570+
571+
# the last two bytes contain the length of the preceding metadata.
572+
metadata_length = int(f"0x{bytecode[-4:]}", base=16)
573+
# extract the metadata
574+
metadata = bytecode[-(metadata_length * 2 + 4) :]
575+
metadata_decoded = cbor2.loads(bytearray.fromhex(metadata))
576+
577+
for k, v in metadata_decoded.items():
578+
if len(v) == 1:
579+
metadata_decoded[k] = bool(v)
580+
elif k == "solc":
581+
metadata_decoded[k] = ".".join([str(d) for d in v])
582+
else:
583+
# there might be nested items or other unforeseen errors
584+
try:
585+
metadata_decoded[k] = v.hex()
586+
except: # pylint: disable=bare-except
587+
pass
588+
589+
return metadata_decoded
590+
549591
def remove_metadata(self) -> None:
550592
"""Remove init bytecode
551593
See
552594
http://solidity.readthedocs.io/en/v0.4.24/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
553-
554-
Note we dont support recent Solidity version, see https://github.com/crytic/crytic-compile/issues/59
555595
"""
556-
self._init_bytecodes = {
557-
key: re.sub(r"a165627a7a72305820.{64}0029", r"", bytecode)
558-
for (key, bytecode) in self._init_bytecodes.items()
559-
}
560-
561-
self._runtime_bytecodes = {
562-
key: re.sub(r"a165627a7a72305820.{64}0029", r"", bytecode)
563-
for (key, bytecode) in self._runtime_bytecodes.items()
564-
}
596+
# the metadata is at the end of the runtime(!) bytecode of each contract
597+
for (key, bytecode) in self._runtime_bytecodes.items():
598+
if not bytecode or bytecode == "0x":
599+
continue
600+
# the last two bytes contain the length of the preceding metadata.
601+
metadata_length = int(f"0x{bytecode[-4:]}", base=16)
602+
# store the metadata here so we can remove it from the init bytecode later on
603+
metadata = bytecode[-(metadata_length * 2 + 4) :]
604+
# remove the metadata from the runtime bytecode, '+ 4' for the two length-indication bytes at the end
605+
self._runtime_bytecodes[key] = bytecode[0 : -(metadata_length * 2 + 4)]
606+
# remove the metadata from the init bytecode
607+
self._init_bytecodes[key] = self._init_bytecodes[key].replace(metadata, "")
565608

566609
# endregion
567610
###################################################################################

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
version="0.2.5",
1515
packages=find_packages(),
1616
python_requires=">=3.8",
17-
install_requires=["pycryptodome>=3.4.6"],
17+
install_requires=["pycryptodome>=3.4.6", "cbor2"],
1818
license="AGPL-3.0",
1919
long_description=long_description,
2020
package_data={"crytic_compile": ["py.typed"]},

0 commit comments

Comments
 (0)