Skip to content

Commit

Permalink
add metadata parsing + removal from bytecode
Browse files Browse the repository at this point in the history
  • Loading branch information
rmi7 committed Oct 28, 2022
1 parent 1ca05b8 commit c1b1f59
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 12 deletions.
62 changes: 51 additions & 11 deletions crytic_compile/compilation_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union

import sha3
import cbor2

from crytic_compile.utils.naming import Filename
from crytic_compile.utils.natspec import Natspec
Expand Down Expand Up @@ -706,22 +707,61 @@ 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]]: fieldname => value
"""
# the metadata is at the end of the runtime(!) bytecode
try:
bytecode = self._runtime_bytecodes[name]
except:
raise ValueError(f"contract {name} does not exist") # pylint: disable=raise-missing-from

metadata_length = int(f"0x{bytecode[-4:]}", base=16)
metadata = bytecode[-(metadata_length * 2 + 4) :]
metadata_decoded = cbor2.loads(bytearray.fromhex(metadata))

# up to (inclusive) 0.4.24

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.4",
packages=find_packages(),
python_requires=">=3.6",
install_requires=["pysha3>=1.0.2"],
install_requires=["pysha3>=1.0.2", "cbor2"],
license="AGPL-3.0",
long_description=long_description,
package_data={"crytic_compile": ["py.typed"]},
Expand Down

0 comments on commit c1b1f59

Please sign in to comment.