|
3 | 3 | """
|
4 | 4 | import re
|
5 | 5 | from typing import Dict, List, Optional, Union, Tuple, Set, TYPE_CHECKING
|
| 6 | +import cbor2 |
6 | 7 |
|
7 | 8 | from Crypto.Hash import keccak
|
8 | 9 |
|
@@ -546,22 +547,64 @@ def _compute_topics_events(self, name: str) -> None:
|
546 | 547 | ###################################################################################
|
547 | 548 | ###################################################################################
|
548 | 549 |
|
| 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 | + |
549 | 591 | def remove_metadata(self) -> None:
|
550 | 592 | """Remove init bytecode
|
551 | 593 | See
|
552 | 594 | 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 |
555 | 595 | """
|
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, "") |
565 | 608 |
|
566 | 609 | # endregion
|
567 | 610 | ###################################################################################
|
|
0 commit comments