Skip to content

Commit

Permalink
Merge pull request #304 from crytic/fix-metadata-issue
Browse files Browse the repository at this point in the history
add metadata parsing + removal from bytecode
  • Loading branch information
montyly authored Dec 22, 2022
2 parents 567ffb0 + 56fd5f5 commit 99c0288
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 12 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Pytest

defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}

on:
push:
branches:
- main
- dev
pull_request:
branches: [main, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'

jobs:
tests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1
- name: Set up Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8

# Used by ci_test.sh
- name: Install dependencies
run: |
python setup.py install
pip install pytest
pip install solc-select
- name: Run Tests
run: |
pytest tests/test_metadata.py
65 changes: 54 additions & 11 deletions crytic_compile/source_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
import re
from typing import Dict, List, Optional, Union, Tuple, Set, TYPE_CHECKING
import cbor2

from Crypto.Hash import keccak

Expand Down Expand Up @@ -546,22 +547,64 @@ def _compute_topics_events(self, name: str) -> None:
###################################################################################
###################################################################################

def metadata_of(self, name: str) -> Dict[str, Union[str, bool]]:
"""Return the parsed metadata of a contract by name
Args:
name (str): contract name
Raises:
ValueError: If no contract/library with that name exists
Returns:
Dict[str, Union[str, bool]]: fielname => value
"""
# the metadata is at the end of the runtime(!) bytecode
try:
bytecode = self._runtime_bytecodes[name]
print("runtime bytecode", bytecode)
except:
raise ValueError( # pylint: disable=raise-missing-from
f"contract {name} does not exist"
)

# the last two bytes contain the length of the preceding metadata.
metadata_length = int(f"0x{bytecode[-4:]}", base=16)
# extract the metadata
metadata = bytecode[-(metadata_length * 2 + 4) :]
metadata_decoded = cbor2.loads(bytearray.fromhex(metadata))

for k, v in metadata_decoded.items():
if len(v) == 1:
metadata_decoded[k] = bool(v)
elif k == "solc":
metadata_decoded[k] = ".".join([str(d) for d in v])
else:
# there might be nested items or other unforeseen errors
try:
metadata_decoded[k] = v.hex()
except: # pylint: disable=bare-except
pass

return metadata_decoded

def remove_metadata(self) -> None:
"""Remove init bytecode
See
http://solidity.readthedocs.io/en/v0.4.24/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
Note we dont support recent Solidity version, see https://github.com/crytic/crytic-compile/issues/59
"""
self._init_bytecodes = {
key: re.sub(r"a165627a7a72305820.{64}0029", r"", bytecode)
for (key, bytecode) in self._init_bytecodes.items()
}

self._runtime_bytecodes = {
key: re.sub(r"a165627a7a72305820.{64}0029", r"", bytecode)
for (key, bytecode) in self._runtime_bytecodes.items()
}
# the metadata is at the end of the runtime(!) bytecode of each contract
for (key, bytecode) in self._runtime_bytecodes.items():
if not bytecode or bytecode == "0x":
continue
# the last two bytes contain the length of the preceding metadata.
metadata_length = int(f"0x{bytecode[-4:]}", base=16)
# store the metadata here so we can remove it from the init bytecode later on
metadata = bytecode[-(metadata_length * 2 + 4) :]
# remove the metadata from the runtime bytecode, '+ 4' for the two length-indication bytes at the end
self._runtime_bytecodes[key] = bytecode[0 : -(metadata_length * 2 + 4)]
# remove the metadata from the init bytecode
self._init_bytecodes[key] = self._init_bytecodes[key].replace(metadata, "")

# endregion
###################################################################################
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
version="0.2.5",
packages=find_packages(),
python_requires=">=3.8",
install_requires=["pycryptodome>=3.4.6"],
install_requires=["pycryptodome>=3.4.6", "cbor2"],
license="AGPL-3.0",
long_description=long_description,
package_data={"crytic_compile": ["py.typed"]},
Expand Down
Loading

0 comments on commit 99c0288

Please sign in to comment.